Visual regression

Визуальная регрессия в контексте картографических интерфейсов представляет собой метод контроля изменений рендеринга, при котором текущий результат отрисовки сравнивается с эталонным изображением. В случае Mapbox GL JS это особенно критично, поскольку итоговое изображение карты формируется динамически на основе стилей, тайлов, шрифтов, источников данных и состояния WebGL-контекста.

Основная цель визуальной регрессии — обнаружение непреднамеренных изменений в отображении карты: смещение слоёв, изменение цвета, исчезновение символов, артефакты рендеринга, нарушения порядка отрисовки или деградация производительности визуализации.

Причины нестабильности рендеринга карт

Рендеринг в Mapbox GL JS зависит от множества факторов, которые могут приводить к флаки-тестам:

  • WebGL окружение Различия между GPU, драйверами и headless-режимами браузеров приводят к отличиям в антиалиасинге и композитинге.

  • Асинхронная загрузка ресурсов Шрифты, спрайты, тайлы и изображения могут загружаться с разной скоростью, влияя на финальный кадр.

  • Нестабильность тайловых данных Изменения в источниках данных или CDN могут менять содержимое карты без изменения кода.

  • Анимации и переходы Камера, easing-функции и интерполяции создают разные промежуточные состояния.

  • Недетерминированность шрифтов Разные системы могут использовать fallback-шрифты, изменяя метрики текста.

  • Параллельная отрисовка слоёв Порядок рендеринга может зависеть от состояния загрузки и внутреннего кэша.

Подготовка среды детерминированного рендеринга

Для стабилизации визуальных тестов необходимо создать воспроизводимую среду:

  • Фиксация версии Mapbox GL JS

  • Использование одинаковых шрифтов и локального сервера glyphs

  • Мокирование тайловых запросов

  • Отключение анимаций:

    • interactive: false
    • fadeDuration: 0
    • crossSourceCollisions: false (в зависимости от конфигурации)
  • Фиксация размера viewport (например, 800×600)

  • Отключение device pixel ratio вариативности (devicePixelRatio: 1)

Дополнительно важно фиксировать:

  • время суток (если используются световые стили)
  • состояние источников данных
  • уровень зума и центр карты

Инструменты визуальной регрессии

На практике используется комбинация инструментов браузерной автоматизации и сравнения изображений:

  • Playwright или Puppeteer — для рендеринга карты в headless Chromium
  • pixelmatch — для покадрового сравнения изображений
  • sharp — для обработки скриншотов
  • CI-системы (GitHub Actions, GitLab CI) — для автоматизации прогонов

Типичный пайплайн:

  1. Инициализация карты
  2. Дождаться события idle
  3. Сделать скриншот canvas
  4. Сравнить с эталоном
  5. Сгенерировать diff-изображение

Снимки карты (snapshots)

Snapshot-тестирование в Mapbox GL JS основывается на захвате визуального состояния карты в конкретный момент времени.

Ключевые аспекты:

  • фиксированная камера (setCenter, setZoom, setBearing, setPitch)

  • ожидание полной загрузки стиля:

    • map.on('load')
    • map.on('idle')
  • ожидание завершения рендеринга WebGL кадра

Пример логики ожидания стабильного состояния:

  • отсутствие активных тайлов
  • отсутствие изменений в дереве слоёв
  • завершение загрузки изображений и шрифтов

Snapshot может включать:

  • базовую карту
  • отдельные слои (heatmap, fill, line, symbol)
  • состояния hover/selected

Сравнение изображений и пороговые значения

Сравнение выполняется пиксель-в-пиксель или с допустимым уровнем отклонения.

Основные подходы:

  • Strict diff — полное совпадение
  • Threshold diff — допустимое расхождение (например, 0.1–1%)
  • Perceptual diff — с учётом человеческого восприятия

Метрики:

  • количество отличающихся пикселей
  • процент различий
  • карта различий (diff image)

Причины необходимости порогов:

  • антиалиасинг WebGL
  • субпиксельное позиционирование текста
  • различия рендеринга GPU

Интеграция в CI/CD

В CI-пайплайнах визуальная регрессия используется как gate перед merge:

  • запуск headless Chromium с GPU emulation
  • фиксация окружения (Docker image)
  • хранение эталонных снимков в репозитории или artifact storage
  • генерация отчётов diff

Типичная структура пайплайна:

  • install dependencies
  • build map application
  • start static server
  • run visual tests
  • upload artifacts (diff images)

Важно учитывать:

  • таймауты рендера WebGL
  • ограничение ресурсов CI-агента
  • параллельный запуск тестов

Стратегии стабилизации тестов

Стабильность визуальных тестов в Mapbox GL JS достигается через системные ограничения:

Контроль асинхронности

  • ожидание idle
  • проверка отсутствия isSourceLoaded === false
  • polling состояния карты

Изоляция данных

  • локальные тайловые серверы
  • фикстуры GeoJSON
  • отключение внешних API

Фиксация рендер-окружения

  • единый WebGL backend (headless-gl)
  • отключение аппаратного ускорения или его фиксация
  • единый набор шрифтов

Контроль анимаций

  • отключение easing
  • установка мгновенных переходов

Тестирование стилей Mapbox

Стили являются центральной частью визуальной системы карты. Изменения в style JSON часто становятся причиной регрессий.

Проверяемые элементы:

  • порядок слоёв
  • выражения (expressions)
  • фильтры слоёв
  • источники данных (sources)
  • layout и paint свойства

Типовые сценарии:

  • изменение цвета fill слоя
  • изменение ширины line слоя
  • добавление symbol layer
  • изменение opacity при zoom level

Для каждого стиля создаётся набор фиксированных состояний карты:

  • zoom 0 / 5 / 10 / 15
  • разные регионы
  • разные состояния данных

Тестирование взаимодействий (zoom, pan)

Визуальная регрессия может включать не только статические снимки, но и состояния после взаимодействия.

Проверяются:

  • плавность zoom transitions
  • корректность cluster expansion
  • стабильность label placement
  • поведение collision detection

Сценарии:

  • zoom in → snapshot
  • zoom out → snapshot
  • pan to region → snapshot
  • rotate bearing → snapshot

Важно фиксировать:

  • скорость анимации
  • отсутствие инерции
  • завершение всех transition

Работа с тайлами и моками

Тайлы являются наиболее нестабильным источником данных в Mapbox GL JS.

Подходы к стабилизации:

Мокирование tile server

  • локальный HTTP сервер
  • фиксированные MBTiles
  • JSON/Vector tile fixtures

Кэширование

  • offline mode
  • pre-warmed cache
  • intercept network requests

Подмена источников

  • использование GeoJSON вместо vector tiles
  • упрощённые datasets

Частые ошибки и диагностика

Несинхронизированный рендер

Симптом: скриншот делается до завершения загрузки слоёв Причина: отсутствие ожидания idle Решение: добавление строгих барьеров синхронизации

Флэйки из-за шрифтов

Симптом: изменяются подписи Причина: fallback fonts Решение: локальный font stack

Различия WebGL

Симптом: разные оттенки и линии Причина: GPU различия Решение: software rendering или threshold diff

Тайловая нестабильность

Симптом: исчезающие/появляющиеся объекты Причина: обновления данных Решение: фиксация dataset snapshot

Неполные diff-изображения

Симптом: пустые или частичные сравнения Причина: преждевременный capture canvas Решение: двойная проверка isStyleLoaded + idle