PW Accessibility Auditor
State check: the dependency is missing
@axe-core/playwright is not in package.json. Install it and say that you did, rather than
emitting code that cannot run:
npm install -D @axe-core/playwright
Where it goes
Specs in src/tests/a11y/, tagged @A11y so the reporter can filter them. Import test and
expect from @fixtures/test-base so the login and navigation fixtures are available; an audit
of a page you cannot reach is worthless.
Workflow
- Reach a stable state with a web-first assertion first. Scanning mid-render produces noise.
- Scope with
.include()/.exclude(), and tag to the standard you hold the product to (wcag2a,wcag2aa). - Gate on
criticalandserious. Logmoderateandminoras debt; do not silently pass them. - Report the WCAG criterion from each violation's
tagsandhelpUrlso the finding is actionable. - State the ceiling: axe catches roughly a third of real issues. Keyboard order, focus management, screen-reader output, and cognitive load still need a human. Automation is the floor.
Output shape
import AxeBuilder from '@axe-core/playwright';
import { test, expect } from '@fixtures/test-base';
import { createLogger } from '@utils/logger';
const log = createLogger('a11y.inventory');
test('@A11y inventory page has no critical or serious violations', async ({ page, loginWithInventory }) => {
const results = await new AxeBuilder({ page })
.include('[data-test="inventory-container"]')
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
const blocking = results.violations.filter((v) => v.impact === 'critical' || v.impact === 'serious');
for (const v of results.violations) {
log.info(`${v.impact}: ${v.id} (${v.tags.join(', ')}) -> ${v.helpUrl}`);
}
expect(blocking, blocking.map((v) => `${v.id}: ${v.help}`).join('\n')).toEqual([]);
});
Passing the violation list as the assertion message is deliberate: otherwise the failure reports only a length mismatch and you have to re-run to learn what broke.