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: 'chronik', path: '/chronik' }, { 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 — dark mode (system preference)', () => { for (const { name, path } of AUTHENTICATED_PAGES) { test(`${name} page has no wcag2a/wcag2aa violations in prefers-color-scheme: dark`, async ({ browser }) => { const context = await browser.newContext({ colorScheme: 'dark', storageState: 'e2e/.auth/user.json' }); const page = await context.newPage(); 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} (dark/media):\n${summary}`); } await context.close(); expect(results.violations).toEqual([]); }); } }); test.describe('Accessibility — dark mode (manual toggle)', () => { for (const { name, path } of AUTHENTICATED_PAGES) { test(`${name} page has no wcag2a/wcag2aa violations with data-theme='dark'`, async ({ page }) => { await page.goto(path); await page.waitForSelector('[data-hydrated]'); await page.evaluate(() => document.documentElement.setAttribute('data-theme', 'dark')); 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} (dark/manual):\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([]); }); });