@axe-core/playwright 4.3.3-alpha.243 → 4.3.3
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/README.md +2 -24
- package/dist/AxePartialRunner.js +7 -11
- package/dist/AxePartialRunner.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +40 -18
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/error-handling.md +43 -0
- package/example.js +12 -0
- package/package.json +7 -10
- package/src/AxePartialRunner.ts +51 -0
- package/src/browser.ts +43 -0
- package/src/index.ts +308 -0
- package/src/types.ts +43 -0
- package/src/utils.ts +50 -0
- package/tests/axe-playwright.spec.ts +664 -0
- package/tests/fixtures/context.html +11 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,664 @@
|
|
|
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.2.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;
|
|
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;
|
|
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
|
+
|
|
418
|
+
describe('axe.finishRun errors', () => {
|
|
419
|
+
const finishRunThrows = `;axe.finishRun = () => { throw new Error("No finishRun")}`;
|
|
420
|
+
|
|
421
|
+
it('throws an error if axe.finishRun throws', async () => {
|
|
422
|
+
await page.goto(`${addr}/external/index.html`);
|
|
423
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
|
424
|
+
//@ts-ignore
|
|
425
|
+
delete page.context().newPage;
|
|
426
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
|
427
|
+
//@ts-ignore
|
|
428
|
+
page.context().newPage = () => {
|
|
429
|
+
return null;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
await new AxeBuilder({
|
|
434
|
+
page,
|
|
435
|
+
axeSource: axeSource
|
|
436
|
+
}).analyze();
|
|
437
|
+
assert.fail('Should have thrown');
|
|
438
|
+
} catch (err) {
|
|
439
|
+
assert.match(
|
|
440
|
+
err.message,
|
|
441
|
+
/Please make sure that you have popup blockers disabled./
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('throws an error if axe.finishRun throws', async () => {
|
|
447
|
+
await page.goto(`${addr}/external/index.html`);
|
|
448
|
+
|
|
449
|
+
try {
|
|
450
|
+
await new AxeBuilder({
|
|
451
|
+
page,
|
|
452
|
+
axeSource: axeSource + finishRunThrows
|
|
453
|
+
}).analyze();
|
|
454
|
+
assert.fail('Should have thrown');
|
|
455
|
+
} catch (err) {
|
|
456
|
+
assert.match(err.message, /Please check out/);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
describe('setLegacyMode', () => {
|
|
462
|
+
const runPartialThrows = `;axe.runPartial = () => { throw new Error("No runPartial")}`;
|
|
463
|
+
it('runs legacy mode when used', async () => {
|
|
464
|
+
await page.goto(`${addr}/external/index.html`);
|
|
465
|
+
const results = await new AxeBuilder({
|
|
466
|
+
page,
|
|
467
|
+
axeSource: axeSource + runPartialThrows
|
|
468
|
+
})
|
|
469
|
+
.setLegacyMode()
|
|
470
|
+
.analyze();
|
|
471
|
+
assert.isNotNull(results);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('prevents cross-origin frame testing', async () => {
|
|
475
|
+
await page.goto(`${addr}/external/cross-origin.html`);
|
|
476
|
+
const results = await new AxeBuilder({
|
|
477
|
+
page,
|
|
478
|
+
axeSource: axeSource + runPartialThrows
|
|
479
|
+
})
|
|
480
|
+
.withRules('frame-tested')
|
|
481
|
+
.setLegacyMode()
|
|
482
|
+
.analyze();
|
|
483
|
+
|
|
484
|
+
const frameTested = results.incomplete.find(
|
|
485
|
+
({ id }) => id === 'frame-tested'
|
|
486
|
+
);
|
|
487
|
+
assert.ok(frameTested);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('can be disabled again', async () => {
|
|
491
|
+
await page.goto(`${addr}/external/cross-origin.html`);
|
|
492
|
+
const results = await new AxeBuilder({ page })
|
|
493
|
+
.withRules('frame-tested')
|
|
494
|
+
.setLegacyMode()
|
|
495
|
+
.setLegacyMode(false)
|
|
496
|
+
.analyze();
|
|
497
|
+
|
|
498
|
+
const frameTested = results.incomplete.find(
|
|
499
|
+
({ id }) => id === 'frame-tested'
|
|
500
|
+
);
|
|
501
|
+
assert.isUndefined(frameTested);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe('for versions without axe.runPartial', () => {
|
|
506
|
+
describe('analyze', () => {
|
|
507
|
+
it('returns results', async () => {
|
|
508
|
+
await page.goto(`${addr}/external/index.html`);
|
|
509
|
+
const results = await new AxeBuilder({
|
|
510
|
+
page,
|
|
511
|
+
axeSource: axeLegacySource
|
|
512
|
+
}).analyze();
|
|
513
|
+
assert.strictEqual(results.testEngine.version, '4.2.3');
|
|
514
|
+
assert.isNotNull(results);
|
|
515
|
+
assert.isArray(results.violations);
|
|
516
|
+
assert.isArray(results.incomplete);
|
|
517
|
+
assert.isArray(results.passes);
|
|
518
|
+
assert.isArray(results.inapplicable);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('throws if axe errors out on the top window', async () => {
|
|
522
|
+
let error: Error | null = null;
|
|
523
|
+
await page.goto(`${addr}/external/crash.html`);
|
|
524
|
+
try {
|
|
525
|
+
await new AxeBuilder({
|
|
526
|
+
page,
|
|
527
|
+
axeSource: axeLegacySource + axeCrasherSource
|
|
528
|
+
}).analyze();
|
|
529
|
+
} catch (e) {
|
|
530
|
+
error = e;
|
|
531
|
+
}
|
|
532
|
+
assert.isNotNull(error);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('tests cross-origin pages', async () => {
|
|
536
|
+
await page.goto(`${addr}/external/cross-origin.html`);
|
|
537
|
+
const results = await new AxeBuilder({
|
|
538
|
+
page,
|
|
539
|
+
axeSource: axeLegacySource
|
|
540
|
+
})
|
|
541
|
+
.withRules('frame-tested')
|
|
542
|
+
.analyze();
|
|
543
|
+
|
|
544
|
+
const frameTested = results.incomplete.find(
|
|
545
|
+
({ id }) => id === 'frame-tested'
|
|
546
|
+
);
|
|
547
|
+
assert.isUndefined(frameTested);
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
describe('frame tests', () => {
|
|
552
|
+
it('injects into nested iframes', async () => {
|
|
553
|
+
await page.goto(`${addr}/external/nested-iframes.html`);
|
|
554
|
+
const { violations } = await new AxeBuilder({
|
|
555
|
+
page,
|
|
556
|
+
axeSource: axeLegacySource
|
|
557
|
+
})
|
|
558
|
+
.withRules('label')
|
|
559
|
+
.analyze();
|
|
560
|
+
|
|
561
|
+
assert.equal(violations[0].id, 'label');
|
|
562
|
+
const nodes = violations[0].nodes;
|
|
563
|
+
assert.lengthOf(nodes, 4);
|
|
564
|
+
assert.deepEqual(nodes[0].target, [
|
|
565
|
+
'#ifr-foo',
|
|
566
|
+
'#foo-bar',
|
|
567
|
+
'#bar-baz',
|
|
568
|
+
'input'
|
|
569
|
+
]);
|
|
570
|
+
assert.deepEqual(nodes[1].target, ['#ifr-foo', '#foo-baz', 'input']);
|
|
571
|
+
assert.deepEqual(nodes[2].target, ['#ifr-bar', '#bar-baz', 'input']);
|
|
572
|
+
assert.deepEqual(nodes[3].target, ['#ifr-baz', 'input']);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('injects into nested frameset', async () => {
|
|
576
|
+
await page.goto(`${addr}/external/nested-frameset.html`);
|
|
577
|
+
const { violations } = await new AxeBuilder({
|
|
578
|
+
page,
|
|
579
|
+
axeSource: axeLegacySource
|
|
580
|
+
})
|
|
581
|
+
.withRules('label')
|
|
582
|
+
.analyze();
|
|
583
|
+
|
|
584
|
+
assert.equal(violations[0].id, 'label');
|
|
585
|
+
assert.lengthOf(violations[0].nodes, 4);
|
|
586
|
+
|
|
587
|
+
const nodes = violations[0].nodes;
|
|
588
|
+
assert.deepEqual(nodes[0].target, [
|
|
589
|
+
'#frm-foo',
|
|
590
|
+
'#foo-bar',
|
|
591
|
+
'#bar-baz',
|
|
592
|
+
'input'
|
|
593
|
+
]);
|
|
594
|
+
assert.deepEqual(nodes[1].target, ['#frm-foo', '#foo-baz', 'input']);
|
|
595
|
+
assert.deepEqual(nodes[2].target, ['#frm-bar', '#bar-baz', 'input']);
|
|
596
|
+
assert.deepEqual(nodes[3].target, ['#frm-baz', 'input']);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('should work on shadow DOM iframes', async () => {
|
|
600
|
+
await page.goto(`${addr}/external/shadow-frames.html`);
|
|
601
|
+
const { violations } = await new AxeBuilder({
|
|
602
|
+
page,
|
|
603
|
+
axeSource: axeLegacySource
|
|
604
|
+
})
|
|
605
|
+
.withRules('label')
|
|
606
|
+
.analyze();
|
|
607
|
+
|
|
608
|
+
assert.equal(violations[0].id, 'label');
|
|
609
|
+
assert.lengthOf(violations[0].nodes, 3);
|
|
610
|
+
|
|
611
|
+
const nodes = violations[0].nodes;
|
|
612
|
+
assert.deepEqual(nodes[0].target, ['#light-frame', 'input']);
|
|
613
|
+
assert.deepEqual(nodes[1].target, [
|
|
614
|
+
['#shadow-root', '#shadow-frame'],
|
|
615
|
+
'input'
|
|
616
|
+
]);
|
|
617
|
+
assert.deepEqual(nodes[2].target, ['#slotted-frame', 'input']);
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
describe('allowedOrigins', () => {
|
|
623
|
+
const getAllowedOrigins = async (): Promise<string[]> => {
|
|
624
|
+
return await page.evaluate('axe._audit.allowedOrigins');
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
it('should not set when running runPartial and not legacy mode', async () => {
|
|
628
|
+
await page.goto(`${addr}/index.html`);
|
|
629
|
+
await new AxeBuilder({ page }).analyze();
|
|
630
|
+
const allowedOrigins = await getAllowedOrigins();
|
|
631
|
+
assert.deepEqual(allowedOrigins, [addr]);
|
|
632
|
+
assert.lengthOf(allowedOrigins, 1);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('should not set when running runPartial and legacy mode', async () => {
|
|
636
|
+
await page.goto(`${addr}/index.html`);
|
|
637
|
+
await new AxeBuilder({ page }).setLegacyMode(true).analyze();
|
|
638
|
+
const allowedOrigins = await getAllowedOrigins();
|
|
639
|
+
assert.deepEqual(allowedOrigins, [addr]);
|
|
640
|
+
assert.lengthOf(allowedOrigins, 1);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it('should not set when running legacy source and legacy mode', async () => {
|
|
644
|
+
await page.goto(`${addr}/index.html`);
|
|
645
|
+
await new AxeBuilder({ page, axeSource: axeLegacySource })
|
|
646
|
+
.setLegacyMode(true)
|
|
647
|
+
.analyze();
|
|
648
|
+
const allowedOrigins = await getAllowedOrigins();
|
|
649
|
+
assert.deepEqual(allowedOrigins, [addr]);
|
|
650
|
+
assert.lengthOf(allowedOrigins, 1);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it('should set when running legacy source and not legacy mode', async () => {
|
|
654
|
+
await page.goto(`${addr}/index.html`);
|
|
655
|
+
await new AxeBuilder({
|
|
656
|
+
page,
|
|
657
|
+
axeSource: axeLegacySource
|
|
658
|
+
}).analyze();
|
|
659
|
+
const allowedOrigins = await getAllowedOrigins();
|
|
660
|
+
assert.deepEqual(allowedOrigins, ['*']);
|
|
661
|
+
assert.lengthOf(allowedOrigins, 1);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|