From c16a9ca602b0d47d5089ed7903ea313e23e689b9 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 23 Apr 2026 14:52:38 +0200 Subject: [PATCH] test(briefwechsel): axe sweep at 3 viewports x 2 themes Adds a dedicated axe-core sweep for /briefwechsel so contrast or semantic regressions on the new row layout fail independently of the catch-all accessibility suite. Scoped to the main landmark so shared chrome violations (if any) aren't double-reported. Refs #305 Co-Authored-By: Claude Opus 4.7 --- frontend/e2e/briefwechsel-a11y.spec.ts | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 frontend/e2e/briefwechsel-a11y.spec.ts diff --git a/frontend/e2e/briefwechsel-a11y.spec.ts b/frontend/e2e/briefwechsel-a11y.spec.ts new file mode 100644 index 00000000..af2dbf97 --- /dev/null +++ b/frontend/e2e/briefwechsel-a11y.spec.ts @@ -0,0 +1,44 @@ +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. + +const VIEWPORTS = [ + { name: 'mobile', width: 375, height: 812 }, + { name: 'tablet', width: 768, height: 1024 }, + { name: 'desktop', width: 1280, height: 800 } +] as const; + +const THEMES = ['light', 'dark'] as const; + +test.describe('Accessibility — /briefwechsel row layout', () => { + 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.waitForSelector('[data-hydrated]'); + + const results = await new AxeBuilder({ page }) + .withTags(['wcag2a', 'wcag2aa']) + .include('main') + .analyze(); + + if (results.violations.length > 0) { + const summary = results.violations + .map((v) => `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`) + .join('\n'); + console.log( + `\nAccessibility violations on briefwechsel (${vp.name}/${theme}):\n${summary}` + ); + } + + expect(results.violations).toEqual([]); + }); + } + } +});