a personal blog for Jamey Alea

Gem of the Week: axe-core-gems

December 3, 2020 Jamey Alea 0 Comments

There are a few reasons why it may not be quite accurate to call axe-core-gems the gem of the week. Well, first, because this is the first “gem of the week” column I’ve written since September 2016 so it’s more like the gem of… the last few years. And second, it’s actually a suite of six gems with similar functionality, so you can choose which ones match your existing testing stack. It supports three webdrivers (selenium, capybara and watir) and two testing frameworks (rspec and cucumber), which covers a lot of combinations!

But what does axe-core actually do that’s got me so excited to write a gem of the week column after four years? It’s a tool for automated accessibility testing. Web accessibility, or a11y as it’s often called, is an important campaign to ensure that the Internet is usable for everyone, regardless of their needs or disabilities. There are some accessibility features that you’ve surely heard about, like the push to include alt text with images, so that screen readers are able to describe the content of the image to users who can’t view it. Others are less well known. Something I just recently learned about is header order—screen readers also use the incremental order of headers (h1, h2, h3, etc) to determine what order content on the page should be presented in. These are just a couple small examples: accessibility is a huge topic that I can’t cover all of in this discussion of the axe-core gems, but I recommend the A11y Project as a good resource to learn more about it.

A11y is also something that has been on my mind more than usual lately. I recently took a new position at True Link Financial, and what we do there is provide financial services for folks in financially at-risk groups, like older people, people who are in recovery from addiction and folks with disabilities. That means accessibility is especially important for us, since our target demographic is more likely to need these kinds of accommodations to be able to use our website. Our quest to ensure that accessibility standards were met at True Link is what brought me to the axe-core gems.

And they are easy to use. Your installation will depend a little on your setup, but all we had to do was add gem ‘axe-core-rspec’ to our Gemfile, and then require it with require ‘axe-rspec’ in spec/rails_helper.rb. That’s all we had to do before we could immediately start using it in our specs. (As you can see, we use rspec in our project, so my discussion around it is in that context and I’m going to use rspec syntax examples. That said, I’ve looked at the docs for the other versions of the gem, particularly cucumber, and it doesn’t seem any more complicated to implement than the rspec version.)

All of your axe tests in rspec are based around the custom matcher be_axe_clean — this is how you prompt your spec to check for accessibility violations. It’s worth noting that this doesn’t check your source code, it looks at what’s actually being rendered on the page at the moment it’s called, via your webdriver. But this is the basic axe command (in rspec):

expect(page).to be_axe_clean

Running this acts similar to a linter, raising violations and warnings and categorizing them as critical, serious, moderate, etc. Here’s an example of the kind of output you may see from a failing axe assertion:

Sign-up page accessibility when I go to the sign-up page raises no axe warnings on the create account page
Failure/Error: expect(page).to be_axe_clean

Found 2 accessibility violations:
label: Form elements must have labels (critical)
The following 1 node violate this rule:
	Selector: #user_email
	HTML: <input id=”user_email” type=”email” autocomplete=”off” data-analytics-key=”email” name=”user.email” value=””>
	Fix any of the following:
area-label attribute does not exist or is empty
area-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
Form element does not have an implicity (wrapped) <label>
Form element does not have an explicit <label>
Element has no title attribute or the title attribute is empty

page-has-heading-one: Page must contain a level one heading (moderate)
The following 1 node violate this rule:

	Selector: html
	HTML: <html lang=”en”>
	Fix all of the following:
Page must have a level one heading

Invocation: axe-run(callback):
# ./spec/features/accessibility/sign_up_spec.rb:40:in ‘block (3 levels) in <top (required)>’
# ./spec/rails_helper.rb:333:in ‘block (2 levels) in <top (required)>’
# ./spec/rails_helper.rb:250:in ‘block (2 levels) in <top (required)>’
Images of text are not a11y friendly! I have added alt text with the content of this image.

Let’s break this down a little. We’ve picked up two violations, one critical and one moderate. After telling you what rule you’re violating, it provides a link to a webpage that explains the violation in more details, including how to fix it and also why it’s an accessibility issue. (This is really helpful, because as I mentioned earlier, it did raise some issues that I didn’t totally understand at first glance.)

Then it tells you which nodes violate the rule. This is not always as helpful as it could be, because of the way it scans your rendered page instead of your source code, so it can still be tough to figure out exactly where in your code the violation is being generated from. It’s a start at least, but it does require some understanding of the page you’re testing and how it’s architected. (Normally I’d hope that I have that kind of understanding of my own code, but this might come up particularly if you’re going back and adding accessibility tests to an existing codebase as I was. It brought me to parts of my own app I hadn’t worked in before, which was a great learning experience, but did make it tougher to work out some of these nodes!) After that, it gets very helpful again, giving you a pretty user-friendly list of ways you can fix the violation, and it tells you if you need to do them all or just pick one. More info about these solutions are also available on the linked webpage.

I said that all the tests are based around the be_axe_clean matcher, but there are a number of clauses available to customize your spec. You can test only specific parts of the page with within '#selector' or exclude specific selectors with excluding '#selector'. You can also specify multiple selectors or compound selectors with slightly different syntaxes.

expect(page).to be_axe_clean.within '#sign_up_form'

# This will test both #sign_up_form and .name_input separately
expect(page).to be_axe_clean.within '#sign_up_form', '.name_input'

# This will test only .name_input within #sign_up_form
expect(page).to be_axe_clean.within '#sign_up_form .name_input'

# All the same rules apply for excluding
expect(page).to be_axe_clean.excluding '#sign_up_form'
expect(page).to be_axe_clean.excluding '#sign_up_form', '.name_input'
expect(page).to be_axe_clean.excluding '#sign_up_form .name_input'

# You can also chain different clauses together
expect(page).to be_axe_clean.within('#sign_up_form').excluding('.name_input')

There are also clauses that specify which rules you are testing for. You can choose only to test for specific rules with checking_only or exclude specific rules with skipping. (I found it particularly useful to be able to skip certain violations, such as color contrast. While it’s certainly important for accessibility, I didn’t feel like I could just start changing around the colors for my company’s entire app, as that’s something that should go through designers. But it didn’t mean I couldn’t test my pages for the rest of the standards!) There’s also functionality for specifying which a11y standard(s) you want to be compliant with.

# You can specify one or multiple a11y standards
expect(page).to be_axe_clean.according_to :wcag2a
expect(page).to be_axe_clean.according_to :wcag2a, :section508

# The same is true for including or excluding rules
expect(page).to be_axe_clean.checking_only(:'image-alt')
expect(page).to be_axe_clean.skipping(:'color-contrast', :'heading-order')

# All of the clauses from all my examples are interoperable and can be chained
expect(page).to be_axe_clean.according_to(:wcag2aa).within('.main', '.header').excluding('.footer').skipping(:'heading-order')

There are a couple more resources that will be helpful to use these clauses effectively. Here is a list of the axe-core “tags”, which are the different valid a11y standards you can use with according_to. There’s also a full list of valid rules, which includes the formatted name of the rule (which you’ll need in order to reference it), as well as metadata info about which standards require which rules.

I hope this guide helped! Accessibility is just so important, and the ability to test for it is very cool. If helping folks learn how to use this suite of gems leads to making the internet even a tiny bit more accessible, that would be very cool too!

#a11y#axe-core-gems#gem#rspec#ruby on rails

Previous Post

Next Post

Leave a Reply

Your email address will not be published / Required fields are marked *