Skip to content

Commit c5fb94f

Browse files
devversionjelbourn
authored andcommitted
chore: run a11y audits on protractor (#2010)
* 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 * Force menu y-positions because viewport might be different on Selenium browsers. * Navigiation links should be hidden by default because those can interfere with the tests.
1 parent 66c25be commit c5fb94f

File tree

10 files changed

+112
-22
lines changed

10 files changed

+112
-22
lines changed

e2e/components/menu/menu-page.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ export class MenuPage {
5353

5454
expectMenuLocation(el: ElementFinder, {x, y}: {x: number, y: number}) {
5555
el.getLocation().then(loc => {
56-
expect(loc.x).toEqual(x);
57-
expect(loc.y).toEqual(y);
56+
expect(loc.x).toEqual(x, 'Expect the x-position to be equal');
57+
expect(loc.y).toEqual(y, 'Expect the y-position to be equal');
5858
});
5959
}
6060

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
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-webdriverjs": "^0.4.0",
5456
"conventional-changelog": "^1.1.0",
5557
"express": "^4.14.0",
5658
"firebase-tools": "^2.2.1",
@@ -79,7 +81,6 @@
7981
"minimist": "^1.2.0",
8082
"node-sass": "^3.4.2",
8183
"protractor": "^4.0.8",
82-
"protractor-accessibility-plugin": "0.1.1",
8384
"resolve-bin": "^0.4.0",
8485
"rollup": "^0.34.13",
8586
"run-sequence": "^1.2.2",

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

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
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]="['progress-bar']">Progress bar</a>
9-
<a md-list-item [routerLink]="['progress-circle']">Progress circle</a>
10-
<a md-list-item [routerLink]="['radio']">Radios</a>
11-
<a md-list-item [routerLink]="['slide-toggle']">Slide Toggle</a>
12-
<a md-list-item [routerLink]="['tabs']">Tabs</a>
1+
<button (click)="showLinks = !showLinks">Toggle Navigation Links</button>
132

14-
<router-outlet></router-outlet>
3+
<md-nav-list *ngIf="showLinks">
4+
<a md-list-item [routerLink]="['button']">Button</a>
5+
<a md-list-item [routerLink]="['checkbox']">Checkbox</a>
6+
<a md-list-item [routerLink]="['dialog']">Dialog</a>
7+
<a md-list-item [routerLink]="['grid-list']">Grid list</a>
8+
<a md-list-item [routerLink]="['icon']">Icon</a>
9+
<a md-list-item [routerLink]="['list']">List</a>
10+
<a md-list-item [routerLink]="['menu']">Menu</a>
11+
<a md-list-item [routerLink]="['progress-bar']">Progress bar</a>
12+
<a md-list-item [routerLink]="['progress-circle']">Progress circle</a>
13+
<a md-list-item [routerLink]="['radio']">Radios</a>
14+
<a md-list-item [routerLink]="['slide-toggle']">Slide Toggle</a>
15+
<a md-list-item [routerLink]="['tabs']">Tabs</a>
16+
</md-nav-list>
17+
18+
<main>
19+
<router-outlet role="main"></router-outlet>
20+
</main>

src/e2e-app/e2e-app/e2e-app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ export class Home {}
1212
selector: 'e2e-app',
1313
templateUrl: 'e2e-app.html',
1414
})
15-
export class E2EApp { }
15+
export class E2EApp {
16+
showLinks: boolean = false;
17+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<button [md-menu-trigger-for]="menu" id="trigger">TRIGGER</button>
66
<button [md-menu-trigger-for]="menu" id="trigger-two">TRIGGER 2</button>
77

8-
<md-menu #menu="mdMenu" class="custom">
8+
<md-menu #menu="mdMenu" y-position="below" class="custom">
99
<button md-menu-item (click)="selected='one'">One</button>
1010
<button md-menu-item (click)="selected='two'">Two</button>
1111
<button md-menu-item (click)="selected='three'" disabled>Three</button>
@@ -15,7 +15,7 @@
1515
<button [md-menu-trigger-for]="beforeMenu" id="before-t">
1616
BEFORE
1717
</button>
18-
<md-menu x-position="before" class="before" #beforeMenu="mdMenu">
18+
<md-menu x-position="before" y-position="below" class="before" #beforeMenu="mdMenu">
1919
<button md-menu-item>Item</button>
2020
</md-menu>
2121

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
@@ -34,6 +34,9 @@ const MD_FIT_MODE = 'fit';
3434
selector: 'md-grid-list, mat-grid-list',
3535
templateUrl: 'grid-list.html',
3636
styleUrls: ['grid-list.css'],
37+
host: {
38+
'role': 'list'
39+
},
3740
encapsulation: ViewEncapsulation.None,
3841
})
3942
export class MdGridList implements OnInit, AfterContentChecked {

test/protractor.conf.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,27 @@ 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+
// Runs the axe-core accessibility checks each time the e2e page changes and
24+
// Angular is ready.
25+
path: '../tools/axe-protractor/axe-protractor.js',
26+
27+
rules: [
28+
// Exclude md-menu elements because those are empty if not active.
29+
{ id: 'aria-required-children', selector: '*:not(md-menu)' },
30+
]
31+
}
32+
]
2133
};
2234

2335
if (process.env['TRAVIS']) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
/* List of pages which were already checked by axe-core and shouldn't run again */
11+
const checkedPages = [];
12+
13+
/**
14+
* Protractor plugin hook which always runs when Angular successfully bootstrapped.
15+
*/
16+
function onPageStable() {
17+
AxeBuilder(browser.driver)
18+
.configure(this.config || {})
19+
.analyze(results => handleResults(this, results));
20+
}
21+
22+
/**
23+
* Processes the axe-core results by reporting recognized violations
24+
* to Protractor and printing them out.
25+
* @param {!protractor.ProtractorPlugin} context
26+
* @param {!axe.AxeResults} results
27+
*/
28+
function handleResults(context, results) {
29+
30+
if (checkedPages.indexOf(results.url) === -1) {
31+
32+
checkedPages.push(results.url);
33+
34+
results.violations.forEach(violation => {
35+
36+
let specName = `${violation.help} (${results.url})`;
37+
let message = '\n' + buildMessage(violation);
38+
39+
context.addFailure(message, {specName});
40+
41+
});
42+
43+
}
44+
45+
}
46+
47+
exports.name = 'protractor-axe';
48+
exports.onPageStable = onPageStable;

tools/axe-protractor/build-message.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Builds a simple message of the violation results of axe-core by listing
3+
* each violation and the associated element selector in a new line.
4+
* @param {!axe.Violation} violation
5+
*/
6+
exports.buildMessage = violation => {
7+
8+
let selectors = violation.nodes.map(node => {
9+
return node.target.join(' ');
10+
});
11+
12+
return selectors.reduce((content, selector) => {
13+
return content + '- ' + selector + '\n';
14+
}, '');
15+
16+
};

0 commit comments

Comments
 (0)