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.

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.

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.

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
- Fix all critical and serious violations immediately.
- Schedule moderate issues for the current sprint.
- 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:
getByRolein 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.
