import AxeBuilder from '@axe-core/playwright' import { expect, test } from '@playwright/test' const email = process.env.DEVRUNBOOK_E2E_EMAIL const password = process.env.DEVRUNBOOK_E2E_PASSWORD const criticalRoutes = [ '/start', '/composer/new', '/repositories', '/runs', '/account', '/account/security', '/repositories/new', '/management', ] as const test.describe('Phase 14 accessibility regression', () => { test.skip(!email || !password, 'live local-account credentials are required') test.use({ storageState: 'test-results/.auth/m2.json', reducedMotion: 'reduce', }) for (const route of criticalRoutes) { test(`${route} has one main and no serious accessibility violations`, async ({ page, }) => { await page.goto(route) await expect(page.locator('main')).toHaveCount(1) await expect(page.locator('[id]')).toHaveCount( await page .locator('[id]') .evaluateAll( (elements) => new Set(elements.map((element) => element.id)).size, ), ) const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) .analyze() expect( results.violations.filter( ({ impact }) => impact === 'critical' || impact === 'serious', ), ).toEqual([]) }) } test('Start remains keyboard complete, reflows and exposes touch targets', async ({ page, }) => { await page.setViewportSize({ width: 390, height: 844 }) await page.goto('/start') await page.keyboard.press('Tab') await expect(page.locator(':focus-visible')).toHaveCount(1) expect( await page.evaluate( () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, ), ).toBe(true) const undersizedPrimaryTargets = await page .locator('main button, main a[href="/settings/integrations/gitea/new"]') .evaluateAll((elements) => elements .filter((element) => { const box = element.getBoundingClientRect() const style = getComputedStyle(element) return ( style.display !== 'none' && style.visibility !== 'hidden' && box.width > 0 && box.height > 0 && (box.width < 44 || box.height < 44) ) }) .map( (element) => element.getAttribute('aria-label') ?? element.textContent, ), ) expect(undersizedPrimaryTargets).toEqual([]) }) test('critical pages reflow at a 200 percent equivalent CSS viewport', async ({ page, }) => { await page.setViewportSize({ width: 640, height: 720 }) for (const route of criticalRoutes) { await page.goto(route) expect( await page.evaluate( () => document.documentElement.scrollWidth <= document.documentElement.clientWidth, ), ).toBe(true) } }) for (const locale of ['en', 'nl'] as const) { for (const mode of ['simple', 'expert'] as const) { test(`${locale}/${mode} presentation stays accessible`, async ({ page, context, baseURL, }) => { const origin = new URL(baseURL ?? 'http://127.0.0.1:3000') await context.addCookies([ { name: 'devrunbook_locale', value: locale, url: origin.origin }, { name: 'devrunbook_mode', value: mode, url: origin.origin }, ]) await page.goto('/start') await expect( page.getByRole('heading', { name: locale === 'nl' ? 'Wat wil je doen?' : 'What do you want to do?', }), ).toBeVisible() const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) .analyze() expect( results.violations.filter( ({ impact }) => impact === 'critical' || impact === 'serious', ), ).toEqual([]) }) } } })