Back to Blog

Accessibility Testing with Playwright and Axe: Catch Every WCAG Violation in CI

Learn how to integrate automated accessibility testing into your E2E test suite using Playwright and the axe-core engine. This comprehensive guide covers WCAG compliance, ARIA patterns, keyboard navigation, and practical strategies for building truly inclusive web applications.

Scanly App

Published

9 min read

Reading time

Related articles: Also see which testing framework makes accessibility testing easiest, extending Playwright beyond functional checks to quality attributes, and catching accessibility violations early with shift-left practices.

Accessibility Testing with Playwright and Axe: Catch Every WCAG Violation in CI

Accessibility is not a feature�it's a fundamental right. Yet, according to WebAIM's 2025 Million Report, over 96% of the top one million home pages had detectable WCAG 2 failures. This is not because developers don't care; it's because accessibility is complex, constantly evolving, and often tested manually if at all.

The good news? Modern tools like Playwright, combined with the axe-core accessibility engine, make it possible to automate a significant portion of accessibility testing. By integrating these checks into your continuous integration pipeline, you can catch and fix violations early, before they reach production.

In this guide, we'll cover:

  • What accessibility testing is and why it matters
  • How Playwright and axe-core work together
  • Step-by-step implementation of automated accessibility checks
  • Best practices for WCAG compliance and ARIA patterns
  • Common pitfalls and how to avoid them

Whether you're a QA engineer, developer, founder, or no-code tester, this article will give you the tools and knowledge to build more inclusive web experiences.

What is Web Accessibility (a11y)?

Web accessibility means ensuring that websites, applications, and digital tools are usable by everyone�including people with disabilities. This includes:

  • Visual impairments: Blindness, low vision, color blindness
  • Auditory impairments: Deafness or hearing loss
  • Motor impairments: Difficulty using a mouse or keyboard
  • Cognitive impairments: Learning disabilities, memory issues, attention disorders

The Web Content Accessibility Guidelines (WCAG), published by the W3C, are the global standard for accessibility. The most recent version is WCAG 2.2, with a forthcoming WCAG 3.0 (formerly "Silver") on the horizon. WCAG is organized around four principles, known as POUR:

Principle Description
Perceivable Information must be presentable to users in ways they can perceive (e.g., text alternatives for images).
Operable UI components and navigation must be operable (e.g., keyboard navigation).
Understandable Information and operation of the UI must be understandable (e.g., clear labels, predictable navigation).
Robust Content must be robust enough to be interpreted by assistive technologies (e.g., valid HTML, ARIA).

WCAG has three conformance levels:

  • Level A: Basic accessibility (minimum legal requirement in many jurisdictions)
  • Level AA: Recommended target for most public-facing sites
  • Level AAA: Enhanced accessibility (aspirational for most organizations)

Why Automate Accessibility Testing?

Manual accessibility testing�using screen readers like JAWS, NVDA, or VoiceOver�is essential, especially for complex interactions and user flows. However, it's time-consuming and requires specialized expertise.

Automated accessibility testing can:

  • Catch a large percentage of common issues (estimated 30-50% of WCAG violations)
  • Run continuously as part of your CI/CD pipeline
  • Provide immediate feedback to developers during local development
  • Scale across hundreds of pages and components with minimal effort
  • Establish a baseline and prevent regressions

Tools like axe-core are highly respected in the a11y community. Developed by Deque Systems, axe-core is an open-source accessibility testing engine that runs against the DOM and reports violations based on WCAG and other standards.

Playwright + Axe: The Perfect Pairing

Playwright is a modern browser automation framework. axe-core is a powerful JavaScript library for accessibility testing. When combined, you can:

  1. Navigate to a page or component with Playwright
  2. Inject axe-core into the page
  3. Run an accessibility scan
  4. Assert that no violations exist

The community-maintained @axe-core/playwright package makes this integration seamless.

Setting Up Playwright with Axe

Prerequisites

Ensure you have a Playwright project set up. If not:

npm init playwright@latest

Installation

Install the axe-core Playwright integration:

npm install --save-dev @axe-core/playwright

Basic Usage

Here's a simple test that scans a page for accessibility violations:

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage should not have accessibility violations', async ({ page }) => {
  await page.goto('https://example.com');

  const accessibilityScanResults = await new AxeBuilder({ page }).analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

If any violations are found, the test will fail and output a detailed report with:

  • The rule that was violated (e.g., color-contrast, label, image-alt)
  • The impact level (minor, moderate, serious, critical)
  • The HTML nodes that failed
  • Suggestions for how to fix the issue

Advanced Configuration

Target Specific Regions

You can limit the scan to a specific part of the page:

const results = await new AxeBuilder({ page }).include('#main-content').exclude('.advertisement').analyze();

Disable Specific Rules

Some rules may not apply to your application or may generate false positives. You can disable them:

const results = await new AxeBuilder({ page }).disableRules(['color-contrast', 'duplicate-id']).analyze();

Caution: Only disable rules when you have a valid reason and document it in your test.

Test Against Specific WCAG Levels

By default, axe tests against WCAG 2.1 Level A and AA. You can customize this:

const results = await new AxeBuilder({ page })
  .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
  .analyze();

Set a Baseline and Allow Only Specific Violations

If you're retrofitting accessibility into an existing app, you might have many existing violations. You can capture a baseline and only fail on new violations:

import fs from 'fs';

test('should not introduce new a11y violations', async ({ page }) => {
  await page.goto('https://example.com');
  const results = await new AxeBuilder({ page }).analyze();

  const baseline = JSON.parse(fs.readFileSync('a11y-baseline.json', 'utf8'));
  const newViolations = results.violations.filter(
    (v) => !baseline.some((b) => b.id === v.id && b.nodes.length === v.nodes.length),
  );

  expect(newViolations).toEqual([]);
});

Testing Common Accessibility Patterns

1. Keyboard Navigation

One of the most critical aspects of accessibility is ensuring that all interactive elements are keyboard accessible.

test('should be fully keyboard navigable', async ({ page }) => {
  await page.goto('https://example.com');

  // Start from the first focusable element
  await page.keyboard.press('Tab');
  let focusedElement = await page.evaluate(() => document.activeElement.tagName);
  console.log('First focused element:', focusedElement);

  // Continue tabbing through the page
  for (let i = 0; i < 10; i++) {
    await page.keyboard.press('Tab');
    focusedElement = await page.evaluate(() => document.activeElement.tagName);
    expect(['A', 'BUTTON', 'INPUT', 'TEXTAREA']).toContain(focusedElement);
  }
});

2. Focus Management in Modals

When a modal opens, focus should move to the modal and be trapped within it until it closes.

test('modal should trap focus', async ({ page }) => {
  await page.goto('https://example.com');

  await page.click('button[aria-label="Open modal"]');
  await page.waitForSelector('[role="dialog"]');

  // First element inside the modal should be focused
  const firstFocusable = page.locator('[role="dialog"] button').first();
  await expect(firstFocusable).toBeFocused();

  // Tab to the last element and ensure focus cycles back
  await page.keyboard.press('Shift+Tab');
  const lastFocusable = page.locator('[role="dialog"] button').last();
  await expect(lastFocusable).toBeFocused();
});

3. ARIA Patterns

ARIA (Accessible Rich Internet Applications) attributes provide semantic meaning to custom components. For example, a navigation menu should have role="navigation", and buttons that control other elements should use aria-controls.

test('navigation should have correct ARIA roles', async ({ page }) => {
  await page.goto('https://example.com');

  const nav = page.locator('nav');
  await expect(nav).toHaveAttribute('aria-label', 'Main navigation');

  const menuButton = page.locator('button[aria-expanded]');
  const isExpanded = await menuButton.getAttribute('aria-expanded');
  expect(isExpanded).toBe('false');

  await menuButton.click();
  await expect(menuButton).toHaveAttribute('aria-expanded', 'true');
});

4. Screen Reader Announcements (Live Regions)

Dynamic content updates should be announced to screen readers using aria-live.

test('notifications should be announced to screen readers', async ({ page }) => {
  await page.goto('https://example.com');

  const liveRegion = page.locator('[aria-live="polite"]');
  await expect(liveRegion).toBeEmpty();

  await page.click('button#trigger-notification');
  await expect(liveRegion).toHaveText('Your action was successful!');
});

Common Accessibility Violations and How to Fix Them

Violation Description Fix
color-contrast Text does not have sufficient contrast. Ensure text has at least 4.5:1 contrast (7:1 for AAA).
image-alt Images missing alt attributes. Add descriptive alt text for informative images; use alt="" for decorative images.
label Form inputs missing associated labels. Use <label for="input-id"> or aria-label attributes.
button-name Buttons without accessible names. Use text content, aria-label, or aria-labelledby.
link-name Links without descriptive text. Avoid "click here". Use descriptive link text like "Read the full report".
aria-required-children ARIA roles used incorrectly. Ensure that roles like list contain listitem children.
heading-order Headings skipped (e.g., <h1> to <h3>). Maintain a logical heading hierarchy.
landmark-unique Multiple landmarks of the same type without labels. Add unique aria-label to each landmark (e.g., <nav aria-label="Primary">, <nav aria-label="Footer">).

Integrating Accessibility Tests into CI/CD

To prevent regressions, run your accessibility tests on every pull request.

Example GitHub Actions Workflow (.github/workflows/a11y-tests.yml):

name: Accessibility Tests

on:
  pull_request:
    branches:
      - main

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '22'
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run test:a11y
      - if: failure()
        run: npx playwright show-report

Create a dedicated test script in package.json:

"scripts": {
  "test:a11y": "playwright test tests/a11y.spec.ts"
}

Accessibility Beyond Automation

While automated testing is incredibly valuable, it's not a complete solution. Manual testing with assistive technologies is essential to catch:

  • Screen reader usability issues: Does the navigation make sense when read aloud?
  • Cognitive load: Is the interface understandable and predictable?
  • Keyboard-only navigation quality: Can users accomplish tasks efficiently?

Combine automated tools with:

  • Manual audits using tools like Lighthouse, WAVE, or browser extensions
  • Real user feedback from people with disabilities
  • Inclusive design reviews during the design phase

The Business Case for Accessibility

Beyond the moral imperative, accessibility is good for business:

  • Legal Compliance: In the US, the ADA applies to websites. The EU has the EAA (European Accessibility Act). Many countries have similar regulations.
  • Market Reach: Over 1 billion people globally live with disabilities. Accessible sites can serve a larger audience.
  • SEO Benefits: Many accessibility best practices (semantic HTML, alt text, clear headings) also improve search rankings.
  • Better UX for All: Accessible design often leads to cleaner, more usable interfaces for everyone.

Conclusion

Accessibility testing is no longer optional�it's a professional and ethical responsibility. By integrating tools like Playwright and axe-core into your development workflow, you can catch and fix accessibility issues early, at scale, and continuously.

Start small: add one accessibility test to your suite. Scan your homepage. Fix the violations. Expand to more pages and user flows. Over time, you'll build a culture of inclusivity and a product that works for everyone.

Ready to build a more accessible web? Sign up for ScanlyApp and integrate automated accessibility testing into your QA workflow today.

Related Posts