Set up end-to-end test infrastructure with Docker Compose test environment, Playwright config, and automated global setup/teardown that seeds a test database and creates fixtures via the API. Tests cover 11 pages across both dark/light themes for WCAG 2.0 AA accessibility (axe-core), and across 3 viewports (mobile, tablet, desktop) for horizontal overflow and touch target validation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import AxeBuilder from '@axe-core/playwright'
|
|
import { expect, test } from '@playwright/test'
|
|
|
|
import { loadFixtures } from './fixtures'
|
|
|
|
const fixtures = loadFixtures()
|
|
|
|
const pages = [
|
|
{ name: 'Home', path: '/' },
|
|
{ name: 'RunList', path: '/runs' },
|
|
{ name: 'NewRun', path: '/runs/new' },
|
|
{ name: 'RunEncounters', path: `/runs/${fixtures.runId}` },
|
|
{ name: 'GenlockeList', path: '/genlockes' },
|
|
{ name: 'NewGenlocke', path: '/genlockes/new' },
|
|
{ name: 'GenlockeDetail', path: `/genlockes/${fixtures.genlockeId}` },
|
|
{ name: 'Stats', path: '/stats' },
|
|
{ name: 'AdminGames', path: '/admin/games' },
|
|
{ name: 'AdminPokemon', path: '/admin/pokemon' },
|
|
{ name: 'AdminEvolutions', path: '/admin/evolutions' },
|
|
]
|
|
|
|
const viewports = [
|
|
{ name: 'mobile', width: 375, height: 667 },
|
|
{ name: 'tablet', width: 768, height: 1024 },
|
|
{ name: 'desktop', width: 1280, height: 800 },
|
|
] as const
|
|
|
|
for (const viewport of viewports) {
|
|
test.describe(`Mobile layout — ${viewport.name} (${viewport.width}x${viewport.height})`, () => {
|
|
test.use({
|
|
viewport: { width: viewport.width, height: viewport.height },
|
|
})
|
|
|
|
for (const { name, path } of pages) {
|
|
test(`${name} (${path}) has no overflow or touch target issues`, async ({ page }) => {
|
|
await page.goto(path, { waitUntil: 'networkidle' })
|
|
|
|
// Assert no horizontal overflow
|
|
const overflow = await page.evaluate(() => ({
|
|
scrollWidth: document.documentElement.scrollWidth,
|
|
innerWidth: window.innerWidth,
|
|
}))
|
|
expect(
|
|
overflow.scrollWidth,
|
|
`${name} at ${viewport.name}: horizontal overflow detected (scrollWidth=${overflow.scrollWidth}, innerWidth=${overflow.innerWidth})`,
|
|
).toBeLessThanOrEqual(overflow.innerWidth)
|
|
|
|
// Run axe-core target-size rule for touch target validation
|
|
const axeResults = await new AxeBuilder({ page })
|
|
.withRules(['target-size'])
|
|
.analyze()
|
|
|
|
const violations = axeResults.violations.map((v) => ({
|
|
id: v.id,
|
|
impact: v.impact,
|
|
nodes: v.nodes.length,
|
|
}))
|
|
expect(
|
|
violations,
|
|
`${name} at ${viewport.name}: ${violations.length} touch target violations:\n${JSON.stringify(violations, null, 2)}`,
|
|
).toHaveLength(0)
|
|
|
|
// Capture full-page screenshot
|
|
await page.screenshot({
|
|
path: `e2e/screenshots/${viewport.name}/${name.toLowerCase()}.png`,
|
|
fullPage: true,
|
|
})
|
|
})
|
|
}
|
|
})
|
|
}
|