@axe-core/playwright 4.3.3-alpha.223 → 4.3.3-alpha.227
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 +5 -2
- package/error-handling.md +0 -43
- package/example.js +0 -12
- package/src/AxePartialRunner.ts +0 -51
- package/src/browser.ts +0 -43
- package/src/index.ts +0 -299
- package/src/types.ts +0 -43
- package/src/utils.ts +0 -50
- package/tests/axe-playwright.spec.ts +0 -710
- package/tests/fixtures/context.html +0 -13
- package/tsconfig.json +0 -15
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axe-core/playwright",
|
|
3
|
-
"version": "4.3.3-alpha.
|
|
3
|
+
"version": "4.3.3-alpha.227+9f1fa5d",
|
|
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": "
|
|
83
|
+
"gitHead": "9f1fa5d8cb7c2c87bd6e92fe7b13b961e3d73f37"
|
|
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
|
-
})();
|
package/src/AxePartialRunner.ts
DELETED
|
@@ -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
|
-
}
|