Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Split from main protractor project
Browse files Browse the repository at this point in the history
  • Loading branch information
sjelin committed Nov 6, 2015
0 parents commit 2389e92
Show file tree
Hide file tree
Showing 22 changed files with 1,086 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
node_modules/
npm-debug.log
6 changes: 6 additions & 0 deletions .npmignore
@@ -0,0 +1,6 @@
testapp/
spec/
test.js
test_util.js
.npmignore
npm-debug.log
22 changes: 22 additions & 0 deletions LICENSE
@@ -0,0 +1,22 @@
The MIT License

Copyright (c) 2010-2015 Google, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

64 changes: 64 additions & 0 deletions README.md
@@ -0,0 +1,64 @@
Accessibility Plugin
====================

Protractor comes with support for two accessibility testing options:
* Accessibility Developer Tools
* Tenon.io

Protractor will run each set of audits (depending on your configuration) on your existing end-to-end
tests to ensure your site is free of obvious errors. In this kind of testing, there is no concept of
"warnings"–only pass or fail. In your configuration, you can decide whether warnings should
pass or fail your build.

To understand how each of these tools can be used, see this support matrix:

| Testing Library | Pricing | API Key | External Request | No. of Tests | Info |
|--------------------------------------|-------------------------------------------|---------|------------------|--------------|-------------------------------------------------------------------------|
| Chrome Accessibility Developer Tools | Free | No | No | 14 | [Github](https://github.com/GoogleChrome/accessibility-developer-tools) |
| Tenon.io | Free limited accounts, paid subscriptions | Yes | Yes | 63 | [Tenon.io](http://tenon.io/) |

Protractor now supports the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools), the same audit library used by the [Chrome browser extension](https://chrome.google.com/webstore/detail/accessibility-developer-t/fpkknkljclfencbdbgkenhalefipecmb?hl=en). Protractor
[runs an audit](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules)
locally by injecting the Dev Tools script into WebDriver pages, and it can diagnose issues including
missing labels, incorrect ARIA attributes and color contrast. This is a great starting point if
you can't send source code over the wire through an API.

[Tenon.io](http://www.tenon.io) has a more robust set of tests to help you find
accessibility issues, but it requires [registering](http://tenon.io/register.php) for an API key
and making an external request for each test, which may not work for everyone. Some people use
Tenon with introspection services like ngrok or localtunnel to securely
test local web servers. Protractor takes the [options you provide](http://tenon.io/documentation/understanding-request-parameters.php) in the plugin configuration and sends them
with the page source to the Tenon API. One limitation of this approach is that all scripts must be reachable from the page source as a string, for example, by using a CDN.
For projects with an MIT license, Tenon is free but with a limited
daily API limit. Paid subscriptions are available for enterprise and commercial projects.

Enable this plugin in your config file:
```js
// Chrome Accessibility Dev Tools only:
exports.config = {
...
plugins: [{
chromeA11YDevTools: {
treatWarningsAsFailures: true
},
path: 'node_modules/protractor/plugins/accessibility'
}]
}
```
```js
// Tenon.io only:
exports.config = {
...
plugins: [{
tenonIO: {
options: {
// See http://tenon.io/documentation/understanding-request-parameters.php
// options.src will be added by the test.
},
printAll: false, // whether the plugin should log API response
},
chromeA11YDevTools: true,
path: 'node_modules/protractor/plugins/accessibility'
}]
}
```
232 changes: 232 additions & 0 deletions index.js
@@ -0,0 +1,232 @@
var q = require('q'),
fs = require('fs'),
path = require('path'),
_ = require('lodash');
request = require('request'),
Entities = require('html-entities').XmlEntities;

/**
* You can audit your website against the Chrome Accessibility Developer Tools,
* Tenon.io, or both by enabling this plugin in your config file:
*
* // Chrome Accessibility Developer Tools:
* exports.config = {
* ...
* plugins: [{
* chromeA11YDevTools: {
* treatWarningsAsFailures: true
* },
* path: 'node_modules/protractor.plugins/accessiblity'
* }]
* }
*
* // Tenon.io:
*
* // Read about the Tenon.io settings and API requirements:
* // -http://tenon.io/documentation/overview.php
*
* exports.config = {
* ...
* plugins: [{
* tenonIO: {
* options: {
* // See http://tenon.io/documentation/understanding-request-parameters.php
* // options.src will be added by the test.
* },
* printAll: false, // whether the plugin should log API response
* },
* chromeA11YDevTools: false,
* path: 'node_modules/protractor/plugins/accessiblity'
* }]
* }
*
*/

var AUDIT_FILE = require.resolve('accessibility-developer-tools/dist/js/axs_testing.js');
var TENON_URL = 'http://www.tenon.io/api/';

/**
* Checks the information returned by the accessibility audit(s) and
* displays passed/failed results as console output.
*
* @this {Object} The plugin context object
* @return {q.Promise} A promise which resolves when all audits are finished
* @public
*/
function teardown() {

var audits = [];

if (this.config.chromeA11YDevTools) {
audits.push(runChromeDevTools(this));
}
// check for Tenon config and an actual API key, not the placeholder
if (this.config.tenonIO && /[A-Za-z][0-9]/.test(
this.config.tenonIO.options.key)) {
audits.push(runTenonIO(this));
}
return q.all(audits);
}

var entities = new Entities();

/**
* Audits page source against the Tenon API, if configured. Requires an API key:
* more information about licensing and configuration available at
* http://tenon.io/documentation/overview.php.
*
* @param {Object} context The plugin context object
* @return {q.Promise} A promise which resolves when the audit is finished
* @private
*/
function runTenonIO(context) {

return browser.driver.getPageSource().then(function(source) {

var options = _.assign(context.config.tenonIO.options, {src: source});

// setup response as a deferred promise
var deferred = q.defer();
request.post({
url: TENON_URL,
form: options
},
function(err, httpResponse, body) {
if (err) { return resolve.reject(new Error(err)); }
else { return deferred.resolve(JSON.parse(body)); }
});

return deferred.promise.then(function(response) {
return processTenonResults(response);
});
});

function processTenonResults(response) {

var testHeader = 'Tenon.io - ';

if (!response.resultSet) {
if (response.code === 'daily_limit_reached') {
console.log(testHeader, 'Daily limit reached');
console.log(response.moreInfo);
}
else {
console.log('Tenon.io error');
}
return;
}

var numResults = response.resultSet.length;

if (numResults === 0) {
context.addSuccess();
return;
}

if (context.config.tenonIO.printAll) {
console.log('\x1b[32m', testHeader + 'API response', '\x1b[39m');
console.log(response);
}

return response.resultSet.forEach(function(result) {
var ref = (result.ref === null) ? '' : result.ref;

context.addFailure(result.errorDescription + '\n\n' +
'\t\t' +entities.decode(result.errorSnippet) +
'\n\n\t\t' + ref, {specName: testHeader + result.errorTitle});
});
}
}

/**
* Audits page source against the Chrome Accessibility Developer Tools, if configured.
*
* @param {Object} context The plugin context object
* @return {q.Promise} A promise which resolves when the audit is finished
* @private
*/
function runChromeDevTools(context) {

var data = fs.readFileSync(AUDIT_FILE, 'utf-8');
data = data + ' return axs.Audit.run();';

var elementPromises = [],
elementStringLength = 200;

function trimText(text) {
if (text.length > elementStringLength) {
return text.substring(0, elementStringLength / 2) + ' ... '
+ text.substring(text.length - elementStringLength / 2);
} else {
return text;
}
}

var testHeader = 'Chrome A11Y - ';

return browser.executeScript_(data, 'a11y developer tool rules').then(function(results) {

var audit = results.map(function(result) {
var DOMElements = result.elements;
if (DOMElements !== undefined) {

DOMElements.forEach(function(elem) {
// get elements from WebDriver, add to promises array
elementPromises.push(
elem.getOuterHtml().then(function(text) {
return {
code: result.rule.code,
list: trimText(text)
};
},
function(reason){
return {
code: result.rule.code,
list: reason
};
})
);
});
result.elementCount = DOMElements.length;
}
return result;
});

// Wait for element names to be fetched
return q.all(elementPromises).then(function(elementFailures) {

return audit.forEach(function(result, index) {
if (result.result === 'FAIL') {
var label = result.elementCount === 1 ? ' element ' : ' elements ';
if (result.rule.severity !== 'Warning'
|| context.config.chromeA11YDevTools.treatWarningsAsFailures) {
result.warning = false;
} else {
result.warning = true;
result.rule.heading = '\x1b[33m(WARNING) '
+ result.rule.heading + ' (' + result.elementCount
+ label + 'failed)';
}
result.output = '\n\t\t' + result.elementCount + label + 'failed:';

// match elements returned via promises
// by their failure codes
elementFailures.forEach(function(element, index) {
if (element.code === result.rule.code) {
result.output += '\n\t\t' + elementFailures[index].list;
}
});
result.output += '\n\n\t\t' + result.rule.url;
(result.warning ? context.addWarning : context.addFailure)(
result.output, {specName: testHeader + result.rule.heading});
}
else {
context.addSuccess({specName: testHeader + result.rule.heading});
}
});
});
});
}

// Export
exports.teardown = teardown;
41 changes: 41 additions & 0 deletions package.json
@@ -0,0 +1,41 @@
{
"name": "protractor-accessibility-plugin",
"version": "0.1.0",
"description": "Runs a set of accessibility audits",
"main": "index.js",
"scripts": {
"start": "./node_modules/httpster/bin/httpster -d testapp/ -p `node -p 'process.env.HTTP_PORT || require(\"./spec/environment\").webServerDefaultPort'`",
"test": "node test.js"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/protractor-accessibility-plugin"
},
"keywords": [
"angular",
"test",
"testing",
"webdriver",
"webdriverjs",
"selenium",
"protractor",
"protractor-plugin"
],
"author": "Marcy Sutton (marcy@substantial.com)",
"license": "MIT",
"bugs": {
"url": "https://github.com/angular/protractor-accessibility-plugin/issues"
},
"homepage": "https://github.com/angular/protractor-accessibility-plugin",
"dependencies": {
"accessibility-developer-tools": "^2.9.0-rc.0",
"html-entities": "^1.2.0",
"lodash": "^3.10.1",
"q": "^1.4.1",
"request": "^2.65.0"
},
"devDependencies": {
"httpster": "^1.0.1",
"protractor": "^2.5.1"
}
}

0 comments on commit 2389e92

Please sign in to comment.