From 4c3d2530669df29f97be58df5d980876649ee2cc Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 28 Mar 2026 18:22:45 +0100 Subject: [PATCH] test(#147): add axe-core accessibility spec with color-contrast enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces the wcag2a/wcag2aa E2E suite from the test-suite branch with the color-contrast rule active — no disableRules exclusion. Also adds /coverage/ to .prettierignore so generated lcov reports don't fail the lint hook. This commit intentionally fails the axe suite until the contrast fixes land in the next commits. Co-Authored-By: Claude Sonnet 4.6 --- frontend/.prettierignore | 1 + frontend/e2e/accessibility.spec.ts | 58 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 frontend/e2e/accessibility.spec.ts diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 2e8d4019..dc7a1d03 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -16,3 +16,4 @@ bun.lockb # Test artifacts /test-results/ /e2e/.auth/ +/coverage/ diff --git a/frontend/e2e/accessibility.spec.ts b/frontend/e2e/accessibility.spec.ts new file mode 100644 index 00000000..06ac90ab --- /dev/null +++ b/frontend/e2e/accessibility.spec.ts @@ -0,0 +1,58 @@ +import AxeBuilder from '@axe-core/playwright'; +import { test, expect } from '@playwright/test'; + +/** + * Automated accessibility checks using axe-core (wcag2a + wcag2aa). + * Authenticated pages use the stored admin session from playwright.config.ts. + * The login page test overrides to an unauthenticated context. + */ + +const AUTHENTICATED_PAGES = [ + { name: 'home', path: '/' }, + { name: 'persons', path: '/persons' }, + { name: 'admin', path: '/admin' } +]; + +function buildAxe(page: Parameters[0]['page']) { + return new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']); +} + +test.describe('Accessibility — authenticated pages', () => { + for (const { name, path } of AUTHENTICATED_PAGES) { + test(`${name} page has no critical wcag2a/wcag2aa violations`, async ({ page }) => { + await page.goto(path); + await page.waitForSelector('[data-hydrated]'); + + const results = await buildAxe(page).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 ${name}:\n${summary}`); + } + + expect(results.violations).toEqual([]); + }); + } +}); + +test.describe('Accessibility — login page', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('login page has no critical wcag2a/wcag2aa violations', async ({ page }) => { + await page.goto('/login'); + await expect(page.getByLabel('Benutzername')).toBeVisible(); + + const results = await buildAxe(page).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 login:\n${summary}`); + } + + expect(results.violations).toEqual([]); + }); +});