154 lines
4.2 KiB
TypeScript
154 lines
4.2 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import AxeBuilder from '@axe-core/playwright'
|
|
import * as fs from 'fs'
|
|
import * as path from 'path'
|
|
|
|
/**
|
|
* Accessibility test suite using axe-core
|
|
*
|
|
* Tests pages against WCAG 2.1 Level AA standards
|
|
* Generates JSON results for report generation
|
|
*/
|
|
|
|
// Pages to test
|
|
const PAGES = [
|
|
{ url: '/', name: 'Homepage' },
|
|
{ url: '/entities', name: 'Entity List' },
|
|
{ url: '/timeline', name: 'Timeline' },
|
|
{ url: '/about', name: 'About' }
|
|
]
|
|
|
|
// WCAG levels to test
|
|
const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']
|
|
|
|
// Result storage
|
|
const results: any[] = []
|
|
|
|
test.describe('Accessibility Tests', () => {
|
|
test.afterAll(async () => {
|
|
// Save results to file for report generation
|
|
const resultsDir = path.join(process.cwd(), 'test-results')
|
|
if (!fs.existsSync(resultsDir)) {
|
|
fs.mkdirSync(resultsDir, { recursive: true })
|
|
}
|
|
|
|
const resultsFile = path.join(resultsDir, 'a11y-results.json')
|
|
fs.writeFileSync(resultsFile, JSON.stringify(results, null, 2))
|
|
|
|
console.log(`Accessibility results saved to: ${resultsFile}`)
|
|
})
|
|
|
|
for (const { url, name } of PAGES) {
|
|
test(`${name} meets WCAG standards`, async ({ page }) => {
|
|
await page.goto(url)
|
|
|
|
// Wait for page to be fully loaded
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
// Run accessibility scan
|
|
const accessibilityScanResults = await new AxeBuilder({ page })
|
|
.withTags(WCAG_TAGS)
|
|
.analyze()
|
|
|
|
// Store results
|
|
results.push({
|
|
url,
|
|
name,
|
|
timestamp: new Date().toISOString(),
|
|
violations: accessibilityScanResults.violations,
|
|
passes: accessibilityScanResults.passes,
|
|
incomplete: accessibilityScanResults.incomplete
|
|
})
|
|
|
|
// Log violations for immediate feedback
|
|
if (accessibilityScanResults.violations.length > 0) {
|
|
console.log(`\n[ERROR] ${name} has ${accessibilityScanResults.violations.length} violations:`)
|
|
|
|
accessibilityScanResults.violations.forEach(violation => {
|
|
console.log(` - [${violation.impact}] ${violation.id}: ${violation.description}`)
|
|
console.log(` Affected: ${violation.nodes.length} elements`)
|
|
})
|
|
} else {
|
|
console.log(`\n[OK] ${name} has no violations`)
|
|
}
|
|
|
|
// Fail test if violations found
|
|
expect(accessibilityScanResults.violations).toEqual([])
|
|
})
|
|
}
|
|
|
|
test('scan for color contrast issues', async ({ page }) => {
|
|
await page.goto('/')
|
|
|
|
const results = await new AxeBuilder({ page })
|
|
.withRules(['color-contrast'])
|
|
.analyze()
|
|
|
|
expect(results.violations).toEqual([])
|
|
})
|
|
|
|
test('scan for keyboard navigation', async ({ page }) => {
|
|
await page.goto('/')
|
|
|
|
const results = await new AxeBuilder({ page })
|
|
.withRules(['keyboard'])
|
|
.analyze()
|
|
|
|
expect(results.violations).toEqual([])
|
|
})
|
|
|
|
test('scan for ARIA usage', async ({ page }) => {
|
|
await page.goto('/')
|
|
|
|
const results = await new AxeBuilder({ page })
|
|
.withTags(['cat.aria'])
|
|
.analyze()
|
|
|
|
expect(results.violations).toEqual([])
|
|
})
|
|
})
|
|
|
|
test.describe('Form Accessibility', () => {
|
|
test('forms have proper labels', async ({ page }) => {
|
|
// Test pages with forms
|
|
const formPages = ['/signup', '/login', '/entities/new']
|
|
|
|
for (const url of formPages) {
|
|
try {
|
|
await page.goto(url, { timeout: 5000 })
|
|
|
|
const results = await new AxeBuilder({ page })
|
|
.withRules(['label', 'label-title-only'])
|
|
.analyze()
|
|
|
|
expect(results.violations).toEqual([])
|
|
} catch (e) {
|
|
// Skip if page doesn't exist
|
|
console.log(`Skipping ${url} - page not found`)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
test.describe('Interactive Elements', () => {
|
|
test('buttons have accessible names', async ({ page }) => {
|
|
await page.goto('/')
|
|
|
|
const results = await new AxeBuilder({ page })
|
|
.withRules(['button-name'])
|
|
.analyze()
|
|
|
|
expect(results.violations).toEqual([])
|
|
})
|
|
|
|
test('links have accessible text', async ({ page }) => {
|
|
await page.goto('/')
|
|
|
|
const results = await new AxeBuilder({ page })
|
|
.withRules(['link-name'])
|
|
.analyze()
|
|
|
|
expect(results.violations).toEqual([])
|
|
})
|
|
})
|