testaro 45.0.0 → 45.0.2

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 CHANGED
@@ -250,7 +250,7 @@ When the texts of multiple elements of the same type will contain the same `whic
250
250
 
251
251
  #### Navigations
252
252
 
253
- An example of a **navigation** is the act of type `launch` in the job example above. That `launch` act has only a `type` property. If you want a particular `launch` act to use a different browser type or to navigate to a different target from the job defaults, the `launch` act can have a `browserID` and/or a `target` property.
253
+ An example of a **navigation** is the act of type `launch` in the job example above. That `launch` act has only a `type` property. If you want a particular `launch` act to use a different browser type or to navigate to a different target URL from the job defaults, the `launch` act can have a `browserID` and/or a `url` property.
254
254
 
255
255
  If any act alters the page, you can restore the page to its original state for the next act by inserting a new `launch` act (and, if necessary, additional page-specific acts) between them.
256
256
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "45.0.0",
3
+ "version": "45.0.2",
4
4
  "description": "Run 1000 web accessibility tests from 10 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/procs/job.js CHANGED
@@ -115,12 +115,14 @@ const hasSubtype = (variable, subtype) => {
115
115
  return true;
116
116
  }
117
117
  };
118
+ // Validates a device ID.
119
+ const isDeviceID = exports.isDeviceID = deviceID => deviceID === 'default' || !! devices[deviceID];
118
120
  // Validates a browser type.
119
121
  const isBrowserID = exports.isBrowserID = type => ['chromium', 'firefox', 'webkit'].includes(type);
120
122
  // Validates a load state.
121
123
  const isState = string => ['loaded', 'idle'].includes(string);
122
124
  // Validates a URL.
123
- const isURL = string => /^(?:https?|file):\/\/[^\s]+$/.test(string);
125
+ const isURL = exports.isURL = string => /^(?:https?|file):\/\/[^\s]+$/.test(string);
124
126
  // Validates a focusable tag name.
125
127
  const isFocusable = string => ['a', 'button', 'input', 'select'].includes(string);
126
128
  // Returns whether all elements of an array are numbers.
@@ -176,8 +178,6 @@ const isValidAct = exports.isValidAct = act => {
176
178
  return false;
177
179
  }
178
180
  };
179
- // Returns whether a device ID is valid.
180
- const isDeviceID = deviceID => deviceID === 'default' || !! devices[deviceID];
181
181
  // Returns blank if a job is valid, or an error message.
182
182
  exports.isValidJob = job => {
183
183
  // If any job was provided:
@@ -215,7 +215,7 @@ exports.isValidJob = job => {
215
215
  if (typeof observe !== 'boolean') {
216
216
  return 'Bad job observe';
217
217
  }
218
- if (device.id !== 'default' && ! devices[device.id]) {
218
+ if (! isDeviceID(device.id)) {
219
219
  return 'Bad job deviceID';
220
220
  }
221
221
  if (! isBrowserID(browserID)) {
@@ -264,3 +264,28 @@ exports.isValidJob = job => {
264
264
  return 'no job';
265
265
  }
266
266
  };
267
+ // Executes an asynchronous function with a time limit.
268
+ exports.doBy = async function(timeLimit, obj, fnName, fnArgs, noticePrefix) {
269
+ let timer;
270
+ // Start a timer.
271
+ const timerPromise = new Promise(resolve => {
272
+ timer = setTimeout(() => {
273
+ console.log(`ERROR: ${noticePrefix} timed out at ${timeLimit} seconds`);
274
+ resolve('timedOut');
275
+ }, 1000 * timeLimit);
276
+ });
277
+ // Start the function execution.
278
+ /*
279
+ const fnPromise = new Promise(async resolve => {
280
+ resolve(await fn(... fnArgs));
281
+ });
282
+ */
283
+ const fnPromise = new Promise(async function(resolve) {
284
+ resolve(await obj[fnName](... fnArgs));
285
+ });
286
+ // Get the timeout or the value returned by the function, whichever is first.
287
+ const result = await Promise.race([timerPromise, fnPromise]);
288
+ clearTimeout(timer);
289
+ // Return the result.
290
+ return result;
291
+ };
package/run.js CHANGED
@@ -30,7 +30,7 @@
30
30
  // Module to keep secrets.
31
31
  require('dotenv').config();
32
32
  // Module to validate jobs.
33
- const {isBrowserID, isValidJob, tools} = require('./procs/job');
33
+ const {isBrowserID, isDeviceID, isURL, isValidJob, tools} = require('./procs/job');
34
34
  // Module to standardize report formats.
35
35
  const {standardize} = require('./procs/standardize');
36
36
  // Module to identify element bounding boxes.
@@ -191,15 +191,15 @@ const browserClose = async () => {
191
191
  }
192
192
  };
193
193
  // Launches a browser, navigates to a URL, and returns browser data.
194
- const launch = async (report, debug, waits, tempBrowserID, tempTarget) => {
195
- const {browserID, device, target} = report;
196
- // Get the default arguments if not overridden.
197
- browserID ??= browserID;
198
- const {url} = (tempTarget || target);
199
- // If the specified browser type exists:
200
- if (isBrowserID(browserID)) {
194
+ const launch = async (report, debug, waits, tempBrowserID, tempURL) => {
195
+ const {device} = report;
196
+ const deviceID = device && device.id;
197
+ const browserID = tempBrowserID || report.browserID || '';
198
+ const url = tempURL || report.target && report.target.url || '';
199
+ // If the specified browser and device types and URL exist:
200
+ if (isBrowserID(browserID) && isDeviceID(deviceID) && isURL(url)) {
201
201
  // Create a browser of the specified or default type.
202
- const browserType = require('playwright')[tempBrowserID || browserID];
202
+ const browserType = require('playwright')[browserID];
203
203
  // Close the current browser, if any.
204
204
  await browserClose();
205
205
  // Define browser options.
@@ -222,7 +222,7 @@ const launch = async (report, debug, waits, tempBrowserID, tempTarget) => {
222
222
  error: 'Browser launch failed'
223
223
  };
224
224
  });
225
- // Open a context (i.e. browser tab).
225
+ // Open a context (i.e. browser window).
226
226
  const browserContext = await browser.newContext(device.windowOptions);
227
227
  // Prevent default timeouts.
228
228
  browserContext.setDefaultTimeout(0);
@@ -325,13 +325,13 @@ const launch = async (report, debug, waits, tempBrowserID, tempTarget) => {
325
325
  };
326
326
  }
327
327
  }
328
- // Otherwise, i.e. if it does not exist:
328
+ // Otherwise, i.e. if the browser or device ID is invalid:
329
329
  else {
330
330
  // Return this.
331
- console.log(`ERROR: Browser of type ${browserID} could not be launched`);
331
+ console.log(`ERROR: Browser ${browserID}, device ${deviceID}, or URL ${url} invalid`);
332
332
  return {
333
333
  success: false,
334
- error: `${browserID} browser launch failed`
334
+ error: `${browserID} browser launch with ${deviceID} device and navigation to ${url} failed`
335
335
  };
336
336
  }
337
337
  };
@@ -611,8 +611,8 @@ const doActs = async (report, actIndex, page) => {
611
611
  report,
612
612
  debug,
613
613
  waits,
614
- act.browserID || report.browserID,
615
- act.target || report.target
614
+ act.browserID || report.browserID || '',
615
+ act.url || report.target && report.target.url || ''
616
616
  );
617
617
  // If the launch and navigation succeeded:
618
618
  if (launchResult && launchResult.success) {
@@ -809,55 +809,25 @@ const doActs = async (report, actIndex, page) => {
809
809
  else if (act.type === 'test') {
810
810
  // Add a description of the tool to the act.
811
811
  act.what = tools[act.which];
812
- // Initialize the options argument.
813
- const options = {
814
- report,
815
- act
816
- };
817
- // Add any specified arguments to it.
818
- Object.keys(act).forEach(key => {
819
- if (! ['type', 'which'].includes(key)) {
820
- options[key] = act[key];
821
- }
822
- });
823
812
  // Get the start time of the act.
824
813
  const startTime = Date.now();
825
- let timer;
826
814
  try {
827
- // Impose a time limit on the act.
828
- let timeoutReport = 'onTime';
815
+ // Get the time limit in milliseconds for the act.
829
816
  const timeLimit = 1000 * timeLimits[act.which] || 15000;
830
- timeoutReport = new Promise(resolve => {
831
- timer = setTimeout(() => {
832
- act.data = {
833
- prevented: true,
834
- error: `Act timed out at ${timeLimit / 1000} seconds`
835
- };
836
- console.log(`ERROR: Timed out at ${timeLimit / 1000} seconds`);
837
- resolve('timedOut');
838
- }, timeLimit);
839
- });
840
- // Try to perform the specified tests of the tool and get a report.
841
- const actReport = require(`./tests/${act.which}`).reporter(page, options);
842
- const raceReport = await Promise.race([timeoutReport, actReport]);
843
- // If the act was finished without timing out:
844
- if (raceReport !== 'timedOut') {
845
- // Disable the timer.
846
- clearTimeout(timer);
847
- // Import the test results and process data into the act.
848
- act.result = raceReport && raceReport.result || {};
849
- act.data = raceReport && raceReport.data || {};
850
- // If the page prevented the tool from operating:
851
- if (act.data.prevented) {
852
- // Add prevention data to the job data.
853
- report.jobData.preventions[act.which] = act.data.error;
854
- }
817
+ // Perform the specified tests of the tool.
818
+ const actReport = await require(`./tests/${act.which}`)
819
+ .reporter(page, report, actIndex, timeLimit);
820
+ // Add the data and result to the act.
821
+ act.data = actReport.data;
822
+ act.result = actReport.result;
823
+ // If the tool reported that the page prevented testing:
824
+ if (actReport.data.prevented) {
825
+ // Add prevention data to the job data.
826
+ report.jobData.preventions[act.which] = act.data.error;
855
827
  }
856
828
  }
857
- // If the testing failed other than by timing out:
829
+ // If the tool invocation failed:
858
830
  catch(error) {
859
- // Disable the timer.
860
- clearTimeout(timer);
861
831
  // Report the failure.
862
832
  const message = error.message.slice(0, 400);
863
833
  console.log(`ERROR: Test act ${act.which} failed (${message})`);
package/tests/alfa.js CHANGED
@@ -30,19 +30,20 @@
30
30
  const {Audit} = require('@siteimprove/alfa-act');
31
31
  const {Playwright} = require('@siteimprove/alfa-playwright');
32
32
  let alfaRules = require('@siteimprove/alfa-rules').default;
33
+ const {doBy} = require('../procs/job');
33
34
 
34
35
  // FUNCTIONS
35
36
 
36
37
  // Conducts and reports the alfa tests.
37
- exports.reporter = async (page, options) => {
38
- const {act} = options;
38
+ exports.reporter = async (page, report, actIndex, timeLimit) => {
39
+ const act = report.acts[actIndex];
39
40
  const {rules} = act;
40
41
  // If only some rules are to be employed:
41
42
  if (rules && rules.length) {
42
43
  // Remove the other rules.
43
44
  alfaRules = alfaRules.filter(rule => rules.includes(rule.uri.replace(/^.+-/, '')));
44
45
  }
45
- // Get the document containing the summaries of the alfa rules.
46
+ // Open a page for the summaries of the alfa rules.
46
47
  const context = page.context();
47
48
  const rulePage = await context.newPage();
48
49
  rulePage.on('console', msg => {
@@ -59,8 +60,12 @@ exports.reporter = async (page, options) => {
59
60
  items: []
60
61
  };
61
62
  try {
62
- const response = await rulePage.goto('https://alfa.siteimprove.com/rules', {timeout: 15000});
63
+ // Get the Alfa rules.
64
+ const response = await rulePage.goto(
65
+ 'https://alfa.siteimprove.com/rules', {timeout: Math.round(1000 * timeLimit / 2)}
66
+ );
63
67
  let ruleData = {};
68
+ // If they were obtained:
64
69
  if (response.status() === 200) {
65
70
  // Compile data on the rule IDs and summaries.
66
71
  ruleData = await rulePage.evaluate(() => {
@@ -86,75 +91,84 @@ exports.reporter = async (page, options) => {
86
91
  const doc = await page.evaluateHandle('document');
87
92
  const alfaPage = await Playwright.toPage(doc);
88
93
  const audit = Audit.of(alfaPage, alfaRules);
89
- const outcomes = Array.from(await audit.evaluate());
90
- // For each failure or warning:
91
- outcomes.forEach((outcome, index) => {
92
- const {target} = outcome;
93
- if (target && ! target._members) {
94
- const outcomeJ = outcome.toJSON();
95
- const verdict = outcomeJ.outcome;
96
- if (verdict !== 'passed') {
97
- // Add to the result.
98
- const {rule} = outcomeJ;
99
- const {tags, uri, requirements} = rule;
100
- const ruleID = uri.replace(/^.+-/, '');
101
- const ruleSummary = ruleData[ruleID] || '';
102
- const targetJ = outcomeJ.target;
103
- const codeLines = target.toString().split('\n');
104
- if (codeLines[0] === '#document') {
105
- codeLines.splice(2, codeLines.length - 3, '...');
106
- }
107
- else if (codeLines[0].startsWith('<html')) {
108
- codeLines.splice(1, codeLines.length - 2, '...');
109
- }
110
- const outcomeData = {
111
- index,
112
- verdict,
113
- rule: {
114
- ruleID,
115
- ruleSummary,
116
- scope: '',
117
- uri,
118
- requirements
119
- },
120
- target: {
121
- type: targetJ.type,
122
- tagName: targetJ.name || '',
123
- path: target.path(),
124
- codeLines: codeLines.map(line => line.length > 300 ? `${line.slice(0, 300)}...` : line)
94
+ const outcomes = Array.from(await doBy(timeLimit, audit, 'evaluate', [], 'alfa testing'));
95
+ // If the testing finished on time:
96
+ if (outcomes !== 'timedOut') {
97
+ // For each failure or warning:
98
+ outcomes.forEach((outcome, index) => {
99
+ const {target} = outcome;
100
+ if (target && ! target._members) {
101
+ const outcomeJ = outcome.toJSON();
102
+ const verdict = outcomeJ.outcome;
103
+ if (verdict !== 'passed') {
104
+ // Add to the result.
105
+ const {rule} = outcomeJ;
106
+ const {tags, uri, requirements} = rule;
107
+ const ruleID = uri.replace(/^.+-/, '');
108
+ const ruleSummary = ruleData[ruleID] || '';
109
+ const targetJ = outcomeJ.target;
110
+ const codeLines = target.toString().split('\n');
111
+ if (codeLines[0] === '#document') {
112
+ codeLines.splice(2, codeLines.length - 3, '...');
125
113
  }
126
- };
127
- // If the rule summary is missing:
128
- if (outcomeData.rule.ruleSummary === '') {
129
- // If a first requirement title exists:
130
- const {requirements} = outcomeData.rule;
131
- if (requirements && requirements.length && requirements[0].title) {
132
- // Make it the rule summary.
133
- outcomeData.rule.ruleSummary = requirements[0].title;
114
+ else if (codeLines[0].startsWith('<html')) {
115
+ codeLines.splice(1, codeLines.length - 2, '...');
134
116
  }
135
- }
136
- const etcTags = [];
137
- tags.forEach(tag => {
138
- if (tag.type === 'scope') {
139
- outcomeData.rule.scope = tag.scope;
117
+ const outcomeData = {
118
+ index,
119
+ verdict,
120
+ rule: {
121
+ ruleID,
122
+ ruleSummary,
123
+ scope: '',
124
+ uri,
125
+ requirements
126
+ },
127
+ target: {
128
+ type: targetJ.type,
129
+ tagName: targetJ.name || '',
130
+ path: target.path(),
131
+ codeLines: codeLines.map(line => line.length > 300 ? `${line.slice(0, 300)}...` : line)
132
+ }
133
+ };
134
+ // If the rule summary is missing:
135
+ if (outcomeData.rule.ruleSummary === '') {
136
+ // If a first requirement title exists:
137
+ const {requirements} = outcomeData.rule;
138
+ if (requirements && requirements.length && requirements[0].title) {
139
+ // Make it the rule summary.
140
+ outcomeData.rule.ruleSummary = requirements[0].title;
141
+ }
140
142
  }
141
- else {
142
- etcTags.push(tag);
143
+ const etcTags = [];
144
+ tags.forEach(tag => {
145
+ if (tag.type === 'scope') {
146
+ outcomeData.rule.scope = tag.scope;
147
+ }
148
+ else {
149
+ etcTags.push(tag);
150
+ }
151
+ });
152
+ if (etcTags.length) {
153
+ outcomeData.etcTags = etcTags;
143
154
  }
144
- });
145
- if (etcTags.length) {
146
- outcomeData.etcTags = etcTags;
147
- }
148
- if (outcomeData.verdict === 'failed') {
149
- result.totals.failures++;
150
- }
151
- else if (outcomeData.verdict === 'cantTell') {
152
- result.totals.warnings++;
155
+ if (outcomeData.verdict === 'failed') {
156
+ result.totals.failures++;
157
+ }
158
+ else if (outcomeData.verdict === 'cantTell') {
159
+ result.totals.warnings++;
160
+ }
161
+ result.items.push(outcomeData);
153
162
  }
154
- result.items.push(outcomeData);
155
163
  }
156
- }
157
- });
164
+ });
165
+ }
166
+ // Otherwise, i.e. if the testing timed out:
167
+ else {
168
+ // Report this.
169
+ data.prevented = true;
170
+ data.error = 'ERROR: Act timed out';
171
+ }
158
172
  }
159
173
  catch(error) {
160
174
  console.log(`ERROR: navigation to URL timed out (${error})`);
package/tests/aslint.js CHANGED
@@ -33,7 +33,7 @@ const fs = require('fs/promises');
33
33
  // FUNCTIONS
34
34
 
35
35
  // Conducts and reports the ASLint tests.
36
- exports.reporter = async (page, options) => {
36
+ exports.reporter = async (page, report, actIndex, timeLimit) => {
37
37
  // Initialize the act report.
38
38
  let data = {};
39
39
  let result = {};
@@ -43,7 +43,7 @@ exports.reporter = async (page, options) => {
43
43
  `${__dirname}/../node_modules/aslint-testaro/aslint.bundle.js`, 'utf8'
44
44
  );
45
45
  // Get the nonce, if any.
46
- const {act, report} = options;
46
+ const act = report.acts[actIndex];
47
47
  const {jobData} = report;
48
48
  const scriptNonce = jobData && jobData.lastScriptNonce;
49
49
  // Inject the ASLint bundle and runner into the page.
@@ -73,17 +73,18 @@ exports.reporter = async (page, options) => {
73
73
  data.prevented = true;
74
74
  data.error = message;
75
75
  });
76
- // If the injection succeeded:
77
76
  const reportLoc = page.locator('#aslintResult');
77
+ // If the injection succeeded:
78
78
  if (! data.prevented) {
79
- // Wait for the test results.
79
+ // Get the test results.
80
80
  await reportLoc.waitFor({
81
81
  state: 'attached',
82
- timeout: 30000
82
+ timeout: 1000 * timeLimit
83
83
  })
84
84
  .catch(error => {
85
- const message = `ERROR: Results timed out (${error.message.slice(0, 400)})`;
86
- console.log(message);
85
+ const message
86
+ = `aslint testing timed out at ${timeLimit} seconds (${error.message.slice(0, 400)})`;
87
+ console.log(`ERROR: ${message}`);
87
88
  data.prevented = true;
88
89
  data.error = message;
89
90
  });
package/tests/axe.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2021–2023 CVS Health and/or one of its affiliates. All rights reserved.
2
+ © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
@@ -43,13 +43,16 @@
43
43
 
44
44
  // IMPORTS
45
45
 
46
- const {injectAxe, getAxeResults} = require('axe-playwright');
46
+ const axePlaywright = require('axe-playwright');
47
+ const {injectAxe} = axePlaywright;
48
+ const {doBy} = require('../procs/job');
47
49
 
48
50
  // FUNCTIONS
49
51
 
50
52
  // Conducts and reports the Axe tests.
51
- exports.reporter = async (page, options) => {
52
- const {detailLevel, rules} = options;
53
+ exports.reporter = async (page, report, actIndex, timeLimit) => {
54
+ const act = report.acts[actIndex];
55
+ const {detailLevel, rules} = act;
53
56
  // Initialize the act report.
54
57
  let data = {};
55
58
  let result = {};
@@ -72,62 +75,69 @@ exports.reporter = async (page, options) => {
72
75
  else {
73
76
  axeOptions.runOnly = ['experimental', 'best-practice', 'wcag2a', 'wcag2aa', 'wcag2aaa', 'wcag21a', 'wcag21aa', 'wcag21aaa'];
74
77
  }
75
- const axeReport = await getAxeResults(page, null, axeOptions)
76
- .catch(error => {
77
- console.log(`ERROR: Axe failed (${error.message}'`);
78
- return '';
79
- });
80
- // If the test succeeded:
81
- const {inapplicable, passes, incomplete, violations} = axeReport;
82
- if (violations) {
83
- // Initialize the result.
84
- result.totals = {
85
- rulesNA: 0,
86
- rulesPassed: 0,
87
- rulesWarned: 0,
88
- rulesViolated: 0,
89
- warnings: {
90
- minor: 0,
91
- moderate: 0,
92
- serious: 0,
93
- critical: 0
94
- },
95
- violations: {
96
- minor: 0,
97
- moderate: 0,
98
- serious: 0,
99
- critical: 0
100
- }
101
- };
102
- result.details = axeReport;
103
- // Populate the totals.
104
- const {totals} = result;
105
- totals.rulesNA = inapplicable.length;
106
- totals.rulesPassed = passes.length;
107
- incomplete.forEach(rule => {
108
- totals.rulesWarned++;
109
- rule.nodes.forEach(node => {
110
- totals.warnings[node.impact]++;
78
+ const axeReport = await doBy(
79
+ timeLimit, axePlaywright, 'getAxeResults', [page, null, axeOptions], 'axe testing'
80
+ );
81
+ // If the testing finished on time:
82
+ if (axeReport !== 'timedOut') {
83
+ const {inapplicable, passes, incomplete, violations} = axeReport;
84
+ // If the test succeeded:
85
+ if (violations) {
86
+ // Initialize the result.
87
+ result.totals = {
88
+ rulesNA: 0,
89
+ rulesPassed: 0,
90
+ rulesWarned: 0,
91
+ rulesViolated: 0,
92
+ warnings: {
93
+ minor: 0,
94
+ moderate: 0,
95
+ serious: 0,
96
+ critical: 0
97
+ },
98
+ violations: {
99
+ minor: 0,
100
+ moderate: 0,
101
+ serious: 0,
102
+ critical: 0
103
+ }
104
+ };
105
+ result.details = axeReport;
106
+ // Populate the totals.
107
+ const {totals} = result;
108
+ totals.rulesNA = inapplicable.length;
109
+ totals.rulesPassed = passes.length;
110
+ incomplete.forEach(rule => {
111
+ totals.rulesWarned++;
112
+ rule.nodes.forEach(node => {
113
+ totals.warnings[node.impact]++;
114
+ });
111
115
  });
112
- });
113
- violations.forEach(rule => {
114
- totals.rulesViolated++;
115
- rule.nodes.forEach(node => {
116
- totals.violations[node.impact]++;
116
+ violations.forEach(rule => {
117
+ totals.rulesViolated++;
118
+ rule.nodes.forEach(node => {
119
+ totals.violations[node.impact]++;
120
+ });
117
121
  });
118
- });
119
- // Delete irrelevant properties from the report details.
120
- const irrelevants = ['inapplicable', 'passes', 'incomplete', 'violations']
121
- .slice(0, 4 - detailLevel);
122
- irrelevants.forEach(irrelevant => {
123
- delete axeReport[irrelevant];
124
- });
122
+ // Delete irrelevant properties from the report details.
123
+ const irrelevants = ['inapplicable', 'passes', 'incomplete', 'violations']
124
+ .slice(0, 4 - detailLevel);
125
+ irrelevants.forEach(irrelevant => {
126
+ delete axeReport[irrelevant];
127
+ });
128
+ }
129
+ // Otherwise, i.e. if the test failed:
130
+ else {
131
+ // Report this.
132
+ data.prevented = true;
133
+ data.error = 'ERROR: Act failed';
134
+ }
125
135
  }
126
- // Otherwise, i.e. if the test failed:
136
+ // Otherwise, i.e. if the testing timed out:
127
137
  else {
128
138
  // Report this.
129
139
  data.prevented = true;
130
- data.error = 'ERROR: Act failed';
140
+ data.error = 'ERROR: Act timed out';
131
141
  }
132
142
  }
133
143
  // Return the result.
package/tests/ed11y.js CHANGED
@@ -35,26 +35,26 @@ const {xPath} = require('playwright-dompath');
35
35
  // FUNCTIONS
36
36
 
37
37
  // Performs and reports the Editoria11y tests.
38
- exports.reporter = async (page, options) => {
38
+ exports.reporter = async (page, report, actIndex, timeLimit) => {
39
39
  // Get the nonce, if any.
40
- const {act, report} = options;
40
+ const act = report.acts[actIndex];
41
41
  const {jobData} = report;
42
42
  const scriptNonce = jobData && jobData.lastScriptNonce;
43
43
  // Get the tool script.
44
44
  const script = await fs.readFile(`${__dirname}/../ed11y/editoria11y.min.js`, 'utf8');
45
45
  // Perform the tests and get the violating elements and violation facts.
46
46
  const reportJSHandle = await page.evaluateHandle(args => new Promise(async resolve => {
47
- // If the report is incomplete after 20 seconds:
47
+ const {scriptNonce, script, rulesToTest, timeLimit} = args;
48
+ // If the report is incomplete after the time limit:
48
49
  const timer = setTimeout(() => {
49
50
  // Return this as the report.
50
51
  resolve({
51
52
  result: {
52
53
  prevented: true,
53
- error: 'ed11y timed out'
54
+ error: `ed11y timed out at ${timeLimit} seconds`
54
55
  }
55
56
  });
56
- }, 20000);
57
- const {scriptNonce, script, rulesToTest} = args;
57
+ }, 1000 * timeLimit);
58
58
  // When the script has been executed, creating data in an Ed11y object:
59
59
  document.addEventListener('ed11yResults', () => {
60
60
  // Initialize a report containing violating elements and violation facts.
@@ -157,7 +157,12 @@ exports.reporter = async (page, options) => {
157
157
  }
158
158
  });
159
159
  };
160
- }), {scriptNonce, script, rulesToTest: act.rules});
160
+ }), {
161
+ scriptNonce,
162
+ script,
163
+ rulesToTest: act.rules,
164
+ timeLimit
165
+ });
161
166
  // Initialize the result as the violation facts.
162
167
  const resultJSHandle = await reportJSHandle.getProperty('facts');
163
168
  const result = await resultJSHandle.jsonValue();
package/tests/htmlcs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
2
+ © 2022–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
@@ -33,8 +33,9 @@ const fs = require('fs/promises');
33
33
  // FUNCTIONS
34
34
 
35
35
  // Conducts and reports the HTML CodeSniffer tests.
36
- exports.reporter = async (page, options) => {
37
- const {report, rules} = options;
36
+ exports.reporter = async (page, report, actIndex) => {
37
+ const act = report.acts[actIndex];
38
+ const {rules} = act;
38
39
  const data = {};
39
40
  const result = {};
40
41
  // Get the HTMLCS script.
package/tests/ibm.js CHANGED
@@ -36,16 +36,29 @@
36
36
 
37
37
  const fs = require('fs').promises;
38
38
  // Scanner. Importing and executing 'close' crashed the Node process.
39
- const {getCompliance} = require('accessibility-checker');
39
+ const accessibilityChecker = require('accessibility-checker');
40
+ const {getCompliance} = accessibilityChecker;
41
+ // Utility module.
42
+ const {doBy} = require('../procs/job');
40
43
 
41
44
  // FUNCTIONS
42
45
 
43
46
  // Runs the IBM test and returns the result.
44
- const run = async content => {
47
+ const run = async (content, timeLimit) => {
45
48
  const nowLabel = (new Date()).toISOString().slice(0, 19);
46
49
  try {
47
- const ibmReport = await getCompliance(content, nowLabel);
48
- return ibmReport;
50
+ const ibmReport = await doBy(
51
+ timeLimit, accessibilityChecker, 'getCompliance', [content, nowLabel], 'ibm getCompliance'
52
+ );
53
+ if (ibmReport !== 'timedOut') {
54
+ return ibmReport;
55
+ }
56
+ else {
57
+ return {
58
+ prevented: true,
59
+ error: `ibm getCompliance timed out at ${timeLimit} seconds`
60
+ };
61
+ }
49
62
  }
50
63
  catch(error) {
51
64
  console.log('ibm getCompliance failed');
@@ -177,39 +190,54 @@ const doTest = async (content, withItems, timeLimit, rules) => {
177
190
  }
178
191
  };
179
192
  // Conducts and reports the IBM Equal Access tests.
180
- exports.reporter = async (page, options) => {
181
- const {withItems, withNewContent, rules} = options;
193
+ exports.reporter = async (page, report, actIndex, timeLimit) => {
194
+ const act = report.acts[actIndex];
195
+ const {withItems, withNewContent, rules} = act;
182
196
  const contentType = withNewContent ? 'new' : 'existing';
183
197
  console.log(`>>>>>> Content type: ${contentType}`);
184
- const timeLimit = 25;
185
198
  try {
186
- const typeContent = contentType === 'existing' ? await page.content() : await page.url();
199
+ const typeContent = contentType === 'existing' ? await page.content() : page.url();
200
+ // Perform the tests.
187
201
  const actReport = await doTest(typeContent, withItems, timeLimit, rules);
188
- const {data, result} = actReport;
189
- // If the act was prevented:
190
- if (data && data.prevented) {
191
- // Report this.
192
- const message = `ERROR: Act failed or timed out at ${timeLimit} seconds`;
193
- console.log(message);
194
- data.error = data.error ? `${data.error}; ${message}` : message;
195
- // Return the failure.
196
- return {
197
- data,
198
- result: {}
199
- };
202
+ // If the testing was finished on time:
203
+ if (actReport !== 'timedOut') {
204
+ const {data, result} = actReport;
205
+ // If the act was prevented:
206
+ if (data && data.prevented) {
207
+ // Report this.
208
+ const message = `ERROR: Act failed or timed out at ${timeLimit} seconds`;
209
+ console.log(message);
210
+ data.error = data.error ? `${data.error}; ${message}` : message;
211
+ // Return the failure.
212
+ return {
213
+ data,
214
+ result: {}
215
+ };
216
+ }
217
+ // Otherwise, i.e. if the act was not prevented:
218
+ else {
219
+ // Return the result.
220
+ return {
221
+ data,
222
+ result
223
+ };
224
+ }
200
225
  }
201
- // Otherwise, i.e. if the act was not prevented:
226
+ // Otherwise, i.e. if the testing timed out:
202
227
  else {
203
- // Return the result.
228
+ // Report this.
204
229
  return {
205
- data,
206
- result
230
+ data: {
231
+ prevented: true,
232
+ error: message
233
+ },
234
+ result: {}
207
235
  };
208
236
  }
209
237
  }
210
238
  catch(error) {
211
- const message = `ERROR: Act crashed (${error.message.slice(0, 200)})`;
212
- console.log(message);
239
+ const message = `Act crashed (${error.message.slice(0, 200)})`;
240
+ console.log(`ERROR: ${message}`);
213
241
  return {
214
242
  data: {
215
243
  prevented: true,
package/tests/nuVal.js CHANGED
@@ -41,8 +41,9 @@ const {getSource} = require('../procs/getSource');
41
41
  // FUNCTIONS
42
42
 
43
43
  // Conducts and reports the Nu Html Checker tests.
44
- exports.reporter = async (page, options) => {
45
- const {rules} = options;
44
+ exports.reporter = async (page, report, actIndex) => {
45
+ const act = report.acts[actIndex];
46
+ const {rules} = act;
46
47
  // Get the browser-parsed page.
47
48
  const pageContent = await page.content();
48
49
  // Get the source.
package/tests/qualWeb.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2023 CVS Health and/or one of its affiliates. All rights reserved.
2
+ © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
@@ -28,6 +28,7 @@
28
28
  // IMPORTS
29
29
 
30
30
  const {QualWeb} = require('@qualweb/core');
31
+ const {doBy} = require('../procs/job');
31
32
 
32
33
  // CONSTANTS
33
34
 
@@ -39,8 +40,9 @@ const clusterOptions = {
39
40
  // FUNCTIONS
40
41
 
41
42
  // Conducts and reports the QualWeb tests.
42
- exports.reporter = async (page, options) => {
43
- const {withNewContent, rules} = options;
43
+ exports.reporter = async (page, report, actIndex, timeLimit) => {
44
+ const act = report.acts[actIndex];
45
+ const {withNewContent, rules} = act;
44
46
  const data = {};
45
47
  let result = {};
46
48
  // Start the QualWeb core engine.
@@ -121,82 +123,85 @@ exports.reporter = async (page, options) => {
121
123
  qualWebOptions.execute.bp = true;
122
124
  }
123
125
  // Get the report.
124
- let actReports = await qualWeb.evaluate(qualWebOptions);
125
- // Remove the copy of the DOM from it.
126
- result = actReports[withNewContent ? qualWebOptions.url : 'customHtml'];
127
- if (result && result.system && result.system.page && result.system.page.dom) {
128
- delete result.system.page.dom;
129
- // For each test section of the act report:
130
- const {modules} = result;
131
- if (modules) {
132
- for (const section of ['act-rules', 'wcag-techniques', 'best-practices']) {
133
- if (qualWebOptions[section]) {
134
- if (modules[section]) {
135
- const {assertions} = modules[section];
136
- if (assertions) {
137
- const ruleIDs = Object.keys(assertions);
138
- ruleIDs.forEach(ruleID => {
139
- // Remove passing results.
140
- const ruleAssertions = assertions[ruleID];
141
- const {metadata} = ruleAssertions;
142
- if (metadata) {
143
- if (metadata.warning === 0 && metadata.failed === 0) {
144
- delete assertions[ruleID];
145
- }
146
- else {
147
- if (ruleAssertions.results) {
148
- ruleAssertions.results = ruleAssertions.results.filter(
149
- raResult => raResult.verdict !== 'passed'
150
- );
126
+ let actReports = await doBy(timeLimit, qualWeb, 'evaluate', [qualWebOptions], 'qualWeb testing');
127
+ // If the testing finished on time:
128
+ if (actReports !== 'timedOut') {
129
+ // Remove the copy of the DOM from it.
130
+ result = actReports[withNewContent ? qualWebOptions.url : 'customHtml'];
131
+ if (result && result.system && result.system.page && result.system.page.dom) {
132
+ delete result.system.page.dom;
133
+ // For each test section of the act report:
134
+ const {modules} = result;
135
+ if (modules) {
136
+ for (const section of ['act-rules', 'wcag-techniques', 'best-practices']) {
137
+ if (qualWebOptions[section]) {
138
+ if (modules[section]) {
139
+ const {assertions} = modules[section];
140
+ if (assertions) {
141
+ const ruleIDs = Object.keys(assertions);
142
+ ruleIDs.forEach(ruleID => {
143
+ // Remove passing results.
144
+ const ruleAssertions = assertions[ruleID];
145
+ const {metadata} = ruleAssertions;
146
+ if (metadata) {
147
+ if (metadata.warning === 0 && metadata.failed === 0) {
148
+ delete assertions[ruleID];
151
149
  }
152
- }
153
- }
154
- // Shorten long HTML codes of elements.
155
- const {results} = ruleAssertions;
156
- results.forEach(raResult => {
157
- const {elements} = raResult;
158
- if (elements && elements.length) {
159
- elements.forEach(element => {
160
- if (element.htmlCode && element.htmlCode.length > 700) {
161
- element.htmlCode = `${element.htmlCode.slice(0, 700)} …`;
150
+ else {
151
+ if (ruleAssertions.results) {
152
+ ruleAssertions.results = ruleAssertions.results.filter(
153
+ raResult => raResult.verdict !== 'passed'
154
+ );
162
155
  }
163
- });
156
+ }
164
157
  }
158
+ // Shorten long HTML codes of elements.
159
+ const {results} = ruleAssertions;
160
+ results.forEach(raResult => {
161
+ const {elements} = raResult;
162
+ if (elements && elements.length) {
163
+ elements.forEach(element => {
164
+ if (element.htmlCode && element.htmlCode.length > 700) {
165
+ element.htmlCode = `${element.htmlCode.slice(0, 700)} …`;
166
+ }
167
+ });
168
+ }
169
+ });
165
170
  });
166
- });
171
+ }
172
+ else {
173
+ data.prevented = true;
174
+ data.error = 'ERROR: No assertions';
175
+ }
167
176
  }
168
177
  else {
169
178
  data.prevented = true;
170
- data.error = 'ERROR: No assertions';
179
+ data.error = `ERROR: No ${section} section`;
171
180
  }
172
181
  }
173
- else {
174
- data.prevented = true;
175
- data.error = `ERROR: No ${section} section`;
176
- }
177
182
  }
178
183
  }
184
+ else {
185
+ data.prevented = true;
186
+ data.error = 'ERROR: No modules';
187
+ }
179
188
  }
180
189
  else {
181
190
  data.prevented = true;
182
- data.error = 'ERROR: No modules';
191
+ data.error = 'ERROR: No DOM';
183
192
  }
193
+ // Stop the QualWeb core engine.
194
+ await qualWeb.stop();
195
+ // Return the result.
196
+ try {
197
+ JSON.stringify(result);
198
+ }
199
+ catch(error) {
200
+ const message = `ERROR: qualWeb result cannot be made JSON (${error.message})`;
201
+ data.prevented = true;
202
+ data.error = message;
203
+ };
184
204
  }
185
- else {
186
- data.prevented = true;
187
- data.error = 'ERROR: No DOM';
188
- }
189
- // Stop the QualWeb core engine.
190
- await qualWeb.stop();
191
- // Return the result.
192
- try {
193
- JSON.stringify(result);
194
- }
195
- catch(error) {
196
- const message = `ERROR: qualWeb result cannot be made JSON (${error.message})`;
197
- data.prevented = true;
198
- data.error = message;
199
- };
200
205
  }
201
206
  catch(error) {
202
207
  const message = error.message.slice(0, 200);
package/tests/testaro.js CHANGED
@@ -143,10 +143,11 @@ const wait = ms => {
143
143
  });
144
144
  };
145
145
  // Conducts and reports Testaro tests.
146
- exports.reporter = async (page, options) => {
147
- const {report, withItems, stopOnFail, args} = options;
146
+ exports.reporter = async (page, report, actIndex) => {
147
+ const act = report.acts[actIndex];
148
+ const {args, stopOnFail, withItems} = act;
148
149
  const argRules = args ? Object.keys(args) : null;
149
- const rules = options.rules || ['y', ... Object.keys(evalRules)];
150
+ const rules = act.rules || ['y', ... Object.keys(evalRules)];
150
151
  // Initialize the act report.
151
152
  const data = {
152
153
  prevented: false,
package/tests/wave.js CHANGED
@@ -35,27 +35,29 @@ const https = require('https');
35
35
  // FUNCTIONS
36
36
 
37
37
  // Conducts and reports the WAVE tests.
38
- exports.reporter = async (page, options) => {
39
- const {reportType, rules} = options;
38
+ exports.reporter = async (page, report, actIndex) => {
39
+ const act = report.acts[actIndex];
40
+ const {reportType, rules} = act;
40
41
  const waveKey = process.env.WAVE_KEY;
41
- // Get the data from a WAVE test.
42
+ // Initialize the results.
42
43
  const data = {};
43
- const result = await new Promise(resolve => {
44
- https.get(
45
- {
46
- host: 'wave.webaim.org',
47
- path: `/api/request?key=${waveKey}&url=${page.url()}&reporttype=${reportType}`
48
- },
49
- response => {
50
- let actReport = '';
51
- response.on('data', chunk => {
52
- actReport += chunk;
53
- });
54
- // When the data arrive:
55
- response.on('end', async () => {
56
- try {
44
+ const result = {};
45
+ try {
46
+ result = await new Promise(resolve => {
47
+ https.get(
48
+ {
49
+ host: 'wave.webaim.org',
50
+ path: `/api/request?key=${waveKey}&url=${page.url()}&reporttype=${reportType}`
51
+ },
52
+ response => {
53
+ let rawReport = '';
54
+ response.on('data', chunk => {
55
+ rawReport += chunk;
56
+ });
57
+ // When the data arrive:
58
+ response.on('end', async () => {
57
59
  // Delete unnecessary properties.
58
- const actResult = JSON.parse(actReport);
60
+ const actResult = JSON.parse(rawReport);
59
61
  const {categories, statistics} = actResult;
60
62
  delete categories.feature;
61
63
  delete categories.structure;
@@ -105,19 +107,16 @@ exports.reporter = async (page, options) => {
105
107
  data.totalElements = statistics.totalelements || null;
106
108
  }
107
109
  // Return the result.
108
- return resolve(actResult);
109
- }
110
- catch (error) {
111
- data.prevented = true;
112
- data.error = error.message;
113
- return resolve({
114
- actReport
115
- });
116
- }
117
- });
118
- }
119
- );
120
- });
110
+ resolve(actResult);
111
+ });
112
+ }
113
+ );
114
+ });
115
+ }
116
+ catch (error) {
117
+ data.prevented = true;
118
+ data.error = error.message;
119
+ };
121
120
  return {
122
121
  data,
123
122
  result