Accessibility regression testing
Accessibility is won by audit and lost by regression. How to stop the fixes silently breaking — what axe-core catches, what visual comparison catches, and the large remainder that needs a human.
Most teams treat accessibility as a project: audit, remediate, ship, done. Then six months later a design-token change drops a button below contrast threshold, a new banner breaks reflow at 320px, and a component refactor removes the focus ring. Nothing alerts. The audit is now describing a version of the site that no longer exists.
Accessibility is achieved by audit and lost by regression. This is about the second half.
Three layers, and what each actually covers
| Layer | Catches | Misses |
|---|---|---|
| Rule-based (axe-core etc.) | Missing alt attributes, unlabelled inputs, bad ARIA, contrast on solid backgrounds, heading order | Anything requiring judgement about meaning or usability |
| Visual comparison | Contrast changes, lost focus indicators, reflow breaking at 320px, text-spacing breakage, 200% zoom breakage | Everything with no visual signature |
| Human | Whether it is actually usable by keyboard and screen reader; whether alt text is meaningful | Nothing — but does not scale to every PR |
Layer 1: rule-based checks in CI
The cheapest thing you can add, and it should come first. axe-core runs inside your existing test framework and catches a large class of mechanical defects.
import AxeBuilder from "@axe-core/playwright";
import { test, expect } from "@playwright/test";
test("pricing page has no detectable a11y violations", async ({ page }) => {
await page.goto("/pricing");
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
.analyze();
expect(results.violations).toEqual([]);
});Two practical notes. Scope the tags to the standard you are actually held to, or you will drown in best-practice suggestions you never agreed to. And introduce it non-blocking on an existing codebase — turning it on hard will fail every build on day one and get it removed by Friday.
Layer 2: the visual criteria
These have measurable visual signatures, which means a comparison can guard them. They are also the ones rule-based tools handle least reliably, because they depend on rendering rather than markup.
Reflow at 320px — start here
WCAG 1.4.10 requires content to be usable at 320px wide without two-dimensional scrolling. It maps onto an ordinary overflow assertion, takes minutes, and almost never produces a false positive:
test("reflows at 320px without horizontal scrolling", async ({ page }) => {
await page.setViewportSize({ width: 320, height: 800 });
await page.goto("/pricing");
const overflows = await page.evaluate(() => {
const de = document.documentElement;
return de.scrollWidth > de.clientWidth;
});
expect(overflows).toBe(false);
});Focus indicators
WCAG 2.4.7 requires visible keyboard focus. A component refactor removing an outline is invisible to rule-based tools and to anyone testing with a mouse — which is most people, most of the time.
// Focus each interactive element and capture it. A diff here means
// someone changed or removed a focus style.
for (const name of ["Start free", "Log in", "Pricing"]) {
const el = page.getByRole("link", { name });
await el.focus();
await expect(el).toHaveScreenshot(`focus-${name}.png`);
}Text spacing
WCAG 1.4.12 requires no loss of content when users override text spacing. Apply the overrides, then screenshot and diff — content that gets clipped or overlaps shows up immediately:
await page.addStyleTag({
content: `* {
line-height: 1.5 !important;
letter-spacing: 0.12em !important;
word-spacing: 0.16em !important;
}
p { margin-bottom: 2em !important; }`,
});
await expect(page).toHaveScreenshot("pricing-text-spacing.png");200% zoom
WCAG 1.4.4. Capture at double scale and compare — fixed-height containers and absolutely positioned elements are what break.
Layer 3: what still needs a person
No amount of tooling replaces these, and pretending otherwise is how organisations end up technically passing and practically unusable:
- Keyboard navigation end to end. Can you complete the primary flow without a mouse, in a sensible order, without a focus trap?
- Screen reader passes. Does the page make sense read aloud? Are landmarks and headings useful for navigation?
- Whether alt text is meaningful. A tool confirms the attribute exists; only a human knows
alt="image"is useless. - Error recovery. Are errors announced, associated with their field, and described well enough to act on?
- Cognitive load. Is the thing actually understandable? Nothing automates this.
A sequence that works
- Audit first. Establish where you stand; automation cannot tell you that.
- Fix, then freeze. As each page is remediated, add its regression guards immediately — while someone still remembers what correct looks like.
- Rule-based checks non-blocking, on the remediated pages only. Expand as more pages are fixed.
- Visual guards on the criteria above, starting with reflow at 320px.
- Make it blocking once the false-positive rate is low, on the pages that have been remediated.
- Keep a manual pass for anything new. A new screen has no baseline and no audit.
Step 2 is the one teams skip and later regret. The window right after remediation is when the correct state is known and cheap to capture; six months on, nobody is sure whether the current state is the fixed one.
If this is being driven by a regulatory deadline rather than by choice, the scope and dates are in the European Accessibility Act and visual testing.
Frequently asked questions
What is accessibility regression testing?
Automated checks that detect when previously-fixed accessibility issues come back. Accessibility conformance is established by audit and lost gradually as the product changes — a design-token update drops contrast, a refactor removes a focus ring, a new banner breaks reflow. Regression testing catches those before they reach users.
Can automated tools test accessibility?
Partly. Rule-based tools such as axe-core catch mechanical defects like missing labels and bad ARIA, and visual comparison guards criteria with a visual signature such as contrast, focus indicators and reflow. Widely-cited estimates put automated coverage of WCAG success criteria at well under half, so keyboard and screen-reader testing remain necessary.
Which accessibility check is easiest to automate first?
Reflow at 320px, WCAG 1.4.10. Set the viewport to 320px and assert that document.documentElement.scrollWidth does not exceed clientWidth. It takes minutes, almost never produces a false positive, and catches a common and highly visible defect.