How to Automate Accessibility Testing with Cypress

As developers, it‘s our responsibility to build websites and applications that are accessible to all users, including those with disabilities. An accessible web benefits people with auditory, cognitive, neurological, physical, speech, and visual disabilities by providing them equal access to information and functionality.

Accessibility is not just a "nice to have" – it‘s a fundamental human right. Over one billion people worldwide have a disability that may affect their access to technology. By ensuring your website is accessible, you open up your content and services to a much wider audience.

While manual accessibility testing with real users is ideal, it can be time-consuming and expensive to do on a regular basis. This is where automated accessibility testing tools like Cypress come in. They allow you to programmatically check that your site meets established accessibility guidelines and catch regressions before they make it to production.

In this guide, I‘ll show you how to use Cypress and the cypress-axe plugin to automate accessibility testing for your web application. By the end, you‘ll be able to easily incorporate accessibility checks into your development workflow and continuous integration pipeline.

Overview of Accessibility Testing Techniques

Before diving into the technical details, let‘s briefly go over some common accessibility testing techniques:

Manual testing with real users
The most effective way to assess the accessibility of a website is to test it with real users who have different disabilities or use assistive technologies. This provides valuable insight into real-world issues that automated tools may miss. However, manual testing is not always practical to do frequently.

Browser extensions
Browser extensions like axe can identify common accessibility issues while you manually test your site. They provide visual feedback and recommendations within the browser‘s developer tools. Extensions are a quick way to spot problems during development.

Automated testing
Automated accessibility testing tools programmatically scan your web pages and check them against accessibility guidelines like WCAG 2.0. They can be run via the command line or integrated into your test suite. While they may not catch every issue, they‘re a scalable way to prevent blatant accessibility violations.

A comprehensive accessibility testing strategy incorporates elements of all three techniques. For the rest of this guide, we‘ll focus on automated testing with Cypress and axe.

Setting up Cypress and cypress-axe

Cypress is a popular end-to-end testing framework that allows you to write tests in JavaScript and run them in a real browser. We‘ll be using Cypress to programmatically interact with our web pages and components.

The cypress-axe plugin integrates the open-source axe accessibility testing engine into Cypress. It provides a simple API for analyzing a web page for accessibility issues based on WCAG guidelines.

Let‘s walk through setting up Cypress and cypress-axe in a new project.

First, create a new directory and install Cypress and cypress-axe as development dependencies:

mkdir my-a11y-app
cd my-a11y-app
npm init -y
npm install cypress cypress-axe --save-dev

Next, open the Cypress test runner to generate some boilerplate files:

npx cypress open

This will launch the Cypress GUI and create a cypress directory with example tests. We‘ll add our accessibility tests here later.

To include cypress-axe, add this line to the top of cypress/support/index.js:

import ‘cypress-axe‘

This imports the cypress-axe commands like cy.injectAxe() and cy.checkA11y() which we‘ll use in our tests.

Finally, let‘s add a script to run Cypress in headless mode. In package.json, add:

"scripts": {
  "test": "cypress run"
}

Now we can run our Cypress tests by typing npm test. With the setup complete, let‘s move on to writing an accessibility test.

Writing an Accessibility Test

For this example, let‘s assume we have a simple website with a few pages (Home, About, Contact) and some reusable UI components (Button, Form). Our goal is to visit each page and component, and check it for accessibility issues using cypress-axe.

Here‘s what the test might look like:

// cypress/integration/a11y.spec.js

const pages = [‘/‘, ‘/about‘, ‘/contact‘]
const components = [‘/button‘, ‘/form‘]

describe(‘Accessibility tests‘, () => {
  pages.forEach((page) => {
    it(`Page ${page} has no detectable accessibility violations on load`, () => {
      cy.visit(page)
      cy.injectAxe()
      cy.checkA11y()
    })
  })

  components.forEach((component) => {  
    it(`Component ${component} has no detectable accessibility violations on load`, () => {
      cy.visit(component)
      cy.injectAxe()

      // Test each instance of the component
      cy.get(‘[data-cy=component-container]‘).each((element) => {
        cy.checkA11y(element, {
          runOnly: {
            type: ‘tag‘,
            values: [‘wcag2a‘, ‘wcag2aa‘],
          },
        })
      })
    })
  })
})

Let‘s break this down:

  1. We define two arrays pages and components that contain the URLs we want to test.
  2. For each page, we use cy.visit() to load it, cy.injectAxe() to inject the axe script, and cy.checkA11y() to run the accessibility checks. This will check the entire page.
  3. For each component, we do the same, except we scope the cy.checkA11y() to each instance of that component on the page. This targets specific elements to test.
  4. In the options for cy.checkA11y(), we specify that we want to run both WCAG 2.0 Level A and AA rules. You can customize this based on your accessibility goals.

If this test passes, it means that each page and component has no automatically detectable accessibility issues. But what happens when there are violations?

By default, cypress-axe just logs the violations to the console. For easier debugging, let‘s customize how the violations are formatted by using a Cypress task.

In cypress/plugins/index.js, add:

module.exports = (on, config) => {
  on(‘task‘, {
    log(message) {
      console.log(message)
      return null
    },
    table(message) {
      console.table(message)
      return null
    }
  })
}

This defines two tasks log and table that we can call to output the violation data in a visually friendly way.

Back in the test, add this function:

function terminalLog(violations) {
  cy.task(
    ‘log‘,
    `${violations.length} accessibility violation${
      violations.length === 1 ? ‘‘ : ‘s‘
    } ${violations.length === 1 ? ‘was‘ : ‘were‘} detected`
  ) 

  const violationData = violations.map(
    ({ id, impact, description, nodes }) => ({
      id,
      impact,
      description,
      nodes: nodes.length
    })
  )

  cy.task(‘table‘, violationData)
}

And pass it as a callback to cy.checkA11y():

cy.checkA11y(element, {
  runOnly: {
    type: ‘tag‘,
    values: [‘wcag2a‘, ‘wcag2aa‘],
  },
}, terminalLog)

Now when there are accessibility violations, we‘ll get a nice summary in the console like:

3 accessibility violations were detected

┌─────────┬────────┬───────────────────────────────────┬───────┐
│ (index) │   id   │            description            │ nodes │
├─────────┼────────┼───────────────────────────────────┼───────┤
│    0    │ ‘abc‘  │  Images must have alternate text  │   1   │
│    1    │ ‘def‘  │ Form elements must have labels    │   2   │
│    2    │ ‘ghi‘  │ Heading levels should increase by │   1   │
│         │        │            one                    │       │
└─────────┴────────┴───────────────────────────────────┴───────┘

This makes it easier to tell at a glance what accessibility issues were found across the site.

Advanced topics

Integrating accessibility tests into CI/CD

Automated accessibility tests are most useful when they‘re run consistently as part of your continuous integration (CI) pipeline. This catches accessibility regressions before they make it to production.

To run the accessibility tests in CI, you‘ll need to:

  1. Ensure your CI environment has all the dependencies installed (Node.js, Cypress, etc)
  2. Start up your web application and wait for it to be available
  3. Run the Cypress tests in headless mode
  4. Report the test results

Check out the Cypress documentation for examples of integrating with common CI providers.

Other ways to use the axe engine

The axe accessibility engine is very flexible and can be used in several other contexts besides Cypress tests:

  • axe browser extensions: Quickly identify and debug accessibility issues while manually testing your site
  • Webdriver.io: Write accessibility tests and integrate them with your existing Webdriver.io UI tests
  • Selenium: Use the axe-selenium packages to inject axe into browsers automated by Selenium
  • React and Vue: Use the axe-core-react and vue-axe devtools to catch accessibility issues while developing your app

The axe API is quite powerful, allowing you to customize which accessibility rules are checked, exclude parts of the page, and extend it with custom rules. Exploring these options can help you adapt axe to your specific use case.

Conclusion

Automated accessibility testing is a scalable way to incorporate accessibility checks into your development workflow. In this guide, we learned how to:

  • Configure Cypress and cypress-axe for accessibility testing
  • Programmatically check pages and components for WCAG 2.0 violations
  • Customize the output of accessibility test failures
  • Integrate accessibility tests into continuous integration

This is a solid foundation, but it‘s just the beginning of your accessibility testing journey. Some next steps to consider:

  • Add more in-depth accessibility tests with tools like Lighthouse or Puppeteer
  • Manually test your site with real users, including those with disabilities
  • Provide training to your team on accessibility best practices
  • Make accessibility a core requirement of your definition of done

Remember, the goal of accessibility testing is not to meet some arbitrary technical standard, but to improve the real-life experience for your users. By putting in this work, you have the opportunity to make a positive impact.

I encourage you explore the additional resources below to continue learning about web accessibility. Feel free to reach out if you have any questions!

Resources:

Similar Posts