Documentation · GitHub Action v2
Accessibility testing in GitHub Actions
The Accessibility Pro Action scans a URL against WCAG 2.2 on every pull request with five engines and live-DOM verification, posts one comment that updates itself, and fails the build when the result crosses the threshold you set. This page is the complete reference. For what the product does beyond CI, see Accessibility Pro for developers.
Install
Add one workflow file. There is no checkout, no setup-node and no browser install: the scan runs on Accessibility Pro's
infrastructure, and the Action is a dependency-free Node script that
calls it. The Action is listed on the GitHub Marketplace and its source is MIT licensed.
# .github/workflows/accessibility.yml
name: Accessibility
on: pull_request
permissions:
pull-requests: write # post the results comment
id-token: write # count free-tier quota against this repo
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: accessibility-pro/action@v2
with:
url: ${{ secrets.STAGING_URL }}The URL must be reachable from the internet, because the scanner loads it the way a visitor's browser would. A preview deployment per pull request works well; store its address as a secret if it is not public.
Permissions
pull-requests: write lets the Action post or update its
comment. Without it you get a warning and the results still appear in
the job summary. id-token: write lets the Action prove,
with a signed GitHub OIDC token, which repository the free-tier quota
belongs to, so no other workflow can spend it. Without it the scan
still runs and quota is bucketed on the repository name the workflow
reports, which nothing verifies.
Pull requests from forks receive a read-only token by design, so the comment is skipped there. That never fails the build.
Inputs
| Input | Default | What it does |
|---|---|---|
| url | required | URL to scan. One per line to scan several pages in one step. Store preview URLs that need authentication as repository secrets. |
| wcag-level | AA | Conformance level to scan against: A, AA or AAA. |
| fail-on | error | error fails on any critical or high violation; warning adds medium; wcag fails on any Level A or AA success-criterion failure at any severity; none reports only. |
| thresholds | (empty) | JSON object of per-severity allowances that overrides fail-on, for example {"critical": 0, "high": 3}. Severities you leave out are unlimited. |
| fail-on-unrepresentative | true | Fail when the scanner could not see the real page: a bot challenge, a full-page consent wall, an HTTP block, or filtering that did not complete. Ignored when fail-on is none. |
| engines | all five | Comma-separated subset of axe-core, lighthouse, pa11y, ibm-equal-access and arc-style. Fewer engines is faster but gives less cross-engine corroboration. |
| comment | sticky | sticky updates one pull-request comment in place, new adds one per run, off disables it. |
| top-issues | 5 | How many findings to list inline, ranked by measured impact (1 to 25). |
| annotations | true | Emit build-failing findings as workflow annotations. |
| sarif-file | (empty) | Path to write a SARIF 2.1.0 report to, for the Security tab. |
| results-file | (empty) | Path to write the raw scan payload (JSON) to, for your own tooling. |
| accessibility-pro-token | (empty) | API token. Scans are attributed to your account, appear in your dashboard and draw on your plan's CI allowance instead of the free tier. |
| github-token | github.token | Token used to post the pull-request comment. |
| timeout-minutes | 15 | Per-attempt budget for one scan request (1 to 60). |
| retries | 1 | Retries for transient backend or network failures (0 to 3). |
| backend-url | production | Override for a self-hosted backend. |
| report-domain | production | Override for a self-hosted frontend. |
| oidc-audience | https://api.accessibilitypro.app | Audience for the GitHub OIDC token. Change only when self-hosting. |
Outputs
With several URLs, counts are summed across pages, and score, scan-id and report-url describe the worst page, the one to open first.
| Output | Value |
|---|---|
| passed | true when every scanned URL cleared the gate. |
| score | Score from 0 to 100 of the lowest-scoring URL. |
| scan-id | Scan id of the lowest-scoring URL. |
| report-url | Hosted report for the lowest-scoring URL, including its share token. Store the whole URL: the id alone does not open a private report. |
| violations-critical, -high, -medium, -low | Violation counts summed across every scanned URL. |
| total-issues | Total findings reported as violations. |
| manual-review-count | Findings that need a human look rather than counting as violations. |
| warnings-count | Scan warnings that make the result unrepresentative; 0 for a clean scan. |
| engines-used | Sources that produced findings: the engines that ran plus any in-house analyzers. |
| plan-tier | Plan the scan was billed to. Empty on the anonymous free tier. |
| ci-scans-remaining | CI scans left in this month's allowance. Empty when anonymous or unlimited. |
| sarif-file, results-file | Absolute paths of the files written, when requested. |
Sample workflows
Gate on conformance, not severity
fail-on: wcag fails the build for any Level A or AA
success-criterion failure, whatever severity it carries. Use it when you
are working to a compliance deadline such as the European Accessibility Act or ADA.
- uses: accessibility-pro/action@v2
with:
url: https://staging.example.com
wcag-level: AA
fail-on: wcagScan several pages in one step
Each page is a separate scan against your quota.
- uses: accessibility-pro/action@v2
with:
url: |
https://staging.example.com/
https://staging.example.com/pricing
https://staging.example.com/checkoutTrack findings in the Security tab
SARIF fingerprints match the scanner's own baseline keys, so code scanning tracks a finding across runs as fixed or reintroduced. Results appear in the Security tab, not on source lines, because a URL scan has no source file to annotate.
- uses: accessibility-pro/action@v2
id: a11y
with:
url: https://staging.example.com
sarif-file: a11y.sarif
fail-on: none # let code scanning own the verdict
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: a11y.sarif
category: accessibilityPay down a backlog without a red build every day
No new criticals, and no more than five high-severity findings.
- uses: accessibility-pro/action@v2
with:
url: https://staging.example.com
thresholds: '{"critical": 0, "high": 5}'Use the results in later steps
- uses: accessibility-pro/action@v2
id: a11y
with:
url: https://staging.example.com
fail-on: none
- name: Block release below 90
if: steps.a11y.outputs.score < 90
run: |
echo "Score ${{ steps.a11y.outputs.score }}, report ${{ steps.a11y.outputs.report-url }}"
exit 1Watch production overnight
A scheduled run has no pull request to comment on, so results go to the job summary and the workflow's failure notification.
on:
schedule:
- cron: '0 6 * * *'
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: accessibility-pro/action@v2
with:
url: https://www.example.com
fail-on: wcagExit codes
The step exits with 0 when every URL was scanned and
cleared the gate, and always when fail-on: none is set and
every URL was scanned. It exits with 1, with the reason
in the log, in four cases:
- the gate failed: a threshold was crossed on at least one URL, with a link to the worst report;
- one or more URLs could not be scanned at all; results for the others are still in the job summary;
- the result was unrepresentative and
fail-on-unrepresentativeis true; - the scan could not run: a rate limit, a rejected token, a blocked URL or a backend error after the retries.
The verdict is computed by the scanner, not by the Action, so the build result and the hosted report always agree. Two kinds of finding appear in the report but never gate a build: markup your code does not own (component-library portals, consent widgets, reCAPTCHA and Turnstile frames) and rules that are not WCAG success criteria, including 4.1.1, which WCAG 2.2 removed. Both are disclosed in the comment.
Free tier and tokens
Without a token, a repository gets 10 scans a day and 10 an hour, so an
accidental loop cannot drain the day's allowance in minutes. With an accessibility-pro-token, scans are attributed to your account and
draw on your plan's CI allowance, which is separate from interactive
scans: 300 a month signed in on the free plan, 1,000 on Developer,
10,000 on Team and 40,000 on Business. See plans and CI allowances.
- uses: accessibility-pro/action@v2
with:
url: ${{ secrets.STAGING_URL }}
accessibility-pro-token: ${{ secrets.ACCESSIBILITY_PRO_TOKEN }}The Action prints the remaining allowance on each run and warns at 90%. Copy-as-PR fixes are generated in the hosted report while you are signed in there; the token does not carry into that surface.
When your staging site blocks the scanner
Bot protection, a web application firewall or an IP allow-list can refuse the scan before it sees a page. The Action then fails as unrepresentative instead of passing on an error page. The guide to allowing the scanner through your firewall lists the fixed addresses, the User-Agent token and the per-site request header to allow, with a ready-made Cloudflare rule.
How accurate the gate is
Findings carry their evidence level, from verified by interaction down to a single engine's report, and AI-written text is labelled as such. The scanner's precision and recall on annotated fixtures and a field audit of real websites are published.