Gating prerequisite for the CSS -> Tailwind v4 migration: a pixel-for-pixel safety net so each utility conversion can be verified against a known-good baseline. There were previously no visual tests. Approach: a lightweight Vite-served harness (NOT @playwright/experimental-ct-react) that renders one presentational leaf component in isolation, with no Python backend. Chosen because it adds zero new deps (root bun.lock untouched -> no Docker frozen-lockfile risk), reuses the existing @playwright/test + bundled chromium, and renders through the project's real Vite 8 + Tailwind v4 + token pipeline so snapshots reflect the actual build output. CT's experimental React runner on Vite 8 + React 19 was an avoidable compatibility risk. - harness.html / harness.jsx: isolated render target driven by ?component=&theme= URL params; applies themes via [data-theme] (default = bare :root Gruvbox), loads the same fonts + token layers as the app, signals font-ready for stable shots. - specs.jsx: registry of pure variant spreads for Badge, Button, Panel, SettingRow, SettingsToggle. - manifest.ts: COMPONENTS x THEMES (default, midnight, catppuccin) the spec iterates -> 15 committed baselines in __screenshots__/. - playwright.visual.config.ts: dedicated config (separate from e2e), own Vite server on port 3902, animations disabled, caret hidden. - scripts: test:visual / test:visual:update. - README: how to add a component, how to update baselines after an intentional change, and why this stays local/manual (font/anti-alias differences across OSes) rather than a blocking CI gate for now. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { defineConfig, devices } from '@playwright/test';
|
|
|
|
// Visual-regression config — SEPARATE from playwright.config.ts (e2e).
|
|
//
|
|
// The e2e suite drives the full app against a Python backend; this one renders
|
|
// individual presentational components in isolation via the Vite harness
|
|
// (src/test/visual/harness.html), so NO backend is required. It runs its own
|
|
// Vite dev server on a dedicated port and snapshots leaf components across
|
|
// themes. Local/manual only (`bun run test:visual`) — see
|
|
// src/test/visual/README.md for why it is not yet a CI gate.
|
|
const PORT = Number(process.env.VISUAL_PORT || 3902);
|
|
|
|
export default defineConfig({
|
|
testDir: './src/test/visual',
|
|
testMatch: /.*\.visual\.spec\.ts$/,
|
|
timeout: 30_000,
|
|
fullyParallel: true,
|
|
retries: 0,
|
|
reporter: [['list']],
|
|
// Flat, platform-agnostic baseline filenames (Badge-midnight.png …). These
|
|
// are committed; they are correct for the machine that generated them.
|
|
snapshotPathTemplate: '{testDir}/__screenshots__/{arg}{ext}',
|
|
expect: {
|
|
timeout: 10_000,
|
|
toHaveScreenshot: {
|
|
animations: 'disabled',
|
|
caret: 'hide',
|
|
// Small tolerance absorbs sub-pixel font anti-aliasing jitter on the
|
|
// same OS without masking real layout/color regressions.
|
|
maxDiffPixelRatio: 0.01,
|
|
},
|
|
},
|
|
use: {
|
|
baseURL: `http://localhost:${PORT}`,
|
|
headless: true,
|
|
deviceScaleFactor: 1,
|
|
// Default to Playwright's managed chromium; set PLAYWRIGHT_CHROMIUM to
|
|
// pin a specific binary (e.g. the system chromium used by e2e in CI).
|
|
...(process.env.PLAYWRIGHT_CHROMIUM
|
|
? { launchOptions: { executablePath: process.env.PLAYWRIGHT_CHROMIUM } }
|
|
: {}),
|
|
},
|
|
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'], deviceScaleFactor: 1 } }],
|
|
webServer: {
|
|
command: 'bun run dev',
|
|
url: `http://localhost:${PORT}`,
|
|
reuseExistingServer: !process.env.CI,
|
|
timeout: 60_000,
|
|
env: { OMNIVOICE_UI_PORT: String(PORT) },
|
|
},
|
|
});
|