Visual battery
Run the default harness directory in update mode, or enforce committed baselines in CI:
pnpm exec game-harness-visual-battery tests/harness
pnpm exec game-harness-visual-battery tests/harness --citest-harness-visual-battery remains as a compatibility alias. Programmatic
callers can pass testCommand: { command, args }; the executable and
discovered harness paths are invoked directly rather than interpolated
through a shell.
runVisualBattery() owns one canonical __screenshots__ tree directly under
the configured harness directory. Vitest screenshot paths are relative to the
test file, so a harness must write __screenshots__/name.png, not a
repository-relative path such as tests/harness/__screenshots__/name.png.
The battery rejects any second __screenshots__ directory nested elsewhere
under the harness tree; otherwise an apparently green run could leave an
important screenshot outside the Git diff gate.
When two renderers cannot produce byte-identical PNGs, keep strict profiles
instead of adding a pixel threshold. Pass baselineProfile: 'linux'; the
battery compares __screenshots__/linux/ and exposes the same value to Vite
as VITE_VISUAL_BASELINE_PROFILE. Screenshot helpers should include that
optional directory in their path:
const profile = import.meta.env.VITE_VISUAL_BASELINE_PROFILE?.trim();
const path = profile ? `__screenshots__/${profile}/scene.png` : '__screenshots__/scene.png';baselinesDir names the baseline root. When it is combined with
baselineProfile, the profile is always appended below that root. For
example, { baselinesDir: 'visual-baselines', baselineProfile: 'linux' }
owns and diffs visual-baselines/linux/.
For WebGL scenes, capture the canvas locator instead of the full browser page:
await page.locator('canvas').screenshot({ path: '__screenshots__/scene.png' });If a canvas baseline is stable alone but changes after other harnesses have run in the same long-lived Chromium process, isolate that file so it gets a fresh browser process while the remaining files stay in one fast batch:
runVisualBattery('tests/harness', {
isolatedHarnessFiles: ['scene.browser.test.tsx'],
});