The most surprising thing about testing accessibility with Cypress and axe-core is that you don’t actually write new tests for accessibility; you augment your existing end-to-end tests to also check for accessibility violations.

Imagine you’re testing a login form. Your Cypress test might look something like this:

describe('Login Form Accessibility', () => {
  beforeEach(() => {
    cy.visit('/login'); // Navigate to the login page
  });

  it('allows a user to log in with valid credentials', () => {
    cy.get('input[name="username"]').type('testuser');
    cy.get('input[name="password"]').type('password123');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard'); // Verify successful login
  });
});

Now, let’s sprinkle in some accessibility magic. First, you need to install the cypress-axe plugin:

npm install --save-dev cypress-axe
# or
yarn add --dev cypress-axe

Then, you’ll import it into your cypress/support/e2e.js (or cypress/support/index.js in older versions) file:

import 'cypress-axe';

And in your test file, you’ll add the cy.checkA11y() command:

describe('Login Form Accessibility', () => {
  beforeEach(() => {
    cy.visit('/login');
    cy.injectAxe(); // Inject axe-core into the page
  });

  it('allows a user to log in with valid credentials and has no accessibility violations', () => {
    cy.get('input[name="username"]').type('testuser');
    cy.get('input[name="password"]').type('password123');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
    cy.checkA11y(); // Check accessibility on the dashboard page
  });

  it('shows an error for invalid login and has no accessibility violations', () => {
    cy.get('input[name="username"]').type('wronguser');
    cy.get('input[name="password"]').type('wrongpassword');
    cy.get('button[type="submit"]').click();
    cy.get('.error-message').should('be.visible');
    cy.checkA11y(); // Check accessibility on the login page after error
  });
});

The cy.injectAxe() command is crucial. It loads the axe-core library into the browser context of your Cypress test. cy.checkA11y() then runs axe-core against the current DOM and asserts that there are no critical or serious accessibility violations.

How this works under the hood:

When you run cy.checkA11y(), it’s not just looking at your page’s structure. It’s executing a sophisticated analysis powered by axe-core, which is a widely-respected accessibility testing engine. Axe-core checks for compliance with WCAG (Web Content Accessibility Guidelines) 2.0, 2.1, and 2.2 standards. It identifies issues like:

  • Missing alt text for images: Screen readers can’t describe the image content.
  • Insufficient color contrast: Makes it hard for users with visual impairments to read text.
  • Non-semantic HTML: Screen readers rely on semantic tags (like <nav>, <button>, <main>) to navigate and understand the page structure.
  • Missing form labels: Users of assistive technologies don’t know what input field is for.
  • Keyboard navigation issues: Users who can’t use a mouse need to be able to tab through and activate all interactive elements.

By default, cy.checkA11y() will fail the test if it finds any violations with a "critical" or "serious" impact. You can customize this behavior. For instance, to only fail on critical issues:

it('has no critical accessibility violations', () => {
  cy.checkA11y({
    rules: {
      'color-contrast': { enabled: true }, // Keep color contrast check
      'aria-input-field-name': { enabled: true }, // Keep label check
      // Disable rules with lower impact if desired
      'link-name': { enabled: false },
    }
  }, null, false); // The 'false' here disables the default reporting of all rules
});

You can also get a detailed report of all violations found, even if they don’t cause the test to fail, by passing true as the third argument to cy.checkA11y():

it('logs all accessibility violations', () => {
  cy.checkA11y(null, null, true); // The 'true' here enables detailed reporting
});

This output will appear in your Cypress command log, showing each violation with its ID, description, help URL, and the DOM element(s) that are affected.

The real power here is integrating accessibility checks directly into your CI/CD pipeline. If an accessibility regression is introduced, your automated tests will catch it before it gets to production, saving you significant rework and ensuring a more inclusive user experience.

The most common pitfall when starting with cypress-axe is not understanding that cy.checkA11y() asserts against the current state of the DOM. If you perform an action and then immediately check accessibility without waiting for any asynchronous updates or rendering, you might miss violations or get false positives.

Want structured learning?

Take the full Cypress course →