import { test, expect } from '@chromatic-com/playwright'; import AxeBuilder from '@axe-core/playwright'; import { loginViaAPI, navigateTo } from '../helpers'; import { TEST_USERS, ROUTES } from '../design-tokens'; test.describe('ACCESSIBILITÉ — axe-core WCAG AA sur chaque page', () => { // --- Pages publiques --- for (const route of ROUTES.public) { test(`[PUBLIC] ${route.name} (${route.path}) — zéro violation WCAG AA critique`, async ({ page }) => { await navigateTo(page, route.path); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21aa']) .exclude('[aria-hidden="true"]') // Ignorer les éléments cachés .analyze(); const critical = results.violations.filter(v => v.impact === 'critical' || v.impact === 'serious'); for (const violation of results.violations) { console.log(`[AXE] [${violation.impact?.toUpperCase()}] ${violation.id}: ${violation.description}`); console.log(` Help: ${violation.helpUrl}`); for (const node of violation.nodes.slice(0, 3)) { console.log(` Element: ${node.html.slice(0, 100)}`); console.log(` FIX: ${node.failureSummary}`); } } expect(critical.length, `${critical.length} violation(s) WCAG critique(s) sur ${route.path}:\n` + critical.map(v => `• [${v.impact}] ${v.id}: ${v.description}\n` + ` URL: ${v.helpUrl}\n` + v.nodes.slice(0, 3).map(n => ` Element: ${n.html.slice(0, 80)}\n FIX: ${n.failureSummary}` ).join('\n') ).join('\n\n') ).toBe(0); }); } // --- Pages protégées --- for (const route of ROUTES.listener.slice(0, 12)) { test(`[PROTECTED] ${route.name} (${route.path}) — zéro violation WCAG AA critique`, async ({ page }) => { await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password); await navigateTo(page, route.path); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21aa']) .exclude('[aria-hidden="true"]') .analyze(); const critical = results.violations.filter(v => v.impact === 'critical' || v.impact === 'serious'); for (const violation of results.violations) { console.log(`[AXE] [${violation.impact?.toUpperCase()}] ${violation.id}: ${violation.description}`); for (const node of violation.nodes.slice(0, 3)) { console.log(` Element: ${node.html.slice(0, 100)}`); console.log(` FIX: ${node.failureSummary}`); } } expect(critical.length, `${critical.length} violation(s) WCAG critique(s) sur ${route.path}:\n` + critical.map(v => `• [${v.impact}] ${v.id}: ${v.description}\n` + v.nodes.slice(0, 3).map(n => ` FIX: ${n.failureSummary}` ).join('\n') ).join('\n\n') ).toBe(0); }); } // --- Pages admin --- for (const route of ROUTES.admin) { test(`[ADMIN] ${route.name} (${route.path}) — zéro violation WCAG AA critique`, async ({ page }) => { await loginViaAPI(page, TEST_USERS.admin.email, TEST_USERS.admin.password); await navigateTo(page, route.path); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21aa']) .exclude('[aria-hidden="true"]') .analyze(); const critical = results.violations.filter(v => v.impact === 'critical' || v.impact === 'serious'); for (const violation of results.violations) { console.log(`[AXE] [${violation.impact?.toUpperCase()}] ${violation.id}: ${violation.description}`); for (const node of violation.nodes.slice(0, 2)) { console.log(` FIX: ${node.failureSummary}`); } } expect(critical.length, `${critical.length} violation(s) WCAG critique(s) sur ${route.path}:\n` + critical.map(v => `• [${v.impact}] ${v.id}: ${v.description}`).join('\n') ).toBe(0); }); } });