E2E тестирование

E2E тестирование в контексте картографических интерфейсов на Leaflet отличается высокой долей асинхронности, сетевых зависимостей и графической отрисовки через DOM и canvas. Основная сложность заключается в том, что карта формируется не как статический набор элементов, а как результат последовательной загрузки тайлов, слоёв и интерактивных объектов.

Ключевые особенности поведения Leaflet в тестовой среде:

  • асинхронная загрузка тайлов через HTTP-запросы;
  • динамическая отрисовка маркеров и слоёв;
  • использование трансформаций DOM и canvas;
  • зависимость от размеров viewport;
  • частичная виртуализация элементов (часть объектов существует только в видимой области карты).

Архитектура E2E тестов для Leaflet-приложений

Типичная структура E2E тестирования картографического интерфейса включает следующие уровни:

  • инициализация карты (создание экземпляра L.map);
  • контроль состояния тайлового слоя;
  • проверка интерактивных объектов (markers, popups, layers);
  • взаимодействие с картой (zoom, pan);
  • контроль сетевых запросов к tile server;
  • проверка геометрии GeoJSON-слоёв.

Важным аспектом является разделение логики UI и сетевого слоя через перехват HTTP-запросов.


Подготовка тестового окружения

Для E2E тестирования Leaflet обычно используются:

  • Cypress
  • Playwright
  • Selenium WebDriver

Наиболее распространённый подход — Cypress или Playwright, так как они позволяют перехватывать сетевые запросы и работать с DOM в реальном времени.

Базовые требования к окружению:

  • фиксированный viewport;
  • отключённые анимации карты;
  • предсказуемый tile server (mock или stub);
  • отключение случайных данных (например, внешних API).

Пример настройки viewport:

cy.viewport(1280, 800);

или Playwright:

await page.setViewportSize({ width: 1280, height: 800 });

Инициализация карты в тестах

При тестировании важно контролировать момент создания карты и её готовности к взаимодействию.

Типичная инициализация Leaflet:

const map = L.map('map', {
  center: [51.505, -0.09],
  zoom: 13,
  zoomControl: false
});

В E2E тестах проверяется:

  • наличие контейнера карты;
  • корректная установка центра;
  • завершение загрузки базового слоя.

Признак готовности карты часто определяется через событие load:

map.whenReady(() => {
  console.log('map ready');
});

Работа с тайлами и сетевыми запросами

Основная проблема E2E тестов Leaflet — нестабильность внешних tile server’ов. Поэтому используется перехват запросов.

Перехват тайлов в Cypress

cy.intercept('GET', '**/*.png', {
  fixture: 'tile.png'
}).as('tiles');

После чего тест ожидает загрузки:

cy.wait('@tiles');

Подход в Playwright

await page.route('**/*.png', route => {
  route.fulfill({
    path: 'fixtures/tile.png'
  });
});

Контроль сетевых запросов позволяет:

  • исключить внешние зависимости;
  • стабилизировать визуальное состояние карты;
  • ускорить выполнение тестов.

Тестирование маркеров и всплывающих окон

Маркер в Leaflet создаётся через L.marker, и в DOM он представлен как элемент внутри overlay-pane.

const marker = L.marker([51.5, -0.09]).addTo(map);
marker.bindPopup('Hello');

Проверки в E2E тестах:

  • наличие маркера в DOM;
  • корректная позиция;
  • открытие popup при клике.

Cypress пример

cy.get('.leaflet-marker-icon').should('exist');
cy.get('.leaflet-marker-icon').click();
cy.get('.leaflet-popup').should('contain', 'Hello');

Работа с слоями (Layers)

Leaflet активно использует слои: tile layers, vector layers, overlays.

Тестирование слоёв включает:

  • добавление/удаление слоя;
  • переключение visibility;
  • корректность z-index.
const layer = L.tileLayer(url).addTo(map);
map.removeLayer(layer);

Проверка в E2E:

cy.get('.leaflet-tile').should('exist');

или проверка исчезновения слоя:

cy.get('.leaflet-tile').should('not.exist');

GeoJSON и векторные данные

GeoJSON слои требуют проверки геометрии и интерактивности.

L.geoJSON(data, {
  onEachFeature: function (feature, layer) {
    layer.bindPopup(feature.properties.name);
  }
}).addTo(map);

E2E проверки:

  • наличие SVG-элементов;
  • кликабельность областей;
  • отображение popup.
cy.get('path.leaflet-interactive').should('exist');
cy.get('path.leaflet-interactive').first().click();
cy.get('.leaflet-popup').should('be.visible');

Асинхронность и ожидания

Leaflet сильно зависит от асинхронных операций:

  • загрузка тайлов;
  • отрисовка canvas;
  • пересчёт координат при zoom/pan.

Неправильные ожидания приводят к флаки тестам.

Используются стратегии:

  • ожидание сетевых запросов;
  • проверка DOM-условий;
  • polling через should.

Пример:

cy.get('.leaflet-tile-loaded', { timeout: 10000 }).should('exist');

Управление zoom и pan

Взаимодействие с картой — ключевой сценарий E2E.

map.setView([51.505, -0.09], 10);
map.zoomIn();
map.panTo([52.0, -0.1]);

В тестах:

cy.get('.leaflet-container').trigger('wheel', { deltaY: -100 });
cy.get('.leaflet-container').trigger('mousedown', { which: 1 });
cy.get('.leaflet-container').trigger('mousemove', 200, 200);
cy.get('.leaflet-container').trigger('mouseup');

Cypress: комплексный сценарий

describe('Leaflet map E2E', () => {
  it('loads map and displays marker', () => {
    cy.visit('/map');

    cy.intercept('GET', '**/*.png', { fixture: 'tile.png' });

    cy.get('#map').should('exist');

    cy.get('.leaflet-marker-icon', { timeout: 10000 })
      .should('be.visible')
      .click();

    cy.get('.leaflet-popup')
      .should('contain', 'Location');
  });
});

Playwright: пример сценария

import { test, expect } from '@playwright/test';

test('leaflet map renders', async ({ page }) => {
  await page.route('**/*.png', route => {
    route.fulfill({ path: 'fixtures/tile.png' });
  });

  await page.goto('/map');

  const marker = page.locator('.leaflet-marker-icon');
  await expect(marker).toBeVisible();

  await marker.click();

  await expect(page.locator('.leaflet-popup')).toBeVisible();
});

Проблемы нестабильности (flaky tests)

Типичные причины нестабильности:

  • асинхронная загрузка тайлов;
  • различия в render timing;
  • анимации zoom/pan;
  • отсутствие фиксации viewport;
  • реальные внешние tile server’ы.

Методы стабилизации:

  • мокирование сетевых запросов;
  • отключение CSS transitions:
.leaflet-fade-anim .leaflet-tile {
  transition: none !important;
}
  • фиксированное время ожидания;
  • контроль состояния DOM вместо таймеров.

Headless-режим и особенности рендеринга

В headless браузерах возможны отличия:

  • задержка canvas отрисовки;
  • некорректные размеры контейнера до layout pass;
  • отличие в font rendering внутри popups.

Решения:

  • явная установка размеров контейнера;
  • ожидание requestAnimationFrame-циклов;
  • проверка bounding box элементов.

Визуальная регрессия

Для картографических интерфейсов часто применяется snapshot-тестирование:

  • сравнение скриншотов карты;
  • контроль положения маркеров;
  • проверка корректности слоёв.

Проблема — динамические тайлы, поэтому:

  • используются мок-тайлы;
  • фиксируются seed-данные;
  • отключаются анимации.

Производительность в E2E тестах

Карта — тяжёлый DOM/Canvas объект, поэтому важно:

  • ограничивать количество тайлов;
  • использовать минимальный zoom range;
  • избегать реальных tile server запросов;
  • переиспользовать map instance при необходимости.

Метрики:

  • время загрузки карты;
  • время появления первого маркера;
  • latency взаимодействия (click-to-popup).