test(visual): regression baselines for stable list/landing pages
All checks were successful
Build & Push Docker Images / lint (pull_request) Successful in 1m7s
Build & Push Docker Images / build-and-push (pull_request) Has been skipped

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>
This commit is contained in:
Matt Ciaccio
2026-04-27 15:42:40 +02:00
parent 65b241805e
commit ea8181d108
8 changed files with 79 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
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,
});
});
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB