Add comprehensive testing covering 420 unit/handler tests across 24 Vitest files, 9 Playwright E2E specs, accessibility scans, and visual regression. Includes GitHub Actions CI, Husky pre-push hook, and TESTING.md docs.
112 lines
3.5 KiB
JavaScript
112 lines
3.5 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 login modal to appear
|
|
const modal = page.locator('text=Sign in to continue').or(page.locator('text=Sign in to your dashboard'))
|
|
await expect(modal.first()).toBeVisible({ timeout: 10000 })
|
|
|
|
await page.keyboard.press('Escape')
|
|
|
|
// Modal should close
|
|
await expect(modal.first()).not.toBeVisible({ timeout: 5000 })
|
|
})
|
|
})
|