Визуальное тестирование в приложениях на основе OpenLayers отличается от классического UI-тестирования высокой чувствительностью к рендерингу. Карта представляет собой композицию множества слоёв, каждый из которых может влиять на итоговое изображение: тайлы, векторные объекты, стили, подписи, анимации и WebGL-рендеринг.
Ключевая проблема заключается в том, что результат работы карты — это
не DOM-структура, а пиксельное изображение, формируемое в
<canvas> или WebGL-контексте. Любое изменение в
зависимости, браузере, шрифте или даже порядке загрузки тайлов может
привести к различиям на уровне пикселей.
Основные источники нестабильности:
Рендеринг в OpenLayers строится вокруг нескольких ключевых компонентов:
Каждый из этих уровней может влиять на итоговый пиксельный результат.
Особенно критичны:
EPSG:3857,
EPSG:4326)В визуальном тестировании важно зафиксировать состояние всех входных параметров:
map.getView().setCenter([0, 0]);
map.getView().setZoom(5);
map.getView().setRotation(0);
Любое отклонение от фиксированного состояния делает сравнение скриншотов нестабильным.
Базовый подход — сравнение эталонного изображения с текущим рендером карты.
Типовой цикл:
Пример с использованием Playwright:
import { test, expect } from '@playwright/test';
test('map visual regression', async ({ page }) => {
await page.goto('http://localhost:3000/map');
await page.waitForFunction(() => {
return window.mapLoaded === true;
});
const map = page.locator('#map');
await expect(map).toHaveScreenshot('map-baseline.png');
});
Ключевая проблема — необходимость стабилизации тайлов и шрифтов.
При более низкоуровневом подходе изображение преобразуется в массив пикселей и сравнивается с допустимым порогом различий.
Используется алгоритмическое сравнение:
Пример логики:
const diff = pixelmatch(img1.data, img2.data, null, width, height, {
threshold: 0.1
});
Такой подход позволяет учитывать допустимые отклонения в антиалиасинге.
Для стабильного тестирования необходимо полностью детерминировать состояние:
map.getView().animate = () => {};
map.renderSync();
Также важно отключать инерцию:
import { defaults as defaultInteractions } from 'ol/interaction';
const map = new Map({
interactions: defaultInteractions({
pinchRotate: false,
doubleClickZoom: false
})
});
Tile-based источники (XYZ, OSM, WMTS) являются основной причиной нестабильности визуальных тестов.
Причины:
Используется локальный фиктивный сервер или мокирование:
const source = new XYZ({
url: 'http://localhost:8081/tiles/{z}/{x}/{y}.png'
});
или полностью статический слой:
const source = new Static({
url: '/fixtures/tile.png',
imageExtent: [0, 0, 256, 256]
});
Векторные данные более стабильны, но подвержены другим проблемам:
Принудительная стабилизация порядка:
vectorSource.getFeatures().sort((a, b) => {
return a.getId() - b.getId();
});
Также важно фиксировать стили:
const style = new Style({
fill: new Fill({ color: 'rgba(0, 150, 255, 0.5)' }),
stroke: new Stroke({ color: '#3399CC', width: 2 })
});
OpenLayers поддерживает WebGL для ускоренной отрисовки точечных и векторных данных.
Проблема заключается в:
Для тестирования часто используется отключение WebGL:
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
const layer = new WebGLPointsLayer({
disable: true
});
или принудительный Canvas renderer:
const map = new Map({
renderer: 'canvas'
});
Наиболее стабильная среда — Chromium в headless-режиме.
Критические параметры окружения:
await page.setViewportSize({ width: 1280, height: 720 });
Для Docker-окружений часто добавляется:
Подписи на карте часто ломают визуальные тесты.
Причины:
Решения:
await page.evaluate(() => document.fonts.ready);
Используется для базового pixel diff:
Семантическое визуальное сравнение:
Интеграция с CI:
Типичный pipeline включает:
Пример структуры:
npm run start:test-server
npm run test:visual
npm run diff:images
В CI важно обеспечить:
OpenLayers рендерит сцены в несколько этапов, поэтому простого
await page.waitForTimeout недостаточно.
Используются сигналы готовности:
map.once('rendercomplete', () => {
window.mapReady = true;
});
или polling:
await page.waitForFunction(() => window.mapReady === true);
Проекционные преобразования могут давать микросдвиги.
Особенно критично:
Фиксация состояния:
view.setConstrainResolution(true);
view.setCenter([0, 0]);
view.setResolution(156543.0339);
Любые transition-эффекты нарушают визуальное сравнение.
Отключение анимации:
view.setAnimate(false);
или глобально:
map.beforeRender = () => true;
Основные подходы:
Маскирование:
const mask = {
x: 0,
y: 0,
width: 200,
height: 100
};
Baseline-изображения должны формироваться:
Часто используется:
Наиболее частые проблемы:
Визуальное тестирование в OpenLayers обычно разделяется на:
Каждый уровень требует собственной стратегии стабилизации рендера и контроля данных