Automated Accessibility Testing — Complete Guide
In this tutorial, you will learn about Automated Accessibility Testing. We cover key concepts, practical examples, and best practices to help you master this topic.
Automated accessibility testing uses tools like axe-core, Pa11y, and Lighthouse to programmatically detect WCAG violations, integrate with CI/CD pipelines, and prevent accessibility regressions before code reaches production.
What You'll Learn
- Setting up automated accessibility tests
- Integrating with CI/CD pipelines
- Configuring rule sets and thresholds
- Handling known issues and false positives
- Reporting and tracking violations over time
Why It Matters
- Automated tests catch regressions instantly
- Testing early reduces fix costs
- CI/CD integration ensures every PR is checked
- Automated tests establish a baseline and track progress
Real-World Use
- A financial services company runs axe-core on every PR
- An e-commerce site uses Lighthouse CI for performance and accessibility
- A government portal requires 100 percent accessibility pass on deployment
- A design system enforces accessibility with automated component tests
flowchart LR
A[Developer Push] --> B[CI Pipeline]
B --> C[Build Project]
C --> D[Run axe-core Tests]
D --> E{Pass?}
E -->|Yes| F[Deploy]
E -->|No| G[Block PR]
G --> H[Developer Fixes]
H --> C
Setting Up Automated Testing
Automated accessibility testing integrates into your existing testing framework. The most common approach is running axe-core in your test suite.
Code Example: Jest with axe-core
// jest.config.js
module.exports = {
testMatch: ['**/*.a11y.test.js']
};
// setup.js
const { configureAxe } = require('jest-axe');
const axe = configureAxe({
rules: {
// Skip color contrast in CI (test separately)
'color-contrast': { enabled: false },
// Skip region rule for known patterns
'region': { enabled: false }
}
});
// home.a11y.test.js
const { axe } = require('jest-axe');
describe('Homepage accessibility', () => {
beforeEach(async () => {
await page.goto('http://localhost:3000');
await page.waitForSelector('main');
});
it('should have no WCAG A or AA violations', async () => {
const results = await axe(page, {
runOnly: {
type: 'tag',
values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']
}
});
expect(results.violations).toHaveLength(0);
});
it('should have no best-practice violations', async () => {
const results = await axe(page, {
runOnly: {
type: 'tag',
values: ['best-practice']
}
});
expect(results.violations).toHaveLength(0);
});
it('should pass text spacing check', async () => {
// Test that text remains readable with increased spacing
await page.addStyleTag({
content: `
* { line-height: 1.5 !important; }
* { letter-spacing: 0.12em !important; }
* { word-spacing: 0.16em !important; }
`
});
const results = await axe(page, {
rules: { 'text-spacing': { enabled: true } }
});
expect(results.violations).toHaveLength(0);
});
});
Expected output: Tests run automatically on each test run. Violations cause the test to fail with detailed reports of which elements violate which WCAG criteria and how to fix them.
Code Example: Custom Rule Configuration
// a11y.config.js
const axeConfig = {
// Include only WCAG 2.2 AA rules
runOnly: {
type: 'tag',
values: [
'wcag2a',
'wcag2aa',
'wcag21a',
'wcag21aa',
'wcag22a',
'wcag22aa'
]
},
// Exclude specific rules known to have issues
rules: {
// Custom component with known contrast issue (tracked in issue #123)
'color-contrast': { enabled: false },
// Skip due to third-party widget
'region': { enabled: false }
},
// Custom checks
checks: [
{
id: 'custom-label-check',
evaluate: (node) => {
const label = node.querySelector('label');
const input = node.querySelector('input');
return label && input && label.getAttribute('for') === input.id;
}
}
]
};
// Usage in tests
export const createAxeInstance = () => configureAxe(axeConfig);
Expected output: Tests focus on relevant WCAG levels. Known issues are excluded with documentation references. Custom checks validate project-specific patterns.
Code Example: CI/CD Integration with GitHub Actions
name: Accessibility Checks
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
a11y:
runs-on: ubuntu-latest
strategy:
matrix:
page: [
'/',
'/products',
'/products/123',
'/checkout',
'/account'
]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Start preview server
run: |
npm run preview &
sleep 5
- name: Run axe-core on ${{ matrix.page }}
run: |
npx @axe-core/cli http://localhost:4173${{ matrix.page }} \
--exit \
--save report-${{ matrix.page }}.json
- name: Upload axe report
if: always()
uses: actions/upload-artifact@v4
with:
name: axe-report-${{ matrix.page }}
path: report-${{ matrix.page }}.json
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
Expected output: Each Pull Request triggers automated accessibility scans across all key pages. Results are uploaded as artifacts. Pull requests with violations are flagged for review.
Common Mistakes
- Enabling too many or too few rules — Focus on WCAG A and AA rules. Too many rules produce noise. Too few miss important issues.
- Not handling false positives — Some rules produce false positives for common patterns. Document and exclude them with a tracking issue reference.
- Testing only the homepage — Accessibility issues vary by page. Test all unique templates and user flows.
- Ignoring the results — Running tests but not reviewing or acting on the results defeats the purpose.
- Not testing dynamic content — Single-page apps with JavaScript-rendered content need special handling. Wait for content to render before testing.
- Failing to establish a baseline — Without a baseline, teams cannot measure progress or detect regressions meaningfully.
- Not training the team — Automated tools are only useful if the team understands how to read and fix violations.
Practice Questions
- What is the industry standard library for automated accessibility testing? axe-core (from Deque Systems).
- How do you exclude a known accessibility issue from automated tests? Disable the specific rule in the axe configuration with a comment referencing the tracking issue.
- Why should automated testing be part of the CI/CD pipeline? To catch accessibility regressions before they reach production, when they are cheapest to fix.
- What is the difference between axe-core's "violations" and "incomplete" results? Violations are confirmed accessibility issues. Incomplete means axe could not determine if there is a violation and requires manual review.
- Challenge: Set up a complete automated accessibility testing pipeline for a small project. Use axe-core for WCAG testing, Lighthouse CI for performance and accessibility scoring, and configure GitHub Actions to run on pull requests. Include at least 5 page templates. Generate a combined report with violation details and fix guidance.
FAQ
Mini Project
Create a complete automated accessibility testing infrastructure for a sample web application. Set up: axe-core tests for 5 key pages (homepage, product listing, product detail, checkout, account), Lighthouse CI configuration, a GitHub Actions workflow that runs tests on every PR, a custom axe configuration that excludes project-specific known issues, a reporting script that generates a violation summary with screenshots, and a dashboard that tracks violation counts over time. Run the pipeline and produce a sample report with at least 3 violations found and fixed.
What's Next
Continue with Lesson 27: Manual Accessibility Testing to learn manual testing techniques that complement automated tools.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro