How to Set Up Automated Accessibility Testing with Playwright and Axe

How to Set Up Automated Accessibility Testing with Playwright and Axe

by | Jul 28, 2026 | Uncategorized | 0 comments

Why Automated Accessibility Testing with Playwright Matters in 2026

Accessibility is no longer an afterthought. With the European Accessibility Act now in full enforcement and WCAG 2.2 widely adopted as the standard, development teams need reliable ways to catch violations before they ship to production. Manual audits are valuable, but they don’t scale. That’s where automated accessibility testing with Playwright and the axe-core engine becomes essential. This guide goes deeper on it.

At Box Software, we’ve integrated this exact stack into multiple client projects, and in this hands-on tutorial we’ll walk you through the entire process: from installation to running audits in your CI/CD pipeline, to making sense of the results.

accessibility testing code

What You’ll Build in This Tutorial

  • A working Playwright test suite with @axe-core/playwright integration
  • Reusable accessibility test helpers
  • Scoped audits for specific components
  • A GitHub Actions workflow that runs accessibility checks on every pull request
  • A reporting strategy to triage and fix violations

Playwright vs. Other Tools: Why This Combination Wins

Feature Playwright + Axe Cypress + Axe Selenium + Axe
Multi-browser support Chromium, Firefox, WebKit Limited All major
Auto-waiting Yes Yes Manual
Parallel execution Native Paid tier Requires setup
Headless CI speed Fast Medium Slow
WCAG 2.2 rules Full via axe-core Full via axe-core Full via axe-core

Step 1: Setting Up Your Playwright Project

If you don’t already have a Playwright project, create one from scratch:

npm init playwright@latest

Follow the prompts to choose TypeScript or JavaScript, your test folder, and whether to install browsers. Once finished, install the axe-core Playwright integration:

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

Verifying Your Installation

Run the default example to make sure Playwright is working:

npx playwright test

Step 2: Writing Your First Accessibility Test

Create a file named tests/accessibility.spec.ts:

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

test.describe('Homepage accessibility', () => {
  test('should not have any automatically detectable WCAG violations', async ({ page }) => {
    await page.goto('https://your-app.com');

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

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

This test loads your homepage, runs axe-core against the latest WCAG 2.2 AA ruleset, and fails if any violations are detected.

accessibility testing code

Step 3: Scoping and Excluding Selectors

Real-world apps include third-party widgets, ads, or legacy components you can’t immediately fix. AxeBuilder lets you scope or exclude regions:

const results = await new AxeBuilder({ page })
  .include('main')
  .exclude('#legacy-widget')
  .exclude('.third-party-chat')
  .analyze();

Disabling Specific Rules

When a rule produces false positives or is tracked in a backlog ticket, disable it explicitly so you stay honest:

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

Step 4: Creating a Reusable Helper

Don’t repeat yourself. Create tests/utils/a11y.ts:

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

export async function checkA11y(page: Page, context?: string) {
  const builder = new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag22aa']);

  if (context) builder.include(context);

  const results = await builder.analyze();
  expect(results.violations, formatViolations(results.violations)).toEqual([]);
}

function formatViolations(violations: any[]) {
  return violations
    .map(v => `[${v.id}] ${v.help} - ${v.nodes.length} node(s)`) 
    .join('\n');
}

Now your tests stay clean:

import { checkA11y } from './utils/a11y';

test('checkout flow is accessible', async ({ page }) => {
  await page.goto('/checkout');
  await checkA11y(page, 'main');
});

Step 5: Testing Authenticated and Interactive States

Many accessibility issues only appear after user interaction: opened modals, expanded menus, validation messages. Run audits at each meaningful state.

test('modal dialog is accessible when open', async ({ page }) => {
  await page.goto('/dashboard');
  await page.getByRole('button', { name: 'Open settings' }).click();
  await expect(page.getByRole('dialog')).toBeVisible();
  await checkA11y(page, '[role="dialog"]');
});

Step 6: Integrating with GitHub Actions CI/CD

Create .github/workflows/accessibility.yml:

name: Accessibility Tests

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test tests/accessibility.spec.ts
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

This pipeline fails the build if any WCAG violation is introduced, and uploads the HTML report as an artifact for review.

accessibility testing code

Step 7: Interpreting Axe-Core Results

Each violation object returned by axe-core contains valuable triage information:

Field What it tells you
id Rule identifier (e.g. color-contrast)
impact minor, moderate, serious, or critical
help Short human-readable explanation
helpUrl Link to Deque’s full documentation
nodes Array of affected DOM elements with selectors
tags WCAG levels and rule categories

Prioritization Strategy

  1. Fix all critical and serious violations immediately.
  2. Schedule moderate issues for the current sprint.
  3. Track minor issues in your backlog but don’t let them block deployment.

Step 8: Generating Rich HTML Reports

For better visibility, attach axe results to the Playwright HTML report:

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

test('homepage with attached report', async ({ page }, testInfo) => {
  await page.goto('/');
  const results = await new AxeBuilder({ page }).analyze();

  await testInfo.attach('accessibility-scan-results', {
    body: JSON.stringify(results, null, 2),
    contentType: 'application/json'
  });
});

What Automated Testing Cannot Catch

Be transparent with your team: axe-core typically catches about 30 to 40 percent of accessibility issues. The rest require human judgment. Automated tests will miss: This guide goes deeper on it.

  • Whether alt text is meaningful (only that it exists)
  • Keyboard navigation flow and logical tab order
  • Screen reader announcement quality
  • Cognitive accessibility and content clarity
  • Color choices that meet contrast but confuse colorblind users contextually

Pair automated audits with manual testing using NVDA, VoiceOver, or JAWS, plus keyboard-only navigation sessions.

Best Practices for Long-Term Success

  • Test early and often: run audits on every PR, not just before releases.
  • Test multiple states: empty, loading, error, and success states each have unique a11y concerns.
  • Use semantic locators: getByRole in Playwright doubles as an accessibility check.
  • Track violations over time: store JSON results in a database or tool like Allure to monitor trends.
  • Educate the team: share helpUrl links during code review.

FAQ

Can Playwright be used for accessibility testing?

Yes. Playwright combined with the official @axe-core/playwright package provides a robust automated accessibility testing solution that supports WCAG 2.0, 2.1, and 2.2 across Chromium, Firefox, and WebKit.

Is axe-core enough to ensure WCAG compliance?

No tool guarantees full compliance. Axe-core detects around a third of common issues. You still need manual testing, keyboard testing, and screen reader validation to claim WCAG conformance.

How much does it slow down my CI pipeline?

An axe audit typically adds 200 to 800 milliseconds per page. Even on a large suite, total overhead remains under a minute when tests run in parallel.

Should accessibility tests fail the build?

Yes for new code. We recommend a strict policy on pull requests touching production code, while granting a temporary allowlist for legacy areas with tracked remediation tickets.

Can I run axe-core on a single component instead of the whole page?

Absolutely. Use .include(selector) on the AxeBuilder to scope audits to a specific component, which is ideal for component-level tests.

Conclusion

Setting up automated accessibility testing with Playwright and axe-core is one of the highest-ROI investments your team can make. It catches regressions early, educates developers, and protects you against legal risk under modern accessibility legislation. Start with one critical page, expand to user flows, then enforce it in CI. Your users, and your future self, will thank you.

Need help implementing this stack across a complex application? The team at Box Software specializes in test automation and accessibility audits. Get in touch through our contact page to discuss your project.