Визуальное регрессионное тестирование

Визуальное регрессионное тестирование для Kepler.gl представляет собой проверку стабильности визуального результата картографических слоёв и интерфейса при изменениях кода, данных или окружения рендера. В отличие от классических unit-тестов, здесь объектом контроля становится изображение: карта, её слои, подписи, легенды, взаимодействие цветов, шрифтов и геометрии.

Геовизуализация отличается высокой чувствительностью к изменениям рендера. Даже незначительные модификации могут приводить к визуальным сдвигам:

  • различия в WebGL-рендеринге между версиями браузеров
  • изменение алгоритмов интерполяции цветов
  • округление координат при трансформациях
  • обновление Mapbox-стилей
  • асинхронная загрузка тайлов и шрифтов
  • порядок отрисовки слоёв

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

Основная идея подхода snapshot-based testing

Базовая модель тестирования:

  1. Рендер карты с заданной конфигурацией
  2. Создание скриншота canvas или DOM-контейнера
  3. Сравнение с эталонным изображением
  4. Подсветка пиксельных различий
  5. Оценка допустимого порога отклонений

Ключевое требование — воспроизводимость состояния карты.

Проблемы детерминированности в Kepler.gl

WebGL и GPU-рендеринг

WebGL не гарантирует идентичность пикселей на разных устройствах. Даже одинаковые шейдеры могут давать различия из-за:

  • драйверов GPU
  • floating-point precision
  • оптимизаций браузера

Асинхронные данные

Карта может зависеть от:

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

Любая асинхронность делает тест нестабильным без мокирования.

Камера и viewport

Малейшее изменение:

  • zoom
  • pitch
  • bearing
  • center

приводит к полностью отличающемуся изображению.

Архитектура тестового окружения

Типичная структура визуального тестирования Kepler.gl:

tests/
  visual/
    fixtures/
    snapshots/
    configs/
    helpers/

Компоненты:

  • fixtures — входные данные (GeoJSON, CSV)
  • configs — конфигурации Kepler.gl (layers, filters, mapsState)
  • snapshots — эталонные изображения
  • helpers — рендеринг и захват canvas

Фиксация состояния приложения

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

Карта

const MAP_CONFIG = {
  latitude: 37.7749,
  longitude: -122.4194,
  zoom: 10,
  pitch: 0,
  bearing: 0
};

Стили

Используется фиксированная версия map style:

  • Mapbox Light
  • или локально сохранённый стиль JSON

Данные

Все входные данные должны быть статичными:

  • CSV фиксированной версии
  • GeoJSON без внешних ссылок
  • отсутствие API-зависимостей

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

На практике используются несколько подходов.

Jest + image snapshot

Один из наиболее распространённых вариантов:

  • jest-image-snapshot
  • pixelmatch
  • sharp для обработки изображений

Пример базового теста:

import { toMatchImageSnapshot } from 'jest-image-snapshot';
expect.extend({ toMatchImageSnapshot });

test('kepler map render', async () => {
  const image = await renderKeplerMap(config, data);
  expect(image).toMatchImageSnapshot({
    failureThreshold: 0.01,
    failureThresholdType: 'percent'
  });
});

Рендеринг Kepler.gl в тестовой среде

Headless Chromium

Чаще всего используется:

  • Puppeteer
  • Playwright

Принцип:

  1. Поднимается headless браузер
  2. Загружается приложение с Kepler.gl
  3. Применяется конфигурация
  4. Делается скриншот canvas

Пример:

const browser = await chromium.launch();
const page = await browser.newPage();

await page.goto('http://localhost:3000');

await page.evaluate((config, data) => {
  window.renderKepler(config, data);
}, config, data);

await page.waitForSelector('.kepler-gl-container');

const screenshot = await page.screenshot();

Изоляция WebGL окружения

Для повышения стабильности:

  • отключаются анимации
  • фиксируется deviceScaleFactor
  • блокируются сетевые запросы
  • отключаются переходные состояния UI
await page.emulateMedia({ reducedMotion: 'reduce' });

Борьба с флаками

Визуальные тесты часто нестабильны без нормализации.

1. Маскирование областей

Используется для динамических элементов:

  • tooltips
  • timestamps
  • labels

Пример логики:

const diffOptions = {
  mask: [
    { x: 10, y: 10, width: 100, height: 50 }
  ]
};

2. Порог чувствительности

{
  threshold: 0.02
}

Позволяет игнорировать мелкие артефакты рендера.

3. Отключение нестабильных слоёв

  • анимации
  • live data
  • clustering в режиме динамики

Pixel diff стратегия

Наиболее популярные библиотеки:

  • pixelmatch
  • resemble.js

Алгоритм:

  1. Приведение изображений к одинаковому размеру
  2. Сравнение пикселей
  3. Подсчёт отличий
  4. Генерация diff-изображения
const diffPixels = pixelmatch(
  img1.data,
  img2.data,
  diff.data,
  width,
  height,
  { threshold: 0.1 }
);

Контроль WebGL-рендера

Для Kepler.gl критично:

  • фиксировать WebGL context
  • избегать floating camera transitions
  • отключать antialiasing при необходимости

Иногда используется патч:

HTMLCanvasElement.prototype.getContext = function(type) {
  if (type === 'webgl') {
    return originalGetContext(type, { preserveDrawingBuffer: true });
  }
  return originalGetContext(type);
};

CI интеграция

В CI pipeline визуальные тесты выполняются после сборки:

GitHub Actions пример

- name: Run visual tests
  run: npm run test:visual

- name: Upload diff artifacts
  uses: actions/upload-artifact@v3
  with:
    name: visual-diffs
    path: tests/visual/__diff_output__

Важно:

  • хранить эталонные скриншоты в репозитории или артефактах
  • использовать стабильные окружения (Docker)

Версионирование снапшотов

Любое изменение:

  • стиля карты
  • алгоритма кластеризации
  • рендера слоёв

может требовать обновления baseline.

Практика:

  • __snapshots__/v1
  • __snapshots__/v2

или семантические версии тестов.

Тестирование слоёв Kepler.gl

Каждый слой требует отдельной стратегии:

Point Layer

  • проверка позиции точек
  • плотности кластеров

Arc Layer

  • проверка кривизны
  • толщины линий

Heatmap Layer

  • цветовая интерполяция
  • градиенты

Hexagon Layer

  • корректность агрегации
  • стабильность биннинга

Работа с шрифтами

Различия в font rendering могут ломать тесты.

Решения:

  • локальное кеширование шрифтов
  • фиксация font stack
  • использование системных шрифтов в CI

Оптимизация времени тестов

Визуальные тесты дорогие по времени, поэтому применяются:

  • параллелизация тестов
  • сокращение viewport
  • отключение ненужных слоёв
  • headless режим без GPU ускорения (в некоторых случаях)

Стратегии устойчивости

Для сложных картографических систем применяются комбинированные подходы:

  • pixel diff + structural similarity (SSIM)
  • multi-snapshot comparison
  • layered testing (по слоям отдельно)

Типичные причины падения тестов

  • изменение Mapbox GL версии
  • обновление драйверов CI runner
  • изменение порядка отрисовки
  • изменение z-index слоёв
  • обновление данных
  • неточная фиксация viewport

Практика организации baseline

Эталонные изображения формируются:

  • вручную после стабилизации фичи
  • или автоматически при первом запуске
  • с обязательной ревизией diff

Визуальное регрессионное тестирование в Kepler.gl становится не вспомогательным инструментом, а обязательной частью контроля качества при работе с WebGL-картографией, где малейшие изменения в графическом пайплайне приводят к каскадным визуальным эффектам, требующим строгой фиксации поведения рендера.