New `visual` project covers six low-volatility screens — portal login, dashboard, and the four core lists (clients/yachts/berths/invoices) — with full-page screenshots that diff to a 2% pixel-ratio tolerance. Animations and the cursor caret are disabled inline so transient rendering doesn't trigger flaky diffs. Detail screens (yacht detail, EOI dialog, invoice form steps) are intentionally deferred until we have stable per-id fixtures so snapshots don't drift with seed data. Regenerate with: pnpm exec playwright test --project=visual --update-snapshots Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
|
|
import { login, navigateTo } from '../smoke/helpers';
|
|
|
|
/**
|
|
* Visual regression baselines for stable list/landing pages.
|
|
*
|
|
* On first run (or after intentional UI changes), regenerate:
|
|
* pnpm exec playwright test --project=visual --update-snapshots
|
|
*
|
|
* Subsequent runs diff against the committed PNGs under
|
|
* tests/e2e/visual/snapshots.spec.ts-snapshots/.
|
|
*
|
|
* Pages chosen are list/landing screens that don't depend on per-row
|
|
* fixture data — they tolerate seed drift between runs. Detail screens
|
|
* (yacht detail, EOI dialog, invoice form review) are intentionally
|
|
* deferred until we have stable fixtures wired up.
|
|
*/
|
|
|
|
const PAGES = [
|
|
{ name: 'portal-login', path: '/portal/login', requireAuth: false },
|
|
{ name: 'dashboard', path: '/dashboard', requireAuth: true },
|
|
{ name: 'clients-list', path: '/clients', requireAuth: true },
|
|
{ name: 'yachts-list', path: '/yachts', requireAuth: true },
|
|
{ name: 'berths-list', path: '/berths', requireAuth: true },
|
|
{ name: 'invoices-list', path: '/invoices', requireAuth: true },
|
|
] as const;
|
|
|
|
async function settle(page: Page) {
|
|
// Quiet the page so dynamic content (timers, spinners, blinking cursors)
|
|
// doesn't cause flaky pixel diffs.
|
|
await page.addStyleTag({
|
|
content: `
|
|
*, *::before, *::after {
|
|
animation-duration: 0s !important;
|
|
animation-delay: 0s !important;
|
|
transition-duration: 0s !important;
|
|
transition-delay: 0s !important;
|
|
caret-color: transparent !important;
|
|
}
|
|
`,
|
|
});
|
|
await page.waitForLoadState('networkidle');
|
|
// Tiny pause to let TanStack Query flush
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
test.describe('Visual regression', () => {
|
|
for (const p of PAGES) {
|
|
test(`${p.name} matches baseline`, async ({ page }) => {
|
|
if (p.requireAuth) {
|
|
await login(page, 'super_admin');
|
|
await navigateTo(page, p.path);
|
|
} else {
|
|
await page.goto(p.path);
|
|
}
|
|
|
|
await settle(page);
|
|
|
|
await expect(page).toHaveScreenshot(`${p.name}.png`, {
|
|
fullPage: true,
|
|
// Tolerate small text-rendering differences across machines/runs.
|
|
maxDiffPixelRatio: 0.02,
|
|
});
|
|
});
|
|
}
|
|
});
|