Files
DevRunbook-Public/tests/e2e/phase-fourteen-accessibility.spec.ts
DevRunbook release export cfd2804e27
Managed validation / full (push) Successful in 3m18s
Publish DevRunbook source
2026-09-03 04:09:17 +02:00

133 lines
4.0 KiB
TypeScript

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([])
})
}
}
})