# Accessibility Testing Guide Comprehensive guide for implementing accessibility testing in Next.js applications. ## Overview Accessibility testing ensures applications are usable by people with disabilities and comply with WCAG standards. ## Tools ### axe-core Industry-standard accessibility testing engine that detects WCAG violations. **Installation:** ```bash npm install -D @axe-core/playwright jest-axe ``` ### @axe-core/playwright Playwright integration for axe-core enabling E2E accessibility testing. ### jest-axe Jest/Vitest matcher for accessibility assertions in component tests. ## Component-Level Testing ### Setup ```typescript import { axe, toHaveNoViolations } from 'jest-axe' expect.extend(toHaveNoViolations) ``` ### Basic Usage ```typescript it('has no accessibility violations', async () => { const { container } = render() const results = await axe(container) expect(results).toHaveNoViolations() }) ``` ### Testing Specific Elements ```typescript it('form has no violations', async () => { const { container } = render() const form = container.querySelector('form') const results = await axe(form) expect(results).toHaveNoViolations() }) ``` ### Custom Rules ```typescript const results = await axe(container, { rules: { 'color-contrast': { enabled: true }, 'valid-aria-role': { enabled: true } } }) ``` ## E2E Accessibility Testing ### Setup ```typescript import AxeBuilder from '@axe-core/playwright' ``` ### Page-Level Scanning ```typescript test('homepage meets a11y standards', async ({ page }) => { await page.goto('/') const accessibilityScanResults = await new AxeBuilder({ page }).analyze() expect(accessibilityScanResults.violations).toEqual([]) }) ``` ### Scanning Specific Regions ```typescript test('navigation is accessible', async ({ page }) => { await page.goto('/') const results = await new AxeBuilder({ page }) .include('#navigation') .analyze() expect(results.violations).toEqual([]) }) ``` ### Excluding Elements ```typescript const results = await new AxeBuilder({ page }) .exclude('#third-party-widget') .analyze() ``` ### Custom Tags Test specific WCAG levels: ```typescript // WCAG 2.1 Level AA const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) .analyze() ``` ## Common Violations and Fixes ### Missing Alt Text **Violation:** Images without alt attributes **Fix:** ```tsx // Bad // Good User avatar // Decorative images ``` ### Form Labels **Violation:** Form inputs without labels **Fix:** ```tsx // Bad // Good // Or use aria-label ``` ### Color Contrast **Violation:** Insufficient contrast ratio **Fix:** - Use contrast ratio of at least 4.5:1 for normal text - Use contrast ratio of at least 3:1 for large text - Test with tools like WebAIM Contrast Checker ### Heading Hierarchy **Violation:** Skipped heading levels **Fix:** ```tsx // Bad

Page Title

Section

// Good

Page Title

Section

``` ### Keyboard Navigation **Violation:** Interactive elements not keyboard accessible **Fix:** ```tsx // Bad
Click me
// Good // Or add keyboard handlers
{ if (e.key === 'Enter' || e.key === ' ') { handleClick() } }} > Click me
``` ### Focus Indicators **Violation:** Invisible focus indicators **Fix:** ```css /* Ensure visible focus */ :focus-visible { outline: 2px solid blue; outline-offset: 2px; } ``` ## ARIA Best Practices ### Landmarks ```tsx
``` ### Live Regions ```tsx
Form submitted successfully
Error: Please correct the following fields
``` ### Dynamic Content ```tsx
{/* Menu items */}
``` ## Testing Checklist - [ ] All images have alt text - [ ] Form inputs have labels - [ ] Heading hierarchy is logical - [ ] Color contrast meets WCAG AA - [ ] Keyboard navigation works - [ ] Focus indicators are visible - [ ] ARIA attributes are correct - [ ] Dynamic content announces properly - [ ] No violations in axe scans - [ ] Screen reader tested (optional but recommended) ## CI Integration ### GitHub Actions ```yaml - name: Run accessibility tests run: npm run test:e2e -- --grep @a11y - name: Upload a11y results uses: actions/upload-artifact@v3 with: name: accessibility-results path: test-results/ ``` ### Failed Test Reporting ```typescript test('check accessibility', async ({ page }) => { await page.goto('/') const results = await new AxeBuilder({ page }).analyze() if (results.violations.length > 0) { console.log('Accessibility violations:') results.violations.forEach(violation => { console.log(`- ${violation.id}: ${violation.description}`) console.log(` Impact: ${violation.impact}`) console.log(` Elements: ${violation.nodes.length}`) }) } expect(results.violations).toEqual([]) }) ``` ## Resources - [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/) - [axe-core Documentation](https://github.com/dequelabs/axe-core) - [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) - [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)