import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; const routes = ['/', '/dashboard', '/workspaces', '/search', '/reviews']; for (const route of routes) { test(`accessibility: ${route} has no critical violations`, async ({ page }) => { await page.goto(route); await page.waitForLoadState('networkidle'); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'best-practice']) .analyze(); const critical = results.violations.filter( (v) => v.impact === 'critical' || v.impact === 'serious' ); if (critical.length > 0) { const summary = critical .map((v) => `[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} nodes)`) .join('\n'); console.log(`Accessibility violations on ${route}:\n${summary}`); } expect(critical).toHaveLength(0); }); } test('accessibility: focus-visible ring appears on tab navigation', async ({ page }) => { // Use /login because it doesn't require auth and has known focusable // form fields. /dashboard would be redirected by AuthGuard when no // session is present, leaving the DOM empty (AuthGuard returns null // until verifySessionAndReadiness completes) and Tab presses would // focus nothing. await page.goto('/login'); await page.waitForLoadState('networkidle'); // Click the page first to ensure keyboard focus tracking starts. await page.locator('body').click(); await page.keyboard.press('Tab'); const focused = page.locator(':focus-visible'); const count = await focused.count(); expect(count).toBeGreaterThan(0); });