Accessbility testing for developers

Alexander Vassbotn Røyne-Helgesen

Manager, Front-end Developer @ Bekk
 

github@phun-ky
twitter@phun_ky

A website is not necessarily universally designed, even though it is in line with the current requirements and guidelines

Meaningfull html

Enable the keyboard

Handle focus

Coverage with tests

Meaningfull HTML

<label for="roundTrip">
  Round Trip
</label>
<input type="radio" id="roundTrip">

Intro to ARIA

Role: What is it?  

<span role="button" onClick={} onKeypress={} tabIndex={}></span>

State: What state is it in?

<button aria-checked="false"></button>

Property: What's the nature of it?

aria-haspopup="true" 
aria-label="Close modal"

People with and without disabilities

Disability simulation

Manual

Automatic

One cannot test all WCAG criteria automatically. In fact, very few of them can be tested automatically, without even evaluating them manually. I believe that at best, only four of the 35 criteria apply according to Norwegian law, which can be fully tested automatically:

4.1.1 Parsing must be possible. In other words, the HTML must be valid and according to specification

1.4.3 Contrast between text and background should be good enough

2.1.1 All functionality is available via keyboard

3.1.1 Page language must be specified (lang attribute)

Automated testing

Directive-specific unit tests

Text alt. / Labelling

Keyboard operability

ARIA attribute matching

Labeling unit test

it('should apply aria-hidden="true" when parent has valid label', inject(function() {
  const el = make(
    `<md-radio-button
          aria-label="Squishy Avatar"
          role="radio" tabindex="0"
        >
          <div class="md-container"></div>
          <div class="md-label">
            <md-icon md-svg-icon="android"></md-icon>
          </div>
        </md-radio-button>
     `
  );

  expect(el.find("md-icon").attr("aria-hidden")).toEqual("true");
}));

Keyboard unit test

it("closes on escape", inject((document, CONSTANTS) => {
  const menu = setup();
  openMenu(menu);
  expect(getOpenMenuContainer(menu).length).toBe(1);

  const openMenuEl = document.querySelector("md-menu-content");

  pressKey(openMenuEl, CONSTANTS.KEY_CODE.ESCAPE);
  waitForMenuClose();

  expect(getOpenMenuContainer(menu).length).toBe(0);
}));

Integration / End to end tests

Color contrast

Widget keyboard interop

Document-level rules

Get help with an API

Label / name computation

Incorrect aria usage

Color contrast

Data table markup

Viewport and zooming issues

$ npm i axe-core --save-dev

Runs locally

Open source

Free

Integration tests

Unit tests

Example

/*eslint-env node, mocha */
import React from "react";
import chai, { expect } from "chai";
import chaiEnzyme from "chai-enzyme";
import Cover from "components/details/Cover";
import axe from "axe-core";
import { mountToDoc, check } from "../../../../../test/helpers";

chai.use(chaiEnzyme());

const props = {};

describe("<Cover />", () => {
  it("has no aXe violations", done => {
    const component = mountToDoc(<Cover {...props} />);
    const linkNode = component.getDOMNode();
    axe.run(linkNode, (err, { violations }) => {
      if (err) done(err);
      expect(err).to.equal(null);
      check(done, () => {
        expect(
          violations.length,
          violations
            .map(
              ({ help, helpUrl, description }) =>
                `${help}: ${description}. Please check => ${helpUrl}`
            )
            .join("")
        ).to.equal(0);
      });
    });
  }).timeout(5000);
});

End to end keyboard test

/*----- menu.e2e.ts -------*/
  import { MenuPage } from './menu-page';

  describe('menu', () => {
    let page: MenuPage;

    beforeEach(function() {
      page = new MenuPage();
    });

    describe('keyboard events', () => {
      beforeEach(() => {
        // click start button to avoid tabbing past navigation
        page.start().click();
        page.pressKey(protractor.Key.TAB);
      });

      it('should auto-focus the first item when opened with keyboard', () => {
        page.pressKey(protractor.Key.ENTER);
        page.expectFocusOn(page.items(0));
     });

      it('should focus subsequent items when down arrow is pressed', () => {
        page.pressKey(protractor.Key.ENTER);
        page.pressKey(protractor.Key.DOWN);
        page.expectFocusOn(page.items(1));
      });
    });
  });

  /*----- menu-page.ts -------*/
  pressKey(key: any): void {
    browser.actions().sendKeys(key).perform();
  }

  expectFocusOn(el: ElementFinder): void {
    expect(browser.driver.switchTo().activeElement().getInnerHtml())
        .toBe(el.getInnerHtml());
  }

End to end test with axe-core

import * as axe from "axe-core";

it("should have no accessibility violations", done => {
  browser.executeScript(axe.source);

  // Run A11Y tests in the browsers event loop.
  browser
    .executeAsyncScript((resolve: any) => {
      return (
        new Promise() <
        axe.AxeResults >
        (res => {
          axe.a11yCheck(document, {}, resolve);
        })
      );
    })
    .then((results: any) => {
      if (results.violations.length > 0) {
        console.log(results.violations);
      }
      expect(results.violations.length).toBe(0);
      done();
    });
});

Similar with all web frameworks

Component Accessibility Contract

Redirect Human Resources

Extra a11y muscle

prevent broken experiences

Automatic testing of WCAG undoubtedly contributes to both quality and efficiency, but it often reveals only the tip of the iceberg. I believe human evaluation and user testing are absolutely necessary when we work on creating solutions that can be used by everyone in any situation. The effect of seeing and knowing the problems associated with disability itself should also not be underestimated - Anders Skifte

Extensions

QUESTIONS?

Alexander Vassbotn Røyne-Helgesen

Manager, Front-end Developer @ Bekk
 

github@phun-ky
twitter@phun_ky