Production Accessibility Engineering: Axe-Core, Headless Audits & Continuous Quality Gates
Static linting catches less than 25% of accessibility defects. This article implements the complete 4-tier Accessibility Testing Pyramid: unit testing with vitest-axe, headless full-page and component audits in Playwright, synthetic keyboard traversal tests, APCA/WCAG contrast mathematical validation, and CI pull-request quality gates.
Series·Part 6 of 6
Frontend Accessibility Architecture: From Semantic DOM Foundations to Resilient Accessible State Engines
Parts 1–5 built the full accessibility architecture of the Enterprise Accessible Design System & Portal Canvas: AT runtime awareness, AXTree compilation, focus state machines, APG-compliant compound widgets, and a production live region system. The remaining architectural gap is the one that makes the entire investment permanent rather than temporary: automated testing that prevents regressions from being merged.
Accessibility regressions are the silent counterpart to visual regressions. They do not produce JavaScript errors. They do not break builds. A developer removes aria-label from a navigation landmark in a refactor, the site ships, and NVDA users navigate to a region announced only as "navigation" — indistinguishable from the three other navigation regions on the page. No error was thrown. No unit test failed. No CI gate caught it.
This article builds the complete CI accessibility testing pyramid for the Portal Canvas: unit tests with vitest-axe that validate ARIA roles and names at the component level, integration tests with @testing-library/user-event that simulate real keyboard navigation sequences, and Playwright end-to-end tests with axe-playwright that run the full AXTree analysis on every build. The pyramid includes a violation policy system for managing accepted violations with documented justification and owner accountability.
Full AXTree, platform AT behavior, visual contrast
E2E
Playwright + axe-playwright
Full AXTree violations, contrast failures, landmark structure
Browser-specific AT quirks, braille output
Crucial Requirement
The testing pyramid is an additive coverage model — each layer catches different failure categories. No single layer is sufficient. A component can pass all axe unit tests (valid ARIA) while still failing keyboard navigation integration tests (incorrect key handling). Both failures are accessibility regressions.
// tests/a11y-config.ts — shared axe configurationimporttype { RunOptions } from'axe-core';
exportconstbaseAxeConfig: RunOptions = {
rules: {
// Colour contrast is handled by design system tokens — verified manually// and covered in Playwright E2E contrast tests. Disable in unit tests// to avoid false positives from test environment (no computed styles).'color-contrast': { enabled: false },
// Region rule: every section of content must be in a landmark// — enforced by architectural convention; individual component tests// render fragments, not full pages'region': { enabled: false },
},
};
Pro Tip & Optimization
Disable color-contrast in unit and integration tests — jsdom does not compute CSS styles, so computed contrast ratios are always wrong in the test environment. Run contrast tests only in Playwright E2E tests against a real Chromium instance with computed styles.
Playwright's accessibility snapshot captures the AXTree as a structured object, enabling regression detection on the AT-visible structure without a real screen reader:
Not every accessibility violation is immediately fixable. Third-party embeds, legacy content, and known browser-AT incompatibilities may produce violations that require documented exceptions. The violation policy system provides structure for managing these exceptions with accountability.
TYPESCRIPT
// tests/a11y-violations-policy.ts// Every accepted violation must have:// - A unique ID// - The axe rule ID it suppresses// - An owner (GitHub handle)// - An expiry date (no longer than 90 days)// - A documented justificationexportinterfaceAcceptedViolation {
id: string;
ruleId: string;
owner: string;
expiresAt: string; // ISO 8601justification: string;
affectedSelectors: string[];
}
exportconstacceptedViolations: AcceptedViolation[] = [
{
id: 'AV-001',
ruleId: 'color-contrast',
owner: '@design-system-team',
expiresAt: '2027-01-15',
justification:
'Legacy chart library color palette requires upstream fix. Tracked in issue #1234. ' +
'Chart data is available in accessible data table form via the "View data" toggle.',
affectedSelectors: ['.recharts-legend-item-text'],
},
];
Accessibility regressions produce no JavaScript errors — they must be caught by an automated testing pyramid that covers ARIA roles/names (unit), keyboard interaction sequences (integration), and full AXTree correctness (E2E).
Every PR that touches interactive components must pass all three layers of the accessibility testing pyramid.
axe unit tests should disable color-contrast — jsdom computes no CSS, making contrast results meaningless; run contrast checks only in Playwright E2E against a real Chromium instance.
Configure baseAxeConfig with contrast disabled for unit/integration; enable it in Playwright only.
Playwright's toMatchAriaSnapshot() catches structural AXTree regressions — an engineer renaming an aria-label will break the snapshot test before the change is merged.
Add AXTree snapshot tests for all landmark regions and compound widgets.
The violation policy system provides structured, time-boxed exceptions for accepted violations; entries without a documented justification, owner, and expiry date are never valid.
Every accepted violation in the policy file must have an owner, a 90-day max expiry, and a tracked issue link.
This is the final article in the Frontend Web Accessibility & Inclusive Architecture series. The complete series has built a production-grade accessible design system from the AT runtime model (Part 1) through AXTree compilation (Part 2), focus state machines (Part 3), APG-compliant compound widgets (Part 4), live region architecture (Part 5), and the full automated testing pyramid (Part 6). The Portal Canvas now has every layer of its accessibility contract enforced — architecturally, at runtime, and in CI.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.