diff --git a/packages/playwright/README.md b/packages/playwright/README.md index bd9796f8..1f7ee46c 100644 --- a/packages/playwright/README.md +++ b/packages/playwright/README.md @@ -68,6 +68,21 @@ Constructor for the AxeBuilder helper. You must pass an instance of Playwright a const builder = new AxeBuilder({ page }); ``` +### AxeBuilder#analyze(): Promise + +Performs analysis and passes any encountered error and/or the result object. + +```js +new AxeBuilder({ page }) + .analyze() + .then(results => { + console.log(results); + }) + .catch(e => { + // Do something with error + }); +``` + ### AxeBuilder#include(selector: String) Adds a CSS selector to the list of elements to include in analysis @@ -124,17 +139,16 @@ Skips verification of the rules provided. Accepts a String of a single rule ID o new AxeBuilder({ page }).disableRules('color-contrast'); ``` -### AxeBuilder#analyze(): Promise +### AxeBuilder#setLegacyMode(legacyMode: boolean = true) -Performs analysis and passes any encountered error and/or the result object. +Set the frame testing method to "legacy mode". In this mode, axe will not open a blank page in which to aggregate its results. This can be used in an environment where opening a blank page is causes issues. + +With legacy mode turned on, axe will fall back to its test solution prior to the 4.3 release, but with cross-origin frame testing disabled. The `frame-tested` rule will report which frames were untested. + +**Important** Use of `.setLegacyMode()` is a last resort. If you find there is no other solution, please [report this as an issue](https://github.com/dequelabs/axe-core-npm/issues/). ```js -new AxeBuilder({ page }) - .analyze() - .then(results => { - console.log(results); - }) - .catch(e => { - // Do something with error - }); +const axe = new AxeBuilder({ page }).setLegacyMode(); +const result = await axe.analyze(); +axe.setLegacyMode(false); // Disables legacy mode ``` diff --git a/packages/playwright/package-lock.json b/packages/playwright/package-lock.json index 7b7a2121..3d875243 100644 --- a/packages/playwright/package-lock.json +++ b/packages/playwright/package-lock.json @@ -638,7 +638,7 @@ "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==" }, "axe-test-fixtures": { - "version": "github:dequelabs/axe-test-fixtures#09e7b5562e5a2972c889ed31ae7633c745bc0bc5", + "version": "github:dequelabs/axe-test-fixtures#df007093b42d3dcdd803b1a94565053d5383871a", "from": "github:dequelabs/axe-test-fixtures#v1", "dev": true }, diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index 33d92d81..64cebaae 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -22,6 +22,8 @@ export default class AxeBuilder { private excludes: string[]; private option: RunOptions; private source: string; + private legacyMode = false; + constructor({ page, axeSource }: AxePlaywrightParams) { const axePath = require.resolve('axe-core'); const source = fs.readFileSync(axePath, 'utf-8'); @@ -122,6 +124,18 @@ export default class AxeBuilder { return this; } + /** + * Use frameMessenger with + * + * This disables use of axe.runPartial() which is called in each frame, and + * axe.finishRun() which is called in a blank page. This uses axe.run() instead, + * but with the restriction that cross-origin frames will not be tested. + */ + public setLegacyMode(legacyMode = true): this { + this.legacyMode = legacyMode; + return this; + } + /** * Perform analysis and retrieve results. *Does not chain.* * @return Promise @@ -138,7 +152,7 @@ export default class AxeBuilder { let results: AxeResults; - if (!runPartialDefined) { + if (!runPartialDefined || this.legacyMode) { results = await this.runLegacy(context); return results; } @@ -169,12 +183,12 @@ export default class AxeBuilder { private script(): string { return ` - ${this.source} - axe.configure({ - allowedOrigins: [''], - branding: { application: 'playwright' } - }) - `; + ${this.source} + axe.configure({ + ${this.legacyMode ? '' : 'allowedOrigins: [""],'} + branding: { application: 'playwright' } + }) + `; } private async runLegacy(context: ContextObject): Promise { diff --git a/packages/playwright/tests/axe-playwright.spec.ts b/packages/playwright/tests/axe-playwright.spec.ts index 75384687..152af94e 100644 --- a/packages/playwright/tests/axe-playwright.spec.ts +++ b/packages/playwright/tests/axe-playwright.spec.ts @@ -397,6 +397,50 @@ describe('@axe-core/playwright', () => { }); }); + describe('setLegacyMode', () => { + const runPartialThrows = `;axe.runPartial = () => { throw new Error("No runPartial")}`; + it('runs legacy mode when used', async () => { + await page.goto(`${addr}/external/index.html`); + const results = await new AxeBuilder({ + page, + axeSource: axeSource + runPartialThrows + }) + .setLegacyMode() + .analyze(); + assert.isNotNull(results); + }); + + it('prevents cross-origin frame testing', async () => { + await page.goto(`${addr}/external/cross-origin.html`); + const results = await new AxeBuilder({ + page, + axeSource: axeSource + runPartialThrows + }) + .withRules('frame-tested') + .setLegacyMode() + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.ok(frameTested); + }); + + it('can be disabled again', async () => { + await page.goto(`${addr}/external/cross-origin.html`); + const results = await new AxeBuilder({ page }) + .withRules('frame-tested') + .setLegacyMode() + .setLegacyMode(false) + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); + }); + describe('for versions without axe.runPartial', () => { describe('analyze', () => { it('returns results', async () => { @@ -426,6 +470,21 @@ describe('@axe-core/playwright', () => { } assert.isNotNull(error); }); + + it('tests cross-origin pages', async () => { + await page.goto(`${addr}/external/cross-origin.html`); + const results = await new AxeBuilder({ + page, + axeSource: axeLegacySource + }) + .withRules('frame-tested') + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); }); describe('frame tests', () => { diff --git a/packages/puppeteer/README.md b/packages/puppeteer/README.md index a9e7913a..205f363d 100644 --- a/packages/puppeteer/README.md +++ b/packages/puppeteer/README.md @@ -87,6 +87,34 @@ const builder = new AxePuppeteer(page, axeSource); Note that you might need to bypass the Content Security Policy in some cases. +### AxePuppeteer#analyze([callback: (Error | null[, Object]) => void]) + +Performs analysis and passes any encountered error and/or the result object to the provided callback function or promise function. **Does not chain as the operation is asynchronous** + +Using the returned promise (optional): + +```js +new AxePuppeteer(page) + .analyze() + .then(function (results) { + console.log(results); + }) + .catch(err => { + // Handle error somehow + }); +``` + +Using a callback function + +```js +new AxePuppeteer(page).analyze(function (err, results) { + if (err) { + // Handle error somehow + } + console.log(results); +}); +``` + ### AxePuppeteer#include(selector: string | string[]) Adds a CSS selector to the list of elements to include in analysis @@ -184,30 +212,16 @@ const results = await new AxePuppeteer(page).configure(config).analyze(); console.log(results); ``` -### AxePuppeteer#analyze([callback: (Error | null[, Object]) => void]) - -Performs analysis and passes any encountered error and/or the result object to the provided callback function or promise function. **Does not chain as the operation is asynchronous** +### AxePuppeteer#setLegacyMode(legacyMode: boolean = true) -Using the returned promise (optional): +Set the frame testing method to "legacy mode". In this mode, axe will not open a blank page in which to aggregate its results. This can be used in an environment where opening a blank page is causes issues. -```js -new AxePuppeteer(page) - .analyze() - .then(function (results) { - console.log(results); - }) - .catch(err => { - // Handle error somehow - }); -``` +With legacy mode turned on, axe will fall back to its test solution prior to the 4.3 release, but with cross-origin frame testing disabled. The `frame-tested` rule will report which frames were untested. -Using a callback function +**Important** Use of `.setLegacyMode()` is a last resort. If you find there is no other solution, please [report this as an issue](https://github.com/dequelabs/axe-core-npm/issues/). ```js -new AxePuppeteer(page).analyze(function (err, results) { - if (err) { - // Handle error somehow - } - console.log(results); -}); +const axe = new AxePuppeteer(page).setLegacyMode(); +const result = await axe.analyze(); +axe.setLegacyMode(false); // Disables legacy mode ``` diff --git a/packages/puppeteer/package-lock.json b/packages/puppeteer/package-lock.json index 4df24c1f..ea4c245e 100644 --- a/packages/puppeteer/package-lock.json +++ b/packages/puppeteer/package-lock.json @@ -403,8 +403,8 @@ "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==" }, "axe-test-fixtures": { - "version": "git+ssh://git@github.com/dequelabs/axe-test-fixtures.git#f3613cc5a93a7af228e88c5b4a7c41f228a14b3a", - "from": "axe-test-fixtures@github:dequelabs/axe-test-fixtures#v1", + "version": "github:dequelabs/axe-test-fixtures#df007093b42d3dcdd803b1a94565053d5383871a", + "from": "github:dequelabs/axe-test-fixtures#v1", "dev": true }, "balanced-match": { diff --git a/packages/puppeteer/src/axePuppeteer.ts b/packages/puppeteer/src/axePuppeteer.ts index 8455eef6..10e15edd 100644 --- a/packages/puppeteer/src/axePuppeteer.ts +++ b/packages/puppeteer/src/axePuppeteer.ts @@ -28,6 +28,7 @@ export class AxePuppeteer { private config: Spec | null; private disabledFrameSelectors: string[]; private page: Page | undefined; + private legacyMode = false; constructor(pageFrame: Page | Frame, source?: string) { if ('mainFrame' in pageFrame) { @@ -130,6 +131,11 @@ export class AxePuppeteer { return this; } + public setLegacyMode(legacyMode = true): this { + this.legacyMode = legacyMode; + return this; + } + public async analyze(): Promise; public async analyze( callback?: T @@ -162,7 +168,11 @@ export class AxePuppeteer { await frameSourceInject(frame, axeSource, config); const runPartialSupported = await frame.evaluate(axeRunPartialSupport); - if (runPartialSupported !== true || this.page === undefined) { + if ( + runPartialSupported !== true || + this.page === undefined || + this.legacyMode + ) { return this.runLegacy(context); } const partialRunner = await this.runPartialRecursive(frame, context); @@ -226,14 +236,21 @@ export class AxePuppeteer { const options = this.axeOptions as JSONObject; const selector = iframeSelector(this.disabledFrameSelectors); const source = this.axeSource; - await injectJS(this.frame, { source, selector }); + let config = this.config; + if (!this.legacyMode) { + config = { + ...(config || {}), + allowedOrigins: [''] + }; + } + + await injectJS(this.frame, { source, selector }); await injectJS(this.frame, { source: axeConfigure, selector, - args: [this.config] + args: [config] }); - return this.frame.evaluate(axeRunLegacy, context, options); } } diff --git a/packages/puppeteer/src/browser.ts b/packages/puppeteer/src/browser.ts index a6642808..261ebb09 100644 --- a/packages/puppeteer/src/browser.ts +++ b/packages/puppeteer/src/browser.ts @@ -25,7 +25,6 @@ export function axeConfigure(config?: Axe.Spec): void { window.axe.configure(config); } window.axe.configure({ - allowedOrigins: [''], branding: { application: 'axe-puppeteer' } }); } diff --git a/packages/puppeteer/test/axePuppeteer.test.ts b/packages/puppeteer/test/axePuppeteer.test.ts index e0cf0a77..8696efad 100644 --- a/packages/puppeteer/test/axePuppeteer.test.ts +++ b/packages/puppeteer/test/axePuppeteer.test.ts @@ -694,6 +694,44 @@ describe('AxePuppeteer', function () { }); }); + describe('setLegacyMode', () => { + const runPartialThrows = `;axe.runPartial = () => { throw new Error("No runPartial")}`; + it('runs legacy mode when used', async () => { + await page.goto(`${addr}/external/index.html`); + const results = await new AxePuppeteer(page, axeSource + runPartialThrows) + .setLegacyMode() + .analyze(); + assert.isNotNull(results); + }); + + it('prevents cross-origin frame testing', async () => { + await page.goto(`${addr}/external/cross-origin.html`); + const results = await new AxePuppeteer(page, axeSource + runPartialThrows) + .withRules('frame-tested') + .setLegacyMode() + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.ok(frameTested); + }); + + it('can be disabled again', async () => { + await page.goto(`${addr}/external/cross-origin.html`); + const results = await new AxePuppeteer(page) + .withRules('frame-tested') + .setLegacyMode() + .setLegacyMode(false) + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); + }); + describe('without runPartial', () => { let axe403Source: string; before(() => { @@ -739,5 +777,17 @@ describe('AxePuppeteer', function () { assert.equal(results.violations[0].id, 'label'); assert.lengthOf(results.violations[0].nodes, 2); }); + + it('tests cross-origin pages', async () => { + await page.goto(`${addr}/external/cross-origin.html`); + const results = await new AxePuppeteer(page, axe403Source) + .withRules('frame-tested') + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); }); }); diff --git a/packages/webdriverio/README.md b/packages/webdriverio/README.md index de467636..498debdc 100644 --- a/packages/webdriverio/README.md +++ b/packages/webdriverio/README.md @@ -52,6 +52,30 @@ const { remote } = require('webdriverio'); })(); ``` +### AxeBuilder#analyze(): Promise + +Performs analysis and passes any encountered error and/or the result object. + +```js +new AxeBuilder({ client }).analyze((err, results) => { + if (err) { + // Do something with error + } + console.log(results); +}); +``` + +```js +new AxeBuilder({ client }) + .analyze() + .then(results => { + console.log(results); + }) + .catch(e => { + // Do something with error + }); +``` + ## AxeBuilder({ client: WebdriverIO.BrowserObject }) Constructor for the AxeBuilder helper. You must pass an instance of WebdriverIO as the first argument. @@ -116,26 +140,16 @@ Skips verification of the rules provided. Accepts a String of a single rule ID o new AxeBuilder({ client }).disableRules('color-contrast'); ``` -### AxeBuilder#analyze(): Promise +### AxeBuilder#setLegacyMode(legacyMode: boolean = true) -Performs analysis and passes any encountered error and/or the result object. +Set the frame testing method to "legacy mode". In this mode, axe will not open a blank page in which to aggregate its results. This can be used in an environment where opening a blank page is causes issues. -```js -new AxeBuilder({ client }).analyze((err, results) => { - if (err) { - // Do something with error - } - console.log(results); -}); -``` +With legacy mode turned on, axe will fall back to its test solution prior to the 4.3 release, but with cross-origin frame testing disabled. The `frame-tested` rule will report which frames were untested. + +**Important** Use of `.setLegacyMode()` is a last resort. If you find there is no other solution, please [report this as an issue](https://github.com/dequelabs/axe-core-npm/issues/). ```js -new AxeBuilder({ client }) - .analyze() - .then(results => { - console.log(results); - }) - .catch(e => { - // Do something with error - }); +const axe = new AxeBuilder({ client }).setLegacyMode(); +const result = await axe.analyze(); +axe.setLegacyMode(false); // Disables legacy mode ``` diff --git a/packages/webdriverio/package-lock.json b/packages/webdriverio/package-lock.json index 1ff0d91f..7f1e637b 100644 --- a/packages/webdriverio/package-lock.json +++ b/packages/webdriverio/package-lock.json @@ -889,7 +889,7 @@ "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==" }, "axe-test-fixtures": { - "version": "github:dequelabs/axe-test-fixtures#e5cfe71f8ba0b23b7e7398aeeade1591d4adea7d", + "version": "github:dequelabs/axe-test-fixtures#df007093b42d3dcdd803b1a94565053d5383871a", "from": "github:dequelabs/axe-test-fixtures#v1", "dev": true }, diff --git a/packages/webdriverio/src/index.ts b/packages/webdriverio/src/index.ts index 00275b77..a9111a29 100644 --- a/packages/webdriverio/src/index.ts +++ b/packages/webdriverio/src/index.ts @@ -28,6 +28,8 @@ export default class AxeBuilder { private excludes: string[]; private option: RunOptions; private disableFrameSelectors: string[]; + private legacyMode = false; + constructor({ client, axeSource }: Options) { assert( isWebdriverClient(client), @@ -146,6 +148,18 @@ export default class AxeBuilder { return this; } + /** + * Use frameMessenger with + * + * This disables use of axe.runPartial() which is called in each frame, and + * axe.finishRun() which is called in a blank page. This uses axe.run() instead, + * but with the restriction that cross-origin frames will not be tested. + */ + public setLegacyMode(legacyMode = true): AxeBuilder { + this.legacyMode = legacyMode; + return this; + } + /** * Performs an analysis and retrieves results. * @param {CallbackFunction} callback @@ -179,7 +193,7 @@ export default class AxeBuilder { return ` ${this.axeSource} axe.configure({ - allowedOrigins: [''], + ${this.legacyMode ? '' : `allowedOrigins: [''],`} branding: { application: 'webdriverio' } }) `; @@ -234,7 +248,7 @@ export default class AxeBuilder { axeSource }); - if (!runPartialSupported) { + if (!runPartialSupported || this.legacyMode) { return await this.runLegacy(context); } const partials = await this.runPartialRecursive(context); diff --git a/packages/webdriverio/src/test.ts b/packages/webdriverio/src/test.ts index 8301a4ce..e4fa9ee3 100644 --- a/packages/webdriverio/src/test.ts +++ b/packages/webdriverio/src/test.ts @@ -87,9 +87,6 @@ describe('@axe-core/webdriverio', () => { ) { binaryPath = `C:/Program Files/Google/Chrome/Application/chrome.exe`; } - // Only run headless on CI. This makes it a bit easier to debug - // tests (because we can inspect the browser's devtools). - const chromeArgs = isCI ? ['--headless', '--no-sandbox'] : []; const options: webdriverio.RemoteOptions = { port, @@ -98,7 +95,7 @@ describe('@axe-core/webdriverio', () => { capabilities: { browserName: 'chrome', 'goog:chromeOptions': { - args: chromeArgs, + args: ['headless'], binary: binaryPath } }, @@ -148,6 +145,21 @@ describe('@axe-core/webdriverio', () => { } assert.isNotNull(error); }); + + it('tests cross-origin pages', async () => { + await client.url(`${addr}/cross-origin.html`); + const results = await new AxeBuilder({ + client, + axeSource: axeLegacySource + }) + .withRules(['frame-tested']) + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); }); describe('disableFrames', () => { @@ -738,6 +750,50 @@ describe('@axe-core/webdriverio', () => { }); }); + describe('setLegacyMode', () => { + const runPartialThrows = `;axe.runPartial = () => { throw new Error("No runPartial")}`; + it('runs legacy mode when used', async () => { + await client.url(`${addr}/index.html`); + const results = await new AxeBuilder({ + client, + axeSource: axeSource + runPartialThrows + }) + .setLegacyMode() + .analyze(); + assert.isNotNull(results); + }); + + it('prevents cross-origin frame testing', async () => { + await client.url(`${addr}/cross-origin.html`); + const results = await new AxeBuilder({ + client, + axeSource: axeSource + runPartialThrows + }) + .withRules('frame-tested') + .setLegacyMode() + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.ok(frameTested); + }); + + it('can be disabled again', async () => { + await client.url(`${addr}/cross-origin.html`); + const results = await new AxeBuilder({ client }) + .withRules('frame-tested') + .setLegacyMode() + .setLegacyMode(false) + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); + }); + describe('callback()', () => { it('returns results when callback is provided', async () => { await client.url(`${addr}/index.html`); @@ -790,9 +846,6 @@ describe('@axe-core/webdriverio', () => { ) { binaryPath = `C:/Program Files/Google/Chrome/Application/chrome.exe`; } - // Only run headless on CI. This makes it a bit easier to debug - // tests (because we can inspect the browser's devtools). - const chromeArgs = isCI ? ['--headless', '--no-sandbox'] : []; const options: webdriverio.RemoteOptions = { port, @@ -801,7 +854,7 @@ describe('@axe-core/webdriverio', () => { capabilities: { browserName: 'chrome', 'goog:chromeOptions': { - args: chromeArgs, + args: ['headless'], binary: binaryPath } }, diff --git a/packages/webdriverio/src/utils.ts b/packages/webdriverio/src/utils.ts index 32b3f430..5ee01a78 100644 --- a/packages/webdriverio/src/utils.ts +++ b/packages/webdriverio/src/utils.ts @@ -1,9 +1,4 @@ -import type { - AxeResults, - ElementContext, - PartialResult, - ContextObject -} from 'axe-core'; +import type { AxeResults, PartialResult, ContextObject } from 'axe-core'; import type { BrowserObject, AxeRunPartialParams, diff --git a/packages/webdriverjs/README.md b/packages/webdriverjs/README.md index ae01ed31..70c613b2 100644 --- a/packages/webdriverjs/README.md +++ b/packages/webdriverjs/README.md @@ -51,6 +51,30 @@ const axeSource = fs.readFileSync('./axe-1.0.js', 'utf-8'); const builder = new AxeBuilder(driver, axeSource); ``` +### AxeBuilder#analyze(): Promise + +Performs analysis and passes any encountered error and/or the result object. + +```js +new AxeBuilder(driver).analyze((err, results) => { + if (err) { + // Do something with error + } + console.log(results); +}); +``` + +```js +new AxeBuilder(driver) + .analyze() + .then(results => { + console.log(results); + }) + .catch(e => { + // Do something with error + }); +``` + ### AxeBuilder#include(selector: String) Adds a CSS selector to the list of elements to include in analysis @@ -125,26 +149,16 @@ new AxeBuilder(driver).configure(config).analyze((err, results) => { }) ``` -### AxeBuilder#analyze(): Promise +### AxeBuilder#setLegacyMode(legacyMode: boolean = true) -Performs analysis and passes any encountered error and/or the result object. +Set the frame testing method to "legacy mode". In this mode, axe will not open a blank page in which to aggregate its results. This can be used in an environment where opening a blank page is causes issues. -```js -new AxeBuilder(driver).analyze((err, results) => { - if (err) { - // Do something with error - } - console.log(results); -}); -``` +With legacy mode turned on, axe will fall back to its test solution prior to the 4.3 release, but with cross-origin frame testing disabled. The `frame-tested` rule will report which frames were untested. + +**Important** Use of `.setLegacyMode()` is a last resort. If you find there is no other solution, please [report this as an issue](https://github.com/dequelabs/axe-core-npm/issues/). ```js -new AxeBuilder(driver) - .analyze() - .then(results => { - console.log(results); - }) - .catch(e => { - // Do something with error - }); +const axe = new AxeBuilder(driver).setLegacyMode(); +const result = await axe.analyze(); +axe.setLegacyMode(false); // Disables legacy mode ``` diff --git a/packages/webdriverjs/package-lock.json b/packages/webdriverjs/package-lock.json index 1d936ed5..1e87846d 100644 --- a/packages/webdriverjs/package-lock.json +++ b/packages/webdriverjs/package-lock.json @@ -805,7 +805,7 @@ "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==" }, "axe-test-fixtures": { - "version": "github:dequelabs/axe-test-fixtures#c7efae59617cd308aeb597f6313389ba231ddd58", + "version": "github:dequelabs/axe-test-fixtures#df007093b42d3dcdd803b1a94565053d5383871a", "from": "github:dequelabs/axe-test-fixtures#v1", "dev": true }, diff --git a/packages/webdriverjs/src/axe-injector.ts b/packages/webdriverjs/src/axe-injector.ts index ff7db5ce..0a64e249 100644 --- a/packages/webdriverjs/src/axe-injector.ts +++ b/packages/webdriverjs/src/axe-injector.ts @@ -71,8 +71,7 @@ export default class AxeInjectorLegacy { return ` ${this.axeSource} ${this.config ? `axe.configure(${this.config})` : ''} - axe.configure({ - allowedOrigins: [''], + axe.configure({ branding: { application: 'webdriverjs' } }) `; diff --git a/packages/webdriverjs/src/index.ts b/packages/webdriverjs/src/index.ts index 688b7591..76c6ae16 100644 --- a/packages/webdriverjs/src/index.ts +++ b/packages/webdriverjs/src/index.ts @@ -21,6 +21,7 @@ class AxeBuilder { private option: RunOptions; private config: Spec | null; private builderOptions: BuilderOptions; + private legacyMode = false; constructor( driver: WebDriver, @@ -136,6 +137,18 @@ class AxeBuilder { }); } + /** + * Use frameMessenger with + * + * This disables use of axe.runPartial() which is called in each frame, and + * axe.finishRun() which is called in a blank page. This uses axe.run() instead, + * but with the restriction that cross-origin frames will not be tested. + */ + public setLegacyMode(legacyMode = true): AxeBuilder { + this.legacyMode = legacyMode; + return this; + } + /** * Analyzes the page, returning a promise */ @@ -147,7 +160,7 @@ class AxeBuilder { this.axeSource, this.config ); - if (runPartialSupported !== true) { + if (runPartialSupported !== true || this.legacyMode) { return this.runLegacy(context); } @@ -159,7 +172,14 @@ class AxeBuilder { * Use axe.run() to get results from the page */ private async runLegacy(context: ContextObject): Promise { - const { driver, axeSource, config, builderOptions } = this; + const { driver, axeSource, builderOptions } = this; + let config = this.config; + if (this.legacyMode !== true) { + config = { + ...(config || {}), + allowedOrigins: [''] + }; + } const injector = new AxeInjector({ driver, axeSource, diff --git a/packages/webdriverjs/tests/axe-webdriverjs.spec.ts b/packages/webdriverjs/tests/axe-webdriverjs.spec.ts index 9aad113b..e6bfd58d 100644 --- a/packages/webdriverjs/tests/axe-webdriverjs.spec.ts +++ b/packages/webdriverjs/tests/axe-webdriverjs.spec.ts @@ -453,6 +453,44 @@ describe('@axe-core/webdriverjs', () => { }); }); + describe('setLegacyMode', () => { + const runPartialThrows = `;axe.runPartial = () => { throw new Error("No runPartial")}`; + it('runs legacy mode when used', async () => { + await driver.get(`${addr}/external/index.html`); + const results = await new AxeBuilder(driver, axeSource + runPartialThrows) + .setLegacyMode() + .analyze(); + assert.isNotNull(results); + }); + + it('prevents cross-origin frame testing', async () => { + await driver.get(`${addr}/external/cross-origin.html`); + const results = await new AxeBuilder(driver, axeSource + runPartialThrows) + .withRules(['frame-tested']) + .setLegacyMode() + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.ok(frameTested); + }); + + it('can be disabled again', async () => { + await driver.get(`${addr}/external/cross-origin.html`); + const results = await new AxeBuilder(driver) + .withRules(['frame-tested']) + .setLegacyMode() + .setLegacyMode(false) + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); + }); + describe('for versions without axe.runPartial', () => { let axe403Source: string; before(() => { @@ -511,5 +549,17 @@ describe('@axe-core/webdriverjs', () => { assert.equal(results.violations[0].id, 'label'); assert.lengthOf(results.violations[0].nodes, 2); }); + + it('tests cross-origin pages', async () => { + await driver.get(`${addr}/external/cross-origin.html`); + const results = await new AxeBuilder(driver, axe403Source) + .withRules(['frame-tested']) + .analyze(); + + const frameTested = results.incomplete.find( + ({ id }) => id === 'frame-tested' + ); + assert.isUndefined(frameTested); + }); }); });