axe-a11y ยท diff

git:20260724.d4db756 to git:20260726.19ddd36

38 added, 66 removed. Audit B to B.

---
name: axe-a11y
description: "Authors and runs axe-core accessibility scans - the most-deployed open-source a11y engine - via the `axe.run()` JavaScript API or the @axe-core/playwright / @axe-core/cli wrappers, parses the `violations[]` results into per-rule severity (critical / serious / moderate / minor), configures rule disable / disable-by-tag patterns, and emits JUnit-shaped output for CI gating. Use when the project ships UI tests in JavaScript / TypeScript and wants automated a11y coverage on every PR."
---
# axe-a11y
## Overview
axe-core is "an accessibility testing engine for websites and other
HTML-based user interfaces" maintained by Deque
([axe-core][readme]). It identifies approximately **57% of WCAG
issues automatically** and flags items requiring human review as
"incomplete" ([axe-core][readme]).
[readme]: https://github.com/dequelabs/axe-core
The integration shape: load the engine into a page (via test
fixture, browser extension, or framework adapter), call
`axe.run()`, and parse the `violations[]` array.
## When to use
- The project ships JavaScript / TypeScript UI tests
(Playwright / Cypress / Jest / Vitest).
- Automated a11y coverage on every PR is a goal.
- The team values **WCAG SC tagging** - axe rules map cleanly to
WCAG 2.0 / 2.1 / 2.2 SCs.
- Pair with `a11y-violation-gate`
for the ratchet pattern over baseline.
If the team is on a non-JS stack, evaluate
`pa11y-a11y` (CLI; uses axe-core
under the hood),
`lighthouse-a11y` (CI-friendly,
broader perf + a11y), or
`ibm-equal-access-a11y`.
## Install
```bash
npm install --save-dev axe-core
```
(Per [axe-core][readme].)
For framework integration (preferred over raw `axe.run()`):
| Package | When to use |
|------------------------------|-------------------------------------------|
| `@axe-core/playwright` | Playwright tests. |
| `@axe-core/react` | React-component scans during rendering. |
| `@axe-core/cli` | Headless CLI for arbitrary URLs. |
| `axe-core/api/install` (raw) | Custom integrations. |
## Authoring scans
### Raw `axe.run()` API
```javascript
import axe from 'axe-core';
axe.run()
.then(results => {
if (results.violations.length) {
console.error('a11y violations:', results.violations);
}
})
.catch(err => {
console.error('axe error:', err.message);
});
```
(Adapted from [axe-core][readme].)
For test environments where loading axe via `<script>` is easier:
```html
<script src="node_modules/axe-core/axe.min.js"></script>
```
### Playwright integration
```typescript
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('checkout page passes axe scan', async ({ page }) => {
await page.goto('/checkout');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa']) // limit to AA
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
```
### Cypress integration
```javascript
// cypress/support/commands.js
import 'cypress-axe';
// In a test
cy.visit('/checkout');
cy.injectAxe();
cy.checkA11y();
```
- ## Results structure
-
- `axe.run()` resolves to an object with four arrays
- ([axe-core][readme]):
-
- | Field | Meaning |
- |----------------|---------------------------------------------------------------|
- | `violations` | Definite issues - block this in CI. |
- | `incomplete` | Items needing human review (axe couldn't determine). |
- | `passes` | Successful checks. |
- | `inapplicable` | Rules that don't apply to this page. |
-
- Each violation has:
-
- | Field | Meaning |
- |----------------|---------------------------------------------------------------|
- | `id` | Rule ID (e.g. `color-contrast`, `label`, `aria-required-attr`). |
- | `impact` | `critical` / `serious` / `moderate` / `minor`. |
- | `tags` | Includes `wcag2a`, `wcag22aa`, etc. - for severity-by-SC tagging. |
- | `description` | One-line explanation. |
- | `help` | Longer remediation guidance. |
- | `helpUrl` | Direct link to Deque's rule documentation. |
- | `nodes` | Array of failing elements with `target` (selector), `html`, `failureSummary`. |
-
- Triage with `jq`:
-
- ```bash
- # Top violations by impact
- jq -r '.violations[] | "\(.impact): \(.id) - \(.description)"' axe-results.json
-
- # Just the failing selectors per rule
- jq -r '.violations[] | "\(.id):", (.nodes[].target | tostring)' axe-results.json
- ```
-
- ## Rule configuration
-
- ### By tags
-
- axe ships rules tagged with conformance levels:
-
- ```javascript
- new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
- ```
+ ## How to use
- Common tag sets:
+ 1. Install `axe-core` plus the framework wrapper (`@axe-core/playwright`
+ for Playwright; `cypress-axe` for Cypress).
+ 2. Add one scan test per page or flow, and drive the page into the
+ state you want scanned first (open modals, expand menus, submit
+ forms) before calling axe.
+ 3. Pin the conformance target with `.withTags([...])` (e.g. `wcag2a`,
+ `wcag2aa`, `wcag22aa`), then `.analyze()`.
+ 4. Suppress known false positives narrowly with `.disableRules([...])`
+ or `.exclude(selector)`, each with an inline reason.
+ 5. Persist the raw `accessibilityScanResults` JSON as a CI artifact.
+ 6. Feed that JSON to `a11y-violation-gate` for the ratchet gate instead
+ of asserting `violations.length === 0`.
+ 7. Route `incomplete` items to periodic manual screen-reader review
+ (per `screen-reader-test-author`).
- | Tag set | Coverage |
- |------------------------------------------------------------|----------------------------------|
- | `['wcag2a', 'wcag2aa']` | WCAG 2.0/2.1 A + AA. Default for most teams. |
- | `['wcag2a', 'wcag2aa', 'wcag22aa']` | Adds WCAG 2.2 AA criteria. |
- | `['wcag2aaa']` | AAA-only (rarely the gate). |
- | `['best-practice']` | Non-WCAG good practices. |
- | `['experimental']` | Beta rules. |
+ ## Results structure and rule configuration
- ### Disable specific rules
+ `axe.run()` resolves to `violations` / `incomplete` / `passes` /
+ `inapplicable` arrays; each violation carries `id`, `impact`, `tags`,
+ and a `nodes[]` array of failing selectors. Rules are selected by WCAG
+ tag (`withTags`) and suppressed by rule id or selector (`disableRules`
+ / `exclude`). Full field tables, tag sets, and `jq` triage:
+ [references/results-and-config.md](references/results-and-config.md).
- ```javascript
- new AxeBuilder({ page }).disableRules(['color-contrast'])
- ```
+ ## Worked example
- For per-page disabling (e.g. a known false positive on a specific
- component):
+ A Playwright suite adds `checkout.a11y.spec.ts`. The test navigates to
+ `/checkout`, runs
+ `new AxeBuilder({ page }).withTags(['wcag2a','wcag2aa','wcag22aa']).analyze()`,
+ and writes the result JSON to `axe-results.json`.
- ```javascript
- new AxeBuilder({ page })
- .exclude('.legacy-component') // selector exclusion
- .analyze();
- ```
+ The run returns one entry in `violations[]`: `id: color-contrast`,
+ `impact: serious`, tags including `wcag2aa`, and one node targeting
+ `button.primary` with a `failureSummary`.
- For per-rule severity in CI gating (e.g. block on `critical` /
- `serious` only): handle in
- `a11y-violation-gate` using
- the `impact` field.
+ `jq -r '.violations[] | "\(.impact): \(.id)"' axe-results.json` prints
+ `serious: color-contrast`. Piping the JSON to `a11y-violation-gate`
+ compares it against the baseline: because this rule/selector pair is
+ new, the gate fails the PR with the failing selector attributed. Fixing
+ the button's foreground colour clears it, and the next run reports
+ `violations: []`.
## CI integration
```yaml
# .github/workflows/a11y.yml
name: a11y
on:
pull_request:
push:
branches: [main]
jobs:
axe:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx playwright install --with-deps
- name: Run a11y tests
run: npx playwright test tests/a11y/
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: a11y-results
path: playwright-report/
retention-days: 14
```
For richer reporting, persist the raw `accessibilityScanResults`
JSON as a build artifact and pipe to
`a11y-violation-gate`.
## Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---------------------------------------------------------------|---------------------------------------------------------------------|-----|
| Asserting `violations.length === 0` | Existing legacy debt blocks every PR. | Use the ratchet pattern via `a11y-violation-gate`. |
| Disabling rules without comment in code | Reviewer can't tell which rules are intentionally off vs. forgotten. | Inline comment explaining why; quarterly review. |
| Scanning only the homepage | Most a11y bugs hide in less-traveled flows. | Scan a representative URL set: home + 1 logged-in dashboard + 1 form-heavy + 1 long-content. |
| Running axe in production | Performance overhead; possible info leak via verbose error logging. | CI / staging only. |
| Treating `incomplete` as `pass` | Items needing human review go unreviewed; defects escape. | Track `incomplete` separately; manual review per quarter. |
| One mega-test that runs axe across every page | One failure = whole test fails; remediation hard. | One axe test per page; failure attribution clear. |
## Limitations
- **Catches ~57% of WCAG issues** ([axe-core][readme]) - the rest
require manual screen-reader testing
(per `screen-reader-test-author`).
- **Rule false positives.** Rare but real; `disableRules` /
`exclude` are escape hatches with documented rationale.
- **Doesn't cover dynamic state changes well.** A modal that
opens after user interaction won't be scanned unless the test
triggers the open before calling `axe.run()`.
## References
- [axe-core][readme] - main repo: install, `axe.run()`, results
structure.
+ - [references/results-and-config.md](references/results-and-config.md) -
+ results arrays, violation fields, tag sets, disable / exclude.
- Deque rule documentation - https://dequeuniversity.com/rules/axe/
- @axe-core/playwright - https://github.com/dequelabs/axe-core-npm/tree/master/packages/playwright
- W3C WCAG 2.2 - https://www.w3.org/TR/WCAG22/
- `a11y-violation-gate` -
CI gate using axe results.
- `pa11y-a11y`,
`lighthouse-a11y`,
`wave-a11y`,
`ibm-equal-access-a11y` -
alternative scanners.