Accessibility Testingwith Angular



Accessibility Testingwith Angular

2 10


a11y-testing-with-angular

Slides for my talks on accessibility testing with Angular

On Github marcysutton / a11y-testing-with-angular

Accessibility Testingwith Angular

By Marcy Sutton / @MarcySutton Senior Front-End Engineer, Deque Systems

Who is Marcy Sutton?

Why Accessibility?

1/5 of the world's population has some kind of disability It's the right thing to do Lose potential customers You might need it in the future

15% of the population has some kind of disability

Source: World Health Organization

Some people can’t:

  • Use a mouse
  • View a screen
  • See low contrast text
  • Hear dialogue or music
  • Understand complex language

Some people need:

  • Keyboard support
  • Screen reader support
  • High contrast text
  • Captions and transcripts
  • Plain language

Design and develop for ‘future you’

Microsoft Inclusive Design Toolkit

Guess what: you can help!

…by making the Internet more accessible.

#A11y in Angular Apps

  • Write meaningful HTML
  • Enable the keyboard
  • Handle focus
  • Alert the user
  • Coverage with tests
WebExpo: This stuff is all new and necessary. Accessibility basics that can be tested. How-to in ng1 and ng2. Angular Connect: While the basics are important, testing is critical to success. Here's how-to in ng1 and ng2. Back in 2014 I gave a talk at ngEurope that covered the basics of Angular accessibility. These are still the essential things to consider for accessibility as it relates to Angular and all client-rendered frameworks.

Meaningful HTML

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

Also Meaningful HTML

 <button>Button</button>
							
HTML Button

Angular Material button in Chrome Accessibility Inspector

CSS changes button text to uppercase following the Material Design guidelines, which shows in the accessibility tree

Enable the keyboard

Handle focus

Alert the user

Intro to ARIA

https://www.w3.org/TR/wai-aria/

Role: what is it? role="button" State: what state is it in? aria-checked="false" Property: what's the nature of it? aria-haspopup="true" aria-label="Close modal"

Accessibility with ngAria: Angular 1 documentation

ngAria

aria-checked code example and link to documentation https://docs.angularjs.org/api/ngAria

While ngAria was a worthwhile effort,it only helps certain use cases.

Build accessibility into your workflow ~ with ~ Automated Testing

DOM vs. accessibility/ ax tree keyboard event stubbing tests as documentation for accessibility features

~ But ~ It’s no substitute for real user feedback

Because of the differences in screen readers vs. browser tools / best practices

Testing for accessibility in:

Angular 1*

Angular 2

Directive-SpecificUnit Tests

Good for:

  • Text alternatives / Labeling
  • Keyboard operability
  • ARIA attribute watching
Escape key, tabbing, hitting Enter/Space, Arrow keys offscreen/attribute text computation (stuff you don't see) ARIA default values for custom elements

Labeling unit test

  it('should apply aria-hidden="true" when parent has valid label', 
  inject(function() {

    var 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');
  }));
						
mdIcon Spec

Keyboard unit test

   it('closes on escape', inject(function($document, $mdConstant) {
      var menu = setup();
      openMenu(menu);
      expect(getOpenMenuContainer(menu).length).toBe(1);

      var openMenuEl = $document[0].querySelector('md-menu-content');

      pressKey(openMenuEl, $mdConstant.KEY_CODE.ESCAPE);
      waitForMenuClose();

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

Integration/ End-to-End Tests

Good for:

  • Color contrast
  • Widget keyboard interrop
  • Document-level rules
Escape key, tabbing, hitting Enter/Space, Arrow keys offscreen/attribute text computation (stuff you don't see) ARIA default values for custom elements

Get help with an API

Save yourself from:

  • Label/name computation
  • Incorrect ARIA usage
  • Color contrast
  • Data table markup
  • Viewport/zooming probz
Escape key, tabbing, hitting Enter/Space, Arrow keys offscreen/attribute text computation (stuff you don't see) ARIA default values for custom elements

A11y test APIs: You’ve got options

  • aXe
  • Chrome A11y Developer Tools
  • QUAIL
  • Tenon
  • WAVE

axe-core API

https://github.com/dequelabs/axe-core

  • Runs locally
  • Open source
  • Free †
  • Good for integration tests
  • Also unit tests

†There’s also a supported/enterprise option, Worldspace Attest

aXe Chrome extension ~ http://bit.ly/axe-chrome
Angular Material Demo App

Demo App Template

  <md-toolbar layout="row">
	<h1 layout-align-gt-sm="center center" class="md-toolbar-tools">My Favorite Muppets</h1>
  </md-toolbar>
  <div layout="row" flex class="content-wrapper">
    <md-sidenav layout="column" class="md-sidenav-left md-whiteframe-z2" md-component-id="left">
        <md-list class="muppet-list">
          <md-item ng-repeat="it in muppets">
            <md-item-content>
              <md-button ng-click="selectMuppet(it)" ng-class="{'selected' : it === selected }">
                <img ng-src="{{it.iconurl}}" class="face" alt="">
                {{it.name}}
              </md-button>
            </md-item-content>
          </md-item>
        </md-list>
      </md-sidenav>
      <div layout="column" flex class="content-wrapper" id="primary-col">
          <md-content layout="column" flex class="md-padding">
            <h2>{{selected.name}}</h2>
            <p>{{selected.content}}</p>
            <div class="cell">
              <img ng-src="{{selected.imgurl}}" alt="{{selected.imgalt}}">
            </div>
          </md-content>
      </div>
  </div>
						
http://bit.ly/ngmaterial-muppets

End-to-End Test

  var AxeBuilder = require('axe-webdriverjs');

  describe('view1', function() {

    beforeEach(function() { browser.get('index.html'); });

    it('should change Muppets', function() {
      element.all(by.css('[ng-click="selectMuppet(it)"]')).first().
        sendKeys(protractor.Key.ENTER);

      expect(element.all(by.css('#primary-col h2')).first().getText()).
        toMatch('Animal');
    });

    it('should have no accessibility violations', function(done) {
      AxeBuilder(browser)
        .analyze(function(results) {
          if (results.violations.length > 0) {
            console.log(results.violations);
          }
          expect(results.violations.length).toBe(0);
          done();
      });
  });
						
https://github.com/dequelabs/axe-webdriverjs

Command-Line Demo

axe-angular1-demo

Testing for accessibility in:

Angular 1

Angular 2*

Angular 2: Big differences

  • Component-based
  • TypeScript & ES6
  • Property bindings
Controllers and $scope are no longer used. They have been replaced by components and directives. Components are directives with a template.

Medium article by Victor Savkin: Three Ways to Test Angular 2 Components

Test requirements for a11y

Test method Rendered Attached Unit Keyboard mechanics X X Labeling ARIA attributes ~ ~ End-to-end Color contrast X X Event handlers ~ ~ A11y API Audit X X

Angular Material 2 Preview

Material 2 checkbox using Voiceover

Angular 2 Unit Test 1/2

Applying a text alternative with `aria-labelledby`

 describe('mdCheckbox with provided aria-labelledby ', () => {
    let checkboxDebugElement: DebugElement;
    let checkboxNativeElement: HTMLElement;
    let inputElement: HTMLInputElement;

    it('should use the provided aria-labelledby', () => {
      fixture = TestBed.createComponent(CheckboxWithAriaLabelledby);
      
      checkboxDebugElement = fixture.debugElement.query(By.directive(MdCheckbox));
      checkboxNativeElement = checkboxDebugElement.nativeElement;

      inputElement = <HTMLInputElement>checkboxNativeElement.querySelector('input');

      fixture.detectChanges();
      expect(inputElement.getAttribute('aria-labelledby')).toBe('some-id');
  });

  /** Simple test component with aria-labelledby set. */
  @Component({
    template: ``
  })
  class CheckboxWithAriaLabelledby {}
					
Material 2 Checkbox Spec

Angular 2 Unit Test 2/2

ARIA attribute watching

	
    it('toggles "aria-checked" on the host element', function() {
      builder.createAsync(CheckboxController).then(function(fixture) {
        let el = fixture.debugElement.query(By.css('.md-checkbox'));

        expect(el.nativeElement.getAttribute('aria-checked')).toEqual('false');

        controller = fixture.componentInstance;
        changePromise = waitForChange();
        controller.isChecked = true;

        fixture.detectChanges();

        expect(el.nativeElement.getAttribute('aria-checked')).toEqual('true');
      });
    });
							
Older Material 2 Checkbox Spec

Angular 2 Keyboard Unit Tests

The pain: events not supported, even click is rough TypeScript woes for third-party help

Angular 2 issue on Github: Creating fake event objects for testing components

Angular 2 Keyboard Test

    it('should respond to keyboard events', () => {
      let fixture = TestBed.createComponent(Board);
      fixture.detectChanges();

      var event1 = new KeyboardEvent('keydown', { key: '1' });

      var event3 = new KeyboardEvent('keydown', { key: '3' });

      window.dispatchEvent(event1);
      window.dispatchEvent(event3);
      fixture.detectChanges();

      let board = fixture.nativeElement;
      let box = board.querySelectorAll('.ttt-box')[2];

      expect(box.textContent).toContain('x');
    });
						
Tic-tac-toe Game Spec by Julie Ralph

End-to-End Testing in Angular 2

~ with ~

Protractor

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());
  }

	 				
Material 2 Menu

Mocking Keyboard Events

Bug in ChromeDriver

DOM helper getKeyEvent does not use keyCodehttps://github.com/angular/angular/issues/9419

End-to-end Testing with aXe-core

  import * as axe from 'axe-core';
  
  ...

  it('should have no accessibility violations', function(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();
	});
  });
							
Protractor test

Recap

  • Accessibility is similar with all web frameworks.
  • Unit tests provide a component accessibility contract.
  • Use integration tests to redirect human resources.
  • Use an API for extra a11y muscle.
  • Prevent broken experiences from going out the door!

Thanks!

twitter.com/marcysutton

github.com/marcysutton

Accessibility Testingwith Angular By Marcy Sutton / @MarcySutton Senior Front-End Engineer, Deque Systems