@axe-core/playwright 4.3.3-alpha.224 → 4.3.3-alpha.228

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@axe-core/playwright",
3
- "version": "4.3.3-alpha.224+14e5125",
3
+ "version": "4.3.3-alpha.228+0e566c2",
4
4
  "description": "Provides a method to inject and analyze web pages using axe",
5
5
  "contributors": [
6
6
  {
7
7
  "name": "Michael Siek (me@michaelsiek.com)"
8
8
  }
9
9
  ],
10
+ "files": [
11
+ "/dist"
12
+ ],
10
13
  "keywords": [
11
14
  "a11y",
12
15
  "unit",
@@ -77,5 +80,5 @@
77
80
  "functions": 100,
78
81
  "lines": 95
79
82
  },
80
- "gitHead": "14e512551506a333b0249049c3c08c605b2026c3"
83
+ "gitHead": "0e566c246a4a3ab0e5b7b129fbf7006bb67aa7b6"
81
84
  }
package/error-handling.md DELETED
@@ -1,43 +0,0 @@
1
- # Error Handling
2
-
3
- ## Table of Content
4
-
5
- 1. [Having Popup blockers enabled](#having-popup-blockers-enabled)
6
- 2. [AxeBuilder.setLegacyMode(legacy: boolean)](#axebuildersetlegacymodelegacy-boolean)
7
-
8
- Version 4.3.0 and above of the axe-core integrations use a new technique when calling `AxeBuilder.analyze()` which opens a new window at the end of a run. Many of the issues outlined in this document address common problems with this technique and their potential solutions.
9
-
10
- ### Having Popup blockers enabled
11
-
12
- Popup blockers prevent us from opening the new window when calling `AxeBuilder.analyze()`. The default configuration for most automation testing libraries should allow popups. Please make sure that you do not explicitly enable popup blockers which may cause an issue while running the tests.
13
-
14
- ### AxeBuilder.setLegacyMode(legacy: boolean)
15
-
16
- If for some reason you are unable to run the new `AxeBuilder.analyze` technique without errors, axe provides a new chainable method that allows you to run the legacy version of `AxeBuilder.analyze`. When using this method axe excludes accessibility issues that may occur in cross-domain frames and iframes.
17
-
18
- **Please Note:** `AxeBuilder.setLegacyMode` is deprecated and will be removed in v5.0. Please report any errors you may have while running `AxeBuilder.analyze` so that they can be fixed before the legacy version is removed.
19
-
20
- #### Example:
21
-
22
- ```js
23
- const AxeBuilder = require('@axe-core/playwright').default;
24
- const playwright = require('playwright');
25
-
26
- (async () => {
27
- const browser = await playwright.chromium.launch();
28
- const context = await browser.newContext();
29
- const page = await context.newPage();
30
- await page.goto('https://dequeuniversity.com/demo/mars/');
31
-
32
- try {
33
- const results = await new AxeBuilder({ page })
34
- // enables legacy mode
35
- .setLegacyMode()
36
- .analyze();
37
- console.log(results);
38
- } catch (e) {
39
- // do something with the error
40
- }
41
- await browser.close();
42
- })();
43
- ```
package/example.js DELETED
@@ -1,12 +0,0 @@
1
- const AxeBuilder = require('@axe-core/playwright').default;
2
- const playwright = require('playwright');
3
-
4
- (async () => {
5
- const browser = await playwright.chromium.launch();
6
- const context = await browser.newContext();
7
- const page = await context.newPage();
8
- await page.goto('https://dequeuniversity.com/demo/mars/');
9
- const results = await new AxeBuilder({ page }).analyze();
10
- console.log(results);
11
- await browser.close();
12
- })();
@@ -1,51 +0,0 @@
1
- import * as axe from 'axe-core';
2
- /**
3
- * This class parallelizes the async calls to axe.runPartial.
4
- *
5
- * In this project, most async calls needs to block execution, such as
6
- * the axeGetFrameContext() and getChildFrame() and calls.
7
- *
8
- * Unlike those calls, axe.runPartial() calls must run in parallel, so that
9
- * frame tests don't wait for each other. This is necessary to minimize the time
10
- * between when axe-core finds a frame, and when it is tested.
11
- */
12
-
13
- type GetPartialResultsResponse = Parameters<typeof axe.finishRun>[0];
14
- export default class AxePartialRunner {
15
- private partialPromise: Promise<axe.PartialResult>;
16
- private childRunners: Array<AxePartialRunner | null> = [];
17
-
18
- constructor(
19
- partialPromise: Promise<axe.PartialResult>,
20
- private initiator: boolean = false
21
- ) {
22
- this.partialPromise = caught(partialPromise);
23
- }
24
-
25
- public addChildResults(childResultRunner: AxePartialRunner | null): void {
26
- this.childRunners.push(childResultRunner);
27
- }
28
-
29
- public async getPartials(): Promise<GetPartialResultsResponse> {
30
- try {
31
- const parentPartial = await this.partialPromise;
32
- const childPromises = this.childRunners.map(childRunner => {
33
- return childRunner ? caught(childRunner.getPartials()) : [null];
34
- });
35
- const childPartials = (await Promise.all(childPromises)).flat(1);
36
- return [parentPartial, ...childPartials];
37
- } catch (e) {
38
- if (this.initiator) {
39
- throw e;
40
- }
41
- return [null];
42
- }
43
- }
44
- }
45
-
46
- // Utility to tell NodeJS not to worry about catching promise errors async.
47
- // See: https://stackoverflow.com/questions/40920179/should-i-refrain-from-handling-promise-rejection-asynchronously
48
- export const caught = ((f: () => void) => {
49
- return <T>(p: Promise<T>): Promise<T> => (p.catch(f), p);
50
- /* eslint-disable @typescript-eslint/no-empty-function */
51
- })(() => {});
package/src/browser.ts DELETED
@@ -1,43 +0,0 @@
1
- /* istanbul ignore file */
2
- import type {
3
- GetFrameContextsParams,
4
- RunPartialParams,
5
- FinishRunParams,
6
- ShadowSelectParams
7
- } from './types';
8
- import { FrameContext, AxeResults, PartialResult } from 'axe-core';
9
- import * as axeCore from 'axe-core';
10
-
11
- // Expect axe to be set up.
12
- // Tell Typescript that there should be a global variable called `axe` that follows
13
- // the shape given by the `axe-core` typings (the `run` and `configure` functions).
14
- declare global {
15
- interface Window {
16
- axe: typeof axeCore;
17
- }
18
- }
19
- export const axeGetFrameContexts = ({
20
- context
21
- }: GetFrameContextsParams): FrameContext[] => {
22
- return window.axe.utils.getFrameContexts(context);
23
- };
24
-
25
- export const axeShadowSelect = ({
26
- frameSelector
27
- }: ShadowSelectParams): Element | null => {
28
- return window.axe.utils.shadowSelect(frameSelector);
29
- };
30
-
31
- export const axeRunPartial = ({
32
- context,
33
- options
34
- }: RunPartialParams): Promise<PartialResult> => {
35
- return window.axe.runPartial(context, options);
36
- };
37
-
38
- export const axeFinishRun = ({
39
- partialResults,
40
- options
41
- }: FinishRunParams): Promise<AxeResults> => {
42
- return window.axe.finishRun(partialResults, options);
43
- };
package/src/index.ts DELETED
@@ -1,299 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as assert from 'assert';
3
- import type { Page, Frame, ElementHandle } from 'playwright';
4
- import type {
5
- RunOptions,
6
- AxeResults,
7
- ContextObject,
8
- PartialResults
9
- } from 'axe-core';
10
- import { normalizeContext, analyzePage } from './utils';
11
- import type { AxePlaywrightParams } from './types';
12
- import {
13
- axeFinishRun,
14
- axeGetFrameContexts,
15
- axeRunPartial,
16
- axeShadowSelect
17
- } from './browser';
18
- import AxePartialRunner from './AxePartialRunner';
19
-
20
- export default class AxeBuilder {
21
- private page: Page;
22
- private includes: string[][];
23
- private excludes: string[][];
24
- private option: RunOptions;
25
- private source: string;
26
- private legacyMode = false;
27
-
28
- constructor({ page, axeSource }: AxePlaywrightParams) {
29
- const axePath = require.resolve('axe-core');
30
- const source = fs.readFileSync(axePath, 'utf-8');
31
- this.page = page;
32
- this.includes = [];
33
- this.excludes = [];
34
- this.option = {};
35
- this.source = axeSource || source;
36
- }
37
-
38
- /**
39
- * Selector to include in analysis.
40
- * This may be called any number of times.
41
- * @param String selector
42
- * @returns this
43
- */
44
-
45
- public include(selector: string | string[]): this {
46
- selector = Array.isArray(selector) ? selector : [selector];
47
- this.includes.push(selector);
48
- return this;
49
- }
50
-
51
- /**
52
- * Selector to exclude in analysis.
53
- * This may be called any number of times.
54
- * @param String selector
55
- * @returns this
56
- */
57
-
58
- public exclude(selector: string | string[]): this {
59
- selector = Array.isArray(selector) ? selector : [selector];
60
- this.excludes.push(selector);
61
- return this;
62
- }
63
-
64
- /**
65
- * Set options to be passed into axe-core
66
- * @param RunOptions options
67
- * @returns AxeBuilder
68
- */
69
-
70
- public options(options: RunOptions): this {
71
- this.option = options;
72
- return this;
73
- }
74
-
75
- /**
76
- * Limit analysis to only the specified rules.
77
- * Cannot be used with `AxeBuilder#withTags`
78
- * @param String|Array rules
79
- * @returns this
80
- */
81
-
82
- public withRules(rules: string | string[]): this {
83
- rules = Array.isArray(rules) ? rules : [rules];
84
- /* istanbul ignore next */
85
- this.option = this.option || {};
86
- this.option.runOnly = {
87
- type: 'rule',
88
- values: rules
89
- };
90
-
91
- return this;
92
- }
93
-
94
- /**
95
- * Limit analysis to only specified tags.
96
- * Cannot be used with `AxeBuilder#withRules`
97
- * @param String|Array tags
98
- * @returns this
99
- */
100
-
101
- public withTags(tags: string | string[]): this {
102
- tags = Array.isArray(tags) ? tags : [tags];
103
- /* istanbul ignore next */
104
- this.option = this.option || {};
105
- this.option.runOnly = {
106
- type: 'tag',
107
- values: tags
108
- };
109
- return this;
110
- }
111
-
112
- /**
113
- * Set the list of rules to skip when running an analysis.
114
- * @param String|Array rules
115
- * @returns this
116
- */
117
-
118
- public disableRules(rules: string | string[]): this {
119
- rules = Array.isArray(rules) ? rules : [rules];
120
- /* istanbul ignore next */
121
- this.option = this.option || {};
122
- this.option.rules = {};
123
-
124
- for (const rule of rules) {
125
- this.option.rules[rule] = { enabled: false };
126
- }
127
- return this;
128
- }
129
-
130
- /**
131
- * Use frameMessenger with <same_origin_only>
132
- *
133
- * This disables use of axe.runPartial() which is called in each frame, and
134
- * axe.finishRun() which is called in a blank page. This uses axe.run() instead,
135
- * but with the restriction that cross-origin frames will not be tested.
136
- */
137
- public setLegacyMode(legacyMode = true): this {
138
- this.legacyMode = legacyMode;
139
- return this;
140
- }
141
-
142
- /**
143
- * Perform analysis and retrieve results. *Does not chain.*
144
- * @return Promise<Result | Error>
145
- */
146
-
147
- public async analyze(): Promise<AxeResults> {
148
- const context = normalizeContext(this.includes, this.excludes);
149
- const { page, option: options } = this;
150
-
151
- page.evaluate(this.script());
152
- const runPartialDefined = await page.evaluate<boolean>(
153
- 'typeof window.axe.runPartial === "function"'
154
- );
155
-
156
- let results: AxeResults;
157
-
158
- if (!runPartialDefined || this.legacyMode) {
159
- results = await this.runLegacy(context);
160
- return results;
161
- }
162
- const partialResults = await this.runPartialRecursive(
163
- page.mainFrame(),
164
- context
165
- );
166
- const partials = await partialResults.getPartials();
167
-
168
- try {
169
- return await this.finishRun(partials);
170
- } catch (error) {
171
- throw new Error(
172
- `${
173
- (error as Error).message
174
- }\n Please check out https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/error-handling.md`
175
- );
176
- }
177
- }
178
-
179
- /**
180
- * Injects `axe-core` into all frames.
181
- * @param Page - playwright page object
182
- * @returns Promise<void>
183
- */
184
-
185
- private async inject(frames: Frame[]): Promise<void> {
186
- for (const iframe of frames) {
187
- await iframe.evaluate(this.script());
188
- }
189
- }
190
-
191
- /**
192
- * Get axe-core source and configurations
193
- * @returns String
194
- */
195
-
196
- private script(): string {
197
- return `
198
- ${this.source}
199
- axe.configure({
200
- ${this.legacyMode ? '' : 'allowedOrigins: ["<unsafe_all_origins>"],'}
201
- branding: { application: 'playwright' }
202
- })
203
- `;
204
- }
205
-
206
- private async runLegacy(context: ContextObject): Promise<AxeResults> {
207
- // in playwright all frames are available in `.frames()`, even nested and
208
- // shadowDOM iframes. also navigating to a url causes it to be put into
209
- // an iframe so we don't need to inject into the page object itself
210
- const frames = this.page.frames();
211
- await this.inject(frames);
212
- const axeResults = await this.page.evaluate(analyzePage, {
213
- context,
214
- options: this.option
215
- });
216
-
217
- if (axeResults.error) {
218
- throw new Error(axeResults.error);
219
- }
220
-
221
- return axeResults.results;
222
- }
223
-
224
- /**
225
- * Inject `axe-core` into each frame and run `axe.runPartial`.
226
- * Because we need to inject axe into all frames all at once
227
- * (to avoid any potential problems with the DOM becoming out-of-sync)
228
- * but also need to not process results for any child frames if the parent
229
- * frame throws an error (requirements of the data structure for `axe.finishRun`),
230
- * we have to return a deeply nested array of Promises and then flatten
231
- * the array once all Promises have finished, throwing out any nested Promises
232
- * if the parent Promise is not fulfilled.
233
- * @param frame - playwright frame object
234
- * @param context - axe-core context object
235
- * @returns Promise<AxePartialRunner>
236
- */
237
-
238
- private async runPartialRecursive(
239
- frame: Frame,
240
- context: ContextObject
241
- ): Promise<AxePartialRunner> {
242
- const frameContexts = await frame.evaluate(axeGetFrameContexts, {
243
- context
244
- });
245
- const partialPromise = frame.evaluate(axeRunPartial, {
246
- context,
247
- options: this.option
248
- });
249
- const initiator = frame === this.page.mainFrame();
250
- const axePartialRunner = new AxePartialRunner(partialPromise, initiator);
251
-
252
- for (const { frameSelector, frameContext } of frameContexts) {
253
- let childResults: AxePartialRunner | null = null;
254
- try {
255
- const iframeHandle = await frame.evaluateHandle(axeShadowSelect, {
256
- frameSelector
257
- });
258
- // note: these can return null but the catch will handle this properly for all cases
259
- const iframeElement =
260
- iframeHandle.asElement() as ElementHandle<Element>;
261
- const childFrame = await iframeElement.contentFrame();
262
- if (childFrame) {
263
- await this.inject([childFrame]);
264
- childResults = await this.runPartialRecursive(
265
- childFrame,
266
- frameContext
267
- );
268
- }
269
- } catch {
270
- /* do nothing */
271
- }
272
- axePartialRunner.addChildResults(childResults);
273
- }
274
-
275
- return axePartialRunner;
276
- }
277
-
278
- private async finishRun(partialResults: PartialResults): Promise<AxeResults> {
279
- const { page, option: options } = this;
280
- const context = page.context();
281
- const blankPage = await context.newPage();
282
-
283
- assert(
284
- blankPage,
285
- 'Please make sure that you have popup blockers disabled.'
286
- );
287
-
288
- blankPage.evaluate(this.script());
289
-
290
- return await blankPage
291
- .evaluate(axeFinishRun, {
292
- partialResults,
293
- options
294
- })
295
- .finally(() => {
296
- blankPage.close();
297
- });
298
- }
299
- }
package/src/types.ts DELETED
@@ -1,43 +0,0 @@
1
- import type { Page } from 'playwright';
2
- import type {
3
- RunOptions,
4
- AxeResults,
5
- ContextObject,
6
- CrossTreeSelector,
7
- PartialResult
8
- } from 'axe-core';
9
-
10
- export type PartialResults = PartialResult | null;
11
-
12
- export interface AnalyzePageParams {
13
- context: ContextObject;
14
- options: RunOptions | null;
15
- }
16
-
17
- export interface AxePlaywrightParams {
18
- page: Page;
19
- axeSource?: string;
20
- }
21
-
22
- export interface AnalyzePageResponse {
23
- results: AxeResults;
24
- error: string | null;
25
- }
26
-
27
- export interface GetFrameContextsParams {
28
- context: ContextObject;
29
- }
30
-
31
- export interface ShadowSelectParams {
32
- frameSelector: CrossTreeSelector;
33
- }
34
-
35
- export interface RunPartialParams {
36
- context: ContextObject;
37
- options: RunOptions;
38
- }
39
-
40
- export interface FinishRunParams {
41
- partialResults: PartialResults[];
42
- options: RunOptions;
43
- }
package/src/utils.ts DELETED
@@ -1,50 +0,0 @@
1
- import type { AxeResults, ContextObject } from 'axe-core';
2
- import type { AnalyzePageParams, AnalyzePageResponse } from './types';
3
-
4
- /**
5
- * Get running context
6
- * @param Array include
7
- * @param Array exclude
8
- * @returns ContextObject
9
- */
10
-
11
- export const normalizeContext = (
12
- includes: string[][],
13
- excludes: string[][]
14
- ): ContextObject => {
15
- const base: ContextObject = {
16
- exclude: []
17
- };
18
- if (excludes.length && Array.isArray(base.exclude)) {
19
- base.exclude.push(...excludes);
20
- }
21
-
22
- if (includes.length) {
23
- base.include = includes;
24
- }
25
- return base;
26
- };
27
-
28
- /**
29
- * Analyze the page.
30
- * @param AnalyzePageParams analyzeContext
31
- * @returns Promise<AnalyzePageResponse>
32
- */
33
-
34
- export const analyzePage = ({
35
- context,
36
- options
37
- }: AnalyzePageParams): Promise<AnalyzePageResponse> => {
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- const axeCore = (window as any).axe;
40
-
41
- // Run axe-core
42
- return axeCore
43
- .run(context || document, options || {})
44
- .then((results: AxeResults) => {
45
- return { error: null, results };
46
- })
47
- .catch((err: Error) => {
48
- return { error: err.message, results: null };
49
- });
50
- };
@@ -1,710 +0,0 @@
1
- import 'mocha';
2
- import * as fs from 'fs';
3
- import * as playwright from 'playwright';
4
- import * as express from 'express';
5
- import type { AxeResults } from 'axe-core';
6
- import testListen = require('test-listen');
7
- import { assert } from 'chai';
8
- import * as path from 'path';
9
- import { Server, createServer } from 'http';
10
- import AxeBuilder from '../src';
11
-
12
- describe('@axe-core/playwright', () => {
13
- let server: Server;
14
- let addr: string;
15
- let page: playwright.Page;
16
- const axePath = require.resolve('axe-core');
17
- const axeSource = fs.readFileSync(axePath, 'utf8');
18
- let browser: playwright.ChromiumBrowser;
19
- const axeTestFixtures = path.resolve(__dirname, 'fixtures');
20
- const externalPath = path.resolve(axeTestFixtures, 'external');
21
- const axeLegacySource = fs.readFileSync(
22
- path.join(externalPath, 'axe-core@legacy.js'),
23
- 'utf-8'
24
- );
25
- const axeCrasherSource = fs.readFileSync(
26
- path.join(externalPath, 'axe-crasher.js'),
27
- 'utf8'
28
- );
29
- const axeForceLegacy = fs.readFileSync(
30
- path.join(externalPath, 'axe-force-legacy.js'),
31
- 'utf8'
32
- );
33
-
34
- before(async () => {
35
- const app = express();
36
- app.use(express.static(axeTestFixtures));
37
- server = createServer(app);
38
- addr = await testListen(server);
39
- });
40
-
41
- after(async () => {
42
- server.close();
43
- });
44
-
45
- beforeEach(async () => {
46
- browser = await playwright.chromium.launch({
47
- args: ['--disable-dev-shm-usage']
48
- });
49
- const context = await browser.newContext();
50
- page = await context.newPage();
51
- });
52
-
53
- afterEach(async () => {
54
- await browser.close();
55
- });
56
-
57
- describe('analyze', () => {
58
- it('returns results using a different version of axe-core', async () => {
59
- await page.goto(`${addr}/index.html`);
60
- const results = await new AxeBuilder({
61
- page,
62
- axeSource: axeLegacySource
63
- }).analyze();
64
- assert.strictEqual(results.testEngine.version, '4.0.3');
65
- assert.isNotNull(results);
66
- assert.isArray(results.violations);
67
- assert.isArray(results.incomplete);
68
- assert.isArray(results.passes);
69
- assert.isArray(results.inapplicable);
70
- });
71
-
72
- it('returns results', async () => {
73
- await page.goto(`${addr}/index.html`);
74
- const results = await new AxeBuilder({ page }).analyze();
75
- assert.isNotNull(results);
76
- assert.isArray(results.violations);
77
- assert.isArray(results.incomplete);
78
- assert.isArray(results.passes);
79
- assert.isArray(results.inapplicable);
80
- });
81
-
82
- it('returns correct results metadata', async () => {
83
- await page.goto(`${addr}/external/index.html`);
84
- const results = await new AxeBuilder({ page }).analyze();
85
- assert.isDefined(results.testEngine.name);
86
- assert.isDefined(results.testEngine.version);
87
- assert.isDefined(results.testEnvironment.orientationAngle);
88
- assert.isDefined(results.testEnvironment.orientationType);
89
- assert.isDefined(results.testEnvironment.userAgent);
90
- assert.isDefined(results.testEnvironment.windowHeight);
91
- assert.isDefined(results.testEnvironment.windowWidth);
92
- assert.isDefined(results.testRunner.name);
93
- assert.isDefined(results.toolOptions.reporter);
94
- assert.equal(results.url, `${addr}/external/index.html`);
95
- });
96
-
97
- it('properly isolates the call to axe.finishRun', async () => {
98
- let err;
99
- await page.goto(`${addr}/external/isolated-finish.html`);
100
- try {
101
- await new AxeBuilder({ page }).analyze();
102
- } catch (e) {
103
- err = e;
104
- }
105
- assert.isUndefined(err);
106
- });
107
-
108
- it('reports frame-tested', async () => {
109
- await page.goto(`${addr}/external/crash-parent.html`);
110
- const results = await new AxeBuilder({
111
- page,
112
- axeSource: axeSource + axeCrasherSource
113
- })
114
- .options({ runOnly: ['label', 'frame-tested'] })
115
- .analyze();
116
- assert.equal(results.incomplete[0].id, 'frame-tested');
117
- assert.lengthOf(results.incomplete[0].nodes, 1);
118
- assert.equal(results.violations[0].id, 'label');
119
- assert.lengthOf(results.violations[0].nodes, 2);
120
- });
121
-
122
- it('throws when injecting a problematic source', async () => {
123
- let error: Error | null = null;
124
- await page.goto(`${addr}/external/crash-me.html`);
125
- try {
126
- await new AxeBuilder({
127
- page,
128
- axeSource: 'throw new Error()'
129
- }).analyze();
130
- } catch (e) {
131
- error = e as Error;
132
- }
133
- assert.isNotNull(error);
134
- });
135
-
136
- it('throws when a setup fails', async () => {
137
- let error: Error | null = null;
138
-
139
- const brokenSource = axeSource + `;window.axe.utils = {}`;
140
- await page.goto(`${addr}/external/index.html`);
141
- try {
142
- await new AxeBuilder({ page, axeSource: brokenSource })
143
- .withRules('label')
144
- .analyze();
145
- } catch (e) {
146
- error = e as Error;
147
- }
148
-
149
- assert.isNotNull(error);
150
- });
151
-
152
- it('returns the same results from runPartial as from legacy mode', async () => {
153
- await page.goto(`${addr}/nested-iframes.html`);
154
- const legacyResults = await new AxeBuilder({
155
- page,
156
- axeSource: axeSource + axeForceLegacy
157
- }).analyze();
158
- assert.equal(legacyResults.testEngine.name, 'axe-legacy');
159
-
160
- const normalResults = await new AxeBuilder({ page, axeSource }).analyze();
161
- normalResults.timestamp = legacyResults.timestamp;
162
- normalResults.testEngine.name = legacyResults.testEngine.name;
163
- assert.deepEqual(normalResults, legacyResults);
164
- });
165
- });
166
-
167
- describe('disableRules', () => {
168
- it('disables the given rules(s) as array', async () => {
169
- await page.goto(`${addr}/external/index.html`);
170
- const results = await new AxeBuilder({ page })
171
- .disableRules(['region'])
172
- .analyze();
173
- const all = [
174
- ...results.passes,
175
- ...results.inapplicable,
176
- ...results.violations,
177
- ...results.incomplete
178
- ];
179
- assert.isTrue(!all.find(r => r.id === 'region'));
180
- });
181
-
182
- it('disables the given rules(s) as string', async () => {
183
- await page.goto(`${addr}/external/index.html`);
184
- const results = await new AxeBuilder({ page })
185
- .disableRules('region')
186
- .analyze();
187
- const all = [
188
- ...results.passes,
189
- ...results.inapplicable,
190
- ...results.violations,
191
- ...results.incomplete
192
- ];
193
- assert.isTrue(!all.find(r => r.id === 'region'));
194
- });
195
- });
196
-
197
- describe('withRules', () => {
198
- it('only runs the provided rules as an array', async () => {
199
- await page.goto(`${addr}/external/index.html`);
200
- const results = await new AxeBuilder({ page })
201
- .withRules(['region'])
202
- .analyze();
203
- const all = [
204
- ...results.passes,
205
- ...results.inapplicable,
206
- ...results.violations,
207
- ...results.incomplete
208
- ];
209
- assert.strictEqual(all.length, 1);
210
- assert.strictEqual(all[0].id, 'region');
211
- });
212
-
213
- it('only runs the provided rules as a string', async () => {
214
- await page.goto(`${addr}/external/index.html`);
215
- const results = await new AxeBuilder({ page })
216
- .withRules('region')
217
- .analyze();
218
- const all = [
219
- ...results.passes,
220
- ...results.inapplicable,
221
- ...results.violations,
222
- ...results.incomplete
223
- ];
224
- assert.strictEqual(all.length, 1);
225
- assert.strictEqual(all[0].id, 'region');
226
- });
227
- });
228
-
229
- describe('options', () => {
230
- it('passes options to axe-core', async () => {
231
- await page.goto(`${addr}/external/index.html`);
232
- const results = await new AxeBuilder({ page })
233
- .options({ rules: { region: { enabled: false } } })
234
- .analyze();
235
- const all = [
236
- ...results.passes,
237
- ...results.inapplicable,
238
- ...results.violations,
239
- ...results.incomplete
240
- ];
241
- assert.isTrue(!all.find(r => r.id === 'region'));
242
- });
243
- });
244
-
245
- describe('withTags', () => {
246
- it('only rules rules with the given tag(s) as an array', async () => {
247
- await page.goto(`${addr}/external/index.html`);
248
- const results = await new AxeBuilder({ page })
249
- .withTags(['best-practice'])
250
- .analyze();
251
- const all = [
252
- ...results.passes,
253
- ...results.inapplicable,
254
- ...results.violations,
255
- ...results.incomplete
256
- ];
257
- assert.isOk(all);
258
- for (const rule of all) {
259
- assert.include(rule.tags, 'best-practice');
260
- }
261
- });
262
-
263
- it('only rules rules with the given tag(s) as a string', async () => {
264
- await page.goto(`${addr}/external/index.html`);
265
- const results = await new AxeBuilder({ page })
266
- .withTags('best-practice')
267
- .analyze();
268
- const all = [
269
- ...results.passes,
270
- ...results.inapplicable,
271
- ...results.violations,
272
- ...results.incomplete
273
- ];
274
- assert.isOk(all);
275
- for (const rule of all) {
276
- assert.include(rule.tags, 'best-practice');
277
- }
278
- });
279
-
280
- it('No results provided when the given tag(s) is invalid', async () => {
281
- await page.goto(`${addr}/external/index.html`);
282
- const results = await new AxeBuilder({ page })
283
- .withTags(['foobar'])
284
- .analyze();
285
-
286
- const all = [
287
- ...results.passes,
288
- ...results.inapplicable,
289
- ...results.violations,
290
- ...results.incomplete
291
- ];
292
- // Ensure all run rules had the "foobar" tag
293
- assert.deepStrictEqual(0, all.length);
294
- });
295
- });
296
-
297
- describe('frame tests', () => {
298
- it('injects into nested iframes', async () => {
299
- await page.goto(`${addr}/external/nested-iframes.html`);
300
- const { violations } = await new AxeBuilder({ page })
301
- .options({ runOnly: 'label' })
302
- .analyze();
303
-
304
- assert.equal(violations[0].id, 'label');
305
- const nodes = violations[0].nodes;
306
- assert.lengthOf(nodes, 4);
307
- assert.deepEqual(nodes[0].target, [
308
- '#ifr-foo',
309
- '#foo-bar',
310
- '#bar-baz',
311
- 'input'
312
- ]);
313
- assert.deepEqual(nodes[1].target, ['#ifr-foo', '#foo-baz', 'input']);
314
- assert.deepEqual(nodes[2].target, ['#ifr-bar', '#bar-baz', 'input']);
315
- assert.deepEqual(nodes[3].target, ['#ifr-baz', 'input']);
316
- });
317
-
318
- it('injects into nested frameset', async () => {
319
- await page.goto(`${addr}/external/nested-frameset.html`);
320
- const { violations } = await new AxeBuilder({ page })
321
- .options({ runOnly: 'label' })
322
- .analyze();
323
-
324
- assert.equal(violations[0].id, 'label');
325
- assert.lengthOf(violations[0].nodes, 4);
326
-
327
- const nodes = violations[0].nodes;
328
- assert.deepEqual(nodes[0].target, [
329
- '#frm-foo',
330
- '#foo-bar',
331
- '#bar-baz',
332
- 'input'
333
- ]);
334
- assert.deepEqual(nodes[1].target, ['#frm-foo', '#foo-baz', 'input']);
335
- assert.deepEqual(nodes[2].target, ['#frm-bar', '#bar-baz', 'input']);
336
- assert.deepEqual(nodes[3].target, ['#frm-baz', 'input']);
337
- });
338
-
339
- it('should work on shadow DOM iframes', async () => {
340
- await page.goto(`${addr}/external/shadow-frames.html`);
341
- const { violations } = await new AxeBuilder({ page })
342
- .options({ runOnly: 'label' })
343
- .analyze();
344
-
345
- assert.equal(violations[0].id, 'label');
346
- assert.lengthOf(violations[0].nodes, 3);
347
-
348
- const nodes = violations[0].nodes;
349
- assert.deepEqual(nodes[0].target, ['#light-frame', 'input']);
350
- assert.deepEqual(nodes[1].target, [
351
- ['#shadow-root', '#shadow-frame'],
352
- 'input'
353
- ]);
354
- assert.deepEqual(nodes[2].target, ['#slotted-frame', 'input']);
355
- });
356
-
357
- it('injects once per iframe', async () => {
358
- await page.goto(`${addr}/external/nested-iframes.html`);
359
-
360
- const builder = new AxeBuilder({ page });
361
- const origScript = (builder as any).script;
362
- let count = 0;
363
- Object.defineProperty(builder, 'script', {
364
- get() {
365
- count++;
366
- return origScript;
367
- }
368
- });
369
-
370
- await builder.analyze();
371
-
372
- assert.strictEqual(count, 9);
373
- });
374
- });
375
-
376
- describe('include/exclude', () => {
377
- const flatPassesTargets = (results: AxeResults): string[] => {
378
- return results.passes
379
- .reduce((acc, pass) => {
380
- return acc.concat(pass.nodes as any);
381
- }, [])
382
- .reduce((acc, node: any) => {
383
- return acc.concat(node.target);
384
- }, []);
385
- };
386
- it('with include and exclude', async () => {
387
- await page.goto(`${addr}/context.html`);
388
- const results = await new AxeBuilder({ page })
389
- .include('.include')
390
- .exclude('.exclude')
391
- .analyze();
392
- const flattenTarget = flatPassesTargets(results);
393
-
394
- assert.strictEqual(flattenTarget[0], '.include');
395
- assert.notInclude(flattenTarget, '.exclude');
396
- });
397
-
398
- it('with only include', async () => {
399
- await page.goto(`${addr}/context.html`);
400
- const results = await new AxeBuilder({ page })
401
- .include('.include')
402
- .analyze();
403
- const flattenTarget = flatPassesTargets(results);
404
- assert.strictEqual(flattenTarget[0], '.include');
405
- });
406
-
407
- it('with only exclude', async () => {
408
- await page.goto(`${addr}/context.html`);
409
- const results = await new AxeBuilder({ page })
410
- .exclude('.exclude')
411
- .analyze();
412
- const flattenTarget = flatPassesTargets(results);
413
-
414
- assert.notInclude(flattenTarget, '.exclude');
415
- });
416
-
417
- it('with chaining includes', async () => {
418
- await page.goto(`${addr}/context.html`);
419
-
420
- const results = await new AxeBuilder({ page })
421
- .include('.include')
422
- .include('.include2')
423
- .analyze();
424
- const flattenTarget = flatPassesTargets(results);
425
-
426
- assert.strictEqual(flattenTarget[0], '.include');
427
- assert.strictEqual(flattenTarget[1], '.include2');
428
- assert.notInclude(flattenTarget, '.exclude');
429
- assert.notInclude(flattenTarget, '.exclude2');
430
- });
431
-
432
- it('with chaining excludes', async () => {
433
- await page.goto(`${addr}/context.html`);
434
- const results = await new AxeBuilder({ page })
435
- .exclude('.exclude')
436
- .exclude('.exclude2')
437
- .analyze();
438
- const flattenTarget = flatPassesTargets(results);
439
-
440
- assert.notInclude(flattenTarget, '.exclude');
441
- assert.notInclude(flattenTarget, '.exclude2');
442
- });
443
-
444
- it('with chaining includes and excludes', async () => {
445
- await page.goto(`${addr}/context.html`);
446
- const results = await new AxeBuilder({ page })
447
- .include('.include')
448
- .include('.include2')
449
- .exclude('.exclude')
450
- .exclude('.exclude2')
451
- .analyze();
452
- const flattenTarget = flatPassesTargets(results);
453
-
454
- assert.strictEqual(flattenTarget[0], '.include');
455
- assert.strictEqual(flattenTarget[1], '.include2');
456
- assert.notInclude(flattenTarget, '.exclude');
457
- assert.notInclude(flattenTarget, '.exclude2');
458
- });
459
-
460
- it('with include using an array of strings', async () => {
461
- await page.goto(`${addr}/context.html`);
462
- const expected = ['.selector-one', '.selector-two', '.selector-three'];
463
-
464
- const axeSource = `
465
- window.axe = {
466
- configure(){},
467
- run({ include }){
468
- return Promise.resolve({ include })
469
- }
470
- }
471
- `;
472
- const results = new AxeBuilder({ page, axeSource: axeSource }).include([
473
- '.selector-one',
474
- '.selector-two',
475
- '.selector-three'
476
- ]);
477
-
478
- const { include: actual } = (await results.analyze()) as any;
479
-
480
- assert.deepEqual(actual[0], expected);
481
- });
482
-
483
- it('with exclude using an array of strings', async () => {
484
- await page.goto(`${addr}/context.html`);
485
- const expected = ['.selector-one', '.selector-two', '.selector-three'];
486
-
487
- const axeSource = `
488
- window.axe = {
489
- configure(){},
490
- run({ exclude }){
491
- return Promise.resolve({ exclude })
492
- }
493
- }
494
- `;
495
- const results = new AxeBuilder({ page, axeSource: axeSource }).exclude([
496
- '.selector-one',
497
- '.selector-two',
498
- '.selector-three'
499
- ]);
500
-
501
- const { exclude: actual } = (await results.analyze()) as any;
502
-
503
- assert.deepEqual(actual[0], expected);
504
- });
505
- });
506
-
507
- describe('axe.finishRun errors', () => {
508
- const finishRunThrows = `;axe.finishRun = () => { throw new Error("No finishRun")}`;
509
-
510
- it('throws an error if axe.finishRun throws', async () => {
511
- await page.goto(`${addr}/external/index.html`);
512
- // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
513
- //@ts-ignore
514
- delete page.context().newPage;
515
- // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
516
- //@ts-ignore
517
- page.context().newPage = () => {
518
- return null;
519
- };
520
-
521
- try {
522
- await new AxeBuilder({
523
- page,
524
- axeSource: axeSource
525
- }).analyze();
526
- assert.fail('Should have thrown');
527
- } catch (err) {
528
- assert.match(
529
- (err as Error).message,
530
- /Please make sure that you have popup blockers disabled./
531
- );
532
- }
533
- });
534
-
535
- it('throws an error if axe.finishRun throws', async () => {
536
- await page.goto(`${addr}/external/index.html`);
537
-
538
- try {
539
- await new AxeBuilder({
540
- page,
541
- axeSource: axeSource + finishRunThrows
542
- }).analyze();
543
- assert.fail('Should have thrown');
544
- } catch (err) {
545
- assert.match((err as Error).message, /Please check out/);
546
- }
547
- });
548
- });
549
-
550
- describe('setLegacyMode', () => {
551
- const runPartialThrows = `;axe.runPartial = () => { throw new Error("No runPartial")}`;
552
- it('runs legacy mode when used', async () => {
553
- await page.goto(`${addr}/external/index.html`);
554
- const results = await new AxeBuilder({
555
- page,
556
- axeSource: axeSource + runPartialThrows
557
- })
558
- .setLegacyMode()
559
- .analyze();
560
- assert.isNotNull(results);
561
- });
562
-
563
- it('prevents cross-origin frame testing', async () => {
564
- await page.goto(`${addr}/external/cross-origin.html`);
565
- const results = await new AxeBuilder({
566
- page,
567
- axeSource: axeSource + runPartialThrows
568
- })
569
- .withRules('frame-tested')
570
- .setLegacyMode()
571
- .analyze();
572
-
573
- const frameTested = results.incomplete.find(
574
- ({ id }) => id === 'frame-tested'
575
- );
576
- assert.ok(frameTested);
577
- });
578
-
579
- it('can be disabled again', async () => {
580
- await page.goto(`${addr}/external/cross-origin.html`);
581
- const results = await new AxeBuilder({ page })
582
- .withRules('frame-tested')
583
- .setLegacyMode()
584
- .setLegacyMode(false)
585
- .analyze();
586
-
587
- const frameTested = results.incomplete.find(
588
- ({ id }) => id === 'frame-tested'
589
- );
590
- assert.isUndefined(frameTested);
591
- });
592
- });
593
-
594
- describe('for versions without axe.runPartial', () => {
595
- describe('analyze', () => {
596
- it('returns results', async () => {
597
- await page.goto(`${addr}/external/index.html`);
598
- const results = await new AxeBuilder({
599
- page,
600
- axeSource: axeLegacySource
601
- }).analyze();
602
- assert.strictEqual(results.testEngine.version, '4.0.3');
603
- assert.isNotNull(results);
604
- assert.isArray(results.violations);
605
- assert.isArray(results.incomplete);
606
- assert.isArray(results.passes);
607
- assert.isArray(results.inapplicable);
608
- });
609
-
610
- it('throws if axe errors out on the top window', async () => {
611
- let error: Error | null = null;
612
- await page.goto(`${addr}/external/crash.html`);
613
- try {
614
- await new AxeBuilder({
615
- page,
616
- axeSource: axeLegacySource + axeCrasherSource
617
- }).analyze();
618
- } catch (e) {
619
- error = e as Error;
620
- }
621
- assert.isNotNull(error);
622
- });
623
-
624
- it('tests cross-origin pages', async () => {
625
- await page.goto(`${addr}/external/cross-origin.html`);
626
- const results = await new AxeBuilder({
627
- page,
628
- axeSource: axeLegacySource
629
- })
630
- .withRules('frame-tested')
631
- .analyze();
632
-
633
- const frameTested = results.incomplete.find(
634
- ({ id }) => id === 'frame-tested'
635
- );
636
- assert.isUndefined(frameTested);
637
- });
638
- });
639
-
640
- describe('frame tests', () => {
641
- it('injects into nested iframes', async () => {
642
- await page.goto(`${addr}/external/nested-iframes.html`);
643
- const { violations } = await new AxeBuilder({
644
- page,
645
- axeSource: axeLegacySource
646
- })
647
- .withRules('label')
648
- .analyze();
649
-
650
- assert.equal(violations[0].id, 'label');
651
- const nodes = violations[0].nodes;
652
- assert.lengthOf(nodes, 4);
653
- assert.deepEqual(nodes[0].target, [
654
- '#ifr-foo',
655
- '#foo-bar',
656
- '#bar-baz',
657
- 'input'
658
- ]);
659
- assert.deepEqual(nodes[1].target, ['#ifr-foo', '#foo-baz', 'input']);
660
- assert.deepEqual(nodes[2].target, ['#ifr-bar', '#bar-baz', 'input']);
661
- assert.deepEqual(nodes[3].target, ['#ifr-baz', 'input']);
662
- });
663
-
664
- it('injects into nested frameset', async () => {
665
- await page.goto(`${addr}/external/nested-frameset.html`);
666
- const { violations } = await new AxeBuilder({
667
- page,
668
- axeSource: axeLegacySource
669
- })
670
- .withRules('label')
671
- .analyze();
672
-
673
- assert.equal(violations[0].id, 'label');
674
- assert.lengthOf(violations[0].nodes, 4);
675
-
676
- const nodes = violations[0].nodes;
677
- assert.deepEqual(nodes[0].target, [
678
- '#frm-foo',
679
- '#foo-bar',
680
- '#bar-baz',
681
- 'input'
682
- ]);
683
- assert.deepEqual(nodes[1].target, ['#frm-foo', '#foo-baz', 'input']);
684
- assert.deepEqual(nodes[2].target, ['#frm-bar', '#bar-baz', 'input']);
685
- assert.deepEqual(nodes[3].target, ['#frm-baz', 'input']);
686
- });
687
-
688
- it('should work on shadow DOM iframes', async () => {
689
- await page.goto(`${addr}/external/shadow-frames.html`);
690
- const { violations } = await new AxeBuilder({
691
- page,
692
- axeSource: axeLegacySource
693
- })
694
- .withRules('label')
695
- .analyze();
696
-
697
- assert.equal(violations[0].id, 'label');
698
- assert.lengthOf(violations[0].nodes, 3);
699
-
700
- const nodes = violations[0].nodes;
701
- assert.deepEqual(nodes[0].target, ['#light-frame', 'input']);
702
- assert.deepEqual(nodes[1].target, ['#slotted-frame', 'input']);
703
- assert.deepEqual(nodes[2].target, [
704
- ['#shadow-root', '#shadow-frame'],
705
- 'input'
706
- ]);
707
- });
708
- });
709
- });
710
- });
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Context Test</title>
5
- </head>
6
- <body>
7
- <h1>Context Test</h1>
8
- <div class="include">include me</div>
9
- <div class="include2">include me</div>
10
- <div class="exclude">exclude me</div>
11
- <div class="exclude2">exclude me</div>
12
- </body>
13
- </html>
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "moduleResolution": "node",
4
- "target": "es5",
5
- "module": "commonjs",
6
- "lib": ["dom", "es2019"],
7
- "strict": true,
8
- "pretty": true,
9
- "sourceMap": true,
10
- "declaration": true,
11
- "outDir": "dist",
12
- "typeRoots": ["node_modules/@types"]
13
- },
14
- "include": ["src"]
15
- }