Accessibility Testing with cypress-axe — Automated WCAG Compliance Checks

Accessibility defects affect real users — screen reader users who cannot navigate your site, keyboard-only users who cannot reach a button, and colour-blind users who cannot distinguish error states from success states. The axe-core engine, developed by Deque Systems, is the industry standard for automated accessibility testing. It checks over 80 WCAG 2.1 rules and identifies violations with severity ratings, affected elements, and remediation guidance. cypress-axe integrates axe-core directly into your Cypress tests.

Automated Accessibility Testing with cypress-axe

cypress-axe injects the axe-core engine into the browser and runs accessibility audits against the current page or a specific element.

// ── Installation ──
// npm install --save-dev cypress-axe axe-core

// cypress/support/e2e.ts
// import 'cypress-axe';

// ── Basic usage: audit the entire page ──

it('should have no accessibility violations on the login page', () => {
  cy.visit('/');
  cy.injectAxe();              // Inject axe-core into the page
  cy.checkA11y();              // Run the audit — fails on any violation
});


// ── Audit a specific element ──

it('should have no a11y violations in the navigation bar', () => {
  cy.visit('/shop');
  cy.injectAxe();
  cy.checkA11y('[data-cy="nav-bar"]');  // Audit only the nav bar
});


// ── Configure severity and rules ──

it('should have no critical or serious a11y violations', () => {
  cy.visit('/checkout');
  cy.injectAxe();
  cy.checkA11y(null, {
    includedImpacts: ['critical', 'serious'],  // Ignore minor/moderate
  });
});

// Skip specific rules (if you have a known, accepted issue)
it('should pass a11y except for known colour contrast issue', () => {
  cy.visit('/legacy-page');
  cy.injectAxe();
  cy.checkA11y(null, {
    rules: {
      'color-contrast': { enabled: false },  // Temporarily skip this rule
    },
  });
});


// ── Custom logging for better error messages ──

function logA11yViolations(violations: any[]) {
  violations.forEach((violation) => {
    const nodes = violation.nodes.map((node: any) => node.target).join(', ');
    cy.log(
      `${violation.impact} [${violation.id}]: ${violation.description} (${nodes})`
    );
  });
}

it('should log detailed a11y violations', () => {
  cy.visit('/shop');
  cy.injectAxe();
  cy.checkA11y(null, null, logA11yViolations);
});


// ── Common WCAG violations axe detects ──

const AXE_RULES_EXAMPLES = [
    {
        rule: 'color-contrast',
        wcag: 'WCAG 2.1 Level AA (1.4.3)',
        description: 'Text must have sufficient contrast ratio against background',
        fix: 'Increase contrast to 4.5:1 for normal text, 3:1 for large text',
    },
    {
        rule: 'image-alt',
        wcag: 'WCAG 2.1 Level A (1.1.1)',
        description: 'Images must have alt text describing their content',
        fix: 'Add alt="description" to every  tag',
    },
    {
        rule: 'label',
        wcag: 'WCAG 2.1 Level A (1.3.1)',
        description: 'Form inputs must have associated labels',
        fix: 'Add 
Note: Automated accessibility testing catches approximately 30-40% of all WCAG violations. The remaining 60-70% require manual testing — keyboard navigation flows, screen reader announcement accuracy, logical reading order, and meaningful focus management. cypress-axe is essential for catching the low-hanging fruit (missing alt text, missing labels, colour contrast failures) automatically, but it is not a substitute for manual accessibility audits and user testing with assistive technologies.
Tip: Add cy.injectAxe() and cy.checkA11y() to your existing functional tests rather than writing separate accessibility tests. This is the most efficient approach: your functional test navigates to the page and interacts with elements, and the a11y check runs at the end to verify accessibility compliance in the same browser state. Two lines added to an existing test provide accessibility coverage at zero additional navigation cost.
Warning: Do not disable axe rules to make tests pass without fixing the underlying violation. rules: { 'color-contrast': { enabled: false } } should be a temporary measure with a filed ticket, not a permanent suppression. Every disabled rule represents an accessibility barrier for real users. Track suppressed rules in a backlog and fix them in upcoming sprints.

Common Mistakes

Mistake 1 — Running cy.checkA11y() before the page is fully loaded

❌ Wrong: cy.visit('/shop'); cy.injectAxe(); cy.checkA11y(); — the audit runs before products load, missing violations in dynamically rendered content.

✅ Correct: cy.visit('/shop'); cy.wait('@products'); cy.injectAxe(); cy.checkA11y(); — wait for data to load so the audit checks the complete page.

Mistake 2 — Treating automated a11y testing as complete accessibility coverage

❌ Wrong: “All our pages pass cypress-axe, so our application is fully accessible.”

✅ Correct: “cypress-axe catches 30-40% of WCAG violations automatically. We supplement with manual keyboard testing, screen reader testing, and user research with people who use assistive technologies.”

🧠 Test Yourself

What percentage of WCAG accessibility violations can automated tools like axe-core typically detect?