/* eslint-disable */ /** * ACCESSIBILITY AUDIT — axe-core automated WCAG check on key pages. * * Scans for: color contrast, missing labels, ARIA issues, keyboard nav, * heading hierarchy, image alt text, form accessibility. * * Run headed: * E2E_MAXIMIZED=1 npx playwright test --headed --project chromium accessibility-audit --workers=1 */ import { test } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; import { attachObservers, observe, loginAndScope, OBS, BASE_URL } from '../../helpers/audit'; import { RoleName } from '../../helpers/helpers'; const M = 'Accessibility'; const ADMIN: RoleName = 'hospital_admin'; type Page = import('@playwright/test').Page; async function login(page: Page) { await page.context().clearCookies(); await loginAndScope(page, ADMIN, M); } // Pages to audit (most user-facing) const PAGES = [ { url: '/', label: 'command-center' }, { url: '/accounts/login/', label: 'login', auth: false }, { url: '/complaints/', label: 'complaint-list' }, { url: '/inquiries/', label: 'inquiry-list' }, { url: '/observations/', label: 'observation-list' }, { url: '/complaints/public/submit/', label: 'public-complaint-form', auth: false }, { url: '/observations/new/', label: 'public-observation-form', auth: false }, { url: '/core/public/submit/', label: 'public-landing', auth: false }, { url: '/analytics/dashboard/', label: 'analytics-dashboard' }, { url: '/projects/', label: 'projects-list' }, { url: '/actions/', label: 'actions-list' }, { url: '/rca/', label: 'rca-list' }, { url: '/surveys/templates/', label: 'survey-templates' }, { url: '/appreciation/', label: 'appreciation-list' }, { url: '/notifications/inbox/', label: 'notifications-inbox' }, ]; test.describe('Accessibility Audit', () => { for (const { url, label, auth = true } of PAGES) { test(`${label}: WCAG audit`, async ({ page }) => { attachObservers(page, M, ADMIN); if (auth) { await login(page); } else { await page.context().clearCookies(); } await page.goto(`${BASE_URL}${url}`); await page.waitForLoadState('domcontentloaded'); await page.waitForTimeout(1000); try { const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) .analyze(); const violations = results.violations; const critical = violations.filter(v => v.impact === 'critical'); const serious = violations.filter(v => v.impact === 'serious'); const moderate = violations.filter(v => v.impact === 'moderate'); const minor = violations.filter(v => v.impact === 'minor'); const totalNodes = violations.reduce((sum, v) => sum + v.nodes.length, 0); if (critical.length > 0 || serious.length > 0) { const topIssues = [...critical, ...serious].slice(0, 5).map(v => `${v.impact}: ${v.id} (${v.nodes.length} nodes) — ${v.description.slice(0, 80)}` ); observe(M, label, 'WARN', `${critical.length} critical, ${serious.length} serious, ${moderate.length} moderate, ${minor.length} minor (${totalNodes} nodes total). Top: ${topIssues.join('; ')}`, {}); } else if (moderate.length > 0) { observe(M, label, 'PASS', `0 critical, 0 serious, ${moderate.length} moderate, ${minor.length} minor (${totalNodes} nodes). OK for WCAG AA.`, {}); } else { observe(M, label, 'PASS', `0 critical, 0 serious, 0 moderate, ${minor.length} minor. Clean.`, {}); } } catch (e) { observe(M, label, 'WARN', `axe scan failed: ${(e as Error).message.slice(0, 100)}`, {}); } }); } }); test.afterAll(async () => { const counts = OBS.reduce>((a, o) => ((a[o.status] = (a[o.status] || 0) + 1), a), {}); const warns = OBS.filter((o) => o.status === 'WARN'); console.log('\n=========== ACCESSIBILITY SUMMARY ==========='); console.log('Total observations:', OBS.length, JSON.stringify(counts)); console.log('Pages with accessibility issues:', warns.length); for (const w of warns) { console.log(` ⚠️ ${w.module}/${w.step}: ${w.detail.slice(0, 120)}`); } console.log('=============================================\n'); });