Skip to content

Commit

Permalink
misc: reorganize accessibility gatherer (#12076)
Browse files Browse the repository at this point in the history
  • Loading branch information
brendankenny authored Mar 2, 2021
1 parent 95ae481 commit c6d9fc0
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 490 deletions.
4 changes: 2 additions & 2 deletions lighthouse-core/audits/accessibility/axe-audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class AxeAudit extends Audit {
const isNotApplicable = notApplicables.find(result => result.id === this.meta.id);
if (isNotApplicable) {
return {
score: 1,
score: null,
notApplicable: true,
};
}
Expand Down Expand Up @@ -66,7 +66,7 @@ class AxeAudit extends Audit {
// Since there is no score impact from informative rules, display the rule as not applicable
if (isInformative && !rule) {
return {
score: 1,
score: null,
notApplicable: true,
};
}
Expand Down
88 changes: 43 additions & 45 deletions lighthouse-core/gather/gatherers/accessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const pageFunctions = require('../../lib/page-functions.js');
/* c8 ignore start */
async function runA11yChecks() {
/** @type {import('axe-core/axe')} */
// @ts-expect-error axe defined by axeLibSource
// @ts-expect-error - axe defined by axeLibSource
const axe = window.axe;
const application = `lighthouse-${Math.random()}`;
axe.configure({
Expand Down Expand Up @@ -65,47 +65,52 @@ async function runA11yChecks() {
// are relative to the top of the page
document.documentElement.scrollTop = 0;

/** @param {import('axe-core/axe').Result} result */
const augmentAxeNodes = result => {
result.helpUrl = result.helpUrl.replace(application, 'lighthouse');
if (axeResults.inapplicable.includes(result)) return;
return {
violations: axeResults.violations.map(createAxeRuleResultArtifact),
incomplete: axeResults.incomplete.map(createAxeRuleResultArtifact),
notApplicable: axeResults.inapplicable.map(result => ({id: result.id})),
version: axeResults.testEngine.version,
};
}

result.nodes.forEach(node => {
// @ts-expect-error - getNodeDetails put into scope via stringification
node.node = getNodeDetails(node.element);
// @ts-expect-error - avoid circular JSON concerns
node.element = node.any = node.all = node.none = node.html = undefined;
});
/**
* @param {import('axe-core/axe').Result} result
* @return {LH.Artifacts.AxeRuleResult}
*/
function createAxeRuleResultArtifact(result) {
// Simplify `nodes` and collect nodeDetails for each.
const nodes = result.nodes.map(node => {
const {target, failureSummary, element} = node;
// TODO: with `elementRef: true`, `element` _should_ always be defined, but need to verify.
// @ts-expect-error - getNodeDetails put into scope via stringification
const nodeDetails = getNodeDetails(/** @type {HTMLElement} */ (element));

// Ensure errors can be serialized over the protocol
/** @type {(Error & {message: string, errorNode: any}) | undefined} */
// @ts-expect-error - when rules error axe sets these properties
// see https://github.com/dequelabs/axe-core/blob/eeff122c2de11dd690fbad0e50ba2fdb244b50e8/lib/core/base/audit.js#L684-L693
const error = result.error;
if (error instanceof Error) {
// @ts-expect-error
result.error = {
name: error.name,
message: error.message,
stack: error.stack,
errorNode: error.errorNode,
};
}
};
return {
target,
failureSummary,
node: nodeDetails,
};
});

// Augment the node objects with outerHTML snippet & custom path string
axeResults.violations.forEach(augmentAxeNodes);
axeResults.incomplete.forEach(augmentAxeNodes);
axeResults.inapplicable.forEach(augmentAxeNodes);
// Ensure errors can be serialized over the protocol.
/** @type {Error | undefined} */
// @ts-expect-error - when rules throw an error, axe saves it here.
// see https://github.com/dequelabs/axe-core/blob/eeff122c2de11dd690fbad0e50ba2fdb244b50e8/lib/core/base/audit.js#L684-L693
const resultError = result.error;
let error;
if (resultError instanceof Error) {
error = {
name: resultError.name,
message: resultError.message,
};
}

// We only need violations, and circular references are possible outside of violations
return {
// @ts-expect-error value is augmented above.
violations: axeResults.violations,
notApplicable: axeResults.inapplicable,
// @ts-expect-error value is augmented above.
incomplete: axeResults.incomplete,
version: axeResults.testEngine.version,
id: result.id,
impact: result.impact || undefined,
tags: result.tags,
nodes,
error,
};
}
/* c8 ignore stop */
Expand All @@ -129,15 +134,8 @@ class Accessibility extends FRGatherer {
deps: [
axeLibSource,
pageFunctions.getNodeDetailsString,
createAxeRuleResultArtifact,
],
}).then(returnedValue => {
if (!returnedValue) {
throw new Error('No axe-core results returned');
}
if (!Array.isArray(returnedValue.violations)) {
throw new Error('Unable to parse axe results' + returnedValue);
}
return returnedValue;
});
}
}
Expand Down
17 changes: 4 additions & 13 deletions lighthouse-core/test/audits/accessibility/axe-audit-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,9 @@ describe('Accessibility: axe-audit', () => {
}
const artifacts = {
Accessibility: {
passes: [{
id: 'fake-axe-pass',
help: 'http://example.com/',
}],
violations: [],
notApplicable: [],
incomplete: [],
},
};

Expand Down Expand Up @@ -134,15 +133,7 @@ describe('Accessibility: axe-audit', () => {
}],
help: 'http://example.com/',
}],
// TODO: remove: axe-core v3.3.0 backwards-compatibility test
violations: [{
id: 'fake-axe-failure-case',
nodes: [{
html: '<input id="multi-label-form-element" />',
node: {},
}],
help: 'http://example.com/',
}],
violations: [],
},
};

Expand Down
28 changes: 0 additions & 28 deletions lighthouse-core/test/gather/gatherers/accessibility-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,6 @@ describe('Accessibility gatherer', () => {
accessibilityGather = new AccessibilityGather();
});

it('fails if nothing is returned', () => {
return accessibilityGather.afterPass({
driver: {
executionContext: {
async evaluate() {},
},
},
}).then(
_ => assert.ok(false),
_ => assert.ok(true));
});

it('fails if result has no violations array', () => {
return accessibilityGather.afterPass({
driver: {
executionContext: {
async evaluate() {
return {
url: 'https://example.com',
};
},
},
},
}).then(
_ => assert.ok(false),
_ => assert.ok(true));
});

it('propagates error retrieving the results', () => {
const error = 'There was an error.';
return accessibilityGather.afterPass({
Expand Down
Loading

0 comments on commit c6d9fc0

Please sign in to comment.