diff --git a/packages/puppeteer/src/axePuppeteer.ts b/packages/puppeteer/src/axePuppeteer.ts index adb56b88..501945a6 100644 --- a/packages/puppeteer/src/axePuppeteer.ts +++ b/packages/puppeteer/src/axePuppeteer.ts @@ -1,6 +1,6 @@ import * as Axe from 'axe-core'; import { ElementHandle, Frame, JSONObject, Page } from 'puppeteer'; -import { pageIsLoaded, runAxe } from './browser'; +import { pageIsLoaded, runAxe, configureAxe } from './browser'; import { AnalyzeCB } from './types'; function arrayify(src: T | T[]): T[] { @@ -11,27 +11,28 @@ function arrayify(src: T | T[]): T[] { } interface IInjectAxeArgs { - source?: string; + source?: string | Function; selector: string; logOnError?: boolean; + args?: any[]; } -function injectAxeModule(frame: Frame): Promise { +function injectJSModule(frame: Frame): Promise { return frame.addScriptTag({ path: require.resolve('axe-core') }); } -function injectAxeString(frame: Frame, source: string): Promise { - return frame.evaluate(source); +function injectJSSource(frame: Frame, source: string | Function, args: any[] = []): Promise { + return frame.evaluate(source as any, ...args); } -async function injectAxe(frame: Frame, {source, selector, logOnError}: IInjectAxeArgs): Promise { +async function injectJS(frame: Frame, {source, selector, logOnError, args}: IInjectAxeArgs): Promise { const frames = await frame.$$(selector); const injections = []; for (const frameElement of frames) { const subFrame = await frameElement.contentFrame(); - const p = injectAxe(subFrame as Frame, { source, selector, logOnError: true}); + const p = injectJS(subFrame as Frame, { source, selector, args, logOnError: true}); injections.push(p); } @@ -42,9 +43,9 @@ async function injectAxe(frame: Frame, {source, selector, logOnError}: IInjectAx let injectP: Promise; if (!source) { - injectP = injectAxeModule(frame); + injectP = injectJSModule(frame); } else { - injectP = injectAxeString(frame, source); + injectP = injectJSSource(frame, source, args); } if (logOnError) { @@ -217,12 +218,13 @@ export class AxePuppeteer { try { await ensureFrameReady(this.frame); - await injectAxe(this.frame, { source: this.source, selector: this.iframeSelector()}); + await injectJS(this.frame, { source: this.source, selector: this.iframeSelector()}); + + await injectJS(this.frame, { source: configureAxe, selector: this.iframeSelector(), args: [this.config] }); const context = normalizeContext(this.includes, this.excludes); const axeResults = await this.frame.evaluate( runAxe, - this.config as JSONObject, context as JSONObject, this.axeOptions as JSONObject ); diff --git a/packages/puppeteer/src/browser.ts b/packages/puppeteer/src/browser.ts index 2c4736fc..1c003d54 100644 --- a/packages/puppeteer/src/browser.ts +++ b/packages/puppeteer/src/browser.ts @@ -13,26 +13,25 @@ declare global { // Defined at top-level to clarify that it can't capture variables from outer scope. export function runAxe( - config?: Axe.Spec, context?: Axe.ElementContext, options?: Axe.RunOptions ): Promise { - if (config) { - window.axe.configure(config); - } - - // This prevents axe from running in iframes. - // TODO: Uncomment when that is fixed in axe-core https://github.com/dequelabs/axe-core/issues/2340 - // const brandingConfig = { - // branding: { - // application: 'axe-puppeteer' - // } - // }; - // window.axe.configure(brandingConfig); - return window.axe.run(context || document, options || {}); } export function pageIsLoaded(): boolean { return document.readyState === 'complete'; } + +export function configureAxe(config?: Axe.Spec) { + if (config) { + window.axe.configure(config); + } + + const brandingConfig = { + branding: { + application: 'axe-puppeteer' + } + }; + window.axe.configure(brandingConfig); +} diff --git a/packages/puppeteer/test/fixtures/frames/bar.html b/packages/puppeteer/test/fixtures/frames/bar.html index 3b816e7b..b199bb80 100644 --- a/packages/puppeteer/test/fixtures/frames/bar.html +++ b/packages/puppeteer/test/fixtures/frames/bar.html @@ -1,5 +1,5 @@ - + Bar diff --git a/packages/puppeteer/test/fixtures/frames/baz.html b/packages/puppeteer/test/fixtures/frames/baz.html index b5f94320..1742ccd7 100644 --- a/packages/puppeteer/test/fixtures/frames/baz.html +++ b/packages/puppeteer/test/fixtures/frames/baz.html @@ -1,5 +1,5 @@ - + Baz diff --git a/packages/puppeteer/test/fixtures/frames/foo.html b/packages/puppeteer/test/fixtures/frames/foo.html index 3c89b56b..0bb2811d 100644 --- a/packages/puppeteer/test/fixtures/frames/foo.html +++ b/packages/puppeteer/test/fixtures/frames/foo.html @@ -1,5 +1,5 @@ - + Foo diff --git a/packages/puppeteer/test/fixtures/nested-frames.html b/packages/puppeteer/test/fixtures/nested-frames.html index 4a9b43d6..28d8470d 100644 --- a/packages/puppeteer/test/fixtures/nested-frames.html +++ b/packages/puppeteer/test/fixtures/nested-frames.html @@ -1,5 +1,5 @@ - + Nested Frames diff --git a/packages/puppeteer/test/integration/doc-dylang.test.ts b/packages/puppeteer/test/integration/doc-dylang.test.ts index cfe8b6cc..dafa759d 100644 --- a/packages/puppeteer/test/integration/doc-dylang.test.ts +++ b/packages/puppeteer/test/integration/doc-dylang.test.ts @@ -3,8 +3,13 @@ import { expect } from 'chai'; import Puppeteer from 'puppeteer'; +import * as path from 'path'; import AxePuppeteer from '../../src/index'; +import Axe from 'axe-core'; import { customConfig, fixtureFilePath } from '../utils'; +import express from 'express'; +import { createServer } from 'http'; +import testListen from 'test-listen'; describe('doc-dylang.html', function () { before(async function () { @@ -19,6 +24,20 @@ describe('doc-dylang.html', function () { after(async function () { await this.browser.close(); }); + before(async function () { + // const app: express.Application = express() + const app: express.Application = express(); + app.use(express.static(path.resolve(__dirname, '..', 'fixtures'))); + this.server = createServer(app); + this.addr = await testListen(this.server); + + this.fixtureFileURL = (filename: string): string => { + return `${this.addr}/${filename}`; + }; + }); + after(function () { + this.server.close(); + }); beforeEach(async function () { this.page = await this.browser.newPage(); }); @@ -26,13 +45,10 @@ describe('doc-dylang.html', function () { await this.page.close(); }); - // Fails since we can't set branding due to a bug. - // TODO: Run once we fix setting branding https://github.com/dequelabs/axe-core/issues/2340 - it.skip('should find violations with customized helpUrl', async function () { - const file = fixtureFilePath('doc-dylang.html'); + it('should find violations with customized helpUrl', async function () { const config = await customConfig(); - await this.page.goto(`file://${file}`); + await this.page.goto(this.fixtureFileURL('doc-dylang.html')); const results = await new AxePuppeteer(this.page) .configure(config) @@ -46,4 +62,21 @@ describe('doc-dylang.html', function () { ).to.not.eql(-1); expect(results.passes).to.have.lengthOf(0); }); + + + it('configures in nested frames', async function() { + await this.page.goto(this.fixtureFileURL('nested-frames.html')) + + const results = await new AxePuppeteer(this.page) + .configure(await customConfig()) + .withRules(['dylang']) + .analyze() + + expect(results.violations.find((r: Axe.Result) => r.id === 'dylang')) + .to.not.be.undefined + expect(results.violations.find((r: Axe.Result) => r.id === 'dylang')) + .to.have.property('nodes') + .and.to.have.lengthOf(4) + }) + }); diff --git a/packages/puppeteer/test/usage.test.ts b/packages/puppeteer/test/usage.test.ts index d1e24b5d..e98f4003 100644 --- a/packages/puppeteer/test/usage.test.ts +++ b/packages/puppeteer/test/usage.test.ts @@ -8,6 +8,7 @@ import Puppeteer from 'puppeteer'; import * as sinon from 'sinon'; import testListen from 'test-listen'; import AxePuppeteer, { loadPage } from '../src/index'; +import { customConfig } from './utils'; type SinonSpy = sinon.SinonSpy; type Frame = Puppeteer.Frame; @@ -169,20 +170,12 @@ describe('AxePuppeteer', function () { const results = await new AxePuppeteer(this.page) .analyze() - const flatResults = [ - ...results.passes, - ...results.incomplete, - ...results.inapplicable, - ...results.violations - ] - expect(results.violations.find((r: Axe.Result) => r.id === 'label')) .to.not.be.undefined }) - - it('injects custom axe source into nexted frames', async function () { + it('injects custom axe source into nested frames', async function () { const axeSource = ` window.axe = { run: () => Promise.resolve({}),