From 19536c1bf459fede92b6ec1f2f7e3247fd7a08bf Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 11 Dec 2016 19:58:31 +0100 Subject: [PATCH 1/3] chore: run a11y audits on protractor * Fixes accessibility issue in grid-list with `role="listitem"` * Adds protractor plugin to run aXe-core accessibility audits * Fixes a bunch of a11y issues in the e2e app to make the aXe audits green --- package.json | 3 +- src/e2e-app/e2e-app/e2e-app.html | 28 ++++++++------- src/e2e-app/radio/radio-e2e.html | 4 ++- src/lib/grid-list/grid-list.ts | 3 ++ test/protractor.conf.js | 16 +++++++-- tools/axe-protractor/axe-protractor.js | 48 ++++++++++++++++++++++++++ tools/axe-protractor/build-message.js | 16 +++++++++ 7 files changed, 102 insertions(+), 16 deletions(-) create mode 100644 tools/axe-protractor/axe-protractor.js create mode 100644 tools/axe-protractor/build-message.js diff --git a/package.json b/package.json index 6f414a545df6..85589623dbe0 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "@types/node": "^6.0.34", "@types/run-sequence": "0.0.27", "@types/rx": "^2.5.33", + "axe-core": "^2.0.7", + "axe-webdriverjs": "^0.4.0", "conventional-changelog": "^1.1.0", "express": "^4.14.0", "firebase-tools": "^2.2.1", @@ -79,7 +81,6 @@ "minimist": "^1.2.0", "node-sass": "^3.4.2", "protractor": "^4.0.8", - "protractor-accessibility-plugin": "0.1.1", "resolve-bin": "^0.4.0", "rollup": "^0.34.13", "run-sequence": "^1.2.2", diff --git a/src/e2e-app/e2e-app/e2e-app.html b/src/e2e-app/e2e-app/e2e-app.html index c8e4c60cee48..7c9995dd6c18 100644 --- a/src/e2e-app/e2e-app/e2e-app.html +++ b/src/e2e-app/e2e-app/e2e-app.html @@ -1,13 +1,17 @@ -Button -Checkbox -Dialog -Grid list -Icon -List -Menu -Progress bar -Progress circle -Radios -Tabs + + Button + Checkbox + Dialog + Grid list + Icon + List + Menu + Progress bar + Progress circle + Radios + Tabs + - +
+ +
diff --git a/src/e2e-app/radio/radio-e2e.html b/src/e2e-app/radio/radio-e2e.html index 83d672df16cd..d14f9cead519 100644 --- a/src/e2e-app/radio/radio-e2e.html +++ b/src/e2e-app/radio/radio-e2e.html @@ -1,10 +1,12 @@
+ id="test-group" aria-label="Select a Pokemon"> + Charmander Squirtle Bulbasaur +
diff --git a/src/lib/grid-list/grid-list.ts b/src/lib/grid-list/grid-list.ts index 94d3ae9ce365..2fb9208d1623 100644 --- a/src/lib/grid-list/grid-list.ts +++ b/src/lib/grid-list/grid-list.ts @@ -34,6 +34,9 @@ const MD_FIT_MODE = 'fit'; selector: 'md-grid-list, mat-grid-list', templateUrl: 'grid-list.html', styleUrls: ['grid-list.css'], + host: { + 'role': 'list' + }, encapsulation: ViewEncapsulation.None, }) export class MdGridList implements OnInit, AfterContentChecked { diff --git a/test/protractor.conf.js b/test/protractor.conf.js index 5a9c2c631008..c613290a021f 100644 --- a/test/protractor.conf.js +++ b/test/protractor.conf.js @@ -9,7 +9,6 @@ require('ts-node').register({ const E2E_BASE_URL = process.env['E2E_BASE_URL'] || 'http://localhost:4200'; const config = { - // TODO(jelbourn): add back plugin for a11y assersions once it supports specifying AXS options. useAllAngular2AppRoots: true, specs: [ path.join(__dirname, '../e2e/**/*.e2e.ts') ], baseUrl: E2E_BASE_URL, @@ -17,7 +16,20 @@ const config = { getPageTimeout: 120000, jasmineNodeOpts: { defaultTimeoutInterval: 120000, - } + }, + + plugins: [ + { + // Runs the axe-core accessibility checks each time the e2e page changes and + // Angular is ready. + path: '../tools/axe-protractor/axe-protractor.js', + + rules: [ + // Exclude md-menu elements because those are empty if not active. + { id: 'aria-required-children', selector: '*:not(md-menu)' }, + ] + } + ] }; if (process.env['TRAVIS']) { diff --git a/tools/axe-protractor/axe-protractor.js b/tools/axe-protractor/axe-protractor.js new file mode 100644 index 000000000000..ca18cc17c866 --- /dev/null +++ b/tools/axe-protractor/axe-protractor.js @@ -0,0 +1,48 @@ +'use strict'; + +/** + * Protractor Plugin to run axe-core accessibility audits after Angular bootstrapped. + */ + +const AxeBuilder = require('axe-webdriverjs'); +const {buildMessage} = require('./build-message'); + +/* List of pages which were already checked by axe-core and shouldn't run again */ +const checkedPages = []; + +/** + * Protractor plugin hook which always runs when Angular successfully bootstrapped. + */ +function onPageStable() { + AxeBuilder(browser.driver) + .configure(this.config || {}) + .analyze(results => handleResults(this, results)); +} + +/** + * Processes the axe-core results by reporting recognized violations + * to Protractor and printing them out. + * @param {!protractor.ProtractorPlugin} context + * @param {!axe.AxeResults} results + */ +function handleResults(context, results) { + + if (checkedPages.indexOf(results.url) === -1) { + + checkedPages.push(results.url); + + results.violations.forEach(violation => { + + let specName = `${violation.help} (${results.url})`; + let message = '\n' + buildMessage(violation); + + context.addFailure(message, {specName}); + + }); + + } + +} + +exports.name = 'protractor-axe'; +exports.onPageStable = onPageStable; diff --git a/tools/axe-protractor/build-message.js b/tools/axe-protractor/build-message.js new file mode 100644 index 000000000000..be07a9bb52d7 --- /dev/null +++ b/tools/axe-protractor/build-message.js @@ -0,0 +1,16 @@ +/** + * Builds a simple message of the violation results of axe-core by listing + * each violation and the associated element selector in a new line. + * @param {!axe.Violation} violation + */ +exports.buildMessage = violation => { + + let selectors = violation.nodes.map(node => { + return node.target.join(' '); + }); + + return selectors.reduce((content, selector) => { + return content + '- ' + selector + '\n'; + }, ''); + +}; \ No newline at end of file From 3c307660be2cead168e9e7bcc6dbf67f2765034a Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 11 Dec 2016 20:21:44 +0100 Subject: [PATCH 2/3] Force menu y-positions because viewport might be different on Selenium browsers. --- e2e/components/menu/menu-page.ts | 4 ++-- src/e2e-app/menu/menu-e2e.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/components/menu/menu-page.ts b/e2e/components/menu/menu-page.ts index 5470c483117f..a89ce7327573 100644 --- a/e2e/components/menu/menu-page.ts +++ b/e2e/components/menu/menu-page.ts @@ -53,8 +53,8 @@ export class MenuPage { expectMenuLocation(el: ElementFinder, {x, y}: {x: number, y: number}) { el.getLocation().then(loc => { - expect(loc.x).toEqual(x); - expect(loc.y).toEqual(y); + expect(loc.x).toEqual(x, 'Expect the x-position to be equal'); + expect(loc.y).toEqual(y, 'Expect the y-position to be equal'); }); } diff --git a/src/e2e-app/menu/menu-e2e.html b/src/e2e-app/menu/menu-e2e.html index 057517b9c8fe..ad1c2f86c817 100644 --- a/src/e2e-app/menu/menu-e2e.html +++ b/src/e2e-app/menu/menu-e2e.html @@ -5,7 +5,7 @@ - + @@ -15,7 +15,7 @@ - + From 6af517bbe5fa8059225caf6830ed49a38bc54af8 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 11 Dec 2016 21:08:29 +0100 Subject: [PATCH 3/3] Navigiation links should be hidden by default because those can interfere with the tests. --- src/e2e-app/e2e-app/e2e-app.html | 4 +++- src/e2e-app/e2e-app/e2e-app.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/e2e-app/e2e-app/e2e-app.html b/src/e2e-app/e2e-app/e2e-app.html index 7c9995dd6c18..e75593ffb40f 100644 --- a/src/e2e-app/e2e-app/e2e-app.html +++ b/src/e2e-app/e2e-app/e2e-app.html @@ -1,4 +1,6 @@ - + + + Button Checkbox Dialog diff --git a/src/e2e-app/e2e-app/e2e-app.ts b/src/e2e-app/e2e-app/e2e-app.ts index 95f768e69dff..64fb2e4504a5 100644 --- a/src/e2e-app/e2e-app/e2e-app.ts +++ b/src/e2e-app/e2e-app/e2e-app.ts @@ -12,4 +12,6 @@ export class Home {} selector: 'e2e-app', templateUrl: 'e2e-app.html', }) -export class E2EApp { } +export class E2EApp { + showLinks: boolean = false; +}