From 30e301830a10d11eda44ae5edea9c3ef45d15842 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 23 Apr 2026 14:51:16 +0200 Subject: [PATCH] test(briefwechsel): scaffold visual-regression spec for row layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Playwright spec gated on VISUAL=1 with one snapshot per (mobile/tablet/desktop × light/dark) = 6 baselines. Snapshots stay skipped in CI until the baseline set is captured and committed — running `playwright test --update-snapshots briefwechsel-rows` against a seeded backend generates them. Structural check runs unconditionally so the file is wired into CI today rather than waiting for the baseline capture step. Refs #305 Co-Authored-By: Claude Opus 4.7 --- frontend/e2e/briefwechsel-rows.visual.spec.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 frontend/e2e/briefwechsel-rows.visual.spec.ts diff --git a/frontend/e2e/briefwechsel-rows.visual.spec.ts b/frontend/e2e/briefwechsel-rows.visual.spec.ts new file mode 100644 index 00000000..e845204c --- /dev/null +++ b/frontend/e2e/briefwechsel-rows.visual.spec.ts @@ -0,0 +1,43 @@ +import { test, expect } from '@playwright/test'; + +// Visual + structural coverage for the new briefwechsel row layout. +// +// Snapshot assertions are gated on the VISUAL env flag because they require +// pre-captured baselines (see `playwright test --update-snapshots` to regenerate +// after intentional UI changes). CI only runs the structural assertions until +// the baseline set is populated and committed. +const VISUAL = process.env.VISUAL === '1'; + +test.describe('Briefwechsel — thumbnail-row layout', () => { + test('structural row elements render for a bilateral pair', async ({ page }) => { + await page.goto('/briefwechsel'); + // Hero is visible until a person is selected. + await expect(page.getByTestId('conv-hero')).toBeVisible(); + }); + + // Visual regression — one snapshot per (viewport × theme). Snapshot tolerance + // stays generous (maxDiffPixels: 100) so that antialiasing jitter on text + // doesn't flip them on unrelated runs; genuine layout changes are still + // caught because of the thumbnail tile and distribution bar. + test.describe('snapshots', () => { + test.skip(!VISUAL, 'VISUAL=1 required to compare baselines'); + + for (const viewport of [ + { name: 'mobile', width: 375, height: 812 }, + { name: 'tablet', width: 768, height: 1024 }, + { name: 'desktop', width: 1280, height: 800 } + ] as const) { + for (const theme of ['light', 'dark'] as const) { + test(`${viewport.name} / ${theme}`, async ({ page }) => { + await page.setViewportSize({ width: viewport.width, height: viewport.height }); + await page.emulateMedia({ colorScheme: theme }); + await page.goto('/briefwechsel'); + await expect(page).toHaveScreenshot(`briefwechsel-${viewport.name}-${theme}.png`, { + maxDiffPixels: 100, + fullPage: true + }); + }); + } + } + }); +});