Автоматизация тестов

Автоматизация тестирования в приложениях на базе CesiumJS требует учета особенностей WebGL-рендеринга, асинхронной подгрузки тайлов, геопространственных источников и состояния камеры. В отличие от классических фронтенд-приложений, здесь критичны стабильность графического контекста, детерминизм сцены и контроль времени симуляции.

Архитектура тестов обычно делится на три уровня: модульные тесты, интеграционные и end-to-end сценарии. Каждый уровень решает собственный класс задач и использует разные стратегии изоляции среды выполнения.


Модульные тесты и изоляция логики

Модульное тестирование применяется к бизнес-логике, не завязанной напрямую на WebGL. Это могут быть утилиты преобразования координат, расчеты траекторий, обработка данных тайлов или конфигурации сцен.

Чаще всего используется Jest, благодаря встроенной системе моков и возможности запускать тесты в Node.js среде без браузера.

Типичный подход — максимальное отделение логики от объектов Viewer и Scene.

// функция без зависимости от Cesium Viewer
export function normalizeHeight(height, ellipsoidRadius) {
  return height / ellipsoidRadius;
}
import { normalizeHeight } from "./math";

test("normalizes height correctly", () => {
  expect(normalizeHeight(10, 2)).toBe(5);
});

Ключевой принцип — избегание прямых вызовов CesiumJS API внутри тестируемых функций. Если зависимость неизбежна, применяется мокирование.


Мокирование объектов Cesium

Объекты Viewer, Scene, Camera, ImageryLayer обладают тяжелой инициализацией и требуют WebGL-контекста. Поэтому в модульных тестах они заменяются заглушками.

const mockScene = {
  requestRender: jest.fn(),
  primitives: {
    add: jest.fn(),
  },
};

const mockViewer = {
  scene: mockScene,
  camera: {
    setView: jest.fn(),
  },
};

Подход позволяет тестировать взаимодействия, не поднимая реальный рендеринг.

Особое внимание уделяется API, связанному с тайлами и источниками данных. Например, ImageryProvider часто заменяется фиктивным провайдером, возвращающим предсказуемые данные.


Интеграционные тесты с Cesium-контекстом

Интеграционный уровень требует запуска браузерного окружения. Здесь уже создается реальный экземпляр CesiumJS, но с ограниченным набором ресурсов.

Используются headless-браузеры, чаще всего в связке с Playwright или Cypress.

Основная задача — проверка корректности взаимодействия компонентов: камеры, сцены, сущностей и данных.

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

test("camera moves to target position", async ({ page }) => {
  await page.goto("http://localhost:3000");

  await page.evaluate(() => {
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(30, 50, 1000),
    });
  });

  const position = await page.evaluate(() =>
    viewer.camera.positionWC.clone()
  );

  expect(position).toBeDefined();
});

Ключевая сложность — нестабильность визуального состояния. Даже небольшие различия в таймингах приводят к разным результатам, поэтому проверки строятся на диапазонах значений, а не строгих равенствах.


End-to-end тестирование сцен

E2E тестирование охватывает полный цикл: загрузка сцены, подгрузка тайлов, рендеринг объектов, взаимодействие пользователя.

Используется Selenium или Playwright в режиме полного рендеринга.

Основной акцент делается на:

  • корректной загрузке 3D Tiles
  • стабильности камеры при навигации
  • отсутствии критических ошибок WebGL
  • консистентности слоев

Проверка визуального состояния часто выполняется через скриншотное тестирование.

await page.screenshot({ path: "scene.png" });
expect(await page.screenshot()).toMatchSnapshot("scene-baseline.png");

При работе с Cesium важно учитывать динамические элементы сцены: облака, атмосферу, тайлы, которые могут изменяться между запусками.


Проблемы детерминизма и времени

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

Для тестов критично фиксировать время:

viewer.clock.currentTime = Cesium.JulianDate.fromIso8601("2025-01-01T00:00:00Z");
viewer.clock.shouldAnimate = false;

Без фиксации времени тесты становятся нестабильными из-за различий в кадрах и обновлениях сцен.

Также применяется ручное управление requestRenderMode, чтобы исключить непрерывный рендеринг:

viewer.scene.requestRenderMode = true;
viewer.scene.requestRender();

Работа с WebGL и headless-окружениями

Одной из ключевых проблем автоматизации является отсутствие полноценной поддержки WebGL в headless-режимах. Некоторые CI-системы требуют использования виртуального GPU или software renderer.

Типичные стратегии:

  • использование headless=new в Chromium
  • включение --use-gl=angle
  • fallback на CPU-рендеринг
  • использование Docker-образов с поддержкой EGL

При этом поведение сцены может отличаться от реального GPU, поэтому визуальные тесты дополняются логическими проверками состояния сцены.


Тестирование 3D Tiles и потоковых данных

Работа с 3D Tiles — одна из самых сложных областей тестирования.

Основные проблемы:

  • асинхронная подгрузка узлов дерева
  • зависимость от уровня детализации (LOD)
  • кэширование тайлов
  • сетевые задержки

Для стабилизации применяются моки Tileset и подмена TileProvider.

const mockTileset = {
  ready: true,
  root: {},
  tilesLoaded: true,
  maximumScreenSpaceError: 16,
};

Также используется искусственная эмуляция загрузки:

await tileset.readyPromise;
tileset.update();

Snapshot-тестирование сцен

Snapshot-подход применяется для контроля визуальной регрессии. Однако в контексте CesiumJS он требует строгой стабилизации сцены:

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

Иначе снимки становятся недетерминированными.


Интеграция с CI/CD пайплайнами

Автоматизация тестов включается в pipeline через этапы:

  • установка зависимостей
  • запуск unit-тестов (Jest)
  • запуск headless E2E (Playwright/Cypress)
  • визуальные проверки
  • сбор артефактов (скриншоты, логи WebGL)

При использовании контейнеризации важно обеспечить наличие графического backend:

  • xvfb (виртуальный дисплей)
  • WebGL через Mesa
  • или GPU passthrough

Логирование и диагностика сцен

При тестировании сложных сцен критично сохранять диагностическую информацию:

  • состояние камеры (position, direction, up)
  • список загруженных тайлов
  • ошибки WebGL контекста
  • FPS и frame time
console.log(viewer.scene.debugShowFramesPerSecond);
console.log(viewer.scene.globe.tilesLoaded);

Это позволяет воспроизводить ошибки, возникающие только при определенных условиях загрузки или прокрутки сцены.


Стабилизация асинхронных процессов

CesiumJS активно использует промисы и событийную модель:

  • tileset.allTilesLoaded
  • camera.moveEnd
  • scene.postRender

В тестах применяется ожидание событий вместо таймаутов:

await new Promise(resolve => {
  viewer.scene.postRender.addEventListener(() => resolve());
});

Такой подход снижает флаки-тесты и повышает воспроизводимость сценариев.


Тестирование пользовательских взаимодействий

Сценарии взаимодействия включают:

  • зум колесом мыши
  • перемещение камеры
  • выбор объектов (picking)
  • работа с entity API

В Playwright:

await page.mouse.wheel(0, -500);
await page.mouse.click(300, 300);

Проверяется изменение состояния камеры и корректность выбранных объектов.


Контроль производительности в тестах

Автоматизированные тесты часто дополняются измерениями производительности:

  • время первой отрисовки сцены
  • время загрузки тайлов
  • FPS под нагрузкой
const start = performance.now();
await viewer.scene.render();
const duration = performance.now() - start;

Это позволяет отслеживать деградацию рендеринга при изменениях кода.