mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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.
This commit is contained in:
committed by
Jeremy Elbourn
parent
66c25be710
commit
c5fb94f2a4
@@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
<a md-list-item [routerLink]="['button']">Button</a>
|
||||
<a md-list-item [routerLink]="['checkbox']">Checkbox</a>
|
||||
<a md-list-item [routerLink]="['dialog']">Dialog</a>
|
||||
<a md-list-item [routerLink]="['grid-list']">Grid list</a>
|
||||
<a md-list-item [routerLink]="['icon']">Icon</a>
|
||||
<a md-list-item [routerLink]="['list']">List</a>
|
||||
<a md-list-item [routerLink]="['menu']">Menu</a>
|
||||
<a md-list-item [routerLink]="['progress-bar']">Progress bar</a>
|
||||
<a md-list-item [routerLink]="['progress-circle']">Progress circle</a>
|
||||
<a md-list-item [routerLink]="['radio']">Radios</a>
|
||||
<a md-list-item [routerLink]="['slide-toggle']">Slide Toggle</a>
|
||||
<a md-list-item [routerLink]="['tabs']">Tabs</a>
|
||||
<button (click)="showLinks = !showLinks">Toggle Navigation Links</button>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
<md-nav-list *ngIf="showLinks">
|
||||
<a md-list-item [routerLink]="['button']">Button</a>
|
||||
<a md-list-item [routerLink]="['checkbox']">Checkbox</a>
|
||||
<a md-list-item [routerLink]="['dialog']">Dialog</a>
|
||||
<a md-list-item [routerLink]="['grid-list']">Grid list</a>
|
||||
<a md-list-item [routerLink]="['icon']">Icon</a>
|
||||
<a md-list-item [routerLink]="['list']">List</a>
|
||||
<a md-list-item [routerLink]="['menu']">Menu</a>
|
||||
<a md-list-item [routerLink]="['progress-bar']">Progress bar</a>
|
||||
<a md-list-item [routerLink]="['progress-circle']">Progress circle</a>
|
||||
<a md-list-item [routerLink]="['radio']">Radios</a>
|
||||
<a md-list-item [routerLink]="['slide-toggle']">Slide Toggle</a>
|
||||
<a md-list-item [routerLink]="['tabs']">Tabs</a>
|
||||
</md-nav-list>
|
||||
|
||||
<main>
|
||||
<router-outlet role="main"></router-outlet>
|
||||
</main>
|
||||
|
||||
@@ -12,4 +12,6 @@ export class Home {}
|
||||
selector: 'e2e-app',
|
||||
templateUrl: 'e2e-app.html',
|
||||
})
|
||||
export class E2EApp { }
|
||||
export class E2EApp {
|
||||
showLinks: boolean = false;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<button [md-menu-trigger-for]="menu" id="trigger">TRIGGER</button>
|
||||
<button [md-menu-trigger-for]="menu" id="trigger-two">TRIGGER 2</button>
|
||||
|
||||
<md-menu #menu="mdMenu" class="custom">
|
||||
<md-menu #menu="mdMenu" y-position="below" class="custom">
|
||||
<button md-menu-item (click)="selected='one'">One</button>
|
||||
<button md-menu-item (click)="selected='two'">Two</button>
|
||||
<button md-menu-item (click)="selected='three'" disabled>Three</button>
|
||||
@@ -15,7 +15,7 @@
|
||||
<button [md-menu-trigger-for]="beforeMenu" id="before-t">
|
||||
BEFORE
|
||||
</button>
|
||||
<md-menu x-position="before" class="before" #beforeMenu="mdMenu">
|
||||
<md-menu x-position="before" y-position="below" class="before" #beforeMenu="mdMenu">
|
||||
<button md-menu-item>Item</button>
|
||||
</md-menu>
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<section>
|
||||
<md-radio-group [disabled]="isGroupDisabled"
|
||||
[(value)]="groupValue"
|
||||
id="test-group">
|
||||
id="test-group" aria-label="Select a Pokemon">
|
||||
|
||||
<md-radio-button value="fire" id="fire">Charmander</md-radio-button>
|
||||
<md-radio-button value="water" id="water">Squirtle</md-radio-button>
|
||||
<md-radio-button value="leaf" id="leaf">Bulbasaur</md-radio-button>
|
||||
|
||||
</md-radio-group>
|
||||
<button (click)="isGroupDisabled=!isGroupDisabled" id="toggle-disable">Disable/enable group</button>
|
||||
</section>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+14
-2
@@ -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']) {
|
||||
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
}, '');
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user