Building a Comprehensive Accessibility Testing Framework for Web Applications
Building a Comprehensive Accessibility Testing Framework for Web Applications
Accessibility is not an add-on feature; it’s a core quality attribute that affects every user, including people with disabilities. This tutorial walks you through designing, implementing, and maintaining a practical, scalable accessibility testing framework for modern web apps. You’ll learn how to combine automated tests, manual checks, and continuous integration to catch accessibility issues early and prevent regressions.
Why build a dedicated accessibility testing framework?
- Accessibility problems are often hidden until late in the release cycle.
- Automated tests catch many issues consistently, but they can miss context-specific problems.
- A structured framework ensures accessibility is treated as a shared responsibility across design, frontend, and QA.
Key goals:
- Detect common accessibility violations early.
- Provide actionable guidance to developers.
- Integrate smoothly with existing CI/CD pipelines.
-
Maintain continuity as the product evolves.
Outline of the framework
Scope and standards
Test pyramid for accessibility
Automation suite
Manual testing practices
Continuous integration and reporting
Accessibility at design time
-
Maintenance and governance
Scope and standards
Base standard: WCAG 2.1/2.2 at least AA level. Include ARIA best practices where appropriate.
-
Focus areas:
- Perceivable: text alternatives, color contrast, scalable text
- Operable: keyboard navigation, focus management, timeouts
- Understandable: clear content, predictable UI
- Robust: compatibility with assistive technologies
-
Documentation: create a living accessibility policy outlining how issues are reported, tracked, and remediated.
Test pyramid for accessibility
-
Unit-level checks (fast, developer-facing)
- Focus management on components
- ARIA attribute presence and correctness
- Keyboard interaction mappings
-
Integration/end-to-end checks
- Page-level scans for common issues (contrast, alt text, semantic HTML)
-
Manual/exploratory testing
- Real-screenreader testing
- Keyboard-only navigation
-
Continuous monitoring
- Automated tests run on CI
- Periodic audits with human reviewers ### Automation suite: tooling and setup
Choose a stack you’re comfortable with. The examples here use a modern React project with Node.js tooling, but the concepts apply broadly.
- Core tools
- Axe-core or Axe-core-integrated libraries for automated accessibility testing
- Pa11y or Lighthouse accessibility audits for automated checks
- Playwright or Cypress for end-to-end automation with accessibility hooks
- Example: Lighthouse CI for continuous monitoring
-
Example: Axe-core with Playwright for component-level checks
1) Installing the tooling
-
Install Playwright and Axe
- npm install -D @playwright/test axe-core
-
Optional: Lighthouse CI
- npm install -D lighthouse lighthouse-ci ### 2) Component-level accessibility test (Playwright + Axe)
Create a reusable helper to run Axe checks on rendered components.
Code snippet (TypeScript):
// tests/accessibility/axe.ts
import { Page } from '@playwright/test';
import axeSource from 'axe-core';
export async function runAxeOnPage(page: Page, selector: string = 'body') {
// Inject axe-core into the page
await page.addScriptTag({ content: `${axeSource.toString()};` });
// Run Axe on the given selector
const result = await page.evaluate(async (sel) => {
// @ts-ignore
const results = await window.axe.run(sel);
return results;
}, selector);
return result;
}
Usage in a component test:
import { test, expect } from '@playwright/test';
import { runAxeOnPage } from './axe';
test.describe('Button component accessibility', () => {
test('button has accessible name and keyboard support', async ({ page }) => {
await page.goto('http://localhost:3000/button');
const results = await runAxeOnPage(page, '#primary-button');
expect(results.violations.length).toBe(0);
});
});
Notes:
- Ensure elements have descriptive text or aria-labels.
- Check keyboard focus behavior in tests where applicable. ### 3) Page-level audits with Lighthouse (inclusive of accessibility)
Lighthouse provides static checks for many WCAG criteria.
Example script to run Lighthouse programmatically:
// lighthouse-run.js
const lighthouse = require('lighthouse');
const { launchChrome } = require('chrome-launcher');
const fs = require('fs');
(async () => {
const chrome = await launchChrome({ chromeFlags: ['headless'] });
const options = { port: chrome.port, logLevel: 'info' };
const runnerResult = await lighthouse('http://localhost:3000/', options, null);
const report = runnerResult.lhr;
fs.writeFileSync('lighthouse-report.json', JSON.stringify(report, null, 2));
await chrome.kill();
})();
CI integration tip: run Lighthouse via a headless browser in CI and fail builds if accessibility score drops below a threshold (e.g., 90).
4) End-to-end accessibility checks with Playwright
Capture common issues during flow, such as modal dialogs, dynamic content, and focus traps.
Example:
// tests/accessibility/modals.ts
import { test, expect } from '@playwright/test';
import { runAxeOnPage } from './axe';
test('sign-up modal is accessible', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.click('#open-signup');
// Ensure modal is visible and trap focus
const modal = await page.locator('#signup-modal');
await expect(modal).toBeVisible();
const results = await runAxeOnPage(page, '#signup-modal');
expect(results.violations.length).toBe(0);
});
Manual testing practices
Automated tests cover many cases, but human judgment is essential.
- Screen reader testing
- Use NVDA/JAWS on Windows or VoiceOver on macOS for representative scenarios.
- Verify that changes in content are announced in a sensible order.
- Keyboard navigation
- Test tab order, visible focus indicators, and skip links.
-
Color contrast checks
- Use simulated color blindness tools or browser extensions to verify contrast ratios meet WCAG AA criteria (4.5:1 for body text, 3:1 for large text).
-
Check for semantic HTML
- Ensure headings, lists, tables, and landmarks are used appropriately.
- Confirm labels are associated with inputs via for/id attributes or aria-labelledby. ### Continuous integration and reporting
-
Integrate automated checks into your pipeline:
- Run unit/component Axe tests on PRs.
- Run page-level Lighthouse accessibility audits on deploy previews.
- Fail builds if violations exceed a threshold or if critical issues are detected.
-
Reporting conventions:
- Keep a centralized accessibility dashboard (e.g., in your CI run summaries).
- Attach actionable details: element selectors, ARIA attributes involved, and suggested fixes.
- Include status: compliant, warning, or fail for quick triage.
-
Issue tracking
- When violations are found, create reproducible issues with:
- Steps to reproduce
- Screenshots or screen recordings
- Affected WCAG criterion
- Suggested remediation ### Design-time accessibility considerations
-
Design systems
- Supply accessible components with clear focus styles, proper roles, and keyboard interactions.
- Include accessibility tokens (e.g., contrast-checked color tokens) in design handoffs.
-
UI patterns
- For dynamic components (accordions, tabs, dialogs), provide proper ARIA attributes and keyboard behavior out of the box.
-
Documentation
- Add accessibility examples in design documentation and component demos.
- Include a checklist for accessibility in design reviews. ### Maintenance and governance
-
Ownership
- Assign an accessibility owner or committee to oversee standards and issue triage.
-
Training
- Provide ongoing training for developers, testers, and designers.
-
Regression protocol
- Treat accessibility regressions as high-priority bugs.
- Include automated checks in PR reviews and require passing tests before merging.
-
Accessibility score audits
- Schedule periodic manual audits (quarterly) to catch issues automated tests miss. ### Practical example: integrating with a React project
-
Project layout
- tests/
- accessibility/
- axe.ts
- modals.ts
- cypress or Playwright tests for E2E accessibility
- ci/
- .github/workflows/ci.yml with steps:
- install dependencies
- run unit Axe tests
- run Lighthouse on preview
- publish report artifacts
Example CI snippet (GitHub Actions)
name: Accessibility CI
on:
pull_request:
branches: [ main, master ]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install
run: npm ci
- name: Run unit Axe tests
run: npm run test:accessibility:unit
- name: Build and test Lighthouse
run: npm run test:accessibility:e2e
- Example npm scripts in package.json
{
"scripts": {
"test:accessibility:unit": "playwright test tests/accessibility/**/*.ts",
"test:accessibility:e2e": "node lighthouse-run.js",
"lint": "eslint ."
}
}
Common pitfalls and how to avoid them
- Pitfall: Relying solely on automated checks
- Solution: Pair automated tests with manual checks and real-world assistive technology testing.
- Pitfall: Overly strict thresholds
- Solution: Start with a sensible baseline (e.g., 90 Lighthouse score) and adjust as your product matures.
-
Pitfall: Slow test suites
- Solution: Parallelize tests, run critical checks on PRs, and schedule full audits in nightly runs. ### Quick-start checklist
Define WCAG scope and acceptance criteria for your project.
Integrate Axe-based tests for components and pages.
Add Lighthouse accessibility audits to CI for previews.
Implement end-to-end accessibility tests with keyboard navigation and screen-reader-relevant flows.
Establish design-time guidance and accessibility reviews in the design system.
Set up a dashboard and ticketing workflow for accessibility issues.
Schedule regular manual audits and keep documentation up to date.
If you’d like, I can tailor this framework to your tech stack (React, Vue, Angular, Svelte, or a non-JS backend-focused app) and your CI environment. Do you want a minimal starter project with a ready-to-run example, or a more extensive blueprint with templates for your team’s conventions?
-
Rizwan Saleem | https://rizwansaleem.co
Top comments (0)