Frontend / Accessibility / 07_testing_accessibility.md

Testing Accessibility

Updated 5 interview angles 4 min read source
On this page11
  1. What can automated accessibility testing actually catch?
  2. What can’t it catch?
  3. How do you wire axe into tests?
  4. How does Testing Library push you toward accessibility?
  5. What’s your manual testing process?
  6. What tools help during development?
  7. How do you prevent accessibility regressions?
  8. Gotchas
  9. Related
  10. Further reading
  11. Interview angle

Testing Accessibility

Automated tools (axe-core via jest-axe, @axe-core/playwright, Lighthouse) catch ~30–40% of WCAG issues — and they’re worth running in CI because they catch real regressions cheaply. But they can’t judge meaningful alt text, sensible focus order, or whether a widget is actually usable. So the senior answer is layered: lint + axe in CI, query by role in component tests (which enforces accessibility), then manual keyboard and screen-reader passes. General testing mechanics live in Frontend Testing — Senior Interview Prep; this is the a11y-specific layer.

What can automated accessibility testing actually catch?

Deterministic, machine-checkable rules: missing/empty alt, form fields with no label, insufficient color contrast, invalid ARIA (bad role, required attrs missing), duplicate ids, missing document language, some focus-order issues. axe-core is the engine behind most tools (Lighthouse, browser extensions, CI integrations). It explicitly avoids false positives, so a clean axe run is meaningful — but it is not “the page is accessible.”

What can’t it catch?

Anything requiring judgment: is the alt text useful or just present? Is the focus order logical? Does the custom combobox work with a keyboard and screen reader? Is the content understandable? Are error messages helpful? These need manual testing. Roughly 60–70% of WCAG criteria are not automatable.

How do you wire axe into tests?

Component/integration tests with jest-axe:

ts
import { render } from "@testing-library/react";
import { axe } from "jest-axe";

test("form has no a11y violations", async () => {
  const { container } = render(<SignupForm />);
  expect(await axe(container)).toHaveNoViolations();
});

End-to-end with Playwright (@axe-core/playwright) catches issues only visible in the real, fully-rendered page:

ts
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);

See A11y Testing + Visual Regression in CI.

How does Testing Library push you toward accessibility?

RTL’s recommended query priority is getByRole first, then label/text — the same way assistive tech finds elements. If you can query getByRole("button", { name: /submit/i }), the element has the right role and an accessible name; if you can’t, it’s probably inaccessible. Writing role-based tests surfaces missing labels/roles as test failures. Reaching for getByTestId is the escape hatch that bypasses this signal. See React Testing Library — Query Priority, Role-Based Queries.

What’s your manual testing process?

  1. Keyboard only — unplug the mouse. Tab through everything: can you reach and operate every control? Is focus visible? Any traps? Does Escape close overlays? Does focus return sensibly?
  2. Screen reader — test the real pairings: NVDA + Firefox/Chrome (Windows), VoiceOver + Safari (macOS/iOS), TalkBack (Android). Listen for correct name/role/state and announced changes.
  3. Zoom / reflow — 200% zoom (WCAG 1.4.4) and 400% reflow (1.4.10): no loss of content or horizontal scroll.
  4. Contrast — DevTools or a contrast checker for text and UI components.
  5. Reduced motion — prefers-reduced-motion respected.

What tools help during development?

Browser DevTools Accessibility panel (inspect the accessibility tree, computed name/role, contrast), the axe DevTools / WAVE extensions, Lighthouse’s accessibility audit, and ESLint’s eslint-plugin-jsx-a11y to catch issues at author time (missing alt, invalid ARIA, click-without-keyboard). Lint is the cheapest gate — it fails the build before review.

How do you prevent accessibility regressions?

Layer the gates: eslint-plugin-jsx-a11y in lint, jest-axe in component tests, @axe-core/playwright in E2E, and an a11y item on the PR checklist (keyboard pass for new interactive UI). Automated gates catch the mechanical regressions; the checklist enforces the human judgment automation can’t.

Gotchas

  • A clean axe run ≠ accessible — the most dangerous misconception; it’s a floor, not a ceiling.
  • Testing only the initial render misses dynamic states — run axe after opening the modal, expanding the menu, showing the error.
  • jsdom (Jest/Vitest) doesn’t compute layout or real contrast — color-contrast and some visibility rules need a real browser (Playwright); axe in jsdom skips them.
  • Overlay/“accessibility widget” vendors don’t make a site compliant and have been litigated against — fix the source.
  • Screen-reader behavior varies by SR + browser pairing — test the common combinations, don’t assume one represents all.
  • getByTestId everywhere hides accessibility gaps that role-based queries would expose.

Further reading

Interview angle 5

  • “What does an automated accessibility check actually cover?” - about a third of the issues: contrast, missing labels, invalid ARIA, structural problems. They are real regressions and cheap to catch in CI, and a clean axe run does not mean the page is accessible - it means nothing statically detectable is wrong.
  • “How do component tests help?” - querying by role in Testing Library mirrors how assistive technology finds elements, so the test fails for the same reason a screen reader user would be stuck. If you cannot query it by role, that is the finding.
  • “What has to be manual?” - a keyboard-only pass through the real flows, a screen reader pass with NVDA or VoiceOver, 200% zoom and 400% reflow, and checking reduced-motion is honoured. None of those are inferable from the DOM.
  • “What do people forget to test?” - dynamic states. Most violations live in an open menu, a validation error or a loading state, and a check that only runs against the initial render never sees any of them.
  • “How would you layer the gates?” - eslint-plugin-jsx-a11y while typing, jest-axe in component tests, axe in Playwright for whole pages, and a review checklist for the judgement calls. Each catches a different class, and the cheap ones catch the most.