Files
ResumeSite/tests/e2e/a11y.spec.ts
T
cesnimda 033c9ec315 test: vitest unit + playwright e2e suites; fix a11y, contrast, no-JS reveal
- vitest: dictionary parity, slug-map bijection, content-schema validation, JSON-LD (23)
- playwright: both locales, language-switch mapping (incl. no-JS), CV downloads,
  form happy-path + honeypot, axe a11y on 5 templates x 2 themes (23)
- fix: raise ink-faint + light accent to meet WCAG AA 4.5:1 (axe-verified)
- fix: gate reveal animations behind html.js so content is visible without JS
  (progressive enhancement - was hidden at opacity 0); scan reduced-motion path
- fix: validate contact form before the anti-bot time-trap (no false success)
- chore: ESLint node globals, typed diagram props, resvg fontFiles, prettier pass

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 06:14:18 +02:00

42 lines
1.5 KiB
TypeScript

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(
[],
);
});
}
}