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, }); }); } });