Introduction
Accessibility testing tools help you catch issues early and often. Automated tools can detect about 30-40% of accessibility issues — things like missing alt text, low contrast, and invalid ARIA. The rest requires manual testing with keyboards and screen readers.
The best approach combines automated testing in CI (catch regressions), browser extensions during development (immediate feedback), and periodic manual audits (catch what automation misses).
This guide covers the most useful tools, how to integrate them, and a testing strategy that balances automation with manual verification.
Key Concepts
axe-core — The Industry Standard
// axe-core is the engine behind most a11y tools
// Install: npm install @axe-core/react (for development)
// In development — React overlay
import React from 'react';
import ReactDOM from 'react-dom/client';
if (process.env.NODE_ENV === 'development') {
import('@axe-core/react').then(axe => {
axe.default(React, ReactDOM, 1000);
// Logs violations to browser console
});
}
Lighthouse Accessibility Audit
// Run from Chrome DevTools → Lighthouse tab → Accessibility
// Or from CLI:
npx lighthouse https://example.com --only-categories=accessibility --output=json
// CI integration
npm install -D @lhci/cli
// lighthouserc.js
module.exports = {
ci: {
assert: {
assertions: {
'categories:accessibility': ['error', { minScore: 0.9 }],
},
},
},
};
Testing Strategy Pyramid
// Level 1: Automated in CI (every PR)
// - axe-core via jest-axe or Playwright
// - Lighthouse CI
// - ESLint jsx-a11y plugin
// Level 2: Development tools (during coding)
// - axe DevTools browser extension
// - Storybook a11y addon
// - CSS contrast checker
// Level 3: Manual testing (weekly/sprint)
// - Keyboard navigation walkthrough
// - Screen reader testing (VoiceOver + NVDA)
// - Zoom to 200% test
// - Color blindness simulation
Practical Examples
1. jest-axe for Unit Tests
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
describe('Button', () => {
it('should have no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
describe('Form', () => {
it('should have labeled inputs', async () => {
const { container } = render(<ContactForm />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
2. Playwright Accessibility Testing
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('homepage has no a11y violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
.analyze();
expect(results.violations).toEqual([]);
});
test('form page is accessible after interaction', async ({ page }) => {
await page.goto('/contact');
await page.fill('#email', 'invalid');
await page.click('button[type="submit"]');
// Test accessibility after error state
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
3. ESLint jsx-a11y Plugin
// .eslintrc.js
module.exports = {
extends: ['plugin:jsx-a11y/recommended'],
plugins: ['jsx-a11y'],
rules: {
'jsx-a11y/anchor-is-valid': 'error',
'jsx-a11y/click-events-have-key-events': 'error',
'jsx-a11y/no-static-element-interactions': 'error',
'jsx-a11y/img-redundant-alt': 'error',
},
};
// Catches at lint time:
// <img src="photo.jpg" /> → Error: img must have alt prop
// <div onClick={fn}> → Error: add onKeyDown and role
Best Practices
- ✅ Run axe-core in CI on every PR — catch regressions automatically
- ✅ Use ESLint jsx-a11y during development — catch issues before commit
- ✅ Test with Storybook a11y addon for component-level checks
- ✅ Combine automated tests with manual keyboard and screen reader testing
- ✅ Set Lighthouse accessibility score thresholds in CI
- ❌ Don't rely solely on automated tools — they catch only 30-40% of issues
- ❌ Don't ignore axe warnings — they often indicate real usability problems
Common Pitfalls
- Only testing the default state — test after interactions (errors, modals, dynamic content)
- Passing axe but failing screen reader testing — automated tools miss interaction patterns
- Ignoring 'needs review' results — they often reveal genuine issues
- Running accessibility tests only on static pages — SPAs need post-navigation testing
Related Guides
- Accessibility Automation in CI — Integrating tools into your pipeline
- Screen Reader Testing — Manual testing that complements automation
- WCAG Practical Guide — The standards your tools check against
- Storybook Setup Guide — a11y addon for component testing