A11y testing with axe-core

When we build applications we like to make sure that they work. To assure this we would write our normal unit, stub and integration tests. However, one thing that isn't usually being tested in these suits is the accessibility of our web applications.

In comes axe-core which as been developed by deque systems. I'll briefly outline how we can easily configure and use axe for our needs and how it can be integrated into your current tooling so we can start finding (and fixing!) accessibility defects in our web applications TODAY :smiley:.

What is axe

From the axe-core repo.

Axe is an accessibility testing engine for websites and other HTML-based user interfaces. It's fast, secure, lightweight, and was built to seamlessly integrate with any existing test environment so you can automate accessibility testing alongside your regular functional testing.

What this means is that we can use axe to analyse our web applications DOM structure for accessibility issues. This is done via a rule-based system allowing you to configure axe for your needs and requirements, you can find all the axe rules here. These rules test against common accessibility guidelines and standards such as WCAG, Section 508 and axe's own ruleset.

An example simple axe configuration below.

// axe-config.js
module.exports = {
  rules: [
    {
      id: 'landmark-one-main',
      enabled: false
    }
  ]
}

This configuration can become much more complex with the extended attributes you can use such as CSS selectors, tags or excluding hidden elements making axe-core highly configurable for your testing requirements.

This same configuration should be importable into any of the projects that utilise the axe-core engine allowing you to standardize and share your configurations across tooling and applications.

How can I use axe to test accessibility

Now that we know axe can allow for us to test accessibility in our web applications how do I actually implement it? Below ill go over a couple of common ways you can easily implement axe into your testing.

Deployed sites (axe-cli)

If your application is already deployed and accessible via HTTP/S it's as simple as running the axe CLI against your applications URL.

npm install -g axe-cli
axe http://your-site.com/page

This solution works best for simple HTML pages without authentication or that can easily be navigated to via URL paths. If you require any kind of user actions to get to the desired pages to test have a look at the sections on either Puppeteer or Selenium implementations of axe.

React (react-axe)

This is a great tool that can be incorporated into almost any react application quite easily with the following snippet being all that's needed, just make sure to load it before you load your main react application.

var React = require('react');
var ReactDOM = require('react-dom');

if (process.env.NODE_ENV !== 'production') {
  var axe = require('react-axe');
  axe(React, ReactDOM, 1000);
}

React axe is now able to show you the a11y errors and even give you some nice DOM highlighting of where the issue is happening when applicable.

react axe dom highlighting
react axe console output

React axe is also smart enough to reload and re-analyse when components in the DOM are re-rendered making the developer experience great when using it.

Jest (jest-axe)

Now if your team is using something like jest integrating axe into your testing suite is as simple as the snippet below.

const { axe, toHaveNoViolations } = require('jest-axe')

expect.extend(toHaveNoViolations)

it('should demonstrate this matcher`s usage', async () => {
  const render = () => '<img src="#"/>'

  // pass anything that outputs html to axe
  const html = render()

  expect(await axe(html)).toHaveNoViolations()
})

There are some nice helper methods provided by this implementation such as the toHaveNoViolations function. Once you run your tests you'll be provided with nicely formated errors as follows.

react axe console output

Puppeteer (axe-puppeteer)

With the use of axe-puppeteer it's again easy to implement axe into your existing tests with axe being injected automatically for you and just need to call the analyze function when the DOM is in the desired state to be tested.

const { AxePuppeteer } = require('axe-puppeteer')
const puppeteer = require('puppeteer')

(async () => {
    const browser = await puppeteer.launch()
    const page = await browser.newPage()
    await page.setBypassCSP(true)

    await page.goto('https://dequeuniversity.com/demo/mars/')

    const results = await new AxePuppeteer(page).analyze()
    console.log(results)

    await page.close()
    await browser.close()
})()

One note with this implementation is that you will have to fail your tests by evaluating the results object to your testing requirements as an easy function is not provided like the jest-axe implementation.

Selenium (axe-webdriverjs)

Finally, if your using something like selenium axe-core can still be integrated into your current tests with axe-webdriverjs. This solution simply hooks into the web driver object and runs analysis on the rendered DOM when the analyze function is called.

  var AxeBuilder = require('axe-webdriverjs');
  var WebDriver = require('selenium-webdriver');

  var driver = new WebDriver.Builder()
    .forBrowser('firefox')
    .build();

  driver
    .get('https://dequeuniversity.com/demo/mars/')
    .then(function() {
      AxeBuilder(driver).analyze(function(err, results) {
        if (err) {
          // Handle error somehow
        }
        console.log(results);
      });
    });

Similar to the puppeteer implementation you'll have to evaluate the results object for more fine-grained control over the failure criteria for the axe analysis.

Wrapping Up

By utilizing axe for testing accessibility in your web application you can make sure that your applications at the minimum cover some of the best standards when it comes to web accessibility. Also moving as much of the simple accessibility testing covered by axe into code should allow you to catch the simple errors faster saving time and money for the user testing later on.

If your tool or framework wasn't covered make sure check out the full list of projects that use the axe engine here.

If you enjoyed this post or any of the content on Code Punnet, please consider subscribing to our weekly newsletter and following our RSS feed or twitter.

Aaron Berry

I'm a big fan of backend programming, dev-ops automation and all things in the cloud. I like to write about things I'm currently working on and new things I've learnt / learning.

Read more posts by this author.