Skip to main content
    Back to blog
    DevelopmentPublished on October 27, 2026

    How to Test Accessibility: Automation, Manual Checks and a Checklist

    What automated tools catch and what they miss, tests with jest-axe and Playwright, a 15-minute manual check, screen reader commands and a pre-release checklist

    How to Test Accessibility: Automation, Manual Checks and a Checklist

    Part 8 of 8 in the "Accessible Web in Practice" series. Previous part: Accessibility in React: IDs, Focus on Route Change and Announcements.

    Automated checks find just over half of accessibility issues. The final part of the series covers a three-layer testing setup, what to check by hand in 15 minutes, and a checklist worth running before every release.

    How to test

    Automation: necessary but not enough

    Automated tools don't find everything. Deque, the makers of axe-core, analyzed audit data (over 13,000 pages and nearly 300,000 issues) and found that automated tests caught 57.4% of issues by volume (Deque). The rest — whether alt text makes sense, whether focus order is logical, whether errors are understandable, how widgets behave — needs a human.

    So a sensible setup has three layers: a linter while writing code, automated tests in CI, and manual checks of key user flows.

    Browser tools:

    • Lighthouse (built into Chrome DevTools) — a quick score covering a subset of axe rules;
    • axe DevTools — Deque's extension, more detailed than Lighthouse;
    • WAVE — WebAIM's extension, shows issues visually right on the page;
    • Accessibility panel in DevTools — the accessibility tree, computed names and roles.

    Unit tests. Testing Library nudges you toward accessibility by design: getByRole and getByLabelText find elements the way a screen reader does. If a test can't find a button with getByRole('button', { name: 'Send' }), neither can a user.

    // Vitest/Jest + Testing Library + jest-axe
    import { render, screen } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import { axe, toHaveNoViolations } from 'jest-axe';
    import { expect, test } from 'vitest';
    import { ContactForm } from './ContactForm';
    
    expect.extend(toHaveNoViolations);
    
    test('form has no axe violations', async () => {
      const { container } = render(<ContactForm />);
      expect(await axe(container)).toHaveNoViolations();
    });
    
    test('error is linked to the field and focus moves to it', async () => {
      const user = userEvent.setup();
      render(<ContactForm />);
    
      await user.click(screen.getByRole('button', { name: 'Send' }));
    
      const email = screen.getByLabelText('Email address');
      expect(email).toHaveAttribute('aria-invalid', 'true');
      expect(email).toHaveAccessibleDescription(/@ sign/);
      expect(email).toHaveFocus();
    });
    

    (toHaveAccessibleDescription and toHaveFocus come from @testing-library/jest-dom.)

    End-to-end. Checking whole pages in Playwright with @axe-core/playwright:

    import { test, expect } from '@playwright/test';
    import AxeBuilder from '@axe-core/playwright';
    
    test('home page has no WCAG A/AA violations', async ({ page }) => {
      await page.goto('/');
      const results = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
        .analyze();
      expect(results.violations).toEqual([]);
    });
    
    test('modal: focus inside, Esc closes, focus returns', async ({ page }) => {
      await page.goto('/');
      const opener = page.getByRole('button', { name: 'Book a consultation' });
      await opener.click();
    
      const dialog = page.getByRole('dialog', { name: 'Book a consultation' });
      await expect(dialog).toBeVisible();
      await expect(dialog.getByLabel('Name')).toBeFocused();
    
      await page.keyboard.press('Escape');
      await expect(dialog).toBeHidden();
      await expect(opener).toBeFocused();
    });
    

    A 15-minute manual check

    For each key user flow (placing an order, sending a request, finding contact info):

    1. Keyboard only. Complete the flow without a mouse. Is focus visible at every step? Is the order logical? Any traps? Do menus and dialogs open and close?
    2. 200% and 400% zoom. Nothing overlaps, no horizontal scrolling, nothing is cut off.
    3. Structure. Look at the list of headings and landmarks (an extension or the screen reader's rotor) — does the page make sense from those alone?
    4. Screen reader. Go through the flow with at least one screen reader. The minimum command set:
    VoiceOver (macOS) NVDA (Windows)
    Turn on Cmd + F5 Ctrl + Alt + N
    Next item Ctrl + Option + → ↓
    Next heading Ctrl + Option + Cmd + H H
    Elements list (rotor) Ctrl + Option + U Insert + F7
    Activate Ctrl + Option + Space Enter

    Ask yourself: does every element have a clear name? Are changes announced (errors, items added to cart)? Do I know where I am?

    1. Display modes. Turn on prefers-reduced-motion, forced-colors and dark mode through DevTools emulation (Rendering).

    Test with people

    No checklist replaces a user who works with a screen reader or voice control every day. If the product matters, include such people in usability testing.

    Pre-release checklist

    Structure and semantics

    • <html lang="..."> is set; passages in other languages are marked up
    • A unique <title> on every page
    • Landmarks: header, nav (labeled if there are several), main (one), footer
    • Headings reflect structure, no skipped levels
    • Buttons are <button>, links are <a href>; no clickable divs

    Images and media

    • Informative images have meaningful alt, decorative ones have alt=""
    • Icon buttons and icon links have an accessible name
    • Videos have captions, audio has a transcript; no autoplay with sound

    Forms

    • Every field has a visible <label> connected to it
    • Radio and checkbox groups use fieldset + legend
    • autocomplete on fields with personal data
    • Errors: text next to the field, aria-invalid, aria-describedby, focus on the first error
    • Paste is allowed in password and code fields

    Keyboard and focus

    • The whole flow works with a keyboard, no traps
    • Focus is always visible and high-contrast; no outline: none without a replacement
    • There's a "Skip to main content" link
    • Sticky elements don't cover focus (scroll-padding-top)
    • Modals: focus inside, Esc closes, focus returns
    • No tabindex greater than 0

    Visual

    • Text contrast ≥ 4.5 : 1 (large text ≥ 3 : 1), components and focus ≥ 3 : 1
    • Information isn't conveyed by color alone
    • The page works at 320px width and 200–400% zoom
    • Zoom isn't disabled in meta viewport
    • prefers-reduced-motion and forced-colors are handled
    • Targets ≥ 24×24px (44×44 is better on mobile)

    Dynamic content

    • Important changes are announced through a live region that exists in advance
    • Route changes in SPAs update the title and move focus
    • ARIA states (aria-expanded, aria-pressed...) stay in sync with the UI

    Testing

    • No jsx-a11y lint errors
    • No axe violations (in tests or the extension)
    • Key flows checked with a keyboard and at least one screen reader

    Sources and further reading

    Standards and references

    Research and data

    Practice and blogs

    Tools

    Accessibility isn't a separate feature or a final audit — it's a habit, like responsive design. Start small: field labels, visible focus, contrast and real buttons cover most of what trips users up. The rest comes with practice.

    The "Accessible Web in Practice" series

    1. Web Accessibility: Who It Really Serves and Where to Start
    2. Semantic HTML and Alt Text: The Foundation of an Accessible Website
    3. Accessible Forms: Labels, Hints, Errors and Autofill
    4. Keyboard and Focus: Building a Website You Can Use Without a Mouse
    5. Contrast, Motion and Button Size: Visual Accessibility
    6. ARIA and Dynamic Content: When Attributes Help and When They Hurt
    7. Accessibility in React: IDs, Focus on Route Change and Announcements
    8. How to Test Accessibility: Automation, Manual Checks and a Checklist — you are here

    Agama Labs builds websites and web applications. Want to check how accessible your site is? Write to us: hello@agamalabs.com

    Contact

    Let’s talk about your project

    Leave your details and we’ll get back to you within 2 business hours for a free consultation. Tell us what you’re building - we’ll help you choose the right approach.

    Business hours

    Mon–Fri: 08:00 AM – 5:00 PM (PST)
    Sat–Sun: Closed

    Request a call

    Fill out the form and we’ll reach out shortly

    By clicking the button, you agree to our Privacy Policy

    We value your privacy

    We use analytics cookies, including third-party ones, to see how the site is used and improve it