testaro 53.1.4 → 53.2.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.
package/netWatch.js CHANGED
@@ -31,8 +31,6 @@
31
31
 
32
32
  // Module to keep secrets.
33
33
  require('dotenv').config();
34
- // Module to perform file operations.
35
- const fs = require('fs/promises');
36
34
  // Module to validate jobs.
37
35
  const {isValidJob} = require('./procs/job');
38
36
  // Modules to make requests to servers.
@@ -65,14 +63,8 @@ const serveObject = (object, response) => {
65
63
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
66
64
  response.end(JSON.stringify(object));
67
65
  };
68
- // Removes the query, if any, or otherwise the final segment, from a URL.
66
+ // Removes the query if any, or otherwise the final segment, from a URL.
69
67
  const getURLBase = url => url.replace(/[?/][^?/.]+$/, '');
70
- // Saves a report.
71
- const rescueReport = async reportJSON => {
72
- const reportDir = 'data/reports';
73
- await fs.mkdir(reportDir, { recursive: true });
74
- await fs.writeFile(reportDir, reportJSON);
75
- };
76
68
  /*
77
69
  Requests a network job and, when found, performs and reports it.
78
70
  Arguments:
@@ -161,7 +153,7 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
161
153
  console.log(`${logStart}no job to do`);
162
154
  resolve(true);
163
155
  }
164
- // Otherwise, i.e. if it is a job:
156
+ // Otherwise, if it is a job:
165
157
  else if (id) {
166
158
  // Check it for validity.
167
159
  const jobInvalidity = isValidJob(contentObj);
@@ -199,15 +191,12 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
199
191
  ? httpsClient
200
192
  : httpClient;
201
193
  const reportLogStart = `Submitted report ${id} to ${publicReportURL} and got `;
202
- const reportJSON = JSON.stringify(report, null, 2);
203
194
  // Send the report to the server that assigned the job.
204
195
  reportClient.request(reportURL, {method: 'POST'}, repResponse => {
205
196
  const chunks = [];
206
197
  repResponse
207
198
  // If the response to the report threw an error:
208
199
  .on('error', async error => {
209
- // Rescue the report.
210
- await rescueReport(reportJSON);
211
200
  // Report this.
212
201
  console.log(`${reportLogStart}error message ${error.message}\n`);
213
202
  resolve(true);
@@ -228,8 +217,6 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
228
217
  }
229
218
  // Otherwise, i.e. if it is not JSON:
230
219
  catch(error) {
231
- // Rescue the report.
232
- await rescueReport(reportJSON);
233
220
  // Report this.
234
221
  console.log(
235
222
  `ERROR: ${reportLogStart}status ${repResponse.statusCode}, error message ${error.message}, and response ${content.slice(0, 1000)}\n`
@@ -243,8 +230,8 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
243
230
  })
244
231
  // If the report submission throws an error:
245
232
  .on('error', async error => {
246
- // Rescue the report.
247
- await rescueReport(reportJSON);
233
+ // Abort the watch.
234
+ abort = true;
248
235
  // Report this.
249
236
  console.log(
250
237
  `ERROR ${error.code} in report submission: ${reportLogStart}error message ${error.message}\n`
@@ -265,7 +252,7 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
265
252
  // Otherwise, i.e. if it is not JSON:
266
253
  catch(error) {
267
254
  // Report this.
268
- console.log(`ERROR: Job request got non-JSON response (${error.message})`);
255
+ console.log(`ERROR: ${logStart}status ${response.statusCode}, error message ${error.message}, and non-JSON response ${content.slice(0, 1000)}\n`);
269
256
  resolve(true);
270
257
  };
271
258
  });
@@ -286,11 +273,15 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
286
273
  else if (error.message) {
287
274
  // Report this.
288
275
  console.log(`ERROR: ${logStart}got error message ${error.message.slice(0, 200)}`);
276
+ // Abort the watch.
277
+ abort = true;
289
278
  }
290
279
  // Otherwise, i.e. if it was any other error with no message:
291
280
  else {
292
281
  // Report this.
293
282
  console.log(`ERROR: ${logStart}got an error with no message`);
283
+ // Abort the watch.
284
+ abort = true;
294
285
  }
295
286
  resolve(true);
296
287
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "53.1.4",
3
+ "version": "53.2.0",
4
4
  "description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -27,7 +27,10 @@
27
27
  },
28
28
  "homepage": "https://github.com/cvs-health/testaro#readme",
29
29
  "dependencies": {
30
- "@qualweb/core": "*",
30
+ "@qualweb/core": "0.0.0-develop-20241217135157",
31
+ "@qualweb/act-rules": "*",
32
+ "@qualweb/wcag-techniques": "*",
33
+ "@qualweb/best-practices": "*",
31
34
  "@siteimprove/alfa-act": "*",
32
35
  "@siteimprove/alfa-playwright": "*",
33
36
  "@siteimprove/alfa-rules": "*",
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2024 CVS Health and/or one of its affiliates. All rights reserved.
2
+ © 2024–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
3
 
4
4
  MIT License
5
5
 
@@ -47,10 +47,9 @@ const actIndex = Number.parseInt(process.argv[2]);
47
47
  // FUNCTIONS
48
48
 
49
49
  const doTestAct = async () => {
50
- const tempReportDir = `${__dirname}/../temp`;
51
- const tempReportPath = `${tempReportDir}/report.json`;
50
+ const reportPath = `${__dirname}/../temp/report.json`;
52
51
  // Get the saved report.
53
- const reportJSON = await fs.readFile(tempReportPath, 'utf8');
52
+ const reportJSON = await fs.readFile(reportPath, 'utf8');
54
53
  const report = JSON.parse(reportJSON);
55
54
  // Get the act.
56
55
  const act = report.acts[actIndex];
@@ -68,7 +67,7 @@ const doTestAct = async () => {
68
67
  if (report.jobData && report.jobData.aborted) {
69
68
  // Save the revised report.
70
69
  const reportJSON = JSON.stringify(report);
71
- await fs.writeFile(tempReportPath, `${reportJSON}\n`);
70
+ await fs.writeFile(reportPath, reportJSON);
72
71
  // Report this.
73
72
  process.send('ERROR: Job aborted');
74
73
  }
@@ -98,7 +97,7 @@ const doTestAct = async () => {
98
97
  }
99
98
  const reportJSON = JSON.stringify(report);
100
99
  // Save the revised report.
101
- await fs.writeFile(tempReportPath, reportJSON);
100
+ await fs.writeFile(reportPath, reportJSON);
102
101
  // Send a completion message.
103
102
  process.send('Act completed');
104
103
  }
@@ -107,7 +106,7 @@ const doTestAct = async () => {
107
106
  catch(error) {
108
107
  // Save the revised report.
109
108
  const reportJSON = JSON.stringify(report);
110
- await fs.writeFile(tempReportPath, reportJSON);
109
+ await fs.writeFile(reportPath, reportJSON);
111
110
  // Report the failure.
112
111
  const message = error.message.slice(0, 400);
113
112
  console.log(`ERROR: Test act ${act.which} failed (${message})`);
@@ -116,6 +115,7 @@ const doTestAct = async () => {
116
115
  }
117
116
  // Otherwise, i.e. if the page does not exist:
118
117
  else {
118
+ // Report this.
119
119
  process.send('ERROR: No page');
120
120
  }
121
121
  }
package/run.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
2
+ © 2021–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
3
 
4
4
  MIT License
5
5
 
@@ -166,7 +166,16 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
166
166
  };
167
167
  }
168
168
  }
169
- // Otherwise, i.e. if the response status was abnormal:
169
+ // Otherwise, if the response status was rejection of excessive requests:
170
+ else if (httpStatus === 429) {
171
+ // Return this.
172
+ console.log(`Visit to ${url} prevented by request frequency restriction (status 429)`);
173
+ return {
174
+ success: false,
175
+ error: 'status429'
176
+ };
177
+ }
178
+ // Otherwise, i.e. if the response status was otherwise abnormal:
170
179
  else {
171
180
  // Return an error.
172
181
  console.log(`ERROR: Visit to ${url} got status ${httpStatus}`);
@@ -197,6 +206,32 @@ const browserClose = async () => {
197
206
  browser = null;
198
207
  }
199
208
  };
209
+ // Adds an error result to an act.
210
+ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
211
+ // If the error is to be logged:
212
+ if (alsoLog) {
213
+ // Log it.
214
+ console.log(message);
215
+ }
216
+ // Add error data to the result.
217
+ const act = report.acts[actIndex];
218
+ act.result ??= {};
219
+ act.result.success ??= false;
220
+ act.result.error ??= message;
221
+ if (act.type === 'test') {
222
+ act.data ??= {};
223
+ act.data.success = false;
224
+ act.data.prevented = true;
225
+ act.data.error = message;
226
+ // Add prevention data to the job data.
227
+ report.jobData.preventions[act.which] = message;
228
+ }
229
+ // If the job is to be aborted:
230
+ if (alsoAbort) {
231
+ // Add this to the report.
232
+ abortActs(report, actIndex);
233
+ }
234
+ };
200
235
  // Launches a browser and navigates to a URL.
201
236
  const launch = exports.launch = async (report, debug, waits, tempBrowserID, tempURL) => {
202
237
  const act = report.acts[actIndex];
@@ -307,16 +342,21 @@ const launch = exports.launch = async (report, debug, waits, tempBrowserID, temp
307
342
  report.jobData.lastScriptNonce = scriptNonce;
308
343
  }
309
344
  }
345
+ // Otherwise, i.e. if the navigation was prevented by a request frequency restriction:
346
+ else if (navResult.error === 'status429') {
347
+ // Report this.
348
+ addError(true, false, report, actIndex, 'status429');
349
+ }
310
350
  // Otherwise, i.e. if the launch or navigation failed:
311
351
  else {
312
- // Report this and abort the job.
352
+ // Report this and nullify the page.
313
353
  addError(true, true, report, actIndex, `ERROR: Launch failed (${navResult.error})`);
314
354
  page = null;
315
355
  }
316
356
  }
317
357
  // If an error occurred:
318
358
  catch(error) {
319
- // Report this and abort the job.
359
+ // Report this.
320
360
  addError(true, true, report, actIndex, `ERROR launching or navigating ${error.message}`);
321
361
  page = null;
322
362
  };
@@ -510,41 +550,13 @@ const abortActs = (report, actIndex) => {
510
550
  // Report that the job is aborted.
511
551
  console.log(`ERROR: Job aborted on act ${actIndex}`);
512
552
  };
513
- // Adds an error result to an act.
514
- const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
515
- // If the error is to be logged:
516
- if (alsoLog) {
517
- // Log it.
518
- console.log(message);
519
- }
520
- // Add error data to the result.
521
- const act = report.acts[actIndex];
522
- act.result ??= {};
523
- act.result.success ??= false;
524
- act.result.error ??= message;
525
- if (act.type === 'test') {
526
- act.data ??= {};
527
- act.data.success = false;
528
- act.data.prevented = true;
529
- act.data.error = message;
530
- // Add prevention data to the job data.
531
- report.jobData.preventions[act.which] = message;
532
- }
533
- // If the job is to be aborted:
534
- if (alsoAbort) {
535
- // Add this to the report.
536
- abortActs(report, actIndex);
537
- }
538
- };
539
553
  // Performs the acts in a report and adds the results to the report.
540
554
  const doActs = async (report) => {
541
555
  const {acts} = report;
542
556
  // Get the standardization specification.
543
557
  const standard = report.standard || 'only';
544
- const tempReportDir = 'temp';
545
- const tempReportPath = `${tempReportDir}/report.json`;
546
- await fs.mkdir(tempReportDir, {recursive: true});
547
- // For each act in the report:
558
+ const reportPath = 'temp/report.json';
559
+ // For each act in the report.
548
560
  for (const doActsIndex in acts) {
549
561
  actIndex = doActsIndex;
550
562
  // If the job has not been aborted:
@@ -628,7 +640,7 @@ const doActs = async (report) => {
628
640
  act.startTime = startTime;
629
641
  // Save the report.
630
642
  let reportJSON = JSON.stringify(report);
631
- await fs.writeFile(tempReportPath, reportJSON);
643
+ await fs.writeFile(reportPath, reportJSON);
632
644
  // Create a process and wait for it to perform the act and add the result to the saved report.
633
645
  const actResult = await new Promise(resolve => {
634
646
  let closed = false;
@@ -649,7 +661,7 @@ const doActs = async (report) => {
649
661
  });
650
662
  });
651
663
  // Get the revised report.
652
- reportJSON = await fs.readFile(tempReportPath, 'utf8');
664
+ reportJSON = await fs.readFile(reportPath, 'utf8');
653
665
  report = JSON.parse(reportJSON);
654
666
  // Get the revised act.
655
667
  act = report.acts[actIndex];
@@ -877,6 +889,9 @@ const doActs = async (report) => {
877
889
  act.actualURL = url;
878
890
  // If the act is a revelation:
879
891
  if (act.type === 'reveal') {
892
+ act.result = {
893
+ success: true
894
+ };
880
895
  // Make all elements in the page visible.
881
896
  await page.$$eval('body *', elements => {
882
897
  elements.forEach(element => {
@@ -888,15 +903,10 @@ const doActs = async (report) => {
888
903
  element.style.visibility = 'inherit';
889
904
  }
890
905
  });
891
- act.result = {
892
- success: true
893
- };
894
906
  })
895
907
  .catch(error => {
896
908
  console.log(`ERROR making all elements visible (${error.message})`);
897
- act.result = {
898
- success: false
899
- };
909
+ act.result.success = false;
900
910
  });
901
911
  }
902
912
  // Otherwise, if the act is a move:
@@ -1347,7 +1357,7 @@ const doActs = async (report) => {
1347
1357
  }
1348
1358
  console.log('Acts completed');
1349
1359
  await browserClose();
1350
- await fs.rm(tempReportPath, {force: true});
1360
+ await fs.rm(reportPath, {force: true});
1351
1361
  return report;
1352
1362
  };
1353
1363
  /*
package/tests/qualWeb.js CHANGED
@@ -30,14 +30,19 @@
30
30
  // IMPORTS
31
31
 
32
32
  const {QualWeb} = require('@qualweb/core');
33
- const {doBy} = require('../procs/job');
33
+ const {ACTRules} = require('@qualweb/act-rules');
34
+ const {WCAGTechniques} = require('@qualweb/wcag-techniques');
35
+ const {BestPractices} = require('@qualweb/best-practices');
34
36
 
35
37
  // CONSTANTS
36
38
 
37
- const qualWeb = new QualWeb({});
38
- const clusterOptions = {
39
- timeout: 25 * 1000
40
- };
39
+ const qualWeb = new QualWeb({
40
+ adBlock: true,
41
+ stealth: true
42
+ });
43
+ const actRulesModule = new ACTRules({});
44
+ const wcagModule = new WCAGTechniques({});
45
+ const bpModule = new BestPractices({});
41
46
 
42
47
  // FUNCTIONS
43
48
 
@@ -47,8 +52,13 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
47
52
  const {withNewContent, rules} = act;
48
53
  const data = {};
49
54
  let result = {};
50
- // Start the QualWeb core engine.
51
- await qualWeb.start(clusterOptions);
55
+ const clusterOptions = {
56
+ maxConcurrency: 1,
57
+ timeout: timeLimit * 1000,
58
+ monitor: false
59
+ };
60
+ // Start the QualWeb core engine.
61
+ await qualWeb.start(clusterOptions, {headless: true});
52
62
  // Specify the invariant test options.
53
63
  const qualWebOptions = {
54
64
  log: {
@@ -57,13 +67,14 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
57
67
  crawlOptions: {
58
68
  maxDepth: 0,
59
69
  maxUrls: 1,
60
- timeout: 25 * 1000,
70
+ timeout: timeLimit * 1000,
61
71
  maxParallelCrawls: 1,
62
72
  logging: true
63
73
  },
64
74
  execute: {
65
75
  counter: true
66
- }
76
+ },
77
+ modules: []
67
78
  };
68
79
  // Specify a URL or provide the content.
69
80
  try {
@@ -73,7 +84,7 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
73
84
  else {
74
85
  qualWebOptions.html = await page.content();
75
86
  }
76
- // Specify which rules to test for.
87
+ // Specify which rules to test for, adding a custom execute property for report processing.
77
88
  const actSpec = rules ? rules.find(typeRules => typeRules.startsWith('act:')) : null;
78
89
  const wcagSpec = rules ? rules.find(typeRules => typeRules.startsWith('wcag:')) : null;
79
90
  const bestSpec = rules ? rules.find(typeRules => typeRules.startsWith('best:')) : null;
@@ -84,6 +95,7 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
84
95
  else {
85
96
  const actRules = actSpec.slice(4).split(',').map(num => `QW-ACT-R${num}`);
86
97
  qualWebOptions['act-rules'] = {rules: actRules};
98
+ qualWebOptions.modules.push(actRulesModule);
87
99
  qualWebOptions.execute.act = true;
88
100
  }
89
101
  }
@@ -92,6 +104,7 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
92
104
  levels: ['A', 'AA', 'AAA'],
93
105
  principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
94
106
  };
107
+ qualWebOptions.modules.push(actRulesModule);
95
108
  qualWebOptions.execute.act = true;
96
109
  }
97
110
  if (wcagSpec) {
@@ -101,6 +114,7 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
101
114
  else {
102
115
  const wcagTechniques = wcagSpec.slice(5).split(',').map(num => `QW-WCAG-T${num}`);
103
116
  qualWebOptions['wcag-techniques'] = {techniques: wcagTechniques};
117
+ qualWebOptions.modules.push(wcagModule);
104
118
  qualWebOptions.execute.wcag = true;
105
119
  }
106
120
  }
@@ -109,6 +123,7 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
109
123
  levels: ['A', 'AA', 'AAA'],
110
124
  principles: ['Perceivable', 'Operable', 'Understandable', 'Robust']
111
125
  };
126
+ qualWebOptions.modules.push(wcagModule);
112
127
  qualWebOptions.execute.wcag = true;
113
128
  }
114
129
  if (bestSpec) {
@@ -118,11 +133,13 @@ exports.reporter = async (page, report, actIndex, timeLimit) => {
118
133
  else {
119
134
  const bestPractices = bestSpec.slice(5).split(',').map(num => `QW-BP${num}`);
120
135
  qualWebOptions['best-practices'] = {bestPractices};
136
+ qualWebOptions.modules.push(bpModule);
121
137
  qualWebOptions.execute.bp = true;
122
138
  }
123
139
  }
124
140
  else {
125
141
  qualWebOptions['best-practices'] = {};
142
+ qualWebOptions.modules.push(bpModule);
126
143
  qualWebOptions.execute.bp = true;
127
144
  }
128
145
  // Get the report.