Skip to content

Commit

Permalink
perf(reporter): add option to limit result types to be processed (#568)
Browse files Browse the repository at this point in the history
* perf(reporter): add option to limit result types to be processed

Closes #512 #512

* docs(resultTypes): add documentation for resultTypes option, and fix a small doc error

Related to #512
  • Loading branch information
isner authored and WilcoFiers committed Oct 18, 2017
1 parent 305db3c commit 42b46d9
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 1 deletion.
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)
* [`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);
});
});
});
});
});

0 comments on commit 42b46d9

Please sign in to comment.