diff --git a/web/e2e/accessibility.spec.ts b/web/e2e/accessibility.spec.ts new file mode 100644 index 0000000..d0451ed --- /dev/null +++ b/web/e2e/accessibility.spec.ts @@ -0,0 +1,39 @@ +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 }) => { + await page.goto('/dashboard'); + await page.waitForLoadState('networkidle'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + + const focused = page.locator(':focus-visible'); + const count = await focused.count(); + expect(count).toBeGreaterThan(0); +});