diff --git a/e2e/components/menu/menu-page.ts b/e2e/components/menu/menu-page.ts
index 5470c483117..a89ce732757 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/package.json b/package.json
index e7ad7f2d5fe..9daa4e0f898 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 6345cffd70f..ae6cffdbbff 100644
--- a/src/e2e-app/e2e-app/e2e-app.html
+++ b/src/e2e-app/e2e-app/e2e-app.html
@@ -1,14 +1,20 @@
-Button
-Checkbox
-Dialog
-Grid list
-Icon
-List
-Menu
-Progress bar
-Progress circle
-Radios
-Slide Toggle
-Tabs
+
-
+
+ Button
+ Checkbox
+ Dialog
+ Grid list
+ Icon
+ List
+ Menu
+ Progress bar
+ Progress circle
+ Radios
+ Slide Toggle
+ Tabs
+
+
+
+
+
diff --git a/src/e2e-app/e2e-app/e2e-app.ts b/src/e2e-app/e2e-app/e2e-app.ts
index 95f768e69df..64fb2e4504a 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;
+}
diff --git a/src/e2e-app/menu/menu-e2e.html b/src/e2e-app/menu/menu-e2e.html
index 057517b9c8f..ad1c2f86c81 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 @@
-
+
diff --git a/src/e2e-app/radio/radio-e2e.html b/src/e2e-app/radio/radio-e2e.html
index 83d672df16c..d14f9cead51 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 94d3ae9ce36..2fb9208d162 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 5a9c2c63100..c613290a021 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 00000000000..ca18cc17c86
--- /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 00000000000..be07a9bb52d
--- /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