Skip to content

Commit

Permalink
feat: Add .setLegacyMode (#356)
Browse files Browse the repository at this point in the history
* feat(wdjs): Add .setLegacyMode

* feat(wdio): add setLegacyMode

* feat(puppeteer): Add setLegacyMode

* feat(playwright): Add setLegacyMode

* chore: upgrade package-lock
  • Loading branch information
WilcoFiers committed Sep 9, 2021
1 parent 321a055 commit f9d021b
Show file tree
Hide file tree
Showing 19 changed files with 430 additions and 104 deletions.
34 changes: 24 additions & 10 deletions packages/playwright/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<axe.Results | Error>

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
Expand Down Expand Up @@ -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<axe.Results | Error>
### 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
```
2 changes: 1 addition & 1 deletion packages/playwright/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 21 additions & 7 deletions packages/playwright/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -122,6 +124,18 @@ export default class AxeBuilder {
return this;
}

/**
* Use frameMessenger with <same_origin_only>
*
* 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<Result | Error>
Expand All @@ -138,7 +152,7 @@ export default class AxeBuilder {

let results: AxeResults;

if (!runPartialDefined) {
if (!runPartialDefined || this.legacyMode) {
results = await this.runLegacy(context);
return results;
}
Expand Down Expand Up @@ -169,12 +183,12 @@ export default class AxeBuilder {

private script(): string {
return `
${this.source}
axe.configure({
allowedOrigins: ['<unsafe_all_origins>'],
branding: { application: 'playwright' }
})
`;
${this.source}
axe.configure({
${this.legacyMode ? '' : 'allowedOrigins: ["<unsafe_all_origins>"],'}
branding: { application: 'playwright' }
})
`;
}

private async runLegacy(context: ContextObject): Promise<AxeResults> {
Expand Down
59 changes: 59 additions & 0 deletions packages/playwright/tests/axe-playwright.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
56 changes: 35 additions & 21 deletions packages/puppeteer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
```
4 changes: 2 additions & 2 deletions packages/puppeteer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 21 additions & 4 deletions packages/puppeteer/src/axePuppeteer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -130,6 +131,11 @@ export class AxePuppeteer {
return this;
}

public setLegacyMode(legacyMode = true): this {
this.legacyMode = legacyMode;
return this;
}

public async analyze(): Promise<AxeResults>;
public async analyze<T extends AnalyzeCB>(
callback?: T
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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: ['<unsafe_all_origins>']
};
}

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);
}
}
1 change: 0 additions & 1 deletion packages/puppeteer/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export function axeConfigure(config?: Axe.Spec): void {
window.axe.configure(config);
}
window.axe.configure({
allowedOrigins: ['<unsafe_all_origins>'],
branding: { application: 'axe-puppeteer' }
});
}
Expand Down
50 changes: 50 additions & 0 deletions packages/puppeteer/test/axePuppeteer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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);
});
});
});

0 comments on commit f9d021b

Please sign in to comment.