Why Accessibility Testing Belongs in CI/CD
Most teams wouldn’t ship without unit tests, but accessibility checks often wait for a manual audit weeks after launch. A missing alt attribute or a broken focus order is cheap to fix in the pull request that added it. Once it’s in production, someone has to find it, file it and come back to code they no longer remember.
Running checks in the pipeline means issues show up in the pull request that caused them.
The cost of finding issues late
A rough picture of how the cost grows:
- Development: a developer fixes a contrast issue in 5 minutes
- QA: a tester files a bug and a developer switches back to fix it, about 30 minutes
- Post-launch: triage, remediation planning and regression testing, which can take days
A check that runs on every pull request keeps most issues in the first bucket.
What AccessHawk Scans For
Each scan has three stages.
1. Browser automation with Playwright
Every scan loads your page in Chromium, so content rendered by JavaScript is included. AccessHawk captures:
- A full-page screenshot for visual review (color contrast, text size, layout)
- The accessibility tree, the structure screen readers use to navigate your page
- The HTML for structural checks (heading hierarchy, landmark regions, form labels)
2. Lighthouse accessibility audit
Google Lighthouse runs its accessibility audit, a set of rule-based checks built on axe-core. It catches clear-cut problems like:
- Images without an
altattribute - Text with too little color contrast
- Form fields without labels, and missing or invalid ARIA attributes
- Headings that skip levels
- Positive
tabindexvalues that change the tab order
3. AI review
Claude, Anthropic’s AI model, reviews the screenshot, accessibility tree, HTML and Lighthouse results together. It can flag issues that need judgment, which rule-based checks can’t make:
- Decorative images marked up as informative, or the reverse
- Ambiguous link text (“click here”, “read more”) with no surrounding context
- Reading order that doesn’t match the visual layout
- ARIA attributes that are valid but wrong for what the element does
Each issue includes the WCAG success criterion, a severity and a recommended fix.
How the API Integration Works
AccessHawk started as a hands-on tool: paste a URL, start a scan, read the results in your browser. That works for one-off checks, but not when you ship several times a day, so there’s now a REST API. You can start scans, fetch results and wire them into your automation without opening the dashboard.
The flow:
- Authenticate: use an API key generated in your account settings
- Submit a scan: send the target URL, a project ID and any options
- Get results: poll the status endpoint, or pass a webhook URL and get a POST when the scan finishes
- Process the response: parse the JSON results in your pipeline
A scan takes 1 to 5 minutes depending on the size of the page, so poll every 10 to 15 seconds or use a webhook. The URL has to be reachable from the public internet. If your preview deployments sit behind a login, the Team plan can send cookies or headers with the scan.
Each issue in the response has its WCAG success criterion (for example 1.4.3 Contrast (Minimum)), level, severity, location, impact and a recommendation.
For full endpoint documentation, request and response schemas, and authentication details, see the API Documentation.
Pipeline Integration Patterns
Here’s where accessibility scanning fits in a typical CI/CD workflow.
GitHub Actions
name: Accessibility Check
on:
pull_request:
branches: [main]
jobs:
a11y-scan:
runs-on: ubuntu-latest
steps:
- name: Wait for preview deployment
# Wait for your preview/staging deployment to be ready
run: ./scripts/wait-for-deploy.sh
- name: Run AccessHawk scan
env:
ACCESSHAWK_API_KEY: ${{ secrets.ACCESSHAWK_API_KEY }}
run: |
# Submit scan against the preview deployment URL
# Parse results and fail the check if critical issues found
./scripts/accesshawk-scan.sh "$PREVIEW_URL"
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: accessibility-report
path: ./a11y-results.json
GitLab CI
accessibility_scan:
stage: test
needs: [deploy_staging]
script:
- ./scripts/accesshawk-scan.sh "$STAGING_URL"
artifacts:
paths:
- a11y-results.json
when: always
rules:
- if: $CI_MERGE_REQUEST_ID
General pattern
Whatever CI platform you use, the steps are the same:
- Deploy to a preview or staging environment
- Run the AccessHawk scan against that URL
- Parse the JSON results
- Apply your threshold rules (for example, fail on critical or high issues)
- Post a summary as a PR comment or build annotation
- Archive the full report as a build artifact
Working with Scan Results
A completed scan returns JSON with an overallScore (the percentage of checked WCAG criteria with no issues found), a lighthouseScore, issue counts in statistics, a reportUrl and an issues array. Each issue has these fields:
id: the issue ID, such asA-001title: a short description of the issuewcagCriteria: the success criterion, such as1.4.3 Contrast (Minimum)wcagLevel:A,AAorAAAseverity:critical,high,mediumorlowlocation: a CSS selector or a description of where the issue isimpact: which criterion fails and whyuserImpact: who is affected and howrecommendation: how to fix itfixPriority:Immediate,High Priority,Medium PriorityorLow Priorityresolved: whether the issue has been marked resolved
With those fields, the pipeline logic is short:
// Example: fail the build on critical or high-severity issues
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('a11y-results.json', 'utf-8'));
const blocking = results.issues.filter(
(issue) => issue.severity === 'critical' || issue.severity === 'high'
);
if (blocking.length > 0) {
console.error(`Found ${blocking.length} blocking accessibility issues:`);
blocking.forEach((issue) => {
console.error(` [${issue.severity}] ${issue.wcagCriteria}: ${issue.title} (${issue.location})`);
});
process.exit(1);
}
For the full response schema, see the API Documentation.
Getting Started
API access is on the Hobby, Pro and Team plans, each with its own monthly scan allowance (see the pricing page). The free Starter plan includes 2 scans in the browser but no API access.
To set it up:
- Create an account: sign up if you haven’t already
- Generate an API key: on the Settings page, create a key for CI use
- Read the API docs: the API Documentation covers endpoints, authentication, request and response schemas, and rate limits
- Write your integration script: use the patterns above as a starting point
- Add your API key as a CI secret: store it as
ACCESSHAWK_API_KEYin your CI platform’s secrets
Best Practices
Scan staging before production
Scan a preview or staging deployment first, so you can fix issues before they ship. It’s still worth scanning production too, since that’s the only way to confirm what real users get.
Add scans to pull request checks
Make the accessibility scan a required check on pull requests. That keeps regressions out of your main branch and gives developers feedback while the change is still fresh. Since a scan takes a few minutes, run it in parallel with your other checks.
Set severity thresholds
Not every issue needs to block a deployment. One approach:
- Critical and high issues: block the merge or deploy
- Medium issues: warn in the PR comment but don’t block
- Low issues: track for a later sprint
Combine with manual audits
Automated scanning, including the AI review, misses things and can get things wrong, so verify findings before you act on them. Use AccessHawk for routine checks and plan periodic manual audits for complex interactions, dynamic content and full user flows.
Watch the numbers over time
Keep the score and issue count from each run (the build artifact works for this). If the issue count keeps creeping up, set aside time to fix it before the backlog grows.