ghostguild-org/e2e/a11y.spec.js
Jennie Robinson Faber c40f2c7c63 fix: accessibility improvements and test infrastructure hardening
Add aria-labels to form controls (selects, checkboxes, switches), set
html lang attribute and page title, fix color contrast for --candle-dim
and --text-faint tokens, underline inline links, remove opacity hack.
Harden dev login endpoints with atomic findOneAndUpdate and tokenVersion
in JWT. Update Playwright timeouts and E2E test helpers.
2026-04-05 21:59:02 +01:00

115 lines
3.6 KiB
JavaScript

import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
import { loginAsAdmin } from './helpers/auth.js'
const publicPages = [
{ name: 'Home', path: '/' },
{ name: 'Join', path: '/join' },
{ name: 'Events', path: '/events' },
{ name: 'Coming Soon', path: '/coming-soon' },
]
const memberPages = [
{ name: 'Member Dashboard', path: '/member/dashboard' },
{ name: 'Member Profile', path: '/member/profile' },
]
const adminPages = [
{ name: 'Admin Members', path: '/admin/members' },
{ name: 'Admin Events Create', path: '/admin/events/create' },
]
test.describe('accessibility — public pages', () => {
for (const { name, path } of publicPages) {
test(`${name} has no critical a11y violations`, async ({ page }) => {
await page.goto(path)
await page.waitForLoadState('networkidle')
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
const critical = results.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
)
expect(critical, `${name} has critical/serious a11y issues`).toEqual([])
})
}
})
test.describe('accessibility — member pages', () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(page)
})
for (const { name, path } of memberPages) {
test(`${name} has no critical a11y violations`, async ({ page }) => {
await page.goto(path)
await page.waitForLoadState('networkidle')
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
const critical = results.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
)
expect(critical, `${name} has critical/serious a11y issues`).toEqual([])
})
}
})
test.describe('accessibility — admin pages', () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(page)
})
for (const { name, path } of adminPages) {
test(`${name} has no critical a11y violations`, async ({ page }) => {
await page.goto(path)
await page.waitForLoadState('networkidle')
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze()
const critical = results.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
)
expect(critical, `${name} has critical/serious a11y issues`).toEqual([])
})
}
})
test.describe('keyboard navigation', () => {
test('tab through join form fields in order', async ({ page }) => {
await page.goto('/join')
await page.waitForLoadState('networkidle')
// Focus the name field and tab through
await page.locator('#join-name').focus()
expect(await page.locator('#join-name').evaluate((el) => el === document.activeElement)).toBe(true)
await page.keyboard.press('Tab')
// Email field should receive focus next
expect(await page.locator('#join-email').evaluate((el) => el === document.activeElement)).toBe(true)
})
test('escape closes login modal', async ({ page }) => {
await page.goto('/member/dashboard')
// Wait for the sign-in prompt to appear, then click to open the modal
await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible({ timeout: 10000 })
await page.getByRole('button', { name: 'Sign In' }).click()
const modalTitle = page.locator('.modal-title')
await expect(modalTitle).toBeVisible({ timeout: 5000 })
await page.keyboard.press('Escape')
// Modal should close
await expect(modalTitle).not.toBeVisible({ timeout: 5000 })
})
})