CI integration
Ready-to-paste templates wrapping the CLI (npx @surea11y/cli scan ...) in GitHub Actions, Bitbucket Pipelines, GitLab CI and Azure DevOps. If you're calling the library directly from your own Node script instead of the CLI, see INTEGRATION.md instead — this page is specifically about the CLI as a pipeline step.
The CLI scans static HTML: it doesn't run the page's JavaScript. That suits built or server-rendered pages; for a client-rendered app, or rules that need layout, scan in a browser through one of the browser bindings instead.
All of these rely on the CLI's own exit codes (0 clean, 1 at least one — or one new, with --baseline — fail outcome, 2 a usage/scan error) to gate the pipeline; no extra scripting is required for basic pass/fail gating. A green pipeline means no check failed, or no new one did. It is not a conformance claim, and cantTell findings don't fail it.
GitHub Actions
Basic: gate on exit code
name: Accessibility scanon: [pull_request]jobs:a11y-scan:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: actions/setup-node@v4with:node-version: 22- run: npm ci && npm run build # produce whatever static HTML you're scanning- run: npx @surea11y/cli scan ./dist/index.html
This fails the job the moment any fail outcome is found. For an existing site with pre-existing violations, see the baseline variant below instead of disabling the step.
With a baseline (existing site, gate only on new violations)
# Once, locally: record every current fail occurrence, commit the file.$ surea11y scan ./dist/index.html --write-baseline a11y-baseline.json$ git add a11y-baseline.json
- run: npm ci && npm run build- run: npx @surea11y/cli scan ./dist/index.html --baseline a11y-baseline.json
A baseline keeps known violations from failing the build; it doesn't make them acceptable. Fix them and shrink the file over time. See Baseline / allowlist for what counts as "known" vs. "new," and how to regenerate the file as violations get fixed.
Uploading SARIF to GitHub Code Scanning
upload-sarif needs security-events: write permission and does not fail the job itself — pair it with a separate gating step (or continue-on-error + your own check) if you also want the build to fail on new violations.
Findings that lean fail are warning alerts by default. Rendered with renderSarifReport(result, { leanFailAs: 'error' }) from @surea11y/core/sarif, they are errors, so a gate on errors stops on likely failures too (SARIF options); a baseline written with buildBaselineEntries(result, { leanFail: true }) accepts the ones a page already has (findings that lean fail).
name: Accessibility scanon: [pull_request]permissions:contents: readsecurity-events: writejobs:a11y-scan:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v4- uses: actions/setup-node@v4with:node-version: 22- run: npm ci && npm run build- name: Scan (report, don't fail the job here)run: npx @surea11y/cli scan ./dist/index.html --baseline a11y-baseline.json --sarif results.sarifcontinue-on-error: trueid: scan- name: Upload SARIF to Code Scanninguses: github/codeql-action/upload-sarif@v3with:sarif_file: results.sarif- name: Fail the job on new violationsif: steps.scan.outcome == 'failure'run: exit 1
See the SARIF report format and a known limitation: a scan of a local file in your checkout gets inline annotations in the Code Scanning UI; a scan of a live URL doesn't (SARIF/Code Scanning can only associate a finding with a real file in the repository).
Publishing what the scan leaves to a person
A gate decides what rules can decide. To hand the rest to a person with each run, write the HTML report and a worksheet of the checks no rule decides in full (--manual-checks, @surea11y/cli 1.5.0 and core 1.15.0 and later), and upload both as an artifact. Neither changes the exit code. See Test procedures.
- run: npx @surea11y/cli scan ./dist/index.html --html reports/report.html --manual-checks reports/manual-checks.html- uses: actions/upload-artifact@v4if: always()with:name: accessibilitypath: reports/
Bitbucket Pipelines
Bitbucket Pipelines has no built-in SARIF-consuming dashboard equivalent to GitHub Code Scanning, so the CLI's exit code (plus, optionally, --html as a downloadable pipeline artifact) is the practical integration point rather than --sarif.
pipelines:pull-requests:'**':- step:name: Accessibility scanimage: node:22script:- npm ci- npm run build- npx @surea11y/cli scan ./dist/index.html --baseline a11y-baseline.json --html a11y-report.htmlartifacts:- a11y-report.html
The step fails the pipeline on the CLI's exit code exactly like any other script entry; a11y-report.html (see the HTML report) is attached as a downloadable build artifact so a reviewer can open it without re-running the scan locally.
JUnit test reports
GitLab, Azure DevOps, Jenkins and CircleCI show JUnit XML in their own test views: each WCAG criterion becomes a suite and each rule a test (see the JUnit report). The CLI writes it with --junit <path> (@surea11y/cli 1.1.0 or later), from the same scan as its other outputs, and the report never changes its exit code, so the step still gates the build.
GitLab CI
a11y:image: node:22script:- npm ci- npm run build- npx @surea11y/cli scan ./dist/index.html --junit a11y.junit.xmlartifacts:when: alwaysreports:junit: a11y.junit.xml
when: always uploads the report even when the scan's exit code fails the job, which is exactly when the merge request widget is worth reading.
Azure DevOps
steps:- script: |npm cinpm run buildnpx @surea11y/cli scan ./dist/index.html --junit a11y.junit.xmldisplayName: Accessibility scan- task: PublishTestResults@2condition: succeededOrFailed()inputs:testResultsFormat: JUnittestResultsFiles: a11y.junit.xml
cantTell rules arrive as skipped tests, never failures. With --baseline, failures already in the baseline are skipped tests too. To show cantTell rules as failures, the CLI has no flag: save the scan with --json and render it with renderJunitReport(result, { cantTellAs: 'failure' }) from @surea11y/core/junit (see the JUnit report), which needs @surea11y/core in the project's own devDependencies. { leanFailAs: 'failure' } is the narrower choice: only the cantTell findings that lean fail become failures, each with why and the steps to confirm it. Either changes the report, not the CLI's exit code.
Free-tier/private-repo minute limits
If your pipeline provider's free tier is minute-limited (Bitbucket Pipelines' free tier is 50 build-minutes/month on private workspaces, for example), a jsdom-based scan of static/server-rendered HTML (what the CLI does) is far cheaper than driving a real browser — see the CLI docs for what that trades away (no client-rendered content, no real CSS layout).