133 lines
4.0 KiB
TypeScript
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([])
|
|
})
|
|
}
|
|
}
|
|
})
|