Initial commit
This commit is contained in:
153
skills/a11y-checker-ci/assets/a11y-test.spec.ts
Normal file
153
skills/a11y-checker-ci/assets/a11y-test.spec.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
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([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user