From 691bd2c2c9b1b98bb1ca326af0b95c562e688eb4 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Wed, 25 Aug 2021 18:18:07 +0200 Subject: [PATCH] chore(playwright): call finishRun in about:blank (#340) --- packages/playwright/fixtures/external | 1 - packages/playwright/fixtures/frames/bar.html | 10 - packages/playwright/fixtures/frames/baz.html | 15 -- packages/playwright/fixtures/frames/foo.html | 10 - .../playwright/fixtures/frames/recursive.html | 10 - packages/playwright/fixtures/index.html | 6 - .../playwright/fixtures/nested-frames.html | 11 - packages/playwright/package-lock.json | 10 +- packages/playwright/package.json | 4 +- packages/playwright/src/index.ts | 37 ++- .../test.ts => tests/axe-playwright.spec.ts} | 254 ++++++++++-------- .../{ => tests}/fixtures/context.html | 0 packages/playwright/tests/fixtures/external | 1 + 13 files changed, 177 insertions(+), 192 deletions(-) delete mode 120000 packages/playwright/fixtures/external delete mode 100644 packages/playwright/fixtures/frames/bar.html delete mode 100644 packages/playwright/fixtures/frames/baz.html delete mode 100644 packages/playwright/fixtures/frames/foo.html delete mode 100644 packages/playwright/fixtures/frames/recursive.html delete mode 100644 packages/playwright/fixtures/index.html delete mode 100644 packages/playwright/fixtures/nested-frames.html rename packages/playwright/{src/test.ts => tests/axe-playwright.spec.ts} (89%) rename packages/playwright/{ => tests}/fixtures/context.html (100%) create mode 120000 packages/playwright/tests/fixtures/external diff --git a/packages/playwright/fixtures/external b/packages/playwright/fixtures/external deleted file mode 120000 index 604f9337..00000000 --- a/packages/playwright/fixtures/external +++ /dev/null @@ -1 +0,0 @@ -../node_modules/axe-test-fixtures/fixtures/ \ No newline at end of file diff --git a/packages/playwright/fixtures/frames/bar.html b/packages/playwright/fixtures/frames/bar.html deleted file mode 100644 index 3b816e7b..00000000 --- a/packages/playwright/fixtures/frames/bar.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Bar - - -

Bar

- - - diff --git a/packages/playwright/fixtures/frames/baz.html b/packages/playwright/fixtures/frames/baz.html deleted file mode 100644 index 123e4e94..00000000 --- a/packages/playwright/fixtures/frames/baz.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - Baz - - -

Baz

-
-
Coffee
-
Black hot drink
-
Milk
-
White cold drink
-
- - diff --git a/packages/playwright/fixtures/frames/foo.html b/packages/playwright/fixtures/frames/foo.html deleted file mode 100644 index 9ab66c76..00000000 --- a/packages/playwright/fixtures/frames/foo.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Foo - - -

Foo

- - - diff --git a/packages/playwright/fixtures/frames/recursive.html b/packages/playwright/fixtures/frames/recursive.html deleted file mode 100644 index 3a3ce063..00000000 --- a/packages/playwright/fixtures/frames/recursive.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Recursive - - -

Recursive

- - - diff --git a/packages/playwright/fixtures/index.html b/packages/playwright/fixtures/index.html deleted file mode 100644 index 62186562..00000000 --- a/packages/playwright/fixtures/index.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - Test - - diff --git a/packages/playwright/fixtures/nested-frames.html b/packages/playwright/fixtures/nested-frames.html deleted file mode 100644 index 6f6f75b4..00000000 --- a/packages/playwright/fixtures/nested-frames.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Nested Frames - - -

This page has nested frames!

-


- - - diff --git a/packages/playwright/package-lock.json b/packages/playwright/package-lock.json index 0e26e6cd..7b7a2121 100644 --- a/packages/playwright/package-lock.json +++ b/packages/playwright/package-lock.json @@ -633,13 +633,13 @@ "dev": true }, "axe-core": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.2.tgz", - "integrity": "sha512-5LMaDRWm8ZFPAEdzTYmgjjEdj1YnQcpfrVajO/sn/LhbpGp0Y0H64c2hLZI1gRMxfA+w1S71Uc/nHaOXgcCvGg==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz", + "integrity": "sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==" }, "axe-test-fixtures": { - "version": "github:dequelabs/axe-test-fixtures#84991f7ce40b302374a7a2928ab6c168ed246992", - "from": "github:dequelabs/axe-test-fixtures", + "version": "github:dequelabs/axe-test-fixtures#09e7b5562e5a2972c889ed31ae7633c745bc0bc5", + "from": "github:dequelabs/axe-test-fixtures#v1", "dev": true }, "balanced-match": { diff --git a/packages/playwright/package.json b/packages/playwright/package.json index 4ac7d18b..d11eea9b 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -31,12 +31,12 @@ "scripts": { "prebuild": "rimraf dist", "build": "tsc", - "test": "mocha --timeout 60000 -r ts-node/register 'src/test.ts'", + "test": "mocha --timeout 60000 -r ts-node/register 'tests/**.spec.ts'", "coverage": "nyc npm run test", "prepare": "npm run build" }, "dependencies": { - "axe-core": "^4.3.2", + "axe-core": "^4.3.3", "playwright": "^1.13.1" }, "devDependencies": { diff --git a/packages/playwright/src/index.ts b/packages/playwright/src/index.ts index f084068f..33d92d81 100644 --- a/packages/playwright/src/index.ts +++ b/packages/playwright/src/index.ts @@ -1,6 +1,11 @@ import * as fs from 'fs'; import type { Page, Frame, ElementHandle } from 'playwright'; -import type { RunOptions, AxeResults, ContextObject } from 'axe-core'; +import type { + RunOptions, + AxeResults, + ContextObject, + PartialResults +} from 'axe-core'; import { normalizeContext, analyzePage } from './utils'; import type { AxePlaywrightParams } from './types'; import { @@ -142,12 +147,7 @@ export default class AxeBuilder { context ); const partials = await partialResults.getPartials(); - results = await page.evaluate(axeFinishRun, { - partialResults: partials, - options - }); - - return results; + return this.finishRun(partials); } /** @@ -197,7 +197,13 @@ export default class AxeBuilder { /** * Inject `axe-core` into each frame and run `axe.runPartial`. - * Because we need to inject axe into all frames all at once (to avoid any potential problems with the DOM becoming out-of-sync) but also need to not process results for any child frames if the parent frame throws an error (requirements of the data structure for `axe.finishRun`), we have to return a deeply nested array of Promises and then flatten the array once all Promises have finished, throwing out any nested Promises if the parent Promise is not fulfilled. + * Because we need to inject axe into all frames all at once + * (to avoid any potential problems with the DOM becoming out-of-sync) + * but also need to not process results for any child frames if the parent + * frame throws an error (requirements of the data structure for `axe.finishRun`), + * we have to return a deeply nested array of Promises and then flatten + * the array once all Promises have finished, throwing out any nested Promises + * if the parent Promise is not fulfilled. * @param frame - playwright frame object * @param context - axe-core context object * @returns Promise @@ -242,4 +248,19 @@ export default class AxeBuilder { return axePartialRunner; } + + private async finishRun(partialResults: PartialResults): Promise { + const { page, option: options } = this; + const context = page.context(); + const blankPage = await context.newPage(); + blankPage.evaluate(this.script()); + return await blankPage + .evaluate(axeFinishRun, { + partialResults, + options + }) + .finally(() => { + blankPage.close(); + }); + } } diff --git a/packages/playwright/src/test.ts b/packages/playwright/tests/axe-playwright.spec.ts similarity index 89% rename from packages/playwright/src/test.ts rename to packages/playwright/tests/axe-playwright.spec.ts index b7e169dc..75384687 100644 --- a/packages/playwright/src/test.ts +++ b/packages/playwright/tests/axe-playwright.spec.ts @@ -7,7 +7,7 @@ import testListen = require('test-listen'); import { assert } from 'chai'; import * as path from 'path'; import { Server, createServer } from 'http'; -import AxeBuilder from '.'; +import AxeBuilder from '../src'; describe('@axe-core/playwright', () => { let server: Server; @@ -16,7 +16,7 @@ describe('@axe-core/playwright', () => { const axePath = require.resolve('axe-core'); const axeSource = fs.readFileSync(axePath, 'utf8'); let browser: playwright.ChromiumBrowser; - const axeTestFixtures = path.resolve(__dirname, '..', 'fixtures'); + const axeTestFixtures = path.resolve(__dirname, 'fixtures'); const axeLegacySource = fs.readFileSync( path.resolve(axeTestFixtures, 'external', 'axe-core@legacy.js'), 'utf-8' @@ -50,108 +50,6 @@ describe('@axe-core/playwright', () => { await browser.close(); }); - describe('for versions without axe.runPartial', () => { - describe('analyze', () => { - it('returns results', async () => { - await page.goto(`${addr}/index.html`); - const results = await new AxeBuilder({ - page, - axeSource: axeLegacySource - }).analyze(); - assert.strictEqual(results.testEngine.version, '4.0.3'); - assert.isNotNull(results); - assert.isArray(results.violations); - assert.isArray(results.incomplete); - assert.isArray(results.passes); - assert.isArray(results.inapplicable); - }); - - it('throws if axe errors out on the top window', async () => { - let error: Error | null = null; - await page.goto(`${addr}/external/crash.html`); - try { - await new AxeBuilder({ - page, - axeSource: axeLegacySource + axeCrasherSource - }).analyze(); - } catch (e) { - error = e; - } - assert.isNotNull(error); - }); - }); - - describe('frame tests', () => { - it('injects into nested iframes', async () => { - await page.goto(`${addr}/external/nested-iframes.html`); - const { violations } = await new AxeBuilder({ - page, - axeSource: axeLegacySource - }) - .withRules('label') - .analyze(); - - assert.equal(violations[0].id, 'label'); - const nodes = violations[0].nodes; - assert.lengthOf(nodes, 4); - assert.deepEqual(nodes[0].target, [ - '#ifr-foo', - '#foo-bar', - '#bar-baz', - 'input' - ]); - assert.deepEqual(nodes[1].target, ['#ifr-foo', '#foo-baz', 'input']); - assert.deepEqual(nodes[2].target, ['#ifr-bar', '#bar-baz', 'input']); - assert.deepEqual(nodes[3].target, ['#ifr-baz', 'input']); - }); - - it('injects into nested frameset', async () => { - await page.goto(`${addr}/external/nested-frameset.html`); - const { violations } = await new AxeBuilder({ - page, - axeSource: axeLegacySource - }) - .withRules('label') - .analyze(); - - assert.equal(violations[0].id, 'label'); - assert.lengthOf(violations[0].nodes, 4); - - const nodes = violations[0].nodes; - assert.deepEqual(nodes[0].target, [ - '#frm-foo', - '#foo-bar', - '#bar-baz', - 'input' - ]); - assert.deepEqual(nodes[1].target, ['#frm-foo', '#foo-baz', 'input']); - assert.deepEqual(nodes[2].target, ['#frm-bar', '#bar-baz', 'input']); - assert.deepEqual(nodes[3].target, ['#frm-baz', 'input']); - }); - - it('should work on shadow DOM iframes', async () => { - await page.goto(`${addr}/external/shadow-frames.html`); - const { violations } = await new AxeBuilder({ - page, - axeSource: axeLegacySource - }) - .withRules('label') - .analyze(); - - assert.equal(violations[0].id, 'label'); - assert.lengthOf(violations[0].nodes, 3); - - const nodes = violations[0].nodes; - assert.deepEqual(nodes[0].target, ['#light-frame', 'input']); - assert.deepEqual(nodes[1].target, ['#slotted-frame', 'input']); - assert.deepEqual(nodes[2].target, [ - ['#shadow-root', '#shadow-frame'], - 'input' - ]); - }); - }); - }); - describe('analyze', () => { it('returns results using a different version of axe-core', async () => { await page.goto(`${addr}/index.html`); @@ -177,6 +75,32 @@ describe('@axe-core/playwright', () => { assert.isArray(results.inapplicable); }); + it('returns correct results metadata', async () => { + await page.goto(`${addr}/external/index.html`); + const results = await new AxeBuilder({ page }).analyze(); + assert.isDefined(results.testEngine.name); + assert.isDefined(results.testEngine.version); + assert.isDefined(results.testEnvironment.orientationAngle); + assert.isDefined(results.testEnvironment.orientationType); + assert.isDefined(results.testEnvironment.userAgent); + assert.isDefined(results.testEnvironment.windowHeight); + assert.isDefined(results.testEnvironment.windowWidth); + assert.isDefined(results.testRunner.name); + assert.isDefined(results.toolOptions.reporter); + assert.equal(results.url, `${addr}/external/index.html`); + }); + + it('properly isolates the call to axe.finishRun', async () => { + let err; + await page.goto(`${addr}/external/isolated-finish.html`); + try { + await new AxeBuilder({ page }).analyze(); + } catch (e) { + err = e; + } + assert.isUndefined(err); + }); + it('reports frame-tested', async () => { await page.goto(`${addr}/external/crash-parent.html`); const results = await new AxeBuilder({ @@ -224,7 +148,7 @@ describe('@axe-core/playwright', () => { describe('disableRules', () => { it('disables the given rules(s) as array', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .disableRules(['region']) .analyze(); @@ -238,7 +162,7 @@ describe('@axe-core/playwright', () => { }); it('disables the given rules(s) as string', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .disableRules('region') .analyze(); @@ -254,7 +178,7 @@ describe('@axe-core/playwright', () => { describe('withRules', () => { it('only runs the provided rules as an array', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .withRules(['region']) .analyze(); @@ -269,7 +193,7 @@ describe('@axe-core/playwright', () => { }); it('only runs the provided rules as a string', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .withRules('region') .analyze(); @@ -286,7 +210,7 @@ describe('@axe-core/playwright', () => { describe('options', () => { it('passes options to axe-core', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .options({ rules: { region: { enabled: false } } }) .analyze(); @@ -302,7 +226,7 @@ describe('@axe-core/playwright', () => { describe('withTags', () => { it('only rules rules with the given tag(s) as an array', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .withTags(['best-practice']) .analyze(); @@ -319,7 +243,7 @@ describe('@axe-core/playwright', () => { }); it('only rules rules with the given tag(s) as a string', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .withTags('best-practice') .analyze(); @@ -336,7 +260,7 @@ describe('@axe-core/playwright', () => { }); it('No results provided when the given tag(s) is invalid', async () => { - await page.goto(`${addr}/index.html`); + await page.goto(`${addr}/external/index.html`); const results = await new AxeBuilder({ page }) .withTags(['foobar']) .analyze(); @@ -413,7 +337,7 @@ describe('@axe-core/playwright', () => { }); it('injects once per iframe', async () => { - await page.goto(`${addr}/nested-frames.html`); + await page.goto(`${addr}/external/nested-iframes.html`); const builder = new AxeBuilder({ page }); const origScript = (builder as any).script; @@ -427,7 +351,7 @@ describe('@axe-core/playwright', () => { await builder.analyze(); - assert.strictEqual(count, 4); + assert.strictEqual(count, 9); }); }); @@ -472,4 +396,106 @@ describe('@axe-core/playwright', () => { assert.notInclude(flattenTarget, '.exclude'); }); }); + + describe('for versions without axe.runPartial', () => { + describe('analyze', () => { + it('returns results', async () => { + await page.goto(`${addr}/external/index.html`); + const results = await new AxeBuilder({ + page, + axeSource: axeLegacySource + }).analyze(); + assert.strictEqual(results.testEngine.version, '4.0.3'); + assert.isNotNull(results); + assert.isArray(results.violations); + assert.isArray(results.incomplete); + assert.isArray(results.passes); + assert.isArray(results.inapplicable); + }); + + it('throws if axe errors out on the top window', async () => { + let error: Error | null = null; + await page.goto(`${addr}/external/crash.html`); + try { + await new AxeBuilder({ + page, + axeSource: axeLegacySource + axeCrasherSource + }).analyze(); + } catch (e) { + error = e; + } + assert.isNotNull(error); + }); + }); + + describe('frame tests', () => { + it('injects into nested iframes', async () => { + await page.goto(`${addr}/external/nested-iframes.html`); + const { violations } = await new AxeBuilder({ + page, + axeSource: axeLegacySource + }) + .withRules('label') + .analyze(); + + assert.equal(violations[0].id, 'label'); + const nodes = violations[0].nodes; + assert.lengthOf(nodes, 4); + assert.deepEqual(nodes[0].target, [ + '#ifr-foo', + '#foo-bar', + '#bar-baz', + 'input' + ]); + assert.deepEqual(nodes[1].target, ['#ifr-foo', '#foo-baz', 'input']); + assert.deepEqual(nodes[2].target, ['#ifr-bar', '#bar-baz', 'input']); + assert.deepEqual(nodes[3].target, ['#ifr-baz', 'input']); + }); + + it('injects into nested frameset', async () => { + await page.goto(`${addr}/external/nested-frameset.html`); + const { violations } = await new AxeBuilder({ + page, + axeSource: axeLegacySource + }) + .withRules('label') + .analyze(); + + assert.equal(violations[0].id, 'label'); + assert.lengthOf(violations[0].nodes, 4); + + const nodes = violations[0].nodes; + assert.deepEqual(nodes[0].target, [ + '#frm-foo', + '#foo-bar', + '#bar-baz', + 'input' + ]); + assert.deepEqual(nodes[1].target, ['#frm-foo', '#foo-baz', 'input']); + assert.deepEqual(nodes[2].target, ['#frm-bar', '#bar-baz', 'input']); + assert.deepEqual(nodes[3].target, ['#frm-baz', 'input']); + }); + + it('should work on shadow DOM iframes', async () => { + await page.goto(`${addr}/external/shadow-frames.html`); + const { violations } = await new AxeBuilder({ + page, + axeSource: axeLegacySource + }) + .withRules('label') + .analyze(); + + assert.equal(violations[0].id, 'label'); + assert.lengthOf(violations[0].nodes, 3); + + const nodes = violations[0].nodes; + assert.deepEqual(nodes[0].target, ['#light-frame', 'input']); + assert.deepEqual(nodes[1].target, ['#slotted-frame', 'input']); + assert.deepEqual(nodes[2].target, [ + ['#shadow-root', '#shadow-frame'], + 'input' + ]); + }); + }); + }); }); diff --git a/packages/playwright/fixtures/context.html b/packages/playwright/tests/fixtures/context.html similarity index 100% rename from packages/playwright/fixtures/context.html rename to packages/playwright/tests/fixtures/context.html diff --git a/packages/playwright/tests/fixtures/external b/packages/playwright/tests/fixtures/external new file mode 120000 index 00000000..b06b8ccb --- /dev/null +++ b/packages/playwright/tests/fixtures/external @@ -0,0 +1 @@ +../../node_modules/axe-test-fixtures/fixtures \ No newline at end of file