UNPKG

lighthouse

Version:

Automated auditing, performance metrics, and best practices for the web.

114 lines (100 loc) 4.01 kB
/** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Base class for all aXe audits. Provides a consistent way to * generate audit results using aXe rule names. */ import {Audit} from '../audit.js'; import * as i18n from '../../lib/i18n/i18n.js'; const UIStrings = { /** Label of a table column that identifies HTML elements that have failed an audit. */ failingElementsHeader: 'Failing Elements', }; const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings); class AxeAudit extends Audit { /** * Base class for audit rules which reflect assessment performed by the aXe accessibility library * See https://github.com/dequelabs/axe-core/blob/6b444546cff492a62a70a74a8fc3c62bd4729400/doc/API.md#results-object for result type and format details * * @param {LH.Artifacts} artifacts Accessibility gatherer artifacts. Note that AxeAudit * expects the meta name for the class to match the rule id from aXe. * @return {LH.Audit.Product} */ static audit(artifacts) { // Indicate if a test is not applicable. // This means aXe did not find any nodes which matched these checks. // Note in Lighthouse we use the phrasing "Not Applicable" (aXe uses "inapplicable", which sounds weird). const notApplicables = artifacts.Accessibility.notApplicable || []; const isNotApplicable = notApplicables.find(result => result.id === this.meta.id); if (isNotApplicable) { return { score: null, notApplicable: true, }; } // Detect errors reported within aXe 'incomplete' results // aXe uses this result type to indicate errors, or rules which require manual investigation // If aXe reports an error, then bubble it up to the caller const incomplete = artifacts.Accessibility.incomplete || []; const incompleteResult = incomplete.find(result => result.id === this.meta.id); if (incompleteResult?.error) { return { score: null, errorMessage: `axe-core Error: ${incompleteResult.error.message || 'Unknown error'}`, }; } const isInformative = this.meta.scoreDisplayMode === Audit.SCORING_MODES.INFORMATIVE; const violations = artifacts.Accessibility.violations || []; const failureCases = isInformative ? violations.concat(incomplete) : violations; const rule = failureCases.find(result => result.id === this.meta.id); const impact = rule?.impact; const tags = rule?.tags; // Handle absence of aXe failure results for informative rules as 'not applicable' // This scenario indicates that no action is required by the web property owner // Since there is no score impact from informative rules, display the rule as not applicable if (isInformative && !rule) { return { score: null, notApplicable: true, }; } /** @type {LH.Audit.Details.Table['items']}>} */ let items = []; if (rule?.nodes) { items = rule.nodes.map(axeNode => ({ node: { ...Audit.makeNodeItem(axeNode.node), explanation: axeNode.failureSummary, }, subItems: axeNode.relatedNodes.length ? { type: 'subitems', items: axeNode.relatedNodes.map(node => ({relatedNode: Audit.makeNodeItem(node)})), } : undefined, })); } /** @type {LH.Audit.Details.Table['headings']} */ const headings = [ /* eslint-disable max-len */ {key: 'node', valueType: 'node', subItemsHeading: {key: 'relatedNode', valueType: 'node'}, label: str_(UIStrings.failingElementsHeader)}, /* eslint-enable max-len */ ]; /** @type {LH.Audit.Details.DebugData|undefined} */ let debugData; if (impact || tags) { debugData = { type: 'debugdata', impact, tags, }; } return { score: Number(rule === undefined), details: {...Audit.makeTableDetails(headings, items), debugData}, }; } } export default AxeAudit; export {UIStrings};