Edit 04/07/13: See the followup article for an alternative to using skip navigation links.
An exploration in automated accessibility testing
Today Grant Hutchins and I took on several stories to enhance the accessibility of a site. One of them was to add a skip-navigation link to the application.
To understand why skip-nav links are important, visit Jim Thatcher’s explanation.
Our immediate inclination was to write a request spec with Capybara to drive out the solution. We came up with the following.
The Test
require "spec_helper"
feature "Keyboard Navigation" do
scenario "hidden skip navigation link shows when focused and jumps to content", js: true do
login_as(users(:user))
visit root_path
skip_link = page.find("#skip-navigation a")
skip_link.should have_content "Skip navigation"
skip_link.native.location.y.should be < 0
body_element = page.find("body")
body_element.native.send_keys(:tab)
skip_link.native.location.y.should == 0
skip_link.native.send_keys(:return)
skip_link.native.location.y.should be < 0
current_url.should match(/#content$/)
end
end
The Markup
%body
#skip-navigation
%p= link_to "Skip navigation", "#content", tabindex: 0
...
#content
...
The Styles
body {
#skip-navigation {
a, a:hover, a:visited {
position:absolute;
left:0px;
top:-500px;
overflow:hidden;
}
a:active, a:focus {
position:absolute;
left:0;
top:0;
}
}
}
What’s happening here?
We are asserting that the “hidden skip navigation link shows when focused and jumps to content” when clicked.
The most important aspect of what we did was emulating keyboard navigation. We’re using js: true
so we have access to Selenium’s native methods and thus the send_keys
method. This allows us to send keypress messages to the browser.
Since we’ve used positioning to hide the element, we also have an assertion around that property.
Problems in CI!
The application behaved as expected and the tests passed locally.
When we ran the tests in CI however, the tests failed. The reason is that _the browser must retain foremost focus in the OS in order for the :focus
css pseudo-selector to fire on the skip-nav element. Without the :focus
style rules applied, the skip-nav element remained invisible, and the tests failed.
We tried a workaround using within_window
and forcing browser focus, but couldn’t get it to work. We’ve got a few more tricks up our sleeve that we’re going to try, and will report back here.
Edit:
We added within_window
to the test to force focus on the browser. This makes the test less brittle locally (because it won’t fail if you click out of the window).
window = page.driver.browser.window_handles.last
page.within_window(window) do
skip_link.native.location.y.should == 0
end
About the Author