WCAG Compliance Auditing and Automated Testing
Audit your site for WCAG 2.2 compliance using automated tools, manual testing checklists, and CI integration. Covers axe-core, Lighthouse, screen reader testing, and remediation.
What you'll learn
- ✓How to run a structured WCAG 2.2 compliance audit
- ✓Using axe-core and Lighthouse for automated accessibility testing
- ✓Integrating accessibility checks into CI/CD pipelines
- ✓Manual testing techniques that catch what automation misses
- ✓Prioritizing and remediating accessibility violations
Prerequisites
None — this post is self-contained.
Knowing that accessibility matters is different from knowing whether your site is actually accessible. A WCAG compliance audit answers that question systematically. It combines automated scanning tools that catch the roughly 30% of issues detectable by machines with manual testing techniques that catch the remaining 70%. This guide walks through both layers and shows how to integrate them into your development workflow.
Automated Scanning with axe-core
axe-core is the most widely used accessibility testing engine. It powers the checks in Chrome DevTools, Lighthouse, and dozens of CI tools. Run it directly in your test suite:
// accessibility.test.js
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.describe('Accessibility', () => {
test('home page has no violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
.analyze();
expect(results.violations).toEqual([]);
});
test('form page has no violations', async ({ page }) => {
await page.goto('/signup');
// Fill the form to test dynamic states
await page.fill('#email', 'test@example.com');
await page.click('#submit');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.exclude('.third-party-widget') // Exclude elements you can't control
.analyze();
expect(results.violations).toEqual([]);
});
});
The withTags method filters rules to specific WCAG levels. Start with wcag2a and wcag2aa for Level A and AA compliance, which covers the requirements most organizations need.
Interpreting Results
axe-core returns violations with impact levels: critical, serious, moderate, and minor. Each violation includes the failing element, the WCAG criterion it violates, and a suggested fix:
// Log detailed violation info for debugging
for (const violation of results.violations) {
console.log(`Rule: ${violation.id}`);
console.log(`Impact: ${violation.impact}`);
console.log(`WCAG: ${violation.tags.filter(t => t.startsWith('wcag')).join(', ')}`);
console.log(`Help: ${violation.helpUrl}`);
for (const node of violation.nodes) {
console.log(` Element: ${node.html}`);
console.log(` Fix: ${node.failureSummary}`);
}
}
Lighthouse Accessibility Audits
Lighthouse provides a scored accessibility audit that is useful for tracking progress over time:
// lighthouse-a11y.js
import lighthouse from 'lighthouse';
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const result = await lighthouse('http://localhost:3000', {
port: new URL(browser.wsEndpoint()).port,
onlyCategories: ['accessibility'],
output: 'json',
});
const score = result.lhr.categories.accessibility.score * 100;
console.log(`Accessibility score: ${score}`);
if (score < 90) {
console.error('Accessibility score below threshold');
process.exit(1);
}
await browser.close();
Run this in CI to fail builds when the accessibility score drops below your target.
CI/CD Integration
Add accessibility checks to your pull request pipeline so violations are caught before they merge:
name: Accessibility
on: [pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build
- name: Start server
run: npm run preview &
env:
PORT: 3000
- name: Wait for server
run: npx wait-on http://localhost:3000
- name: Run accessibility tests
run: npx playwright test accessibility.test.js
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: a11y-report
path: test-results/
For Storybook-based component libraries, use @storybook/addon-a11y to run axe-core on every story and storybook-addon-a11y in CI to scan all stories automatically.
Manual Testing Checklist
Automated tools cannot evaluate whether alt text is meaningful, whether focus order is logical, or whether error messages are helpful. Manual testing covers these gaps.
Keyboard Navigation
Navigate your entire application using only the keyboard:
- Tab moves focus forward, Shift+Tab moves it backward. Every interactive element must be reachable.
- Enter and Space activate buttons and links. Custom components must handle both.
- Escape closes modals and popups. Focus must return to the element that triggered them.
- Arrow keys navigate within composite widgets like menus, tabs, and listboxes.
Check that focus is always visible. If you cannot tell where focus is, your focus styles are insufficient.
Screen Reader Testing
Test with at least one screen reader:
- macOS: VoiceOver (built-in, activated with Cmd+F5)
- Windows: NVDA (free) or JAWS (commercial)
- Mobile: TalkBack (Android) or VoiceOver (iOS)
Listen for these issues:
- Images without alt text are announced as the filename, which is meaningless.
- Form inputs without labels are announced as “edit text” with no context.
- Dynamic content changes (like toast notifications) are not announced unless they use
aria-liveregions. - Custom components (dropdowns, date pickers) are unusable without proper ARIA roles and states.
Color and Contrast
Use a contrast checker to verify text meets the minimum ratios:
- Normal text: 4.5:1 contrast ratio (WCAG AA)
- Large text (18pt+ or 14pt+ bold): 3:1 contrast ratio
- Non-text elements (icons, borders, focus indicators): 3:1 contrast ratio
WCAG 2.2 added a focus appearance criterion (2.4.11) that requires focus indicators to have a minimum area and contrast, not just be visible.
Common Violations and Fixes
Here are the most frequently failing WCAG criteria and how to fix them:
Missing Form Labels
<!-- Violation: input without a label -->
<input type="email" placeholder="Email">
<!-- Fix: associate a label -->
<label for="email">Email address</label>
<input type="email" id="email" placeholder="name@example.com">
Missing Alt Text
<!-- Violation: no alt attribute -->
<img src="chart.png">
<!-- Fix: descriptive alt text -->
<img src="chart.png" alt="Monthly revenue chart showing 15% growth in Q2">
<!-- Decorative images: empty alt -->
<img src="divider.png" alt="">
Insufficient Color Contrast
/* Violation: light gray on white = 2.5:1 */
.subtitle { color: #aaaaaa; }
/* Fix: darker gray = 4.6:1 */
.subtitle { color: #767676; }
Missing Landmark Regions
<!-- Violation: div soup -->
<div class="header">...</div>
<div class="sidebar">...</div>
<div class="content">...</div>
<!-- Fix: semantic landmarks -->
<header>...</header>
<nav aria-label="Main navigation">...</nav>
<main>...</main>
<footer>...</footer>
Prioritizing Remediation
When an audit reveals dozens of violations, prioritize by impact:
- Critical: Users cannot complete core tasks. Missing form labels, keyboard traps, missing page language.
- Serious: Users can complete tasks but with significant difficulty. Poor contrast, missing landmarks, unlabeled icons.
- Moderate: Users experience inconvenience. Missing skip links, redundant links, unclear focus order.
- Minor: Best practice issues. Missing aria-describedby for complex forms, suboptimal heading hierarchy.
Fix critical and serious issues first. Track progress by running automated scans weekly and graphing the violation count over time.
Key Takeaways
A WCAG compliance audit combines automated scanning with axe-core or Lighthouse for the 30% of issues machines can detect, and manual testing with keyboard navigation and screen readers for the 70% they cannot. Integrate automated checks into CI/CD to prevent regressions, and schedule manual audits quarterly for comprehensive coverage. Prioritize fixes by user impact, starting with issues that block core task completion. Accessibility is not a checkbox to achieve once but a quality dimension to maintain continuously.
Related articles
- Web Web Accessibility WCAG Essentials Every Dev Should Know
A practical guide to the WCAG fundamentals: semantic HTML, keyboard support, contrast, focus management, and ARIA used responsibly.
- DevOps Chaos Engineering: Test Your System's Resilience
Learn chaos engineering principles and tools. Run controlled experiments with Chaos Monkey, Litmus, and Gremlin to find weaknesses before they cause real outages.
- Python Python Testing with pytest: Fixtures, Marks, and Plugins
Master pytest from fixtures and parametrize to marks, plugins, and CI integration. Write fast, maintainable Python tests with practical examples.
- Testing Integration Testing: Test Real Dependencies, Not Mocks
Learn how to write effective integration tests that verify real database queries, API calls, and service interactions using testcontainers and proper test infrastructure.