@axe-core/cli 4.7.4-cbd2a5f.0 → 4.8.0

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.
Files changed (63) hide show
  1. package/.eslintrc.js +18 -0
  2. package/CHANGELOG.md +221 -0
  3. package/package.json +3 -7
  4. package/src/bin/cli.test.ts +420 -0
  5. package/src/bin/cli.ts +86 -0
  6. package/src/bin/index.ts +216 -0
  7. package/src/lib/axe-test-urls.test.ts +73 -0
  8. package/src/lib/axe-test-urls.ts +98 -0
  9. package/src/lib/events.test.ts +26 -0
  10. package/src/lib/events.ts +68 -0
  11. package/{dist/src/lib/index.d.ts → src/lib/index.ts} +1 -0
  12. package/src/lib/utils.test.ts +160 -0
  13. package/src/lib/utils.ts +147 -0
  14. package/src/lib/webdriver.test.ts +104 -0
  15. package/src/lib/webdriver.ts +43 -0
  16. package/src/testutils/axe-core@2.5.0.js +18929 -0
  17. package/src/testutils/index.ts +47 -0
  18. package/src/testutils/simple-clean.html +11 -0
  19. package/src/testutils/simple.html +12 -0
  20. package/src/types.ts +42 -0
  21. package/tsconfig.json +19 -0
  22. package/dist/package.json +0 -91
  23. package/dist/src/bin/cli.d.ts +0 -2
  24. package/dist/src/bin/cli.js +0 -40
  25. package/dist/src/bin/cli.js.map +0 -1
  26. package/dist/src/bin/cli.test.d.ts +0 -1
  27. package/dist/src/bin/cli.test.js +0 -259
  28. package/dist/src/bin/cli.test.js.map +0 -1
  29. package/dist/src/bin/index.d.ts +0 -5
  30. package/dist/src/bin/index.js +0 -168
  31. package/dist/src/bin/index.js.map +0 -1
  32. package/dist/src/lib/axe-test-urls.d.ts +0 -4
  33. package/dist/src/lib/axe-test-urls.js +0 -89
  34. package/dist/src/lib/axe-test-urls.js.map +0 -1
  35. package/dist/src/lib/axe-test-urls.test.d.ts +0 -1
  36. package/dist/src/lib/axe-test-urls.test.js +0 -73
  37. package/dist/src/lib/axe-test-urls.test.js.map +0 -1
  38. package/dist/src/lib/events.d.ts +0 -10
  39. package/dist/src/lib/events.js +0 -54
  40. package/dist/src/lib/events.js.map +0 -1
  41. package/dist/src/lib/events.test.d.ts +0 -1
  42. package/dist/src/lib/events.test.js +0 -31
  43. package/dist/src/lib/events.test.js.map +0 -1
  44. package/dist/src/lib/index.js +0 -36
  45. package/dist/src/lib/index.js.map +0 -1
  46. package/dist/src/lib/utils.d.ts +0 -15
  47. package/dist/src/lib/utils.js +0 -127
  48. package/dist/src/lib/utils.js.map +0 -1
  49. package/dist/src/lib/utils.test.d.ts +0 -1
  50. package/dist/src/lib/utils.test.js +0 -165
  51. package/dist/src/lib/utils.test.js.map +0 -1
  52. package/dist/src/lib/webdriver.d.ts +0 -4
  53. package/dist/src/lib/webdriver.js +0 -50
  54. package/dist/src/lib/webdriver.js.map +0 -1
  55. package/dist/src/lib/webdriver.test.d.ts +0 -1
  56. package/dist/src/lib/webdriver.test.js +0 -102
  57. package/dist/src/lib/webdriver.test.js.map +0 -1
  58. package/dist/src/testutils/index.d.ts +0 -19
  59. package/dist/src/testutils/index.js +0 -51
  60. package/dist/src/testutils/index.js.map +0 -1
  61. package/dist/src/types.d.ts +0 -37
  62. package/dist/src/types.js +0 -18
  63. package/dist/src/types.js.map +0 -1
@@ -0,0 +1,420 @@
1
+ import 'mocha';
2
+ import { assert } from 'chai';
3
+ import tempy from 'tempy';
4
+ import http from 'http';
5
+ import net from 'net';
6
+ import path from 'path';
7
+ import fs from 'fs';
8
+ import { version } from '../../package.json';
9
+ import runCLI from '../testutils/';
10
+
11
+ const SIMPLE_HTML_FILE = path.join(__dirname, '..', 'testutils', 'simple.html');
12
+ const SIMPLE_CLEAN_HTML_FILE = path.join(
13
+ __dirname,
14
+ '..',
15
+ 'testutils',
16
+ 'simple-clean.html'
17
+ );
18
+ const SIMPLE_HTML_SOURCE = fs.readFileSync(SIMPLE_HTML_FILE, 'utf8');
19
+ const PATH_TO_AXE_250 = path.resolve(
20
+ __dirname,
21
+ '..',
22
+ 'testutils',
23
+ 'axe-core@2.5.0.js'
24
+ );
25
+
26
+ describe('cli', () => {
27
+ it('--help', async () => {
28
+ const result = await runCLI('--help');
29
+ assert.equal(result.exitCode, 0);
30
+ assert.include(result.stdout, 'Options:');
31
+ });
32
+
33
+ it('--version', async () => {
34
+ const result = await runCLI('--version');
35
+ assert.equal(result.exitCode, 0);
36
+ assert.deepEqual(result.stdout, version);
37
+ });
38
+
39
+ describe('given a file:// url', () => {
40
+ it('should run an analysis', async () => {
41
+ const result = await runCLI(`file://${SIMPLE_HTML_FILE}`);
42
+ assert.equal(result.exitCode, 0);
43
+ assert.include(
44
+ result.stdout,
45
+ 'Violation of "marquee" with 1 occurrences!'
46
+ );
47
+ });
48
+ });
49
+
50
+ describe('given a http:// url', () => {
51
+ let port: number;
52
+ let server: http.Server;
53
+ before(done => {
54
+ server = http.createServer((req, res) => {
55
+ res.setHeader('Content-Type', 'text/html');
56
+ res.write(SIMPLE_HTML_SOURCE);
57
+ res.end();
58
+ });
59
+ server.listen(0, () => {
60
+ port = (server.address() as net.AddressInfo).port;
61
+ done();
62
+ });
63
+ });
64
+
65
+ after(done => server.close(done));
66
+
67
+ it('should run an analysis', async () => {
68
+ const result = await runCLI(`http://127.0.0.1:${port}/`);
69
+ assert.equal(result.exitCode, 0);
70
+ assert.include(
71
+ result.stdout,
72
+ 'Violation of "marquee" with 1 occurrences!'
73
+ );
74
+ });
75
+ });
76
+
77
+ describe('--axe-source', () => {
78
+ it('should use the provided version of axe-core', async () => {
79
+ const result = await runCLI(
80
+ `file://${SIMPLE_HTML_FILE}`,
81
+ '--axe-source',
82
+ PATH_TO_AXE_250
83
+ );
84
+
85
+ assert.equal(result.exitCode, 0);
86
+ assert.include(
87
+ result.stdout,
88
+ 'Violation of "marquee" with 1 occurrences!'
89
+ );
90
+ assert.include(result.stdout, 'Running axe-core 2.5.0');
91
+ });
92
+
93
+ it('error when given invalid axe source path', async () => {
94
+ const result = await runCLI(
95
+ `file://${SIMPLE_HTML_FILE}`,
96
+ '--axe-source',
97
+ 'foobar'
98
+ );
99
+
100
+ assert.equal(result.exitCode, 2);
101
+ assert.include(result.stderr, 'Unable to find the axe-core source file');
102
+ });
103
+ });
104
+
105
+ // cannot run in ci we _should_ have the ability to add arguments to firefox not just chrome to allow users to run this headless
106
+ describe.skip('--browser', () => {
107
+ it('should change the browser', async () => {
108
+ const result = await runCLI(
109
+ `file://${SIMPLE_HTML_FILE}`,
110
+ '--browser',
111
+ 'firefox',
112
+ '--verbose'
113
+ );
114
+ assert.equal(result.exitCode, 0);
115
+ assert.include(result.stdout, 'Firefox');
116
+ });
117
+ });
118
+
119
+ describe('--rules', () => {
120
+ it('should only run the rules with the provided IDs', async () => {
121
+ const result = await runCLI(
122
+ `file://${SIMPLE_HTML_FILE}`,
123
+ '--rules',
124
+ 'region'
125
+ );
126
+ assert.equal(result.exitCode, 0);
127
+ assert.include(result.stdout, 'Violation of "region" with');
128
+ });
129
+ });
130
+
131
+ describe('--tags', () => {
132
+ it('should only run rules with the provided tags', async () => {
133
+ const result = await runCLI(
134
+ `file://${SIMPLE_HTML_FILE}`,
135
+ '--tags',
136
+ 'cat.parsing,wcag222'
137
+ );
138
+ assert.equal(result.exitCode, 0);
139
+ // Region is tagged with "cat.keyboard", "best-practice"
140
+ assert.notInclude(result.stdout, 'Violation of "region" with');
141
+ });
142
+ });
143
+
144
+ describe('--exit', () => {
145
+ it('should exit non-zero if violations are found', async () => {
146
+ try {
147
+ await runCLI(`file://${SIMPLE_HTML_FILE}`, '--exit');
148
+ } catch (error) {
149
+ assert.equal(error.exitCode, 1);
150
+ assert.include(
151
+ error.stdout,
152
+ 'Violation of "marquee" with 1 occurrences!'
153
+ );
154
+ }
155
+ });
156
+
157
+ it('should exit zero if violations are found', async () => {
158
+ try {
159
+ await runCLI(`file://${SIMPLE_CLEAN_HTML_FILE}`, '--exit');
160
+ } catch (error) {
161
+ assert.equal(error.exitCode, 0);
162
+ assert.include(
163
+ error.stdout,
164
+ 'Violation of "marquee" with 1 occurrences!'
165
+ );
166
+ }
167
+ });
168
+ });
169
+
170
+ describe('--dir', () => {
171
+ let reportDir: string;
172
+ beforeEach(() => {
173
+ reportDir = tempy.directory();
174
+ });
175
+
176
+ it('should save a JSON report to the provided directory', async () => {
177
+ const result = await runCLI(
178
+ `file://${SIMPLE_HTML_FILE}`,
179
+ '--dir',
180
+ reportDir
181
+ );
182
+
183
+ assert.equal(result.exitCode, 0);
184
+ const files = fs.readdirSync(reportDir);
185
+ const report = files.find(f => f.endsWith('.json'));
186
+ assert(report, 'Did not create JSON report');
187
+ });
188
+ });
189
+
190
+ describe('--include', () => {
191
+ it('should set a list of elements to include', async () => {
192
+ const result = await runCLI(
193
+ `file://${SIMPLE_HTML_FILE}`,
194
+ '--include',
195
+ 'marquee'
196
+ );
197
+ assert.notInclude(result.stdout, 'Violation of "region"');
198
+ assert.include(
199
+ result.stdout,
200
+ 'Violation of "marquee" with 1 occurrences!'
201
+ );
202
+ });
203
+
204
+ it('should throw error if CSS selector is not found', async () => {
205
+ const result = await runCLI(
206
+ `file://${SIMPLE_HTML_FILE}`,
207
+ '--include',
208
+ '#hazaar',
209
+ '--show-errors'
210
+ );
211
+
212
+ assert.include(
213
+ result.stderr,
214
+ 'javascript error: No elements found for include in page Context'
215
+ );
216
+ assert.equal(result.exitCode, 1);
217
+ });
218
+
219
+ it('should throw error if invalid selector is provided', async () => {
220
+ const result = await runCLI(
221
+ `file://${SIMPLE_HTML_FILE}`,
222
+ '--include',
223
+ '#123',
224
+ '--show-errors'
225
+ );
226
+
227
+ assert.include(result.stderr, 'is not a valid selector');
228
+ assert.equal(result.exitCode, 1);
229
+ });
230
+ });
231
+
232
+ describe('--exclude', () => {
233
+ it('should set a list of elements to exclude', async () => {
234
+ const result = await runCLI(
235
+ `file://${SIMPLE_HTML_FILE}`,
236
+ '--exclude',
237
+ 'marquee'
238
+ );
239
+ assert.notInclude(
240
+ result.stdout,
241
+ 'Violation of "marquee" with 1 occurrences!'
242
+ );
243
+ });
244
+
245
+ it('should throw error if invalid selector is provided', async () => {
246
+ const result = await runCLI(
247
+ `file://${SIMPLE_HTML_FILE}`,
248
+ '--exclude',
249
+ '#123',
250
+ '--show-errors'
251
+ );
252
+
253
+ assert.include(result.stderr, 'is not a valid selector');
254
+ assert.equal(result.exitCode, 1);
255
+ });
256
+ });
257
+
258
+ describe('--disable', () => {
259
+ it('should not run rules with the provided IDs', async () => {
260
+ const result = await runCLI(
261
+ `file://${SIMPLE_HTML_FILE}`,
262
+ '--disable',
263
+ 'region'
264
+ );
265
+ assert.notInclude(result.stdout, 'Violation of "region" with');
266
+ });
267
+ });
268
+
269
+ describe('--stdout', () => {
270
+ it('should only emit JSON to stdout', async () => {
271
+ const result = await runCLI(`file://${SIMPLE_HTML_FILE}`, '--stdout');
272
+ assert.equal(result.exitCode, 0);
273
+ assert.doesNotThrow(
274
+ () => JSON.parse(result.stdout),
275
+ 'Emitted invalid JSON'
276
+ );
277
+ });
278
+ });
279
+
280
+ describe('--timer', () => {
281
+ it('should log the time it takes to run', async () => {
282
+ const result = await runCLI(`file://${SIMPLE_HTML_FILE}`, '--timer');
283
+ assert.equal(result.exitCode, 0);
284
+ assert.isEmpty(result.stderr);
285
+ assert.include(result.stdout, 'axe-core execution time');
286
+ assert.include(result.stdout, 'Total test time');
287
+ });
288
+ });
289
+
290
+ describe('--no-reporter', () => {
291
+ it('should log the time it takes to run', async () => {
292
+ const result = await runCLI(
293
+ `file://${SIMPLE_HTML_FILE}`,
294
+ '--no-reporter'
295
+ );
296
+ assert.equal(result.exitCode, 0);
297
+ assert.notInclude(
298
+ result.stdout,
299
+ 'Violation of "marquee" with 1 occurrences!'
300
+ );
301
+ });
302
+ });
303
+
304
+ describe('--show-errors', () => {
305
+ it('should log the time it takes to run defaults to show errors', async () => {
306
+ const result = await runCLI(
307
+ `file://${SIMPLE_HTML_FILE}`,
308
+ '--include',
309
+ '#hazaar'
310
+ );
311
+ assert.equal(result.exitCode, 1);
312
+ assert.include(
313
+ result.stderr,
314
+ 'Error: JavascriptError: javascript error:'
315
+ );
316
+ });
317
+
318
+ it('do not show errors when passed false', async () => {
319
+ const result = await runCLI(
320
+ `file://${SIMPLE_HTML_FILE}`,
321
+ '--include',
322
+ '#hazaar',
323
+ '--show-errors',
324
+ 'false'
325
+ );
326
+ assert.equal(result.exitCode, 1);
327
+ assert.include(
328
+ result.stderr,
329
+ 'An error occurred while testing this page.'
330
+ );
331
+ });
332
+ });
333
+
334
+ describe('--save', () => {
335
+ let reportDir: string;
336
+ beforeEach(() => {
337
+ reportDir = tempy.directory();
338
+ });
339
+
340
+ it('should save the output as a JSON file', async () => {
341
+ const result = await runCLI(
342
+ `file://${SIMPLE_HTML_FILE}`,
343
+ '--save',
344
+ 'test-name.json',
345
+ '--dir',
346
+ reportDir
347
+ );
348
+ const [report] = fs.readdirSync(reportDir);
349
+ assert.equal(result.exitCode, 0);
350
+ assert.equal(report, 'test-name.json');
351
+ });
352
+ });
353
+
354
+ describe('--load-delay', () => {
355
+ it('should set how much time axe will wait after a page loads before running the audit', async () => {
356
+ const result = await runCLI(
357
+ `file://${SIMPLE_HTML_FILE}`,
358
+ '--load-delay',
359
+ '1000'
360
+ );
361
+ assert.equal(result.exitCode, 0);
362
+ assert.include(
363
+ result.stdout,
364
+ 'Waiting for 1000 milliseconds after page load'
365
+ );
366
+ });
367
+ });
368
+
369
+ describe('--verbose', () => {
370
+ it('should output metadata such as test tool name, version and environment', async () => {
371
+ const result = await runCLI(`file://${SIMPLE_HTML_FILE}`, '--verbose');
372
+ assert.equal(result.exitCode, 0);
373
+ assert.include(result.stdout, 'Test Runner');
374
+ assert.include(result.stdout, 'Test Engine');
375
+ assert.include(result.stdout, 'Test Environment');
376
+ });
377
+ });
378
+
379
+ describe('--timeout', () => {
380
+ // Timeout the page immediately. Ideally we'd block the page for awhile, then timeout based on that. This seemed easier for now tho.
381
+ it('should set the page load timeout', async () => {
382
+ try {
383
+ await runCLI(`file://${SIMPLE_HTML_FILE}`, '--timeout', '0');
384
+ } catch (error) {
385
+ assert.notEqual(error.exitCode, 0);
386
+ assert.include(
387
+ error.stderr,
388
+ 'An error occurred while testing this page.'
389
+ );
390
+ }
391
+ });
392
+ });
393
+
394
+ // disabled during conversion to npm workspaces as the node_module install directory changed
395
+ // @see https://github.com/dequelabs/axe-core-npm/issues/822
396
+ describe.skip('--chromedriver-path', () => {
397
+ it('should throw error if path does not exist', async () => {
398
+ const result = await runCLI(
399
+ `file://${SIMPLE_HTML_FILE}`,
400
+ '--chromedriver-path="someinvalidpath"',
401
+ '--show-errors'
402
+ );
403
+ assert.include(
404
+ result.stderr,
405
+ 'The specified executable path does not exist'
406
+ );
407
+ });
408
+ });
409
+
410
+ describe('--chrome-path', () => {
411
+ it('should throw error if path does not exist', async () => {
412
+ const result = await runCLI(
413
+ `file://${SIMPLE_HTML_FILE}`,
414
+ '--chrome-path="someinvalidpath"',
415
+ '--show-errors'
416
+ );
417
+ assert.include(result.stderr, 'no chrome binary at');
418
+ });
419
+ });
420
+ });
package/src/bin/cli.ts ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { version } from '../../package.json';
5
+ import { splitList } from '../lib/utils';
6
+ import cli from '.';
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .version(version)
12
+ .usage('<url...> [options]')
13
+ .option(
14
+ '-i, --include <list>',
15
+ 'CSS selector of included elements, comma separated',
16
+ splitList
17
+ )
18
+ .option(
19
+ '-e, --exclude <list>',
20
+ 'CSS selector of excluded elements, comma separated',
21
+ splitList
22
+ )
23
+ .option(
24
+ '-r, --rules <list>',
25
+ 'IDs of rules to run, comma separated',
26
+ splitList
27
+ )
28
+ .option(
29
+ '-t, --tags <list>',
30
+ 'Tags of rules to run, comma separated',
31
+ splitList
32
+ )
33
+ .option(
34
+ '-l, --disable <list>',
35
+ 'IDs of rules to disable, comma separated',
36
+ splitList
37
+ )
38
+ .option(
39
+ '-b, --browser [browser-name]',
40
+ 'Which browser to run (Webdriver required)'
41
+ )
42
+ .option(
43
+ '-s, --save [filename]',
44
+ 'Save the output as a JSON file. Filename is optional'
45
+ )
46
+ .option(
47
+ '-j, --stdout',
48
+ 'Output results to STDOUT and silence all other output'
49
+ )
50
+ .option('-d, --dir <path>', 'Output directory')
51
+ .option('-a, --axe-source <path>', 'Path to axe.js file')
52
+ .option('-q, --exit', 'Exit with `1` failure code if any a11y tests fail')
53
+ .option(
54
+ '-v, --verbose',
55
+ 'Output metadata like test tool name, version and environment'
56
+ )
57
+ .option(
58
+ '--load-delay <n>',
59
+ 'Set how much time (milliseconds) axe will wait after page load before running the audit (default: 0)'
60
+ )
61
+ .option(
62
+ '--timeout <n>',
63
+ 'Set how much time (seconds) axe has to run',
64
+ // @ts-ignore
65
+ 90
66
+ )
67
+ .option('--timer', 'Log the time it takes to run')
68
+ .option('--show-errors [boolean]', 'Display the full error stack', true)
69
+ // TODO: Replace this with a reporter option, this required adding
70
+ .option('--no-reporter', 'Turn the CLI reporter off')
71
+ .option(
72
+ '--chrome-options [options]',
73
+ 'Options to provide to headless Chrome',
74
+ splitList
75
+ )
76
+ .option(
77
+ '--chromedriver-path <path>',
78
+ 'Absolute path to the desired chromedriver executable'
79
+ )
80
+ .option(
81
+ '--chrome-path <path>',
82
+ 'Path to the desired chrome executable'
83
+ )
84
+ .action(cli);
85
+
86
+ program.parse(process.argv);