Mapbox GL JS опирается на WebGL-рендеринг, GPU-ускорение и
асинхронную загрузку ресурсов (тайлы, стили, шрифты, спрайты). Это
делает классическое unit-тестирование недостаточным: значительная часть
логики проявляется только в момент отрисовки карты.
Headless-тестирование в данном контексте означает запуск кода без
реального браузерного окна с сохранением возможности проверять:
- корректность инициализации карты
- загрузку и применение стилей
- работу источников (sources)
- поведение слоёв (layers)
- реакцию на взаимодействия
- стабильность рендеринга (snapshot testing)
Ограничения DOM и
WebGL в headless-средах
Основная сложность заключается в том, что Mapbox GL JS требует:
window и полноценного DOM
- WebGL контекста (
WebGLRenderingContext)
- тайлового рендера через GPU или его эмуляцию
- requestAnimationFrame и event loop браузера
В среде Node.js отсутствуют:
- GPU
- WebGL API
- полноценный layout engine браузера
Это приводит к необходимости эмуляции окружения или перехода к
альтернативным стратегиям тестирования.
Базовая конфигурация
Node.js окружения
Для тестирования обычно используется Node.js с набором
polyfill-библиотек:
- jsdom — эмуляция DOM
- global polyfills (window, document)
- fetch mock (или undici mocking)
- timers (jest fake timers)
Типичная инициализация:
- создание
global.window
- установка
global.document
- мок
requestAnimationFrame
- заглушка
URL.createObjectURL
Однако этого недостаточно для WebGL-части Mapbox GL JS.
Проблема WebGL и подход
headless-gl
Mapbox GL JS требует WebGL, который в Node отсутствует. Решение —
использование headless реализации:
headless-gl (node-canvas/webgl binding)
gl package (OSMesa/EGL backend)
Это позволяет создать контекст:
gl(width, height, { preserveDrawingBuffer: true })
Далее этот контекст подменяет
canvas.getContext('webgl').
Ключевая задача:
- связать HTMLCanvasElement с headless WebGL
- обеспечить совместимость API с браузерным поведением
Изоляция Mapbox GL JS в
тестах
При тестировании важно контролировать внешние зависимости:
- запросы к тайлам
- загрузку стилей JSON
- спрайты и glyphs
- токены доступа
Используются подходы:
Перехват сетевых запросов
- mock fetch API
- MSW (Mock Service Worker)
- кастомный interceptor
Пример логики:
- все
mapbox://styles/... заменяются на локальные
JSON
- tile requests возвращают фикстуры
Локальные стили
Стиль карты фиксируется:
- упрощённый JSON style
- минимальный набор layers
- отсутствие внешних URL
Это снижает нестабильность тестов.
Unit-тестирование логики
карты
В unit-тестах проверяется не рендер, а поведение API:
map.addSource
map.addLayer
map.setFilter
map.setLayoutProperty
Используется стратегия:
- создание карты в headless окружении
- проверка состояния через
map.getStyle()
Пример проверяемых аспектов:
- слой добавлен
- порядок слоёв корректен
- источник зарегистрирован
- фильтр применён
Snapshot-тестирование стилей
Один из наиболее эффективных подходов — snapshot testing:
- фиксируется итоговый style JSON
- сравнивается после изменений
Проверяются:
- структура layers
- layout properties
- paint properties
- transitions
Преимущество:
- отсутствие необходимости WebGL
Недостаток:
- не проверяет визуальный результат напрямую
Рендер-снапшоты (pixel
testing)
Более сложный подход — визуальные снапшоты:
- рендер карты в offscreen canvas
- экспорт изображения (
canvas.toDataURL)
- сравнение пикселей
Требуется:
- headless WebGL
- стабильная среда выполнения
- фиксированные данные тайлов
Используются библиотеки:
- pixelmatch
- jest-image-snapshot
Проблемы:
- флаки тесты при изменении антиалиасинга
- различия GPU/OS
- нестабильность шрифтов
Использование Puppeteer и
Playwright
Для более реалистичного тестирования применяется браузерный headless
режим:
Playwright Puppeteer
Преимущества:
- реальный Chromium WebGL
- точное поведение рендера
- поддержка сетевых interception API
Типовой сценарий:
- запуск headless Chromium
- загрузка страницы с Mapbox GL JS
- ожидание
map.load
- снимок canvas или DOM
Дополнительные возможности:
- эмуляция разных viewport
- тестирование взаимодействий (drag, zoom)
- проверка performance metrics
Мокирование Mapbox GL JS
В некоторых сценариях библиотека полностью мокается:
замена Map на stub-класс
эмуляция событий:
Проверяются только вызовы API:
new Map()
map.on('load')
map.addLayer(...)
Такой подход применяется:
- в unit-тестах бизнес-логики
- в React/Vue компонентах
Тестирование React-обёрток
При использовании Mapbox GL JS внутри UI-фреймворков:
- проверяется lifecycle карты
- уничтожение инстанса (
map.remove)
- повторная инициализация
Критично тестировать:
- отсутствие утечек памяти
- корректный cleanup event listeners
Асинхронность и
ожидание состояния карты
Mapbox GL JS работает через события:
В тестах важно синхронизировать выполнение:
await new Promise(resolve => map.on('load', resolve))
- ожидание
idle перед snapshot
Без этого тесты становятся нестабильными.
Работа с тайлами и
офлайн-режимом
Для детерминированных тестов используется:
- локальный tile server
- фикстуры vector tiles
- отключение сетевых запросов
Подход:
- заранее подготовленные
.pbf данные
- локальные style sources
Это позволяет:
- воспроизводить одинаковый рендер
- избегать зависимости от CDN
CI-окружения и стабильность
В CI (GitHub Actions, GitLab CI):
Поэтому применяются стратегии:
- headless-gl fallback
- Playwright Chromium
- отключение антиалиасинга
- фиксированные шрифты
Важно:
- закрепление версий Chromium
- фиксация Mapbox GL JS версии
- контроль таймаутов загрузки
Частые проблемы
и причины нестабильности тестов
- различие WebGL реализаций
- асинхронная загрузка ресурсов
- race conditions при
map.load
- различия в рендере текста
- случайный порядок tile requests
- кэширование браузера
Для устранения:
- отключение кэша
- deterministic fixtures
- явные ожидания событий
- контроль request order
Стратегии
комбинированного тестирования
На практике применяется гибрид:
- unit тесты (API и состояние)
- snapshot тесты (style JSON)
- integration headless-gl (рендер)
- e2e Playwright (реальный браузер)
Такой подход покрывает:
- логику
- конфигурацию
- визуальный результат
- пользовательские сценарии