Skip to main content
Version: v6

Testing with Playwright

Use the AudioEye Accessibility Testing SDK Playwright package to run accessibility checks inside your Playwright end-to-end tests.

Before you start

Complete the package-manager and client-credential setup in getting started before installing this package.

Requirements

  • Node.js 22 or 24
  • @playwright/test version 1.39.0 or later
  • a working Playwright test project
  • AudioEye client credentials (Client ID and Client Token) configured through your package manager and environment

If your project does not already use Playwright, start with the Playwright installation guide.

Install

npm install -D @audioeye/testing-sdk-playwright @playwright/test

If Playwright browser binaries are not installed yet, install them:

npx playwright install

Choose a setup style

The SDK exposes Playwright fixtures named accessibility and a11y, plus an expect re-export with a toBeAccessible matcher. You have two common setup options:

  1. Use AudioEye's extended test and expect exports directly for the fastest setup.
  2. Create your own shared fixture file if your team already centralizes Playwright fixtures.

If you're onboarding a project for the first time, start with option 1.

Quick start: first passing test

Import test and expect from @audioeye/testing-sdk-playwright. This re-exports Playwright's test extended with the accessibility / a11y fixtures, plus an expect extended with the toBeAccessible matcher.

Create a test file like tests/accessibility.spec.ts:

tests/accessibility.spec.ts
import { test, expect } from '@audioeye/testing-sdk-playwright';

test('homepage has no level A or AA accessibility issues', async ({ page }) => {
await page.setContent(`
<main>
<h1>Welcome</h1>
<button type="button">Start free trial</button>
<a href="/learn-more">Learn more</a>
<img src="logo.png" alt="AudioEye company logo" />
</main>
`);

await expect(page).toBeAccessible({ level: 'AA' });
});

Run the test with Playwright:

npx playwright test tests/accessibility.spec.ts

This verifies that:

  • the package installed successfully
  • your client credentials are configured correctly and your license verified
  • Playwright can run the test
  • the AudioEye matcher is wired into expect
  • the SDK can evaluate the page and report issues
Failed assertions auto-attach a structured report

When toBeAccessible fails, the matcher attaches a11y-report.json to the current test.info() automatically. The report shows up in the Playwright HTML report alongside the failing test, with each issue's rule code, target selector, help URL, and full metadata. No extra wiring required.

First failing test

A failing example is useful when you want to confirm the SDK is actively catching issues.

tests/accessibility-failing.spec.ts
import { test, expect } from '@audioeye/testing-sdk-playwright';

test('example failing test', async ({ page }) => {
await page.setContent(`
<main>
<h1>Welcome</h1>
<img src="hero.png" alt="image" />
</main>
`);

// Fails because the alt text is too generic.
await expect(page).toBeAccessible();
});

Setup option 1: use the extended test and expect directly

Import both from @audioeye/testing-sdk-playwright. Don't mix in expect from @playwright/test — you'd lose the toBeAccessible matcher.

tests/example.spec.ts
import { test, expect } from '@audioeye/testing-sdk-playwright';

test('example Playwright test using AudioEye matcher', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page).toBeAccessible({ level: 'AA' });
});

Use this approach when:

  • you are getting started quickly
  • you do not already have a custom shared fixture setup
  • you want the least amount of project wiring

Setup option 2: add AudioEye fixtures to your existing test runner

If your Playwright project already has a shared fixture file, extend that file with AudioEye's fixtures. To get the matcher, also import expect from @audioeye/testing-sdk-playwright.

tests/fixtures.ts
import { type AudioEyeFixtures, AudioEyeA11y } from '@audioeye/testing-sdk-playwright';
import { test as base } from '@playwright/test';

export const test = base.extend<AudioEyeFixtures>({
accessibility: async ({ page }, use) => {
await use(new AudioEyeA11y(page));
},
a11y: async ({ accessibility }, use) => {
await use(accessibility);
},
});
tests/example.spec.ts
// Use AudioEye's expect (with toBeAccessible) plus your shared test runner.
import { expect } from '@audioeye/testing-sdk-playwright';

import { test } from './fixtures';

test('example Playwright test', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page).toBeAccessible({ level: 'AA' });
});

Use this approach when:

  • you already extend Playwright fixtures for authentication, seeded data, or page objects
  • you want one shared test export across your project
  • you want AudioEye integrated into your existing fixture architecture

Usage patterns

Assert the whole page is accessible

import { test, expect } from '@audioeye/testing-sdk-playwright';

test('home page is accessible', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page).toBeAccessible({ level: 'AA' });
});

Assert a specific section is accessible

Pass a Playwright Locator to scope the matcher.

test('navbar is accessible', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page.locator('.navbar__right')).toBeAccessible({ level: 'A' });
});

Inspect issues with accessibility.scan (or a11y.scan)

When you want to assert which issues fired, count them, or chain filters, use scan(...) instead of the matcher. It takes either a Page or a Locator and returns an A11yResults instance.

import { test, expect } from '@audioeye/testing-sdk-playwright';

test('only the expected issues fire', async ({ page, a11y }) => {
await page.goto('http://localhost:3000/');

const report = await a11y.scan(page.locator('.navbar__right'), { level: 'A' });

expect(report.has('Link_VisualIndicator_Missing')).toBe(true);
expect(report.resultCodes).toEqual(['Link_VisualIndicator_Missing', 'Svg_Name_Missing']);
expect(report.issues[0].helpUrl).toMatch(/developer\.audioeye\.com/);
});

scan defaults to scanning the whole page when called with no arguments:

const report = await a11y.scan();
expect(report.conformanceLevel('AA').isEmpty).toBe(true);

Filter, ignore, or restrict rules

The matcher and scan accept the same filter options:

await expect(page).toBeAccessible({
level: 'AA',
ignoreRules: ['Img_Name_WeakName'],
});

const report = await a11y.scan(page, {
onlyRules: ['Link_Name_Missing', 'Button_Name_Missing'],
excludingSelector: '.cookie-banner',
});

Filter by CSS selector

Use withinSelector to keep only issues inside a region, or excludingSelector to ignore issues inside a known container. Selectors use normal CSS ancestor matching in the page.

await expect(page).toBeAccessible({ excludingSelector: '[data-testid="third-party-widget"]' });

const report = await a11y.scan(page, { withinSelector: 'main article' });
expect(report.isEmpty).toBe(true);

Group results by WCAG level

test('groups issues by WCAG level', async ({ page, a11y }) => {
await page.goto('http://localhost:3000/');

const report = await a11y.scan();
const grouped = report.conformanceLevel('AA').resultsGroupedByWcagSuccessCriteriaLevel;
expect(Object.keys(grouped)).toEqual(['A']);
});

For most teams, this sequence works well:

  1. Start with a single passing test using page.setContent(...) and await expect(page).toBeAccessible().
  2. Add a failing test to confirm the SDK catches known issues.
  3. Point the test at a real local page with page.goto(...).
  4. Add accessibility assertions to an existing smoke test or critical user flow.
  5. Reuse fixtures across your Playwright suite if needed.

This approach helps you separate installation problems from application-specific issues.

SDK API

expect(page | locator).toBeAccessible(options?)

Asserts that the given Playwright Page or Locator has no accessibility issues after applying filters. On failure, attaches a11y-report.json to test.info() so the issue list shows up in the Playwright HTML report.

await expect(page).toBeAccessible();
await expect(page.locator('.navbar')).toBeAccessible({ level: 'AA' });

Parameters

options.level ('A' | 'AA' | 'AAA', optional) — WCAG conformance level filter; cumulative by default.

options.onlyRules (string[], optional) — Restrict to these rule codes.

options.ignoreRules (string[], optional) — Drop these rule codes.

options.withinSelector (string, optional) — Keep only issues whose target descends from this CSS selector.

options.excludingSelector (string, optional) — Drop issues whose target descends from this CSS selector.

options.baselineIds (string[], optional) — Drop issues whose stable fingerprint (A11yIssue.id) appears in this list.

accessibility.scan(target?, options?) — also available as a11y.scan(...)

Run AudioEye's rules and return an A11yResults instance for inspection. Does not fail the test on its own — use the matcher or your own assertions for that.

const report = await a11y.scan(); // whole page
const report = await a11y.scan(page.locator('.navbar')); // scoped
const report = await a11y.scan(page.locator('.navbar'), { level: 'A' }); // scoped + filtered

Parameters

target (Page | Locator, optional) — what to scan. Defaults to the page.

options (same shape as toBeAccessible options, optional) — filters applied to the report before returning.

Returns

Promise<A11yResults> — see the Jest API reference for the full list of filters, queries, and the per-issue shape (which is the same across frameworks).

Legacy API (deprecated)

Deprecated in v6, removed in v7

The accessibility.evaluate(...) form continues to work in v6 but emits a one-time deprecation warning the first time it is called per process. New code should use the toBeAccessible matcher or accessibility.scan(...). See the v5 → v6 upgrade guide.

import { test } from '@audioeye/testing-sdk-playwright';
import { expect } from '@playwright/test';

test('legacy evaluate', async ({ page, accessibility }) => {
await page.goto('http://localhost:3000/');

const results = await accessibility.evaluate(page.locator('html:root'));
expect(results.resultCodes).toEqual([]);
});

evaluate(locator?) defaulted to page.locator('html:root'). Its replacement, scan(target?, options?), defaults to the entire page.

Troubleshooting

The package will not install

Make sure you completed the client-credential configuration in getting started.

Playwright is installed but tests will not launch

Install Playwright's browser binaries:

npx playwright install

expect(...).toBeAccessible is not a function

Make sure you import expect from @audioeye/testing-sdk-playwright, not from @playwright/test. The AudioEye package re-exports Playwright's expect extended with the matcher.

I need to test authenticated flows

Use your existing Playwright login strategy, then call await expect(page).toBeAccessible() (or accessibility.scan()) after the page reaches the state you want to verify.

I need help understanding which rules can run in this environment

See How Rules Work in Different Testing Scenarios.