import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; /* Accessibility gate (TECH_SPEC ยง9.4): zero critical/serious violations on the main templates, both locales and both themes. wcag2a/2aa + best-practice rules. */ const pages = [ { name: 'home EN', url: '/' }, { name: 'home NO', url: '/no/' }, { name: 'case study', url: '/projects/inboxintel/' }, { name: 'contact', url: '/contact/' }, { name: 'experience', url: '/experience/' }, ]; for (const p of pages) { for (const theme of ['dark', 'light'] as const) { test(`${p.name} (${theme}) has no serious a11y violations`, async ({ page }) => { // Scan the reduced-motion rendering: it's the accessible path and shows all // reveal content immediately (entrance animations are otherwise mid-flight). await page.emulateMedia({ colorScheme: theme, reducedMotion: 'reduce' }); await page.addInitScript((t) => { try { localStorage.setItem('theme', t); } catch { /* ignore */ } }, theme); await page.goto(p.url); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) .analyze(); const serious = results.violations.filter( (v) => v.impact === 'serious' || v.impact === 'critical', ); expect(serious, JSON.stringify(serious.map((v) => ({ id: v.id, nodes: v.nodes.length })))).toEqual( [], ); }); } }