testaro 60.2.1 → 60.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "60.2.1",
3
+ "version": "60.4.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": {
@@ -50,9 +50,10 @@ const actIndex = Number.parseInt(process.argv[2]);
50
50
 
51
51
  // FUNCTIONS
52
52
 
53
+ // Performs the tests of the act specified by the caller.
53
54
  const doTestAct = async () => {
54
55
  const reportPath = `${tmpDir}/report.json`;
55
- // Get the saved report.
56
+ // Get the report from the temporary directory.
56
57
  const reportJSON = await fs.readFile(reportPath, 'utf8');
57
58
  const report = JSON.parse(reportJSON);
58
59
  // Get a reference to the act in the report.
@@ -0,0 +1,32 @@
1
+ /*
2
+ © 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool. All rights reserved.
4
+ Licensed under the MIT License. See LICENSE file for details.
5
+ */
6
+
7
+ /*
8
+ screenShot
9
+ This procedure creates and returns a full-page screenshot, optionally with an exclused element.
10
+ This procedure uses the Playwright page.screenshot method, which is not implemented for the
11
+ firefox browser type.
12
+ */
13
+
14
+ // FUNCTIONS
15
+
16
+ // Creates and returns a screenshot.
17
+ exports.screenShot = async (page, exclusion = null) => {
18
+ const options = {
19
+ fullPage: true,
20
+ omitBackground: true,
21
+ timeout: 2000
22
+ };
23
+ if (exclusion) {
24
+ options.mask = [exclusion];
25
+ }
26
+ // Make and return a screenshot as a buffer.
27
+ return await page.screenshot(options)
28
+ .catch(error => {
29
+ console.log(`ERROR: Screenshot failed (${error.message})`);
30
+ return '';
31
+ });
32
+ };
@@ -1,5 +1,6 @@
1
1
  /*
2
2
  © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool. All rights reserved.
3
4
 
4
5
  MIT License
5
6
 
@@ -705,7 +706,7 @@ const convert = (toolName, data, result, standardResult) => {
705
706
  // Round the totals of the standard result.
706
707
  standardResult.totals = standardResult.totals.map(total => Math.round(total));
707
708
  };
708
- // Converts the results.
709
+ // Converts the results of a test act.
709
710
  exports.standardize = act => {
710
711
  const {which, data, result, standardResult} = act;
711
712
  if (which && result && standardResult) {
package/procs/testaro.js CHANGED
@@ -68,7 +68,9 @@ const init = exports.init = async (sampleMax, page, locAllSelector, options = {}
68
68
  };
69
69
 
70
70
  // Populates and returns a result.
71
- const report = exports.report = async (withItems, all, ruleID, whats, ordinalSeverity, tagName = '') => {
71
+ const getRuleResult = exports.getRuleResult = async (
72
+ withItems, all, ruleID, whats, ordinalSeverity, tagName = ''
73
+ ) => {
72
74
  const {locs, result} = all;
73
75
  const {data, totals, standardInstances} = result;
74
76
  // For each violation locator:
@@ -149,7 +151,7 @@ exports.simplify = async (page, withItems, ruleData) => {
149
151
  complaints.instance,
150
152
  complaints.summary
151
153
  ];
152
- const result = await report(withItems, all, ruleID, whats, ordinalSeverity, summaryTagName);
154
+ const result = await getRuleResult(withItems, all, ruleID, whats, ordinalSeverity, summaryTagName);
153
155
  // Return the result.
154
156
  return result;
155
157
  };
@@ -1,5 +1,6 @@
1
1
  /*
2
- © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
2
+ © 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool. All rights reserved.
3
4
 
4
5
  MIT License
5
6
 
@@ -25,37 +26,18 @@
25
26
  /*
26
27
  visChange
27
28
  This procedure reports a change in the visible content of a page between two times, optionally
28
- hovering over a locator-defined element immediately after the first time.
29
-
30
- WARNING: This test uses the Playwright page.screenshot method, which produces incorrect results
31
- when the browser type is chromium and is not implemented for the firefox browser type. The only
32
- browser type usable with this test is webkit.
29
+ hovering over a locator-defined element immediately after the first time. This test uses the
30
+ Playwright page.screenshot method, which is not implemented for the firefox browser type.
33
31
  */
34
32
 
35
33
  // IMPORTS
36
34
 
37
35
  const pixelmatch = require('pixelmatch').default;
38
36
  const {PNG} = require('pngjs');
37
+ const {screenShot} = require('./screenShot');
39
38
 
40
39
  // FUNCTIONS
41
40
 
42
- // Creates and returns a screenshot.
43
- const shoot = async (page, exclusion = null) => {
44
- // Make a screenshot as a buffer.
45
- const options = {
46
- fullPage: true,
47
- omitBackground: true,
48
- timeout: 2000
49
- };
50
- if (exclusion) {
51
- options.mask = [exclusion];
52
- }
53
- return await page.screenshot(options)
54
- .catch(error => {
55
- console.log(`ERROR: Screenshot failed (${error.message})`);
56
- return '';
57
- });
58
- };
59
41
  exports.visChange = async (page, options = {}) => {
60
42
  const {delayBefore, delayBetween, exclusion} = options;
61
43
  // Wait, if required.
@@ -74,7 +56,7 @@ exports.visChange = async (page, options = {}) => {
74
56
  });
75
57
  }
76
58
  // Make and get a screenshot, excluding an element if specified.
77
- const shot0 = await shoot(page, exclusion);
59
+ const shot0 = await screenShot(page, exclusion);
78
60
  // If it succeeded:
79
61
  if (shot0.length) {
80
62
  // If an exclusion was specified:
@@ -96,7 +78,7 @@ exports.visChange = async (page, options = {}) => {
96
78
  // Wait as specified, or 3 seconds.
97
79
  await page.waitForTimeout(delayBetween || 3000);
98
80
  // Make and get another screenshot.
99
- const shot1 = await shoot(page, exclusion);
81
+ const shot1 = await screenShot(page, exclusion);
100
82
  // If it succeeded:
101
83
  if (shot1.length) {
102
84
  // Get the shots as PNG images.
package/run.js CHANGED
@@ -34,6 +34,8 @@
34
34
  const fs = require('fs/promises');
35
35
  // Module to keep secrets.
36
36
  require('dotenv').config({quiet: true});
37
+ // Module to execute shell commands.
38
+ const {execSync} = require('child_process');
37
39
  // Module to validate jobs.
38
40
  const {isBrowserID, isDeviceID, isURL, isValidJob, tools} = require('./procs/job');
39
41
  // Module to evade automation detection.
@@ -109,12 +111,12 @@ const tmpDir = os.tmpdir();
109
111
 
110
112
  // Facts about the current session.
111
113
  let actCount = 0;
112
- let browserCloseIntentional = false;
113
114
  // Facts about the current act.
114
115
  let actIndex = 0;
115
116
  let browser;
116
117
  let browserContext;
117
118
  let page;
119
+ let report;
118
120
  let requestedURL = '';
119
121
 
120
122
  // FUNCTIONS
@@ -241,25 +243,6 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
241
243
  };
242
244
  }
243
245
  };
244
- // Closes the current browser.
245
- const browserClose = async () => {
246
- if (browser) {
247
- browserCloseIntentional = true;
248
- for (const context of browser.contexts()) {
249
- try {
250
- await context.close();
251
- }
252
- catch(error) {
253
- console.log(
254
- `ERROR trying to close context: ${error.message.slice(0, 200).replace(/\n.+/s, '')}`
255
- );
256
- }
257
- }
258
- await browser.close();
259
- browserCloseIntentional = false;
260
- browser = null;
261
- }
262
- };
263
246
  // Adds an error result to an act.
264
247
  const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
265
248
  // If the error is to be logged:
@@ -286,6 +269,25 @@ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
286
269
  abortActs(report, actIndex);
287
270
  }
288
271
  };
272
+ // Closes the current browser.
273
+ const browserClose = async () => {
274
+ if (browser) {
275
+ browserCloseIntentional = true;
276
+ for (const context of browser.contexts()) {
277
+ try {
278
+ await context.close();
279
+ }
280
+ catch(error) {
281
+ console.log(
282
+ `ERROR trying to close context: ${error.message.slice(0, 200).replace(/\n.+/s, '')}`
283
+ );
284
+ }
285
+ }
286
+ await browser.close();
287
+ browserCloseIntentional = false;
288
+ browser = null;
289
+ }
290
+ };
289
291
  // Launches a browser and navigates to a URL.
290
292
  const launch = exports.launch = async (
291
293
  report, debug, waits, tempBrowserID, tempURL, retries = 2
@@ -668,6 +670,11 @@ const abortActs = (report, actIndex) => {
668
670
  // Report that the job is aborted.
669
671
  console.log(`ERROR: Job aborted on act ${actIndex}`);
670
672
  };
673
+ // Returns the combination of browser ID and target URL of an act.
674
+ const launchSpecs = (act, report) => [
675
+ act.browserID || report.browserID || '',
676
+ act.target && act.target.url || report.target && report.target.url || ''
677
+ ];
671
678
  // Performs the acts in a report and adds the results to the report.
672
679
  const doActs = async (report, opts = {}) => {
673
680
  const {acts} = report;
@@ -752,13 +759,14 @@ const doActs = async (report, opts = {}) => {
752
759
  }
753
760
  // Otherwise, if the act is a launch:
754
761
  else if (type === 'launch') {
762
+ const actLaunchSpecs = launchSpecs(act, report);
755
763
  // Launch a browser, navigate to a page, and add the result to the act.
756
764
  await launch(
757
765
  report,
758
766
  debug,
759
767
  waits,
760
- act.browserID || report.browserID || '',
761
- act.target && act.target.url || report.target && report.target.url || ''
768
+ actLaunchSpecs[0],
769
+ actLaunchSpecs[1]
762
770
  );
763
771
  // If this failed:
764
772
  if (page.prevented) {
@@ -819,50 +827,6 @@ const doActs = async (report, opts = {}) => {
819
827
  toolTimes[act.which] += time;
820
828
  // If the act was not prevented:
821
829
  if (act.data && ! act.data.prevented) {
822
- // If standardization is required:
823
- if (['also', 'only'].includes(standard)) {
824
- console.log('>>>>>> Standardizing');
825
- // Initialize the standard result.
826
- act.standardResult = {
827
- totals: [0, 0, 0, 0],
828
- instances: []
829
- };
830
- // Populate it.
831
- standardize(act);
832
- // Launch a browser and navigate to the page.
833
- await launch(
834
- report,
835
- debug,
836
- waits,
837
- act.browserID || report.browserID || '',
838
- act.target && act.target.url || report.target && report.target.url || ''
839
- );
840
- // If this failed:
841
- if (page.prevented) {
842
- // Add this to the act.
843
- act.data ??= {};
844
- act.data.prevented = true;
845
- act.data.error = page.error || '';
846
- }
847
- // Otherwise, i.e. if it succeeded:
848
- else {
849
- // Add a box ID and a path ID to each of its standard instances if missing.
850
- for (const instance of act.standardResult.instances) {
851
- const elementID = await identify(instance, page);
852
- if (! instance.boxID) {
853
- instance.boxID = elementID ? elementID.boxID : '';
854
- }
855
- if (! instance.pathID) {
856
- instance.pathID = elementID ? elementID.pathID : '';
857
- }
858
- };
859
- }
860
- // If the original-format result is not to be included in the report:
861
- if (standard === 'only') {
862
- // Remove it.
863
- delete act.result;
864
- }
865
- }
866
830
  // If the act has expectations:
867
831
  const expectations = act.expect;
868
832
  if (expectations) {
@@ -892,7 +856,7 @@ const doActs = async (report, opts = {}) => {
892
856
  // Otherwise, if a current page exists:
893
857
  else if (page) {
894
858
  // If the act is navigation to a url:
895
- if (act.type === 'url') {
859
+ if (type === 'url') {
896
860
  // Identify the URL.
897
861
  const resolved = act.which.replace('__dirname', __dirname);
898
862
  requestedURL = resolved;
@@ -926,7 +890,7 @@ const doActs = async (report, opts = {}) => {
926
890
  }
927
891
  }
928
892
  // Otherwise, if the act is a wait for text:
929
- else if (act.type === 'wait') {
893
+ else if (type === 'wait') {
930
894
  const {what, which} = act;
931
895
  console.log(`>> ${what}`);
932
896
  const result = act.result = {};
@@ -994,7 +958,7 @@ const doActs = async (report, opts = {}) => {
994
958
  }
995
959
  }
996
960
  // Otherwise, if the act is a wait for a state:
997
- else if (act.type === 'state') {
961
+ else if (type === 'state') {
998
962
  // Wait for it.
999
963
  const stateIndex = ['loaded', 'idle'].indexOf(act.which);
1000
964
  await page.waitForLoadState(
@@ -1016,7 +980,7 @@ const doActs = async (report, opts = {}) => {
1016
980
  }
1017
981
  }
1018
982
  // Otherwise, if the act is a page switch:
1019
- else if (act.type === 'page') {
983
+ else if (type === 'page') {
1020
984
  // Wait for a page to be created and identify it as current.
1021
985
  page = await browserContext.waitForEvent('page');
1022
986
  // Wait until it is idle.
@@ -1033,7 +997,7 @@ const doActs = async (report, opts = {}) => {
1033
997
  // Add the URL to the act.
1034
998
  act.actualURL = url;
1035
999
  // If the act is a revelation:
1036
- if (act.type === 'reveal') {
1000
+ if (type === 'reveal') {
1037
1001
  act.result = {
1038
1002
  success: true
1039
1003
  };
@@ -1055,8 +1019,8 @@ const doActs = async (report, opts = {}) => {
1055
1019
  });
1056
1020
  }
1057
1021
  // Otherwise, if the act is a move:
1058
- else if (moves[act.type]) {
1059
- const selector = typeof moves[act.type] === 'string' ? moves[act.type] : act.what;
1022
+ else if (moves[type]) {
1023
+ const selector = typeof moves[type] === 'string' ? moves[type] : act.what;
1060
1024
  // Try up to 5 times to:
1061
1025
  act.result = {found: false};
1062
1026
  let selection = {};
@@ -1165,18 +1129,18 @@ const doActs = async (report, opts = {}) => {
1165
1129
  };
1166
1130
  // FUNCTION DEFINITION END
1167
1131
  // If the move is a button click, perform it.
1168
- if (act.type === 'button') {
1132
+ if (type === 'button') {
1169
1133
  await selection.click({timeout: 3000});
1170
1134
  act.result.success = true;
1171
1135
  act.result.move = 'clicked';
1172
1136
  }
1173
1137
  // Otherwise, if it is checking a radio button or checkbox, perform it.
1174
- else if (['checkbox', 'radio'].includes(act.type)) {
1138
+ else if (['checkbox', 'radio'].includes(type)) {
1175
1139
  await selection.waitForElementState('stable', {timeout: 2000})
1176
1140
  .catch(error => {
1177
- console.log(`ERROR waiting for stable ${act.type} (${error.message})`);
1141
+ console.log(`ERROR waiting for stable ${type} (${error.message})`);
1178
1142
  act.result.success = false;
1179
- act.result.error = `ERROR waiting for stable ${act.type}`;
1143
+ act.result.error = `ERROR waiting for stable ${type}`;
1180
1144
  });
1181
1145
  if (! act.result.error) {
1182
1146
  const isEnabled = await selection.isEnabled();
@@ -1186,9 +1150,9 @@ const doActs = async (report, opts = {}) => {
1186
1150
  timeout: 2000
1187
1151
  })
1188
1152
  .catch(error => {
1189
- console.log(`ERROR checking ${act.type} (${error.message})`);
1153
+ console.log(`ERROR checking ${type} (${error.message})`);
1190
1154
  act.result.success = false;
1191
- act.result.error = `ERROR checking ${act.type}`;
1155
+ act.result.error = `ERROR checking ${type}`;
1192
1156
  });
1193
1157
  if (! act.result.error) {
1194
1158
  act.result.success = true;
@@ -1196,20 +1160,20 @@ const doActs = async (report, opts = {}) => {
1196
1160
  }
1197
1161
  }
1198
1162
  else {
1199
- const report = `ERROR: could not check ${act.type} because disabled`;
1163
+ const report = `ERROR: could not check ${type} because disabled`;
1200
1164
  act.result.success = false;
1201
1165
  act.result.error = report;
1202
1166
  }
1203
1167
  }
1204
1168
  }
1205
1169
  // Otherwise, if it is focusing the element, perform it.
1206
- else if (act.type === 'focus') {
1170
+ else if (type === 'focus') {
1207
1171
  await selection.focus({timeout: 2000});
1208
1172
  act.result.success = true;
1209
1173
  act.result.move = 'focused';
1210
1174
  }
1211
1175
  // Otherwise, if it is clicking a link:
1212
- else if (act.type === 'link') {
1176
+ else if (type === 'link') {
1213
1177
  const href = await selection.getAttribute('href');
1214
1178
  const target = await selection.getAttribute('target');
1215
1179
  act.result.href = href || 'NONE';
@@ -1248,7 +1212,7 @@ const doActs = async (report, opts = {}) => {
1248
1212
  }
1249
1213
  }
1250
1214
  // Otherwise, if it is selecting an option in a select list, perform it.
1251
- else if (act.type === 'select') {
1215
+ else if (type === 'select') {
1252
1216
  const options = await selection.$$('option');
1253
1217
  let optionText = '';
1254
1218
  if (options && Array.isArray(options) && options.length) {
@@ -1271,7 +1235,7 @@ const doActs = async (report, opts = {}) => {
1271
1235
  act.result.option = optionText;
1272
1236
  }
1273
1237
  // Otherwise, if it is entering text in an input element:
1274
- else if (['text', 'search'].includes(act.type)) {
1238
+ else if (['text', 'search'].includes(type)) {
1275
1239
  act.result.attributes = {};
1276
1240
  const {attributes} = act.result;
1277
1241
  const type = await selection.getAttribute('type');
@@ -1294,7 +1258,7 @@ const doActs = async (report, opts = {}) => {
1294
1258
  act.result.success = true;
1295
1259
  act.result.move = 'entered';
1296
1260
  // If the input is a search input:
1297
- if (act.type === 'search') {
1261
+ if (type === 'search') {
1298
1262
  // Press the Enter key and wait for a network to be idle.
1299
1263
  doAndWait(false);
1300
1264
  }
@@ -1318,7 +1282,7 @@ const doActs = async (report, opts = {}) => {
1318
1282
  }
1319
1283
  }
1320
1284
  // Otherwise, if the act is a keypress:
1321
- else if (act.type === 'press') {
1285
+ else if (type === 'press') {
1322
1286
  // Identify the number of times to press the key.
1323
1287
  let times = 1 + (act.again || 0);
1324
1288
  report.jobData.presses += times;
@@ -1334,7 +1298,7 @@ const doActs = async (report, opts = {}) => {
1334
1298
  };
1335
1299
  }
1336
1300
  // Otherwise, if it is a repetitive keyboard navigation:
1337
- else if (act.type === 'presses') {
1301
+ else if (type === 'presses') {
1338
1302
  const {navKey, what, which, withItems} = act;
1339
1303
  const matchTexts = which ? which.map(text => debloat(text)) : [];
1340
1304
  // Initialize the loop variables.
@@ -1501,14 +1465,102 @@ const doActs = async (report, opts = {}) => {
1501
1465
  }
1502
1466
  }
1503
1467
  console.log('Acts completed');
1468
+ // If standardization is required:
1469
+ if (['also', 'only'].includes(standard)) {
1470
+ // If granular reporting has been specified:
1471
+ if (report.observe) {
1472
+ // If a progress callback has been provided:
1473
+ if (onProgress) {
1474
+ // Notify the observer of the start of standardization.
1475
+ try {
1476
+ onProgress({
1477
+ type: 'standardization',
1478
+ which: 'start'
1479
+ });
1480
+ console.log(`${'Standardization started'} (observer notified)`);
1481
+ }
1482
+ catch (error) {
1483
+ console.log(`${message} (observer notification failed: ${errorStart(error)})`);
1484
+ }
1485
+ }
1486
+ // Otherwise, i.e. if no progress callback has been provided:
1487
+ else {
1488
+ // Notify the observer of the act and log it.
1489
+ tellServer(report, messageParams, message);
1490
+ }
1491
+
1492
+ // Notify the observer and log the start of standardization.
1493
+ tellServer(report, '', 'Starting result standardization');
1494
+ }
1495
+ const launchSpecActs = {};
1496
+ // For each act:
1497
+ report.acts.forEach((act, index) => {
1498
+ // If it is a test act:
1499
+ if (act.type === 'test') {
1500
+ // Classify it by its browser ID and target URL.
1501
+ const specs = launchSpecs(act, report);
1502
+ const specString = `${specs[0]}>${specs[1]}`;
1503
+ if (launchSpecActs[specString]) {
1504
+ launchSpecActs[specString].push(index);
1505
+ }
1506
+ else {
1507
+ launchSpecActs[specString] = [index];
1508
+ }
1509
+ }
1510
+ });
1511
+ // For each browser ID/target URL class:
1512
+ for (const specString of Object.keys(launchSpecActs)) {
1513
+ const specs = specString.split('>');
1514
+ // Launch a browser and navigate to the page.
1515
+ await launch(
1516
+ report,
1517
+ debug,
1518
+ waits,
1519
+ specs[0],
1520
+ specs[1]
1521
+ );
1522
+ // For each test act with the class:
1523
+ for (const specActIndex of launchSpecActs[specString]) {
1524
+ const act = report.acts[specActIndex];
1525
+ // Initialize the standard result.
1526
+ act.standardResult = {
1527
+ totals: [0, 0, 0, 0],
1528
+ instances: []
1529
+ };
1530
+ // Populate it.
1531
+ standardize(act);
1532
+ // If the launch and navigation succeeded:
1533
+ if (! page.prevented) {
1534
+ // Add a box ID and a path ID to each of its standard instances if missing.
1535
+ for (const instance of act.standardResult.instances) {
1536
+ const elementID = await identify(instance, page);
1537
+ if (! instance.boxID) {
1538
+ instance.boxID = elementID ? elementID.boxID : '';
1539
+ }
1540
+ if (! instance.pathID) {
1541
+ instance.pathID = elementID ? elementID.pathID : '';
1542
+ }
1543
+ };
1544
+ }
1545
+ // If the original-format result is not to be included in the report:
1546
+ if (standard === 'only') {
1547
+ // Remove it.
1548
+ delete act.result;
1549
+ }
1550
+ };
1551
+ };
1552
+ console.log('>>>> Standardization completed');
1553
+ }
1554
+ // Close the browser.
1504
1555
  await browserClose();
1556
+ // Delete the temporary report file.
1505
1557
  await fs.rm(reportPath, {force: true});
1506
1558
  return report;
1507
1559
  };
1508
1560
  // Runs a job and returns a report.
1509
1561
  exports.doJob = async (job, opts = {}) => {
1510
1562
  // Make a report as a copy of the job.
1511
- let report = JSON.parse(JSON.stringify(job));
1563
+ report = JSON.parse(JSON.stringify(job));
1512
1564
  const jobData = report.jobData = {};
1513
1565
  // Get whether the job is valid and, if not, why.
1514
1566
  const jobInvalidity = isValidJob(job);
@@ -1568,3 +1620,52 @@ exports.doJob = async (job, opts = {}) => {
1568
1620
  // Return the report.
1569
1621
  return report;
1570
1622
  };
1623
+
1624
+ // CLEANUP HANDLERS
1625
+
1626
+ // Track whether cleanup is in progress.
1627
+ let cleanupInProgress = false;
1628
+ let browserCloseIntentional = false;
1629
+
1630
+ // Force-kills any Playwright browser processes synchronously.
1631
+ const forceKillBrowsers = () => {
1632
+ if (cleanupInProgress) {
1633
+ return;
1634
+ }
1635
+ cleanupInProgress = true;
1636
+ try {
1637
+ // Kill any Chromium headless shell processes.
1638
+ execSync('pkill -9 -f "chromium_headless_shell.*headless_shell"', {stdio: 'ignore'});
1639
+ }
1640
+ catch(error) {}
1641
+ };
1642
+ // Force-kills any headless shell processes synchronously on process exit.
1643
+ process.on('exit', () => {
1644
+ forceKillBrowsers();
1645
+ });
1646
+ // Force-kills any headless shell processes synchronously on beforeExit.
1647
+ process.on('beforeExit', async () => {
1648
+ if (!browserCloseIntentional) {
1649
+ await browserClose();
1650
+ }
1651
+ forceKillBrowsers();
1652
+ });
1653
+ // Force-kills any headless shell processes synchronously on uncaught exceptions.
1654
+ process.on('uncaughtException', async error => {
1655
+ console.error('Uncaught exception:', error);
1656
+ await browserClose();
1657
+ forceKillBrowsers();
1658
+ process.exit(1);
1659
+ });
1660
+ // Force-kills any headless shell processes synchronously on SIGINT.
1661
+ process.on('SIGINT', async () => {
1662
+ await browserClose();
1663
+ forceKillBrowsers();
1664
+ process.exit(0);
1665
+ });
1666
+ // Force-kills any headless shell processes synchronously on SIGTERM.
1667
+ process.on('SIGTERM', async () => {
1668
+ await browserClose();
1669
+ forceKillBrowsers();
1670
+ process.exit(0);
1671
+ });
package/testaro/adbID.js CHANGED
@@ -28,7 +28,7 @@
28
28
  Clean-room rule: report elements that reference aria-describedby targets that are missing or ambiguous (duplicate ids).
29
29
  */
30
30
 
31
- const {init, report} = require('../procs/testaro');
31
+ const {init, getRuleResult} = require('../procs/testaro');
32
32
 
33
33
  exports.reporter = async (page, withItems) => {
34
34
  // elements that reference aria-describedby
@@ -63,5 +63,5 @@ exports.reporter = async (page, withItems) => {
63
63
  'Referenced description of the element is ambiguous or missing',
64
64
  'Referenced descriptions of elements are ambiguous or missing'
65
65
  ];
66
- return await report(withItems, all, 'adbID', whats, 3);
66
+ return await getRuleResult(withItems, all, 'adbID', whats, 3);
67
67
  };
@@ -28,7 +28,7 @@
28
28
  Identify img elements whose alt attribute is an entire URL or clearly a file name (favicon).
29
29
  */
30
30
 
31
- const {init, report} = require('../procs/testaro');
31
+ const {init, getRuleResult} = require('../procs/testaro');
32
32
 
33
33
  exports.reporter = async (page, withItems) => {
34
34
  // Candidate images: any img with an alt attribute (including empty)
@@ -54,5 +54,5 @@ exports.reporter = async (page, withItems) => {
54
54
  'Element has an alt attribute with a URL as its entire value',
55
55
  'img elements have alt attributes with URLs as their entire values'
56
56
  ];
57
- return await report(withItems, all, 'altScheme', whats, 2);
57
+ return await getRuleResult(withItems, all, 'altScheme', whats, 2);
58
58
  };
package/testaro/attVal.js CHANGED
@@ -30,7 +30,7 @@
30
30
  // ########## IMPORTS
31
31
 
32
32
  // Module to perform common operations.
33
- const {init, report} = require('../procs/testaro');
33
+ const {init, getRuleResult} = require('../procs/testaro');
34
34
 
35
35
  // ########## FUNCTIONS
36
36
 
@@ -54,5 +54,5 @@ exports.reporter = async (page, withItems, attributeName, areLicit, values) => {
54
54
  `Element has attribute ${attributeName} with illicit value “__param__”`,
55
55
  `Elements have attribute ${attributeName} with illicit values`
56
56
  ];
57
- return await report(withItems, all, 'attVal', whats, 2);
57
+ return await getRuleResult(withItems, all, 'attVal', whats, 2);
58
58
  };