Skip to content

Commit 0fb1dd4

Browse files
committed
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
1 parent cf1b4b9 commit 0fb1dd4

File tree

7 files changed

+88
-14
lines changed

7 files changed

+88
-14
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
"@types/node": "^6.0.34",
5252
"@types/run-sequence": "0.0.27",
5353
"@types/rx": "^2.5.33",
54+
"axe-core": "^2.0.7",
55+
"axe-protractor": "0.0.7",
56+
"axe-webdriverjs": "^0.4.0",
5457
"browserstacktunnel-wrapper": "^2.0.0",
5558
"conventional-changelog": "^1.1.0",
5659
"express": "^4.14.0",
@@ -80,7 +83,6 @@
8083
"minimist": "^1.2.0",
8184
"node-sass": "^3.4.2",
8285
"protractor": "^4.0.8",
83-
"protractor-accessibility-plugin": "0.1.1",
8486
"resolve-bin": "^0.4.0",
8587
"rollup": "^0.34.13",
8688
"run-sequence": "^1.2.2",

src/e2e-app/e2e-app/e2e-app.html

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
<a md-list-item [routerLink]="['button']">Button</a>
2-
<a md-list-item [routerLink]="['checkbox']">Checkbox</a>
3-
<a md-list-item [routerLink]="['dialog']">Dialog</a>
4-
<a md-list-item [routerLink]="['grid-list']">Grid list</a>
5-
<a md-list-item [routerLink]="['icon']">Icon</a>
6-
<a md-list-item [routerLink]="['list']">List</a>
7-
<a md-list-item [routerLink]="['menu']">Menu</a>
8-
<a md-list-item [routerLink]="['radio']">Radios</a>
9-
<a md-list-item [routerLink]="['tabs']">Tabs</a>
1+
<!-- To avoid interfering with tests, we are not using the <md-list> element here -->
2+
<div role="list">
3+
<a md-list-item [routerLink]="['button']">Button</a>
4+
<a md-list-item [routerLink]="['checkbox']">Checkbox</a>
5+
<a md-list-item [routerLink]="['dialog']">Dialog</a>
6+
<a md-list-item [routerLink]="['grid-list']">Grid list</a>
7+
<a md-list-item [routerLink]="['icon']">Icon</a>
8+
<a md-list-item [routerLink]="['list']">List</a>
9+
<a md-list-item [routerLink]="['menu']">Menu</a>
10+
<a md-list-item [routerLink]="['radio']">Radios</a>
11+
<a md-list-item [routerLink]="['tabs']">Tabs</a>
12+
</div>
13+
14+
<main>
15+
<router-outlet role="main"></router-outlet>
16+
</main>
1017

11-
<router-outlet></router-outlet>

src/e2e-app/radio/radio-e2e.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<section>
22
<md-radio-group [disabled]="isGroupDisabled"
33
[(value)]="groupValue"
4-
id="test-group">
4+
id="test-group" aria-label="Select a Pokemon">
5+
56
<md-radio-button value="fire" id="fire">Charmander</md-radio-button>
67
<md-radio-button value="water" id="water">Squirtle</md-radio-button>
78
<md-radio-button value="leaf" id="leaf">Bulbasaur</md-radio-button>
9+
810
</md-radio-group>
911
<button (click)="isGroupDisabled=!isGroupDisabled" id="toggle-disable">Disable/enable group</button>
1012
</section>

src/lib/grid-list/grid-list.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const MD_FIT_MODE = 'fit';
3535
selector: 'md-grid-list',
3636
templateUrl: 'grid-list.html',
3737
styleUrls: ['grid-list.css'],
38+
host: {
39+
'role': 'list'
40+
},
3841
encapsulation: ViewEncapsulation.None,
3942
})
4043
export class MdGridList implements OnInit, AfterContentChecked {

test/protractor.conf.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,25 @@ require('ts-node').register({
99

1010
const E2E_BASE_URL = process.env['E2E_BASE_URL'] || 'http://localhost:4200';
1111
const config = {
12-
// TODO(jelbourn): add back plugin for a11y assersions once it supports specifying AXS options.
1312
useAllAngular2AppRoots: true,
1413
specs: [ path.join(__dirname, '../e2e/**/*.e2e.ts') ],
1514
baseUrl: E2E_BASE_URL,
1615
allScriptsTimeout: 120000,
1716
getPageTimeout: 120000,
1817
jasmineNodeOpts: {
1918
defaultTimeoutInterval: 120000,
20-
}
19+
},
20+
21+
plugins: [
22+
{
23+
path: '../tools/axe-protractor/axe-protractor.js',
24+
25+
rules: [
26+
// Exclude md-menu elements because those are empty if not active.
27+
{ id: 'aria-required-children', selector: '*:not(.md-menu)' },
28+
]
29+
}
30+
]
2131
};
2232

2333
if (process.env['TRAVIS']) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
/*
4+
* Protractor Plugin to run axe-core accessibility audits after Angular bootstrapped.
5+
*/
6+
7+
const AxeBuilder = require('axe-webdriverjs');
8+
const {buildMessage} = require('./build-message');
9+
10+
/* Resolved violations */
11+
const violations = {};
12+
13+
/** When the page is ready and Angular is bootstrapped. */
14+
function onPageStable() {
15+
AxeBuilder(browser.driver)
16+
.configure(this.config || {})
17+
.analyze(results => handleResults(this, results));
18+
}
19+
20+
/** Handles the results of axe-core. */
21+
function handleResults(context, results) {
22+
23+
if (!violations.hasOwnProperty(results.url)) {
24+
violations[results.url] = true;
25+
26+
results.violations.forEach(violation => {
27+
28+
let specName = `${violation.help} (${results.url})`;
29+
let message = '\n' + buildMessage(violation);
30+
31+
context.addFailure(message, {specName});
32+
33+
});
34+
35+
}
36+
37+
}
38+
39+
exports.name = 'protractor-axe';
40+
exports.onPageStable = onPageStable;

tools/axe-protractor/build-message.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
exports.buildMessage = violation => {
2+
3+
let selectors = violation.nodes.map(node => {
4+
return node.target.join(' ');
5+
});
6+
7+
return selectors.reduce((content, selector) => {
8+
return content + '- ' + selector + '\n';
9+
}, '');
10+
11+
};

0 commit comments

Comments
 (0)