WCAG Accessibility Testing Guide: How to Test for Real Compliance
Accessibility compliance is not a checkbox. Running an automated scanner and calling it done leaves the majority of real accessibility barriers undetected. This guide covers how to actually test for WCAG 2.1 and 2.2 compliance — automated tools, manual techniques, and how to fit both into a development workflow.
What WCAG Actually Requires
WCAG (Web Content Accessibility Guidelines) is organized around four principles: Perceivable, Operable, Understandable, and Robust. Each principle contains guidelines, and each guideline has testable success criteria at three levels: A, AA, and AAA.
Level A: Minimum requirements. Failing these makes the content inaccessible to many users.
Level AA: The standard most legal requirements point to. Required by ADA lawsuits in the US, EN 301 549 in the EU, and most government accessibility policies.
Level AAA: Enhanced criteria. Not required across an entire site; applicable to specific content types.
Most teams target WCAG 2.1 AA, which includes 50 success criteria. WCAG 2.2 added nine new criteria, three of which are AA (focus appearance, dragging movements, target size minimum). If you're working on a new project, target 2.2.
What Automated Tools Can and Cannot Catch
Automated tools catch roughly 30–40% of WCAG issues. This is well-documented by Deque, WebAIM, and multiple research studies.
Automated tools reliably catch:
- Missing
altattributes on images - Insufficient color contrast ratios
- Missing form labels
- Missing document language
- Invalid ARIA usage
- Missing heading structure
- Images in links without accessible names
Automated tools cannot reliably catch:
- Whether
alttext is meaningful (presence ≠ quality) - Whether reading order makes sense
- Whether keyboard focus order is logical
- Whether error messages are understandable
- Whether content is operable with only a keyboard
- Screen reader behavior with dynamic content
- Cognitive load and complexity
The implication: automated testing is necessary but not sufficient. A zero-violation automated report does not mean your site is accessible.
Setting Up axe-core for Automated Testing
axe-core is the most widely used accessibility engine. It powers browser extensions, Jest matchers, Playwright integrations, and Cypress plugins.
npm install --save-dev axe-core @axe-core/playwrightWith Playwright
// tests/accessibility/homepage.test.js
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.describe('Homepage accessibility', () => {
test('has no automatically detectable accessibility violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
.analyze();
expect(results.violations).toEqual([]);
});
test('modal dialog is accessible when open', async ({ page }) => {
await page.goto('/');
await page.click('[data-testid="open-modal"]');
await page.waitForSelector('[role="dialog"]');
const results = await new AxeBuilder({ page })
.include('[role="dialog"]')
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(results.violations).toEqual([]);
});
});Handling Known Violations
When you have a known third-party violation you can't fix immediately, exclude it rather than ignoring all results:
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.exclude('#third-party-widget') // known unresolvable violation
.analyze();Never disable the entire check because of one unresolvable issue. Scope the exclusion precisely.
Surfacing Violations Clearly
axe returns structured violation objects. Log them in a way that shows you exactly what failed:
function formatViolations(violations) {
return violations.map(v => ({
id: v.id,
impact: v.impact,
description: v.description,
nodes: v.nodes.map(n => ({
html: n.html,
failureSummary: n.failureSummary,
})),
}));
}
test('no violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
if (results.violations.length > 0) {
console.log(JSON.stringify(formatViolations(results.violations), null, 2));
}
expect(results.violations).toHaveLength(0);
});Manual Keyboard Testing
Keyboard accessibility is one of the highest-impact areas and one that automated tools mostly miss. Test it manually.
The keyboard testing protocol:
- Open the page with no mouse connected (or disabled)
- Tab through every interactive element
- For each element, verify:
- Focus is visible (not just the browser default — it should be clearly visible)
- The element can be activated with Enter or Space
- The element's purpose is clear from focus alone
- Test modal dialogs: focus should move to the dialog on open, be trapped inside, and return to the trigger on close
- Test dropdown menus: Arrow keys should navigate items; Escape should close
- Test date pickers, sliders, and custom widgets: they should follow the ARIA Authoring Practices Guide keyboard patterns
Common keyboard failures:
- Custom click handlers that don't respond to keyboard
- Focus trapped in a widget with no way out
- Focus that jumps unpredictably when content is removed
- Skip navigation links that appear on focus but navigate nowhere useful
Manual Screen Reader Testing
Screen readers interpret the DOM and announce content to users. Testing requires using actual screen readers — NVDA or JAWS on Windows, VoiceOver on macOS/iOS, TalkBack on Android.
Minimum screen reader test matrix:
| Screen Reader | Browser | Platform |
|---|---|---|
| NVDA (free) | Firefox | Windows |
| JAWS | Chrome or Edge | Windows |
| VoiceOver | Safari | macOS |
| VoiceOver | Safari | iOS |
| TalkBack | Chrome | Android |
What to test with a screen reader:
- Page title is announced when page loads
- Heading hierarchy makes sense as an outline
- Images have meaningful alt text (or are marked decorative)
- Form fields are labeled and errors are announced
- Dynamic content updates (modals, toasts, AJAX results) are announced
- Tables have proper headers
- Links make sense out of context ("click here" fails; "Download Q3 report" passes)
Testing ARIA live regions:
When content updates without a page reload, users relying on screen readers won't notice unless you announce the change:
<!-- Status announcements -->
<div aria-live="polite" aria-atomic="true" class="sr-only" id="status-announcer"></div>// After async operation completes
document.getElementById('status-announcer').textContent = '5 results found';aria-live="polite" waits for the user to finish reading before announcing. aria-live="assertive" interrupts immediately — only use it for errors or urgent messages.
Testing Color Contrast
WCAG 2.1 AA requires a 4.5:1 contrast ratio for normal text and 3:1 for large text (18pt or 14pt bold) and UI components. WCAG 2.2 AA adds a minimum 3:1 ratio for focus indicators.
Automated tools catch most contrast violations, but test these manually:
- Text over images or gradients (automated tools see the CSS, not the rendered result)
- Text that changes color on hover or focus
- Disabled state contrast (WCAG exempts disabled elements, but consider your users)
- Text in charts and data visualizations
Tools for manual contrast checking:
- WebAIM Contrast Checker (web)
- Colour Contrast Analyser (desktop app, lets you sample screen colors)
- Browser DevTools accessibility panel
Building an Accessibility Test Suite
Structure your test suite with three layers:
Layer 1: Automated scan on every page (CI)
- Run axe on every route in your application
- Block PRs that introduce new violations
- Track violation count over time — even if you can't fix everything immediately, don't let it get worse
Layer 2: Component-level accessibility tests
- Test interactive components (modals, dropdowns, date pickers) in isolation
- Verify ARIA attributes, keyboard interaction, and focus management
- Use testing-library with jest-axe for React/Vue components
Layer 3: Exploratory manual testing per release
- Keyboard walkthrough of new features
- Screen reader test of new content types
- Contrast check for new design elements
Prioritizing Violations
axe assigns impact levels: critical, serious, moderate, minor. Focus your effort on critical and serious first — these are the barriers that prevent use.
Critical examples: Images without alt text, form inputs without labels, content only accessible by mouse
Serious examples: Low contrast text, missing focus indicators, missing document language
Moderate/minor: Not barriers to use, but degraded experience. Fix in a future iteration.
What WCAG Testing Will Not Tell You
Technical compliance does not equal usability for disabled users. A page can pass all automated checks, have proper ARIA, and still be frustrating or unusable. The most important additional step is usability testing with actual disabled users.
Recruit participants who are blind, have low vision, use voice control, or have motor disabilities. Watch them use your product. The issues you find in 30 minutes of observational testing will outweigh months of automated scanning.