How do you build accessibility checks into automated tests, and what can and can't they catch?
Integrating a11y assertions into CI while understanding the ceiling of automated a11y checking.
You bake accessibility into tests at two levels. First, by HOW you query: using Testing Library's getByRole/getByLabelText already forces your components to expose an accessible name and role — if a test can't find a control by its role/name, neither can a screen reader, so the test surfaces the gap. Second, with a dedicated axe-core assertion: jest-axe in unit/component tests (expect(await axe(container)).toHaveNoViolations()) or @axe-core/playwright in e2e runs the axe rules engine against the rendered DOM and flags violations — missing form labels, insufficient color contrast, invalid ARIA, images without alt, duplicate ids, wrong heading structure. Wiring this into CI catches a whole class of regressions automatically and cheaply. The crucial caveat is the CEILING: automated tools catch only a MINORITY of accessibility issues — commonly cited as roughly 30–50% — because most a11y is about MEANING and EXPERIENCE that a machine can't judge. Axe can tell you an image has alt text, not whether the alt text is meaningful; it can confirm a button has a name, not whether the tab order is logical, whether focus is managed when a modal opens, whether a custom widget is actually operable by keyboard, whether the screen-reader announcement makes sense, or whether an animation triggers vestibular issues. So the strategy is layered: (1) automated axe checks in CI as a regression net for the mechanical rules; (2) role/label-based queries so components are built accessible by default; (3) explicit tests for keyboard operability and focus management (tab through, assert focus lands where it should, Escape closes and returns focus) — things you CAN automate and axe won't check; and (4) manual testing with real assistive tech (VoiceOver/NVDA) and keyboard-only for the judgment calls automation can't make. Framing it honestly in an interview — 'automation is a floor, not a ceiling; it catches the mechanical violations so humans can spend their time on the experiential ones' — is the point.
Adding a11y regression checks to CI; explaining why automated a11y isn't sufficient alone.
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('form has no axe violations', async () => {
const { container } = render(<SignupForm />);
expect(await axe(container)).toHaveNoViolations(); // mechanical rules only (~30-50%)
});
// Also test what axe CAN'T: keyboard operability + focus management
test('modal traps focus and Escape returns it', async () => {
const user = userEvent.setup();
render(<Page />);
await user.click(screen.getByRole('button', { name: /open/i }));
await user.keyboard('{Escape}');
expect(screen.getByRole('button', { name: /open/i })).toHaveFocus();
});