Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(reporter): add option to limit result types to be processed #568

Merged
merged 2 commits into from
Oct 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ axe.run(context, options, callback);
#### Parameters axe.run

* [`context`](#context-parameter): (optional) Defines the scope of the analysis - the part of the DOM that you would like to analyze. This will typically be the `document` or a specific selector such as class name, ID, selector, etc.
* [`options`](#options-parameter): (optional) Set of options passed into rules or checks, temporarily modifying them. This contrasts with `axe.configure`, which is more permanent. [See below for more information](#axerun-parameters)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation for axe.configure is above, not below, and the internal link is invalid. I figured we ought to simply remove this.

* [`options`](#options-parameter): (optional) Set of options passed into rules or checks, temporarily modifying them. This contrasts with `axe.configure`, which is more permanent.
* [`callback`](#callback-parameter): (optional) The callback function which receives either null or an [error result](#error-result) as the first parameter, and the [results object](#results-object) when analysis is completed successfully, or undefined if it did not.

##### Context Parameter
Expand Down Expand Up @@ -331,6 +331,7 @@ Additionally, there are a number or properties that allow configuration of diffe
| `runOnly` | n/a | Limit which rules are executed, based on names or tags
| `rules` | n/a | Allow customizing a rule's properties (including { enable: false })
| `reporter` | `v1` | Which reporter to use (see [Configuration](#api-name-axeconfigure))
| `resultTypes` | n/a | Limit which result types are processed and aggregated
| `xpath` | `false` | Return xpath selectors for elements
| `absolutePaths` | `false` | Use absolute paths when creating element selectors
| `iframes` | `true` | Tell axe to run inside iframes
Expand Down Expand Up @@ -437,6 +438,16 @@ Additionally, there are a number or properties that allow configuration of diffe

This example first includes all `wcag2a` and `wcag2aa` rules. All rules that are tagged as `experimental` are than removed from the list of rules to run.

6. Only process certain types of results

The `resultTypes` option can be used to limit the result types that aXe will process, aggregate, and send to the reporter. This can be useful for improving performance on very large or complicated pages when you are only interested in certain types of results.
```javascript
{
resultTypes: ['violations', 'incomplete', 'inapplicable']
}
```
This example will only process the specified result types: "violations", "incomplete", and "inapplicable". Notably, it will not process "passes". On a series of extremely large pages, this could improve performance considerably.

##### Callback Parameter

The callback parameter is a function that will be called when the asynchronous `axe.run` function completes. The callback function is passed two parameters. The first parameter will be an error thrown inside of aXe if axe.run could not complete. If axe completed correctly the first parameter will be null, and the second parameter will be the results object.
Expand Down
28 changes: 28 additions & 0 deletions lib/core/reporters/helpers/process-aggregate.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,41 @@ function normalizeRelatedNodes(node, options) {
}

var resultKeys = axe.constants.resultGroups;

/**
* Configures the processing of axe results.
*
* @typedef ProcessOptions
* @property {Array} resultsTypes limit the types of results to process ('passes', 'violations', etc.)
* @property {Boolean} elementRef display node's element references
* @property {Boolean} selectors display node's selectors
* @property {Boolean} xpath display node's xpaths
*/

/**
* Aggregrate and process the aXe results,
* adding desired data to nodes and relatedNodes in each rule result.
*
* Prepares result data for reporters.
*
* @method processAggregate
* @memberof helpers
* @param {Array} results
* @param {ProcessOptions} options
* @return {Object}
*
*/
helpers.processAggregate = function (results, options) {
var resultObject = axe.utils.aggregateResult(results);

resultObject.timestamp = new Date().toISOString();
resultObject.url = window.location.href;

resultKeys.forEach(function (key) {
if (options.resultTypes && !options.resultTypes.includes(key)) {
delete resultObject[key];
return;
}
resultObject[key] = (resultObject[key] || []).map(function (ruleResult) {
ruleResult = Object.assign({}, ruleResult);

Expand Down
3 changes: 3 additions & 0 deletions lib/core/reporters/no-passes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ axe.addReporter('no-passes', function (results, options, callback) {
callback = options;
options = {};
}
// limit result processing to types we want to include in the output
options.resultTypes = ['violations'];

var out = helpers.processAggregate(results, options);

callback({
Expand Down
246 changes: 246 additions & 0 deletions test/core/reporters/helpers/process-aggregate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@

describe('helpers.processAggregate', function () {
'use strict';
var results, options;

beforeEach(function () {
results = [{
id: 'passed-rule',
passes: [{
result: 'passed',
node: {
element: document.createElement('div'),
selector: 'header > .thing',
source: '<div class=\"thing\">Thing</div>',
xpath: '/header/div[@class="thing"]'
},
any: [{
id: 'passed-rule',
relatedNodes: [{
element: document.createElement('div'),
selector: 'footer > .thing',
source: '<div class=\"thing\">Thing</div>',
xpath: '/footer/div[@class="thing"]',
}]
}],
all: [],
none: []
}],
inapplicable: [],
incomplete: [],
violations: []
}, {
id: 'failed-rule',
violations: [{
result: 'failed',
node: {
selector: '#dopel',
source: '<input id=\"dopel\"/>',
xpath: '/main/input[@id="dopel"]',
fromFrame: true
},
any: [{
id: 'failed-rule',
relatedNodes: [{
element: document.createElement('input'),
selector: '#dopel',
source: '<input id=\"dopel\"/>',
xpath: '/main/input[@id="dopel"]',
fromFrame: true
}]
}],
all: [],
none: []
}],
inapplicable: [],
passes: [],
incomplete: []
}];
});

it('should add a `timestamp` property to the `resultObject`', function () {
var resultObject = helpers.processAggregate(results, {});
assert.isDefined(resultObject.timestamp);
});

it('should add a `url` property to the `resultObject`', function () {
var resultObject = helpers.processAggregate(results, {});
assert.isDefined(resultObject.url);
});

it('should remove the `result` property from each node in each ruleResult', function () {
assert.isDefined(results.find(function (r) {
return r.id === 'passed-rule';
}).passes[0].result);

var resultObject = helpers.processAggregate(results, {});
var ruleResult = resultObject.passes.find(function (r) {
return r.id === 'passed-rule';
});
assert.isUndefined(ruleResult.nodes[0].result);
});

it('should remove the `node` property from each node in each ruleResult', function () {
assert.isDefined(results.find(function (r) {
return r.id === 'passed-rule';
}).passes[0].node);

var resultObject = helpers.processAggregate(results, {});
var ruleResult = resultObject.passes.find(function (r) {
return r.id === 'passed-rule';
});
assert.isUndefined(ruleResult.nodes[0].node);
});

describe('`options` argument', function () {

describe('`resultTypes` option', function () {

it('should remove non-specified result types from the `resultObject`', function () {
var resultObject = helpers.processAggregate(results, { resultTypes: ['passes', 'violations'] });
assert.isDefined(resultObject.passes);
assert.isDefined(resultObject.violations);
assert.isUndefined(resultObject.incomplete);
assert.isUndefined(resultObject.inapplicable);
});
});

describe('`elementRef` option', function () {

describe('when set to true', function () {

before(function () {
options = { elementRef: true };
});

describe('when node\'s, or relatedNode\'s, `fromFrame` equals false', function () {
it('should add an `element` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isDefined(resultObject.passes[0].nodes[0].element);
assert.isDefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].element);
});
});

describe('when node\'s, or relatedNode\'s, `fromFrame` equals true', function () {
it('should NOT add an `element` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isUndefined(resultObject.violations[0].nodes[0].element);
assert.isUndefined(resultObject.violations[0].nodes[0].any[0].relatedNodes[0].element);
});
});
});

describe('when set to false', function () {

before(function () {
options = { elementRef: false };
});

it('should NOT add an `element` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isUndefined(resultObject.passes[0].nodes[0].element);
assert.isUndefined(resultObject.violations[0].nodes[0].element);
assert.isUndefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].element);
assert.isUndefined(resultObject.violations[0].nodes[0].any[0].relatedNodes[0].element);
});
});

describe('when not set at all', function () {

it('should NOT add an `element` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, {});
assert.isUndefined(resultObject.passes[0].nodes[0].element);
assert.isUndefined(resultObject.violations[0].nodes[0].element);
assert.isUndefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].element);
assert.isUndefined(resultObject.violations[0].nodes[0].any[0].relatedNodes[0].element);
});
});
});

describe('`selectors` option', function () {

describe('when set to false', function () {

before(function () {
options = { selectors: false };
});

describe('when node\'s, or relatedNode\'s, `fromFrame` equals true', function () {
it('should add a `target` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isDefined(resultObject.violations[0].nodes[0].target);
assert.isDefined(resultObject.violations[0].nodes[0].any[0].relatedNodes[0].target);
});
});

describe('when node\'s, or relatedNode\'s, `fromFrame` equals false', function () {
it('should NOT add a `target` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isUndefined(resultObject.passes[0].nodes[0].target);
assert.isUndefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].target);
});
});
});

describe('when set to true', function () {

before(function () {
options = { selectors: true };
});

it('should add a `target` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isDefined(resultObject.passes[0].nodes[0].target);
assert.isDefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].target);
});
});

describe('when not set at all', function () {

it('should add a `target` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, {});
assert.isDefined(resultObject.passes[0].nodes[0].target);
assert.isDefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].target);
});
});
});

describe('`xpath` option', function () {

describe('when set to true', function () {

before(function () {
options = { xpath: true };
});

it('should add an `xpath` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isDefined(resultObject.passes[0].nodes[0].xpath);
assert.isDefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].xpath);
});
});

describe('when set to false', function () {

before(function () {
options = { xpath: false };
});

it('should NOT add an `xpath` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, options);
assert.isUndefined(resultObject.passes[0].nodes[0].xpath);
assert.isUndefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].xpath);
});
});

describe('when not set at all', function () {

it('should NOT add an `xpath` property to the subResult nodes or relatedNodes', function () {
var resultObject = helpers.processAggregate(results, {});
assert.isUndefined(resultObject.passes[0].nodes[0].xpath);
assert.isUndefined(resultObject.passes[0].nodes[0].any[0].relatedNodes[0].xpath);
});
});
});
});
});