diff --git a/lighthouse-core/gather/gatherers/accessibility.js b/lighthouse-core/gather/gatherers/accessibility.js index 178ca46b763b..ae75a17650bd 100644 --- a/lighthouse-core/gather/gatherers/accessibility.js +++ b/lighthouse-core/gather/gatherers/accessibility.js @@ -12,9 +12,6 @@ const axeLibSource = require('../../lib/axe.js').source; const pageFunctions = require('../../lib/page-functions.js'); /** - * This is run in the page, not Lighthouse itself. - * axe.run returns a promise which fulfills with a results object - * containing any violations. * @return {Promise} */ /* c8 ignore start */ @@ -38,8 +35,11 @@ async function runA11yChecks() { 'wcag2aa', ], }, + // resultTypes doesn't limit the output of the axeResults object. Instead, if it's defined, + // some expensive element identification is done only for the respective types. https://github.com/dequelabs/axe-core/blob/f62f0cf18f7b69b247b0b6362cf1ae71ffbf3a1b/lib/core/reporters/helpers/process-aggregate.js#L61-L97 resultTypes: ['violations', 'inapplicable'], rules: { + // Consider http://go/prcpg for expert review of the aXe rules. 'tabindex': {enabled: true}, 'accesskeys': {enabled: true}, 'heading-order': {enabled: true}, @@ -60,6 +60,12 @@ async function runA11yChecks() { // https://github.com/dequelabs/axe-core/issues/2958 'nested-interactive': {enabled: false}, 'frame-focusable-content': {enabled: false}, + 'aria-roledescription': {enabled: false}, + 'scrollable-region-focusable': {enabled: false}, + // TODO(paulirish): create audits and enable these 3. + 'input-button-name': {enabled: false}, + 'role-img-alt': {enabled: false}, + 'select-name': {enabled: false}, }, }); @@ -70,7 +76,8 @@ async function runA11yChecks() { return { violations: axeResults.violations.map(createAxeRuleResultArtifact), incomplete: axeResults.incomplete.map(createAxeRuleResultArtifact), - notApplicable: axeResults.inapplicable.map(result => ({id: result.id})), + notApplicable: axeResults.inapplicable.map(result => ({id: result.id})), // FYI: inapplicable => notApplicable! + passes: axeResults.passes.map(result => ({id: result.id})), version: axeResults.testEngine.version, }; } @@ -150,6 +157,11 @@ class Accessibility extends FRGatherer { supportedModes: ['snapshot', 'navigation'], }; + static pageFns = { + runA11yChecks, + createAxeRuleResultArtifact, + }; + /** * @param {LH.Gatherer.FRTransitionalContext} passContext * @return {Promise} diff --git a/lighthouse-core/test/gather/gatherers/accessibility-test.js b/lighthouse-core/test/gather/gatherers/accessibility-test.js index b7b1d2a282da..2405933d5716 100644 --- a/lighthouse-core/test/gather/gatherers/accessibility-test.js +++ b/lighthouse-core/test/gather/gatherers/accessibility-test.js @@ -9,9 +9,10 @@ const AccessibilityGather = require('../../../gather/gatherers/accessibility.js'); const assert = require('assert').strict; -let accessibilityGather; +const {LH_ROOT} = require('../../../../root.js'); describe('Accessibility gatherer', () => { + let accessibilityGather; // Reset the Gatherer before each test. beforeEach(() => { accessibilityGather = new AccessibilityGather(); @@ -32,3 +33,44 @@ describe('Accessibility gatherer', () => { err => assert.ok(err.message.includes(error))); }); }); + +describe('a11y audits + aXe', () => { + let browser; + const axeLibSource = require('../../../lib/axe.js').source; + const pageFunctions = require('../../../lib/page-functions.js'); + const fs = require('fs'); + + beforeAll(async () => { + browser = await require('puppeteer').launch(); + }); + + afterAll(async () => { + await browser.close(); + }); + + it('only runs the axe rules we have audits defined for', async () => { + const page = await browser.newPage(); + page.setContent(`hivalid.`); + await page.evaluate(axeLibSource); + await page.evaluate(pageFunctions.getNodeDetailsString); + await page.evaluate(AccessibilityGather.pageFns.runA11yChecks.toString()); + await page.evaluate(AccessibilityGather.pageFns.createAxeRuleResultArtifact.toString()); + + // 1. Run axe in the browser. + const a11yArtifact = await page.evaluate(`runA11yChecks()`); + // 2. Get list of the axe rules that ran. + const axeRuleIds = new Set(); + for (const key of ['violations', 'incomplete', 'notApplicable', 'passes']) { + if (a11yArtifact[key]) a11yArtifact[key].forEach(result => axeRuleIds.add(result.id)); + } + + // 3. Get audit list we have implementations for. + // Note: audit ids match their filenames, thx to the getAuditList test in runner-test.js + const filenames = fs.readdirSync(`${LH_ROOT}/lighthouse-core/audits/accessibility/`) + .map(f => f.replace('.js', '')) + .filter(f => f !== 'axe-audit' && f !== 'manual'); + + // 4. Compare. (Received from aXe, Expected is LH audits) + expect(Array.from(axeRuleIds).sort()).toEqual(filenames.sort()); + }); +}); diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts index 2e54af829cde..28adc1d6c5c3 100644 --- a/types/artifacts.d.ts +++ b/types/artifacts.d.ts @@ -238,6 +238,7 @@ declare module Artifacts { interface Accessibility { violations: Array; notApplicable: Array>; + passes: Array>; incomplete: Array; version: string; }