@wcag-checkr/ci
Headless wcagcheckr accessibility audit runner for CI/CD pipelines.
Drives the wcagcheckr Chrome extension via Playwright, running full-page WCAG audits across the same 108-state matrix (hover, focus, dark mode, RTL, breakpoints, etc.) the extension uses interactively. So your CI catches what your developer sees.
Why this matters
Competing tools (axe DevTools Pro, Siteimprove Enterprise) charge for CI/CD integration. We ship it free. And ours is the only CI runner that audits at hover/focus/dark/RTL/breakpoint state combinations — competitors run a single default-state audit.
Install
npm install --save-dev @wcag-checkr/ci playwright
npx playwright install chromiumUse
npx wcagcheckr-ci audit https://your-site.com/Default: outputs JSON to stdout, exits non-zero if any serious or critical violations.
Common flags
# Write SARIF for GitHub PR annotations
npx wcagcheckr-ci audit https://your-site.com/ --format sarif --output a11y.sarif
# Write JUnit XML for Jenkins/GitLab CI
npx wcagcheckr-ci audit https://your-site.com/ --format junit --output a11y.xml
# Strict — fail on any violation, even minor
npx wcagcheckr-ci audit https://your-site.com/ --threshold minor
# Permissive — never fail (just collect findings)
npx wcagcheckr-ci audit https://your-site.com/ --threshold none
# Audit with a license token (unlocks paid features like forensic anchoring)
npx wcagcheckr-ci audit https://your-site.com/ --license $WCAGCHECKR_LICENSE
# Use your own built extension (e.g. self-hosted fork)
npx wcagcheckr-ci audit https://your-site.com/ --extension-dir ./path/to/distVerifying a forensic log
The extension's Forensic tab can export your full audit log as JSON (the export JSON button). That file contains every audit's identity hash, RFC 3161 trusted-timestamp token, and ed25519 server signature. wcagcheckr-ci verify validates it offline (the server is only contacted to fetch the public key by fingerprint):
npx wcagcheckr-ci verify wcagcheckr-forensic-log.jsonPer entry, the verifier:
- Recomputes the SHA-256 identity hash from the stored fields and checks it against the recorded hash.
- Fetches the server's ed25519 public key matching the receipt's fingerprint and verifies the signature over
(hash, anchoredAt, tsaName, productSlug, prevAuditHash). - If
prevAuditHashreferences another entry in the same export, validates the chain link. (Deep chain links require exporting the full history.)
Exit code is 0 only when every entry's hash and signature verify cleanly.
To fully verify the RFC 3161 timestamp (which proves the hash was witnessed by a public TSA at the recorded time), save the receipt's rfc3161TokenBase64 to a .tsr file and run openssl ts -verify against FreeTSA's certificate chain. That step is out-of-scope for v0 of the CLI verifier — the ed25519 signature already commits to the same TSA token bytes, so a verified ed25519 signature is strong evidence the receipt was issued by our server in response to that TSA timestamp.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success — threshold not exceeded |
| 1 | Threshold exceeded — CI failure signal |
| 2 | Runtime error (target unreachable, extension didn't load, etc.) |
GitHub Actions example
name: a11y
on: [pull_request]
jobs:
wcagcheckr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npx playwright install chromium --with-deps
- run: npx wcagcheckr-ci audit ${{ env.PREVIEW_URL }} --format sarif --output a11y.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: a11y.sarif }GitLab CI example
a11y:
image: node:20
before_script:
- npm ci
- npx playwright install chromium --with-deps
script:
- npx wcagcheckr-ci audit "$PREVIEW_URL" --format junit --output a11y.xml
artifacts:
reports:
junit: a11y.xml
when: alwaysLicense gating
The audit command can run without a license — free-tier audits work. To unlock paid features (forensic anchoring, AI summaries) in a CI run, pass --license <token>. The CLI activates the token via the same LICENSE_SET_REQUEST message the extension UI uses; failures are surfaced via a non-zero exit code.
For verification, no license is required. The forensic anchor verifier reads a public-key endpoint and recomputes signatures — anyone who receives a forensic-log JSON can validate it without any wcagcheckr credentials.
Programmatic API (planned)
A Node-importable API is on the roadmap:
import { audit } from '@wcag-checkr/ci';
const { violations, sarif } = await audit('https://your-site.com/');Until then, parse the JSON output of the CLI — same shape as the extension's "Export → JSON" output.
Limitations (v0)
- Auditing happens against the rendered DOM at the URL you pass — preview-deploy URLs work great; localhost requires the CI runner to also be the host (use
npm run start &then audithttp://localhost:PORT). - Authentication: log-in flows aren't yet supported; pass URLs that don't require auth, or fork to inject cookies/auth headers via Playwright's
storageState. - The runner uses
headless: 'new'(Chromium's modern headless mode); some sites that detect headless browsers may behave differently. Workaround: use--extension-dirwith a forked runner that switches toheadless: falseon a virtual display.
License
UNLICENSED until commercial release. See wcagcheckr.com/license.