Visual and Accessibility CI Pipeline
The Complete Pipeline
# .github/workflows/visual-a11y.yml
name: Visual & Accessibility Tests
on: [pull_request]
jobs:
accessibility:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run build
- name: Start app
run: npm run start &
env:
PORT: 3000
- name: Wait for app
run: npx wait-on http://localhost:3000 --timeout 30000
- name: Run axe-core accessibility audit
run: npx playwright test tests/accessibility/ --reporter=json > a11y-results.json
- name: Upload accessibility report
if: always()
uses: actions/upload-artifact@v4
with:
name: accessibility-report
path: a11y-results.json
visual-regression:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- name: Build Storybook
run: npx storybook build -o storybook-static
- name: Run Chromatic
uses: chromaui/action@latest
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitOnceUploaded: true
design-token-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build && npm run start &
- name: Wait for app
run: npx wait-on http://localhost:3000 --timeout 30000
- name: Verify design tokens
run: npx playwright test tests/design-system/
- name: Check Figma drift
if: env.FIGMA_TOKEN != ''
run: python scripts/figma_design_drift.py --report drift-report.json
env:
FIGMA_TOKEN: ${{ secrets.FIGMA_TOKEN }}
keyboard-navigation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build && npm run start &
- name: Wait for app
run: npx wait-on http://localhost:3000 --timeout 30000
- name: Run keyboard navigation tests
run: npx playwright test tests/accessibility/keyboard-navigation.spec.ts
Pipeline Stages by Speed and Scope
| Stage |
Duration |
Scope |
Run When |
| axe-core scan |
30-60 seconds |
All pages, automated rules |
Every PR |
| Keyboard navigation |
1-2 minutes |
Critical flows |
Every PR |
| Visual regression (Playwright) |
2-5 minutes |
Screenshot comparison |
Every PR |
| Visual regression (Chromatic) |
3-10 minutes |
Component snapshots |
Every PR |
| Design token drift |
1-2 minutes |
Token value comparison |
PRs touching components/styles |
| AI accessibility audit |
5-15 minutes |
Qualitative analysis |
Weekly schedule |
| Full Lighthouse audit |
2-5 minutes |
Performance + PWA + a11y |
Release branch |
Failure Handling Strategy
| Violation Type |
CI Action |
Review Required? |
| Critical a11y violation (axe-core) |
Block merge |
No -- fix required |
| Serious a11y violation |
Block merge |
No -- fix required |
| Moderate a11y violation |
Warn, do not block |
Yes -- triage in sprint |
| Minor a11y violation |
Log only |
Review monthly |
| Visual regression (significant) |
Block merge |
Yes -- approve or fix |
| Visual regression (minor) |
Auto-approve if below threshold |
No |
| Design token drift |
Comment on PR |
Yes -- align with design |
| Keyboard navigation failure |
Block merge |
No -- fix required |
Monitoring and Reporting
Accessibility Score Dashboard
# Track accessibility compliance over time
def generate_a11y_dashboard(reports):
"""Generate accessibility trend data from CI reports."""
trends = []
for report in reports:
trends.append({
"date": report["date"],
"total_violations": report["total_violations"],
"critical": report["critical"],
"serious": report["serious"],
"pages_tested": report["pages_tested"],
"compliance_rate": 1 - (report["total_violations"] / report["pages_tested"]),
})
return {
"current_violations": trends[-1]["total_violations"],
"trend": "improving" if trends[-1]["total_violations"] < trends[-2]["total_violations"] else "degrading",
"compliance_rate": f"{trends[-1]['compliance_rate']:.1%}",
"history": trends,
}
Key Takeaways
- AI-powered visual testing eliminates the false positive problem that killed pixel-diff adoption. Vision models can distinguish meaningful changes from rendering noise.
- axe-core catches 30-40% of accessibility issues automatically -- run it on every page on every PR. The remaining 60-70% requires human judgment and AI-assisted auditing.
- Color contrast is the most commonly violated WCAG criterion and the easiest to test automatically. Encode the 4.5:1 and 3:1 ratios into your CI pipeline.
- Keyboard navigation testing catches real user pain -- if your checkout flow is not completable via keyboard only, you are excluding users and inviting lawsuits.
- The European Accessibility Act is now in effect -- if your product is sold in the EU, WCAG 2.1 AA compliance is legally mandatory, not optional.
- Design token verification prevents design drift -- automated comparison between Figma specs and computed CSS styles catches the gradual divergence that manual review misses.
- Component-level visual testing in Storybook catches issues earlier than page-level screenshots, at a fraction of the cost.
Interview Talking Point
"I approach visual and accessibility testing as complementary disciplines. For visual regression, I use a combination of Playwright's built-in screenshot comparison for speed and a tool like Percy or Chromatic for cross-browser coverage with AI-powered diff triage that filters out rendering noise and flags only meaningful changes. For accessibility, I run axe-core against every page on every PR as a minimum -- it catches 30-40% of WCAG 2.1 AA issues automatically. But I go beyond rule-based scanning by using AI agents that can evaluate whether alt text is actually descriptive, whether error messages guide the user, and whether the reading flow is logical -- things axe-core fundamentally cannot assess. I also test keyboard navigation end-to-end: our checkout flow must be completable without a mouse, with visible focus indicators and proper focus trapping in modals. With the European Accessibility Act now in force and US ADA lawsuits exceeding 4,000 per year, accessibility is not a nice-to-have -- it is a legal and business requirement that I build into the CI pipeline from day one."