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