diff --git a/lighthouse-core/audits/accessibility/axe-audit.js b/lighthouse-core/audits/accessibility/axe-audit.js index d053031b9201..2cac0ab1a7d1 100644 --- a/lighthouse-core/audits/accessibility/axe-audit.js +++ b/lighthouse-core/audits/accessibility/axe-audit.js @@ -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, }; } @@ -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, }; } diff --git a/lighthouse-core/gather/gatherers/accessibility.js b/lighthouse-core/gather/gatherers/accessibility.js index 9b1a7ceb239d..9996707bae02 100644 --- a/lighthouse-core/gather/gatherers/accessibility.js +++ b/lighthouse-core/gather/gatherers/accessibility.js @@ -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({ @@ -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 */ @@ -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; }); } } diff --git a/lighthouse-core/test/audits/accessibility/axe-audit-test.js b/lighthouse-core/test/audits/accessibility/axe-audit-test.js index 7364d98662ee..35d68d3120ab 100644 --- a/lighthouse-core/test/audits/accessibility/axe-audit-test.js +++ b/lighthouse-core/test/audits/accessibility/axe-audit-test.js @@ -102,10 +102,9 @@ describe('Accessibility: axe-audit', () => { } const artifacts = { Accessibility: { - passes: [{ - id: 'fake-axe-pass', - help: 'http://example.com/', - }], + violations: [], + notApplicable: [], + incomplete: [], }, }; @@ -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: '', - node: {}, - }], - help: 'http://example.com/', - }], + violations: [], }, }; diff --git a/lighthouse-core/test/gather/gatherers/accessibility-test.js b/lighthouse-core/test/gather/gatherers/accessibility-test.js index 05edeebba86e..b7b1d2a282da 100644 --- a/lighthouse-core/test/gather/gatherers/accessibility-test.js +++ b/lighthouse-core/test/gather/gatherers/accessibility-test.js @@ -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({ diff --git a/lighthouse-core/test/results/artifacts/artifacts.json b/lighthouse-core/test/results/artifacts/artifacts.json index 22ddc0adce4f..751cfd552381 100644 --- a/lighthouse-core/test/results/artifacts/artifacts.json +++ b/lighthouse-core/test/results/artifacts/artifacts.json @@ -1587,12 +1587,8 @@ "wcag311", "ACT" ], - "description": "Ensures every HTML document has a lang attribute", - "help": " element must have a lang attribute", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/html-has-lang?application=lighthouse", "nodes": [ { - "impact": "serious", "target": [ "html" ], @@ -1626,12 +1622,8 @@ "section508.22.a", "ACT" ], - "description": "Ensures elements have alternate text or a role of none or presentation", - "help": "Images must have alternate text", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/image-alt?application=lighthouse", "nodes": [ { - "impact": "critical", "target": [ "img[src=\"lighthouse-480x318\\.jpg\\?iar1\"]" ], @@ -1653,7 +1645,6 @@ } }, { - "impact": "critical", "target": [ "img[src=\"lighthouse-480x318\\.jpg\\?iar2\"]" ], @@ -1675,7 +1666,6 @@ } }, { - "impact": "critical", "target": [ "img[src=\"lighthouse-480x318\\.jpg\\?isr1\"]" ], @@ -1697,7 +1687,6 @@ } }, { - "impact": "critical", "target": [ "img[src=\"lighthouse-480x318\\.jpg\\?isr2\"]" ], @@ -1719,7 +1708,6 @@ } }, { - "impact": "critical", "target": [ "img[src=\"lighthouse-480x318\\.jpg\\?isr3\"]" ], @@ -1741,7 +1729,6 @@ } }, { - "impact": "critical", "target": [ "img[src=\"lighthouse-480x318\\.jpg\\?isr4\"]" ], @@ -1763,7 +1750,6 @@ } }, { - "impact": "critical", "target": [ "img[src$=\"lighthouse-rotating\\.gif\"]" ], @@ -1785,7 +1771,6 @@ } }, { - "impact": "critical", "target": [ "img:nth-child(37)" ], @@ -1807,7 +1792,6 @@ } }, { - "impact": "critical", "target": [ "img:nth-child(43)" ], @@ -1842,12 +1826,8 @@ "section508.22.n", "ACT" ], - "description": "Ensures every form element has a label", - "help": "Form elements must have labels", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/label?application=lighthouse", "nodes": [ { - "impact": "critical", "target": [ "input[onpaste=\"event\\.preventDefault\\(\\)\\;\"]" ], @@ -1869,7 +1849,6 @@ } }, { - "impact": "critical", "target": [ "input:nth-child(35)" ], @@ -1891,7 +1870,6 @@ } }, { - "impact": "critical", "target": [ "input[onpaste=\"return\\ false\\;\"]" ], @@ -1926,12 +1904,8 @@ "section508.22.a", "ACT" ], - "description": "Ensures links have discernible text", - "help": "Links must have discernible text", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/link-name?application=lighthouse", "nodes": [ { - "impact": "serious", "target": [ "a[href=\"javascript\\:void\\(0\\)\"]" ], @@ -1953,7 +1927,6 @@ } }, { - "impact": "serious", "target": [ "a[href$=\"mailto\\:inbox\\@email\\.com\"]" ], @@ -1986,12 +1959,8 @@ "section508", "section508.22.a" ], - "description": "Ensures elements have alternate text", - "help": " elements must have alternate text", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/object-alt?application=lighthouse", "nodes": [ { - "impact": "serious", "target": [ "#\\35 934a" ], @@ -2013,7 +1982,6 @@ } }, { - "impact": "serious", "target": [ "#\\35 934b" ], @@ -2037,433 +2005,124 @@ ] } ], - "notApplicable": [ + "incomplete": [ { - "id": "accesskeys", - "impact": null, + "id": "color-contrast", + "impact": "serious", "tags": [ - "cat.keyboard", - "best-practice" + "cat.color", + "wcag2aa", + "wcag143" ], - "description": "Ensures every accesskey attribute value is unique", - "help": "accesskey attribute value must be unique", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/accesskeys?application=lighthouse", - "nodes": [] - }, + "nodes": [ + { + "target": [ + "a[rel=\"noopener\"]" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it partially overlaps other elements", + "node": { + "lhId": "5-5-A", + "devtoolsNodePath": "3,HTML,1,BODY,46,A", + "selector": "body > a", + "boundingRect": { + "top": 1220, + "bottom": 1256, + "left": 8, + "right": 348, + "width": 340, + "height": 36 + }, + "snippet": "", + "nodeLabel": "external link that uses rel noopener" + } + } + ] + } + ], + "notApplicable": [ { - "id": "aria-command-name", - "impact": null, - "tags": [ - "cat.aria", - "wcag2a", - "wcag412" - ], - "description": "Ensures every ARIA button, link and menuitem has an accessible name", - "help": "ARIA commands must have an accessible name", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-command-name?application=lighthouse", - "nodes": [] + "id": "accesskeys" }, { - "id": "aria-hidden-focus", - "impact": null, - "tags": [ - "cat.name-role-value", - "wcag2a", - "wcag412", - "wcag131" - ], - "description": "Ensures aria-hidden elements do not contain focusable elements", - "help": "ARIA hidden element must not contain focusable elements", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-hidden-focus?application=lighthouse", - "nodes": [] + "id": "aria-command-name" }, { - "id": "aria-input-field-name", - "impact": null, - "tags": [ - "cat.aria", - "wcag2a", - "wcag412", - "ACT" - ], - "description": "Ensures every ARIA input field has an accessible name", - "help": "ARIA input fields must have an accessible name", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-input-field-name?application=lighthouse", - "nodes": [] + "id": "aria-hidden-focus" }, { - "id": "aria-meter-name", - "impact": null, - "tags": [ - "cat.aria", - "wcag2a", - "wcag111" - ], - "description": "Ensures every ARIA meter node has an accessible name", - "help": "ARIA meter nodes must have an accessible name", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-meter-name?application=lighthouse", - "nodes": [] + "id": "aria-input-field-name" }, { - "id": "aria-progressbar-name", - "impact": null, - "tags": [ - "cat.aria", - "wcag2a", - "wcag111" - ], - "description": "Ensures every ARIA progressbar node has an accessible name", - "help": "ARIA progressbar nodes must have an accessible name", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-progressbar-name?application=lighthouse", - "nodes": [] + "id": "aria-meter-name" }, { - "id": "aria-roledescription", - "impact": null, - "tags": [ - "cat.aria", - "wcag2a", - "wcag412" - ], - "description": "Ensure aria-roledescription is only used on elements with an implicit or explicit role", - "help": "Use aria-roledescription on elements with a semantic role", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-roledescription?application=lighthouse", - "nodes": [] + "id": "aria-progressbar-name" }, { - "id": "aria-toggle-field-name", - "impact": null, - "tags": [ - "cat.aria", - "wcag2a", - "wcag412", - "ACT" - ], - "description": "Ensures every ARIA toggle field has an accessible name", - "help": "ARIA toggle fields have an accessible name", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-toggle-field-name?application=lighthouse", - "nodes": [] + "id": "aria-roledescription" }, { - "id": "aria-tooltip-name", - "impact": null, - "tags": [ - "cat.aria", - "wcag2a", - "wcag412" - ], - "description": "Ensures every ARIA tooltip node has an accessible name", - "help": "ARIA tooltip nodes must have an accessible name", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-tooltip-name?application=lighthouse", - "nodes": [] + "id": "aria-toggle-field-name" }, { - "id": "aria-treeitem-name", - "impact": null, - "tags": [ - "cat.aria", - "best-practice" - ], - "description": "Ensures every ARIA treeitem node has an accessible name", - "help": "ARIA treeitem nodes must have an accessible name", - "helpUrl": "https://dequeuniversity.com/rules/axe/4.1/aria-treeitem-name?application=lighthouse", - "nodes": [] + "id": "aria-tooltip-name" }, { - "id": "definition-list", - "impact": null, - "tags": [ - "cat.structure", - "wcag2a", - "wcag131" - ], - "description": "Ensures
elements are structured correctly", - "help": "
elements must only directly contain properly-ordered
and
groups,