// Automated accessibility scan of the AUTHENTICATED app (QA_PLAN B14). Companion // to a11y.spec.js (public pages). Runs axe-core on the main in-app pages using the // logged-in state from auth.setup, and fails on critical/serious WCAG violations // so contrast/label/role regressions are caught every cycle. const { test, expect } = require('@playwright/test'); const AxeBuilder = require('@axe-core/playwright').default; const { STORAGE_STATE } = require('./constants'); test.use({ storageState: STORAGE_STATE }); const PAGES = ['/', '/bills', '/summary', '/spending', '/analytics', '/categories', '/snowball']; for (const path of PAGES) { test(`no critical/serious a11y violations on ${path}`, async ({ page }) => { await page.goto(path); await page.waitForLoadState('networkidle'); const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']).analyze(); const blocking = results.violations.filter((v) => v.impact === 'critical' || v.impact === 'serious'); const summary = blocking .map((v) => `- [${v.impact}] ${v.id}: ${v.help} (${v.nodes.length})\n ${v.nodes.map((n) => (n.html || '').slice(0, 140)).join('\n ')}`) .join('\n'); if (blocking.length) console.log(`[a11y] ${path}\n${summary}\n`); expect.soft(blocking, `axe critical/serious on ${path}:\n${summary}`).toEqual([]); }); }