Guide
WCAG Accessibility Testing: Automated Compliance Guide for 2026
Accessibility is not a feature request. It is a legal requirement, a moral imperative, and a competitive advantage. This guide covers how to automate WCAG 2.2 compliance testing using axe-core and Playwright, what must remain manual, and how to build accessibility gates into your CI/CD pipeline.
“More than a billion people worldwide live with some form of disability, representing the largest minority group on the planet.”
1. Why Accessibility Testing Matters
Over 1.3 billion people globally live with some form of disability. That includes visual impairments (blindness, low vision, color blindness), motor impairments (limited dexterity, tremors, paralysis), auditory impairments (deafness, hard of hearing), and cognitive impairments (dyslexia, ADHD, autism spectrum). When your web application is inaccessible, you exclude a significant portion of potential users.
Legal requirements and ADA lawsuits
The legal landscape for web accessibility has shifted dramatically. In the United States, courts have consistently ruled that the Americans with Disabilities Act (ADA) applies to websites and web applications. ADA-related web accessibility lawsuits exceeded 4,000 in 2023 and continue to rise. The European Accessibility Act (EAA), which takes full effect in June 2025, mandates accessibility for digital products and services across all EU member states. Organizations that ignore accessibility face both legal liability and regulatory penalties.
Better UX for everyone
Accessibility improvements benefit all users, not just those with disabilities. Keyboard navigation helps power users who prefer not to use a mouse. High contrast text is easier to read in bright sunlight. Captions on videos help users in noisy environments or those watching without sound. Clear heading hierarchies improve SEO and help everyone scan content faster. Semantic HTML makes your application more predictable for browser extensions, search engines, and AI assistants.
The cost of retrofitting
Fixing accessibility issues after launch is significantly more expensive than building accessibly from the start. A button that lacks an accessible name is a one-line fix during development but requires regression testing, QA, and a release cycle when caught in production. Automated accessibility testing catches these issues in the development workflow, before they ever reach users.
2. WCAG 2.2 Standards Overview
The Web Content Accessibility Guidelines (WCAG) 2.2, published by the W3C in October 2023, is the current standard for web accessibility. It builds on WCAG 2.1 with nine new success criteria focused on cognitive accessibility, mobile usability, and authenticated sessions. Understanding the three conformance levels is essential for setting realistic testing targets.
Level A: Minimum accessibility
Level A criteria represent the absolute minimum for accessibility. Failing these means your application has barriers that completely prevent certain users from accessing content. Key Level A requirements include providing text alternatives for non-text content (1.1.1), ensuring all functionality is available from a keyboard (2.1.1), not using color as the only means of conveying information (1.4.1), and providing descriptive page titles (2.4.2).
Level AA: Standard target
Level AA is the target most organizations aim for and the level required by most legal and regulatory frameworks. It includes everything in Level A plus additional criteria: color contrast ratios of at least 4.5:1 for normal text and 3:1 for large text (1.4.3), text resizing up to 200% without loss of functionality (1.4.4), consistent navigation across pages (3.2.3), and visible focus indicators for keyboard navigation (2.4.7). WCAG 2.2 adds new AA criteria including focus not obscured (2.4.11) and dragging movements (2.5.7).
Level AAA: Enhanced accessibility
Level AAA provides the highest level of accessibility but is not typically required or expected for entire sites. It includes enhanced contrast ratios of 7:1 (1.4.6), sign language interpretation for audio content (1.2.6), and reading level restrictions (3.1.5). Some organizations selectively implement AAA criteria for critical user flows while maintaining AA compliance site-wide.
3. What Can Be Automated
Research from Deque Systems suggests that automated tools can detect approximately 30 to 40 percent of all WCAG violations. While that may seem low, these are the most common and most frequently introduced violations. Catching them automatically prevents the majority of accessibility regressions from reaching production.
Color contrast
Automated tools calculate the contrast ratio between foreground text and its background color and flag violations of WCAG 1.4.3 (AA) and 1.4.6 (AAA). This includes text rendered in CSS, text inside SVGs, and text overlaid on background images (though image-based backgrounds require special handling). Contrast checking is one of the most reliable automated checks because it is purely mathematical.
Missing alt text
Detecting images without alt attributes is straightforward for automated tools. They can also identify decorative images that should have empty alt text (alt="") versus informative images that need descriptive text. What automation cannot do is evaluate whether the alt text is actually meaningful; that requires human judgment.
ARIA labels and roles
Automated tools validate that ARIA attributes are used correctly: roles match their expected child/parent relationships, required ARIA attributes are present, ARIA IDs are unique and reference existing elements, and interactive elements have accessible names. Invalid ARIA is worse than no ARIA at all, so catching these errors automatically is critical.
Heading hierarchy
Automated checks verify that heading levels are sequential and do not skip levels (for example, an h2 followed directly by an h4 without an intervening h3). They also flag pages without any h1, pages with multiple h1 elements, and heading elements used purely for visual styling rather than semantic structure.
Focus order and tab index
Tools can detect positive tabindex values (which override natural tab order and are almost always a mistake), missing focus indicators on interactive elements, and keyboard traps where focus enters a component but cannot leave. Automated focus testing works best when combined with Playwright scripts that simulate keyboard navigation through the page.
4. What Requires Manual Testing
The remaining 60 to 70 percent of accessibility issues require human judgment to detect. Automated tools cannot understand context, intent, or user experience in the way that a human tester can. These areas must be covered by manual testing, ideally performed by people who use assistive technologies daily.
Screen reader experience
The most critical manual test is navigating your application with a screen reader. VoiceOver on macOS, NVDA on Windows, and TalkBack on Android each interpret HTML and ARIA differently. A page that passes all automated checks can still be unusable for screen reader users if the reading order is illogical, live regions announce updates at the wrong time, or custom components lack proper state announcements (expanded, collapsed, selected, disabled).
Cognitive accessibility
WCAG 2.2 strengthens cognitive accessibility requirements, but these remain almost entirely manual to evaluate. Is the language plain and understandable? Are error messages helpful and specific? Can users recover from mistakes without losing their work? Are timeouts communicated clearly and extendable? These questions require human evaluation of the overall user experience.
Keyboard navigation nuances
While automated tools can detect keyboard traps and missing focus indicators, the overall keyboard navigation experience requires manual evaluation. Does the focus order match the visual reading order? Can users skip repetitive navigation blocks? Do modal dialogs trap focus correctly and return focus to the trigger element when closed? Are custom widgets (date pickers, autocompletes, carousels) navigable with expected keyboard patterns?
5. Tools for Automated Accessibility
The ecosystem of accessibility testing tools has matured significantly. The most effective approach combines a rule engine (for detecting violations), a browser automation framework (for testing dynamic content), and a CI integration (for preventing regressions).
axe-core
axe-core, developed by Deque Systems, is the industry standard accessibility rule engine. It runs in the browser, analyzes the rendered DOM, and returns structured violation data with WCAG references, impact severity, and remediation guidance. axe-core powers the accessibility audits in Chrome DevTools, Lighthouse, and dozens of other tools. It is open source, actively maintained, and has near-zero false positive rates because Deque only ships rules they can verify with certainty.
Playwright with axe integration
The @axe-core/playwright package provides a seamless integration between Playwright and axe-core. You can run accessibility scans on any page state, including after user interactions like opening modals, expanding menus, or completing form flows. This is critical because many accessibility violations only appear in specific UI states.
Lighthouse CI
Lighthouse CI runs Google Lighthouse in your CI pipeline and reports scores for performance, accessibility, SEO, and best practices. Its accessibility audit uses axe-core under the hood but adds additional checks and presents results as a single score. Lighthouse CI can upload results to a server for trend tracking, making it easy to monitor accessibility scores over time and catch gradual degradation.
Pa11y
Pa11y is a command-line tool that runs accessibility tests against URLs. It supports multiple runners (axe, HTML CodeSniffer) and can be configured to test specific WCAG levels and types. Pa11y CI provides a dashboard for tracking results across multiple URLs over time. It is particularly useful for testing static sites and server-rendered pages where Playwright-level interaction is not needed.
6. Playwright Accessibility Testing
Playwright provides two powerful mechanisms for accessibility testing: its built-in role-based locators that encourage accessible markup, and the axe-core integration that scans pages for WCAG violations. Together, they form a comprehensive automated accessibility testing framework.
axe-core integration
Install the axe-core Playwright integration and add accessibility scans to your existing E2E tests. Each scan analyzes the current page state and returns detailed violation information.
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test.describe("Accessibility", () => {
test("home page 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("login form is accessible", async ({ page }) => {
await page.goto("/login");
// Test the default state
const defaultResults = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.analyze();
expect(defaultResults.violations).toEqual([]);
// Trigger validation errors and retest
await page.getByRole("button", { name: "Sign in" }).click();
const errorResults = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.analyze();
expect(errorResults.violations).toEqual([]);
});
test("modal dialog is accessible", async ({ page }) => {
await page.goto("/dashboard");
await page.getByRole("button", { name: "New project" }).click();
// Scan only the modal, not the entire page
const results = await new AxeBuilder({ page })
.include('[role="dialog"]')
.withTags(["wcag2a", "wcag2aa"])
.analyze();
expect(results.violations).toEqual([]);
});
});Role-based locators
Playwright's role-based locators (getByRole, getByLabel, getByText) double as implicit accessibility checks. If a button cannot be found by its role, it probably does not have the correct ARIA role or accessible name. Writing tests with these locators ensures your UI elements are semantically correct.
import { test, expect } from "@playwright/test";
test("navigation is accessible", async ({ page }) => {
await page.goto("/");
// These locators verify semantic HTML
const nav = page.getByRole("navigation");
await expect(nav).toBeVisible();
// Landmarks should be identifiable
const main = page.getByRole("main");
await expect(main).toBeVisible();
// Buttons must have accessible names
const menuButton = page.getByRole("button", { name: "Menu" });
await expect(menuButton).toBeVisible();
// Links must have descriptive text
const settingsLink = page.getByRole("link", { name: "Settings" });
await expect(settingsLink).toBeVisible();
});
test("form labels are properly associated", async ({ page }) => {
await page.goto("/signup");
// getByLabel verifies the label-input association
const emailInput = page.getByLabel("Email address");
await expect(emailInput).toBeVisible();
await emailInput.fill("user@example.com");
const passwordInput = page.getByLabel("Password");
await expect(passwordInput).toBeVisible();
// Verify required fields are communicated
await expect(emailInput).toHaveAttribute("aria-required", "true");
});Focus management testing
Playwright can simulate keyboard navigation and verify focus behavior programmatically. This catches common issues like focus not moving to a modal when it opens, focus not returning to the trigger when a modal closes, and focus jumping to unexpected locations after user interactions.
import { test, expect } from "@playwright/test";
test("modal traps focus correctly", async ({ page }) => {
await page.goto("/dashboard");
const openButton = page.getByRole("button", { name: "New project" });
await openButton.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
// Focus should move to the first focusable element in the dialog
const firstInput = dialog.getByLabel("Project name");
await expect(firstInput).toBeFocused();
// Tab through all focusable elements
await page.keyboard.press("Tab");
await expect(dialog.getByLabel("Description")).toBeFocused();
await page.keyboard.press("Tab");
await expect(
dialog.getByRole("button", { name: "Create" })
).toBeFocused();
// Tab should cycle back to the first element (focus trap)
await page.keyboard.press("Tab");
await expect(
dialog.getByRole("button", { name: "Cancel" })
).toBeFocused();
// Escape closes the dialog
await page.keyboard.press("Escape");
await expect(dialog).toBeHidden();
// Focus returns to the trigger button
await expect(openButton).toBeFocused();
});
test("skip link navigates to main content", async ({ page }) => {
await page.goto("/");
// Tab to activate the skip link
await page.keyboard.press("Tab");
const skipLink = page.getByRole("link", { name: "Skip to content" });
await expect(skipLink).toBeFocused();
await page.keyboard.press("Enter");
// Focus should move to main content area
const main = page.getByRole("main");
await expect(main).toBeFocused();
});7. CI/CD Accessibility Gates
Automated accessibility tests are only valuable if they block deployments when violations are found. Without enforcement, accessibility test results become advisory reports that teams ignore under deadline pressure. CI/CD accessibility gates make accessibility a hard requirement, just like passing unit tests or security scans.
Failing builds on violations
Configure your CI pipeline to fail when axe-core detects violations above a certain severity threshold. Most teams start by blocking on critical and serious violations while tracking moderate and minor violations as warnings. As the codebase becomes more accessible, you can tighten the threshold.
name: Accessibility Tests
on: [push, pull_request]
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps chromium
- name: Start application
run: npm run build && npm start &
env:
NODE_ENV: production
- name: Wait for app
run: npx wait-on http://localhost:3000
- name: Run accessibility tests
run: npx playwright test --project=accessibility
env:
BASE_URL: http://localhost:3000
- name: Upload a11y report
if: always()
uses: actions/upload-artifact@v4
with:
name: accessibility-report
path: test-results/Severity thresholds
axe-core classifies violations into four impact levels: critical, serious, moderate, and minor. A practical approach is to configure your test helper to filter violations by severity, allowing you to gradually increase strictness.
import { Page, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
type Severity = "critical" | "serious" | "moderate" | "minor";
const SEVERITY_ORDER: Severity[] = [
"critical",
"serious",
"moderate",
"minor",
];
interface A11yCheckOptions {
minSeverity?: Severity;
include?: string[];
exclude?: string[];
tags?: string[];
}
export async function checkAccessibility(
page: Page,
options: A11yCheckOptions = {}
) {
const {
minSeverity = "serious",
include,
exclude,
tags = ["wcag2a", "wcag2aa"],
} = options;
let builder = new AxeBuilder({ page }).withTags(tags);
if (include) {
for (const selector of include) {
builder = builder.include(selector);
}
}
if (exclude) {
for (const selector of exclude) {
builder = builder.exclude(selector);
}
}
const results = await builder.analyze();
const severityThreshold =
SEVERITY_ORDER.indexOf(minSeverity);
const blockingViolations = results.violations.filter(
(v) =>
SEVERITY_ORDER.indexOf(v.impact as Severity) <=
severityThreshold
);
if (blockingViolations.length > 0) {
const summary = blockingViolations
.map(
(v) =>
`[${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} instances)`
)
.join("\n");
expect(
blockingViolations,
`Accessibility violations found:\n${summary}`
).toEqual([]);
}
return results;
}Exception handling for known issues
When adopting accessibility testing on an existing codebase, you will likely have pre-existing violations that cannot be fixed immediately. Use axe-core's disable rules feature to create a baseline of known issues. Track these in a separate file and work through them incrementally. The key principle is that the baseline should only shrink over time, never grow. New violations must be fixed before merging.
// Known violations to be fixed incrementally
// Remove entries as they are resolved
export const A11Y_BASELINE: Record<string, string[]> = {
"/dashboard": [
"color-contrast", // Fix scheduled for Sprint 14
],
"/settings": [
"label", // Third-party date picker component
],
};
// Usage in tests:
const knownIssues = A11Y_BASELINE[currentPath] ?? [];
const builder = new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.disableRules(knownIssues);8. Building an Accessibility Testing Strategy
Effective accessibility testing combines automated scanning, manual evaluation, and assistive technology testing into a cohesive strategy. No single approach catches all issues. The goal is to create a layered defense where automated tools catch the easy wins continuously, manual testing catches the nuanced issues periodically, and user testing with people who have disabilities validates the overall experience.
Combine automated and manual testing
Run automated axe-core scans on every pull request to catch regressions immediately. Schedule manual accessibility audits quarterly, covering screen reader testing, keyboard navigation, and cognitive accessibility review. Conduct user testing with people who have disabilities at least twice per year, especially before major releases or redesigns.
The testing matrix
Create a testing matrix that maps WCAG success criteria to testing methods. For each criterion, document whether it can be tested automatically, requires manual testing, or needs both. This matrix becomes your team's reference for what to test and how to test it.
| WCAG Criterion | Automated | Manual | Tool |
|---|---|---|---|
| 1.1.1 Non-text Content | Partial | Alt text quality | axe-core + review |
| 1.4.3 Contrast | Full | N/A | axe-core |
| 2.1.1 Keyboard | Partial | Navigation flow | Playwright + manual |
| 2.4.7 Focus Visible | Partial | Visual check | Playwright + manual |
| 4.1.2 Name, Role, Value | Full | N/A | axe-core |
Training and culture
Tools alone do not create accessible products. Developers need training on semantic HTML, ARIA patterns, and how assistive technologies work. Designers need to consider color contrast, touch target sizes, and focus states in their mockups. Product managers need to include accessibility acceptance criteria in user stories. Accessibility is a team responsibility, not a QA checkbox.
Start with small wins: install the axe browser extension so developers see violations in real time during development. Add a single accessibility test to your CI pipeline. Run a lunch-and-learn where the team navigates your application using only a keyboard. These low-effort activities build awareness and momentum before you formalize a comprehensive testing strategy.
The most effective accessibility programs treat accessibility as a continuous practice rather than a one-time audit. Just as you run tests on every commit and deploy continuously, accessibility checks should be woven into every stage of your development lifecycle. Automated tools handle the continuous monitoring. Human expertise handles the judgment calls. Together, they ensure your application works for everyone.
Related Guides
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.