From d1a69e0b438ff890f87ac37016888863f9464abd Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 23 Apr 2026 20:44:58 +0200 Subject: [PATCH] test(briefwechsel): a11y spec seeds bilateral pair and axes the row layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous version navigated to /briefwechsel with no params, which renders the hero state — axe-core scanned the hero, not the new ThumbnailRow / ConversationThumbnail / DistributionBar. This commit seeds two persons + one document via the API in beforeAll, then drives the URL with ?senderId=X&receiverId=Y so each of the 36 test runs (3 viewports × 2 themes × 2 assertions) actually scans the intended DOM. Also asserts that conv-person-bar is visible first, so a regression that drops the page back to hero fails explicitly rather than silently passing an empty sweep. Refs #305 Fixes @saraholt blocker 1 from PR review Co-Authored-By: Claude Sonnet 4.6 --- frontend/e2e/briefwechsel-a11y.spec.ts | 42 +++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/frontend/e2e/briefwechsel-a11y.spec.ts b/frontend/e2e/briefwechsel-a11y.spec.ts index af2dbf97..2a001afb 100644 --- a/frontend/e2e/briefwechsel-a11y.spec.ts +++ b/frontend/e2e/briefwechsel-a11y.spec.ts @@ -2,9 +2,9 @@ import AxeBuilder from '@axe-core/playwright'; import { test, expect } from '@playwright/test'; // Accessibility coverage specifically for the briefwechsel thumbnail-row -// layout. Runs axe-core (wcag2a + wcag2aa) across three viewports and two -// color schemes so color-contrast regressions on the meta text or the -// distribution bar are caught independently of the site-wide sweep. +// layout. Seeds two persons + a bilateral document via the API so the page +// reaches the results state (not the hero), then runs axe-core +// (wcag2a + wcag2aa) across three viewports and two color schemes. const VIEWPORTS = [ { name: 'mobile', width: 375, height: 812 }, @@ -14,15 +14,49 @@ const VIEWPORTS = [ const THEMES = ['light', 'dark'] as const; +let senderId: string; +let receiverId: string; + test.describe('Accessibility — /briefwechsel row layout', () => { + test.beforeAll(async ({ request }) => { + const timestamp = Date.now(); + const senderRes = await request.post('/api/persons', { + data: { firstName: 'A11y', lastName: `Sender-${timestamp}` } + }); + if (!senderRes.ok()) throw new Error(`Create sender failed: ${senderRes.status()}`); + senderId = (await senderRes.json()).id; + + const receiverRes = await request.post('/api/persons', { + data: { firstName: 'A11y', lastName: `Receiver-${timestamp}` } + }); + if (!receiverRes.ok()) throw new Error(`Create receiver failed: ${receiverRes.status()}`); + receiverId = (await receiverRes.json()).id; + + const docRes = await request.post('/api/documents', { + multipart: { + title: 'A11y Test Brief', + documentDate: '1950-06-15', + senderId, + receiverIds: receiverId + } + }); + if (!docRes.ok()) throw new Error(`Create document failed: ${docRes.status()}`); + }); + for (const vp of VIEWPORTS) { for (const theme of THEMES) { test(`${vp.name} / ${theme} has no wcag2a/wcag2aa violations`, async ({ page }) => { await page.setViewportSize({ width: vp.width, height: vp.height }); await page.emulateMedia({ colorScheme: theme }); - await page.goto('/briefwechsel'); + await page.goto( + `/briefwechsel?senderId=${encodeURIComponent(senderId)}&receiverId=${encodeURIComponent(receiverId)}` + ); await page.waitForSelector('[data-hydrated]'); + // Assert we actually reached the row layout, not the hero — otherwise + // the axe sweep silently scans the wrong DOM. + await expect(page.getByTestId('conv-person-bar')).toBeVisible(); + const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) .include('main')