testaro 67.0.0 → 68.0.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 (95) hide show
  1. package/LICENSE +4 -16
  2. package/README.md +10 -2
  3. package/UPGRADES.md +1 -1
  4. package/dirWatch.js +2 -3
  5. package/ed11y/editoria11y.min.js +109 -690
  6. package/ed11y/editoria11y210.min.js +747 -0
  7. package/netWatch.js +6 -6
  8. package/package.json +1 -1
  9. package/procs/aslint.js +2 -2
  10. package/procs/catalog.js +190 -0
  11. package/procs/{dateOf.js → dateTime.js} +6 -4
  12. package/procs/doActs.js +1227 -0
  13. package/procs/doTestAct.js +63 -29
  14. package/procs/error.js +53 -0
  15. package/procs/job.js +64 -38
  16. package/procs/launch.js +596 -0
  17. package/procs/nu.js +3 -18
  18. package/procs/shoot.js +18 -2
  19. package/procs/testaro.js +102 -125
  20. package/procs/xPath.js +62 -0
  21. package/run.js +42 -1938
  22. package/scratch/README.md +9 -0
  23. package/testaro/adbID.js +3 -3
  24. package/testaro/allCaps.js +4 -5
  25. package/testaro/allHidden.js +19 -18
  26. package/testaro/allSlanted.js +4 -5
  27. package/testaro/altScheme.js +3 -3
  28. package/testaro/attVal.js +19 -35
  29. package/testaro/autocomplete.js +65 -62
  30. package/testaro/bulk.js +21 -20
  31. package/testaro/buttonMenu.js +112 -33
  32. package/testaro/captionLoc.js +3 -3
  33. package/testaro/datalistRef.js +4 -5
  34. package/testaro/distortion.js +3 -3
  35. package/testaro/docType.js +6 -9
  36. package/testaro/dupAtt.js +12 -25
  37. package/testaro/elements.js +4 -3
  38. package/testaro/embAc.js +4 -2
  39. package/testaro/focAll.js +6 -13
  40. package/testaro/focAndOp.js +3 -3
  41. package/testaro/focInd.js +3 -3
  42. package/testaro/focVis.js +4 -3
  43. package/testaro/headEl.js +5 -12
  44. package/testaro/headingAmb.js +45 -88
  45. package/testaro/hovInd.js +5 -5
  46. package/testaro/hover.js +44 -8
  47. package/testaro/hr.js +4 -4
  48. package/testaro/imageLink.js +3 -3
  49. package/testaro/labClash.js +3 -3
  50. package/testaro/legendLoc.js +3 -3
  51. package/testaro/lineHeight.js +3 -3
  52. package/testaro/linkAmb.js +25 -17
  53. package/testaro/linkExt.js +5 -5
  54. package/testaro/linkOldAtt.js +4 -3
  55. package/testaro/linkTo.js +4 -3
  56. package/testaro/linkUl.js +4 -5
  57. package/testaro/miniText.js +4 -3
  58. package/testaro/motion.js +3 -22
  59. package/testaro/nonTable.js +4 -5
  60. package/testaro/optRoleSel.js +3 -3
  61. package/testaro/phOnly.js +3 -3
  62. package/testaro/pseudoP.js +5 -5
  63. package/testaro/radioSet.js +4 -5
  64. package/testaro/role.js +4 -5
  65. package/testaro/secHeading.js +4 -5
  66. package/testaro/shoot0.js +3 -2
  67. package/testaro/shoot1.js +3 -2
  68. package/testaro/styleDiff.js +5 -12
  69. package/testaro/tabNav.js +30 -118
  70. package/testaro/targetSmall.js +30 -15
  71. package/testaro/textNodes.js +3 -1
  72. package/testaro/textSem.js +4 -5
  73. package/testaro/title.js +4 -2
  74. package/testaro/titledEl.js +3 -3
  75. package/testaro/zIndex.js +3 -3
  76. package/tests/alfa.js +28 -54
  77. package/tests/aslint.js +20 -53
  78. package/tests/axe.js +76 -13
  79. package/tests/ed11y.js +69 -141
  80. package/tests/htmlcs.js +69 -38
  81. package/tests/ibm.js +54 -9
  82. package/tests/nuVal.js +65 -12
  83. package/tests/nuVnu.js +76 -26
  84. package/tests/qualWeb.js +89 -44
  85. package/tests/testaro.js +288 -273
  86. package/tests/wave.js +142 -117
  87. package/tests/wax.js +61 -42
  88. package/procs/getLocatorData.js +0 -192
  89. package/procs/identify.js +0 -250
  90. package/procs/isInlineLink.js +0 -42
  91. package/procs/screenShot.js +0 -32
  92. package/procs/standardize.js +0 -524
  93. package/procs/target.js +0 -90
  94. package/procs/tellServer.js +0 -43
  95. package/scripts/dumpAlts.js +0 -28
@@ -1,10 +1,9 @@
1
1
  /*
2
2
  © 2025 CVS Health and/or one of its affiliates. All rights reserved.
3
3
  © 2025 Juan S. Casado.
4
- © 2025 Jonathan Robert Pool.
4
+ © 2025–2026 Jonathan Robert Pool.
5
5
 
6
- Licensed under the MIT License. See LICENSE file at the project root or
7
- https://opensource.org/license/mit/ for details.
6
+ Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
8
7
 
9
8
  SPDX-License-Identifier: MIT
10
9
  */
@@ -21,7 +20,7 @@ const {doTest} = require('../procs/testaro');
21
20
  // FUNCTIONS
22
21
 
23
22
  // Runs the test and returns the result.
24
- exports.reporter = async (page, withItems) => {
23
+ exports.reporter = async (page, catalog, withItems) => {
25
24
  const getBadWhat = element => {
26
25
  // Get the children of the element.
27
26
  const children = Array.from(element.children);
@@ -45,6 +44,6 @@ exports.reporter = async (page, withItems) => {
45
44
  const selector = 'section, article, nav, aside, main';
46
45
  const whats = 'First child headings of sectioning containers are deeper than others';
47
46
  return await doTest(
48
- page, withItems, 'secHeading', selector, whats, 0, null, getBadWhat.toString()
47
+ page, catalog, withItems, 'secHeading', selector, whats, 0, getBadWhat.toString()
49
48
  );
50
49
  };
package/testaro/shoot0.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2025 Jonathan Robert Pool.
2
+ © 2025–2026 Jonathan Robert Pool.
3
3
  Licensed under the MIT License. See LICENSE file for details.
4
4
  */
5
5
 
@@ -14,8 +14,9 @@ const {shoot} = require('../procs/shoot');
14
14
 
15
15
  // FUNCTIONS
16
16
 
17
+ // Makes and saves the first screenshot.
17
18
  exports.reporter = async page => {
18
- // Make and save the first screenshot.
19
+ // Make and save the screenshot.
19
20
  const pngPath = await shoot(page, 0);
20
21
  // Return whether the screenshot was prevented.
21
22
  return {
package/testaro/shoot1.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- © 2025 Jonathan Robert Pool.
2
+ © 2025–2026 Jonathan Robert Pool.
3
3
  Licensed under the MIT License. See LICENSE file for details.
4
4
  */
5
5
 
@@ -20,12 +20,13 @@ const tmpDir = os.tmpdir();
20
20
 
21
21
  // FUNCTIONS
22
22
 
23
+ // Make and save the second screenshot.
23
24
  exports.reporter = async page => {
24
25
  const tempFileNames = await fs.readdir(tmpDir);
25
26
  let pngPath = '';
26
27
  // If there is a shoot0 file:
27
28
  if (tempFileNames.includes('testaro-shoot-0.png')) {
28
- // Make and save the second screenshot.
29
+ // Make and save the screenshot.
29
30
  pngPath = await shoot(page, 1);
30
31
  }
31
32
  // Return whether the screenshot was prevented.
@@ -1,8 +1,8 @@
1
1
  /*
2
2
  © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jonathan Robert Pool.
3
4
 
4
- Licensed under the MIT License. See LICENSE file at the project root or
5
- https://opensource.org/license/mit/ for details.
5
+ Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
6
6
 
7
7
  SPDX-License-Identifier: MIT
8
8
  */
@@ -76,7 +76,8 @@ const linksByType = async page => await page.evaluateHandle(() => {
76
76
  list: listLinks
77
77
  };
78
78
  });
79
- exports.reporter = async (page, withItems) => {
79
+ // Runs the test and returns the result.
80
+ exports.reporter = async (page, _, withItems) => {
80
81
  // Get an object with arrays of list links and adjacent links as properties.
81
82
  const linkTypes = await linksByType(page);
82
83
  return await page.evaluate(args => {
@@ -254,16 +255,8 @@ exports.reporter = async (page, withItems) => {
254
255
  standardInstances.push({
255
256
  ruleID: 'styleDiff',
256
257
  what: `${currentData[1]} have ${elementSubtotals.length} different styles`,
257
- count: extraCount,
258
258
  ordinalSeverity: severity,
259
- tagName: currentData[2],
260
- id: '',
261
- location: {
262
- doc: '',
263
- type: '',
264
- spec: ''
265
- },
266
- excerpt: ''
259
+ count: extraCount
267
260
  });
268
261
  }
269
262
  });
package/testaro/tabNav.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
- © 2025 Jonathan Robert Pool.
3
+ © 2025–2026 Jonathan Robert Pool.
4
4
 
5
5
  Licensed under the MIT License. See LICENSE file at the project root or
6
6
  https://opensource.org/license/mit/ for details.
@@ -13,88 +13,16 @@
13
13
  This test reports nonstandard keyboard navigation among tab elements in visible tab lists. Standards are based on https://www.w3.org/TR/wai-aria-practices-1.1/#tabpanel.
14
14
  */
15
15
 
16
+ // IMPORTS
17
+
18
+ const {getXPathCatalogIndex} = require('../procs/xPath');
19
+
16
20
  // CONSTANTS
17
21
 
18
22
  const data = {};
19
23
 
20
24
  // FUNCTIONS
21
25
 
22
- // Returns the text associated with an element.
23
- const allText = async (page, elementHandle) => await page.evaluate(element => {
24
- // Identify the element, if specified, or else the focused element.
25
- const el = element || document.activeElement;
26
- // Initialize an array of its texts.
27
- const texts = [];
28
- // FUNCTION DEFINITION START
29
- // Removes excess spacing from a string.
30
- const debloat = text => text.trim().replace(/\s+/g, ' ');
31
- // FUNCTION DEFINITION END
32
- // Add any attribute label to the array.
33
- const ariaLabel = el.getAttribute('aria-label');
34
- if (ariaLabel) {
35
- const trimmedLabel = debloat(ariaLabel);
36
- if (trimmedLabel) {
37
- texts.push(trimmedLabel);
38
- }
39
- }
40
- // Add any explicit and implicit labels to the array.
41
- const labelNodeList = el.labels;
42
- if (labelNodeList && labelNodeList.length) {
43
- const labels = Array.from(labelNodeList);
44
- const labelTexts = labels
45
- .map(label => label.textContent && debloat(label.textContent))
46
- .filter(text => text);
47
- if (labelTexts.length) {
48
- texts.push(...labelTexts);
49
- }
50
- }
51
- // Add any referenced labels to the array.
52
- if (el.hasAttribute('aria-labelledby')) {
53
- const labelerIDs = el.getAttribute('aria-labelledby').split(/\s+/);
54
- labelerIDs.forEach(id => {
55
- const labeler = document.getElementById(id);
56
- if (labeler) {
57
- const labelerText = debloat(labeler.textContent);
58
- if (labelerText) {
59
- texts.push(labelerText);
60
- }
61
- }
62
- });
63
- }
64
- // Add any image text alternatives to the array.
65
- const altTexts = Array
66
- .from(element.querySelectorAll('img[alt]:not([alt=""])'))
67
- .map(img => debloat(img.alt))
68
- .join('; ');
69
- if (altTexts.length) {
70
- texts.push(altTexts);
71
- }
72
- // Add the first 100 characters of any text content of the element to the array.
73
- const ownText = element.textContent;
74
- if (ownText) {
75
- const minText = debloat(ownText);
76
- if (minText) {
77
- texts.push(minText.slice(0, 100));
78
- }
79
- }
80
- // Add any ID of the element to the array.
81
- const id = element.id;
82
- if (id) {
83
- texts.push(`#${id}`);
84
- }
85
- // Identify a concatenation of the texts.
86
- let textChain = texts.join('; ');
87
- // If it is empty:
88
- if (! textChain) {
89
- // Substitute the HTML of the element.
90
- textChain = `{${debloat(element.outerHTML)}}`;
91
- if (textChain === '{}') {
92
- textChain = '';
93
- }
94
- }
95
- // Return a concatenation of the texts in the array.
96
- return textChain;
97
- }, elementHandle);
98
26
  // Returns the index of the focused tab in an array of tabs.
99
27
  const focusedTab = async (tabs, page) => await page.evaluate(tabs => {
100
28
  const focus = document.activeElement;
@@ -104,7 +32,7 @@ const focusedTab = async (tabs, page) => await page.evaluate(tabs => {
104
32
  console.log(`ERROR: could not find focused tab (${error.message})`);
105
33
  return -1;
106
34
  });
107
- // Tests a navigation on a tab element.
35
+ // Tests a navigation on a focused tab element.
108
36
  const testKey = async (
109
37
  tabs, tabElement, keyName, keyProp, goodIndex, elementIsCorrect, itemData, withItems, page
110
38
  ) => {
@@ -234,24 +162,9 @@ const testTabs = async (tabs, index, listOrientation, listIsCorrect, withItems,
234
162
  const itemData = {};
235
163
  // If itemization is required:
236
164
  if (withItems) {
237
- let found = true;
238
165
  // Initialize a report on the element.
239
- const moreItemData = await page.evaluate(element => ({
240
- tagName: element.tagName,
241
- id: element.id
242
- }), currentTab)
243
- .catch(error => {
244
- console.log(`ERROR: could not get tag name (${error.message})`);
245
- found = false;
246
- data.prevented = true;
247
- return 'ERROR: not found';
248
- });
249
- if (found) {
250
- itemData.tagName = moreItemData.tagName;
251
- itemData.id = moreItemData.id;
252
- itemData.text = await allText(page, currentTab);
253
- itemData.navigationErrors = [];
254
- }
166
+ itemData.xPath = await page.evaluate(element => window.getXPath(element));
167
+ itemData.navigationErrors = [];
255
168
  }
256
169
  // Test the element with each navigation key.
257
170
  isCorrect = await testKey(
@@ -370,8 +283,8 @@ const testTabLists = async (tabLists, withItems, page) => {
370
283
  }
371
284
  }
372
285
  };
373
- // Tests tab-list navigation and reports results.
374
- exports.reporter = async (page, withItems) => {
286
+ // Runs the test and returns the result.
287
+ exports.reporter = async (page, catalog, withItems) => {
375
288
  // Initialize the results.
376
289
  data.totals = {
377
290
  navigations: {
@@ -453,38 +366,37 @@ exports.reporter = async (page, withItems) => {
453
366
  if (withItems) {
454
367
  // For each bad tab:
455
368
  data.tabElements.incorrect.forEach(item => {
456
- // Create a standard instance.
457
- standardInstances.push({
369
+ // Create a proto-instance.
370
+ const protoInstance = {
458
371
  ruleID: 'tabNav',
459
372
  what: `Tab responds nonstandardly to ${item.navigationErrors.join(', ')}`,
460
373
  ordinalSeverity: 1,
461
- tagName: item.tagName,
462
- id: item.id,
463
- location: {
464
- doc: '',
465
- type: '',
466
- spec: ''
467
- },
468
- excerpt: item.text
469
- });
374
+ count: 1
375
+ };
376
+ // Try to get a catalog index from the xPath of the tab.
377
+ const catalogIndex = getXPathCatalogIndex(catalog, item.xPath);
378
+ // If the acquisition succeeded:
379
+ if (catalogIndex) {
380
+ // Add the catalog index to the proto-instance.
381
+ protoInstance.catalogIndex = catalogIndex;
382
+ }
383
+ // Otherwise, i.e. if the acquisition failed:
384
+ else {
385
+ // Add the XPath to the proto-instance as a path ID.
386
+ protoInstance.pathID = item.xPath;
387
+ }
388
+ // Add the proto-instance to the standard instances.
389
+ standardInstances.push(protoInstance);
470
390
  });
471
391
  }
472
- // Otherwise, if navigation is not required and any navigations are bad:
392
+ // Otherwise, if navigation is not required and any navigations were bad:
473
393
  else if (data.totals.navigations.all.incorrect) {
474
394
  // Create a standard instance.
475
395
  standardInstances.push({
476
396
  ruleID: 'tabNav',
477
397
  what: 'Tab lists have nonstandard navigation',
478
398
  ordinalSeverity: 1,
479
- count: data.totals.navigations.all.incorrect,
480
- tagName: '',
481
- id: '',
482
- location: {
483
- doc: '',
484
- type: '',
485
- spec: ''
486
- },
487
- excerpt: ''
399
+ count: data.totals.navigations.all.incorrect
488
400
  });
489
401
  }
490
402
  // Return the result.
@@ -1,9 +1,8 @@
1
1
  /*
2
2
  © 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
- © 2025 Jonathan Robert Pool.
3
+ © 2025–2026 Jonathan Robert Pool.
4
4
 
5
- Licensed under the MIT License. See LICENSE file at the project root or
6
- https://opensource.org/license/mit/ for details.
5
+ Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
7
6
 
8
7
  SPDX-License-Identifier: MIT
9
8
  */
@@ -17,9 +16,9 @@
17
16
  // FUNCTIONS
18
17
 
19
18
  // Runs the test and returns the result.
20
- exports.reporter = async (page, withItems) => {
19
+ exports.reporter = async (page, _, withItems) => {
21
20
  // Return totals and standard instances for the rule.
22
- return await page.evaluate(withItems => {
21
+ const protoResult = await page.evaluate(withItems => {
23
22
  // Get all pointer targets.
24
23
  const allPTs = Array.from(document.body.querySelectorAll('label, button, input, a'));
25
24
  // Get the visible ones.
@@ -41,7 +40,7 @@ exports.reporter = async (page, withItems) => {
41
40
  });
42
41
  // Initialize the counts of minor and major violations.
43
42
  let violationCounts = [0, 0];
44
- const instances = [];
43
+ const protoInstances = [];
45
44
  // For each visible pointer target:
46
45
  visiblePTs.forEach((element, index) => {
47
46
  const [centerX, centerY] = ptsData[index];
@@ -71,8 +70,14 @@ exports.reporter = async (page, withItems) => {
71
70
  // If itemization is required:
72
71
  if (withItems) {
73
72
  const what = `Pointer-target centerpoint is only ${Math.round(minPlanarDistance)}px from another one`;
74
- // Add an instance to the instances.
75
- instances.push(window.getInstance(element, 'targetSmall', what, 1, ordinalSeverity));
73
+ // Add a proto-instance to the proto-instances.
74
+ protoInstances.push({
75
+ ruleID: 'targetSmall',
76
+ what,
77
+ ordinalSeverity,
78
+ count: 1,
79
+ xPath: window.getXPath(element)
80
+ });
76
81
  }
77
82
  }
78
83
  }
@@ -81,21 +86,31 @@ exports.reporter = async (page, withItems) => {
81
86
  if (! withItems) {
82
87
  // If there were any major violations:
83
88
  if (violationCounts[1]) {
84
- const what = 'Pointer-target centerpoints are less than 24px from others';
85
- // Add a summary instance to the instances.
86
- instances.push(window.getInstance(null, 'targetSmall', what, violationCounts[1], 1));
89
+ // Add a summary instance to the proto-instances.
90
+ protoInstances.push({
91
+ ruleID: 'targetSmall',
92
+ what: 'Pointer-target centerpoints are less than 24px from others',
93
+ ordinalSeverity: 1,
94
+ count: violationCounts[1]
95
+ });
87
96
  }
88
97
  // If there were any minor violations:
89
98
  if (violationCounts[0]) {
90
- const what = 'Pointer-target centerpoints are less than 44px from others';
91
- // Add a summary instance to the instances.
92
- instances.push(window.getInstance(null, 'targetSmall', what, violationCounts[0], 0));
99
+ // Add a summary instance to the proto-instances.
100
+ protoInstances.push({
101
+ ruleID: 'targetSmall',
102
+ what: 'Pointer-target centerpoints are less than 44px from others',
103
+ ordinalSeverity: 0,
104
+ count: violationCounts[0]
105
+ });
93
106
  }
94
107
  }
95
108
  return {
96
109
  data: {},
97
110
  totals: [...violationCounts, 0, 0],
98
- standardInstances: instances
111
+ standardInstances: protoInstances
99
112
  };
100
113
  }, withItems);
114
+ // Return the result.
115
+ return protoResult;
101
116
  };
@@ -1,5 +1,6 @@
1
1
  /*
2
2
  © 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jonathan Robert Pool.
3
4
 
4
5
  Licensed under the MIT License. See LICENSE file at the project root or
5
6
  https://opensource.org/license/mit/ for details.
@@ -16,7 +17,8 @@
16
17
 
17
18
  // FUNCTIONS
18
19
 
19
- exports.reporter = async (page, withItems, detailLevel, text = '') => {
20
+ // Runs the test and returns the result.
21
+ exports.reporter = async (page, _, _, detailLevel, text = '') => {
20
22
  let data = {};
21
23
  // Get the data on the text nodes.
22
24
  try {
@@ -1,9 +1,8 @@
1
1
  /*
2
2
  © 2025 CVS Health and/or one of its affiliates. All rights reserved.
3
- © 2025 Jonathan Robert Pool.
3
+ © 2025–2026 Jonathan Robert Pool.
4
4
 
5
- Licensed under the MIT License. See LICENSE file at the project root or
6
- https://opensource.org/license/mit/ for details.
5
+ Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
7
6
 
8
7
  SPDX-License-Identifier: MIT
9
8
  */
@@ -20,7 +19,7 @@ const {doTest} = require('../procs/testaro');
20
19
  // FUNCTIONS
21
20
 
22
21
  // Runs the test and returns the result.
23
- exports.reporter = async (page, withItems) => {
22
+ exports.reporter = async (page, catalog, withItems) => {
24
23
  const getBadWhat = element => {
25
24
  const isVisible = element.checkVisibility({
26
25
  contentVisibilityAuto: true,
@@ -39,6 +38,6 @@ exports.reporter = async (page, withItems) => {
39
38
  const selector = 'i, b, small';
40
39
  const whats = 'Semantically vague elements i, b, and/or small are used';
41
40
  return await doTest(
42
- page, withItems, 'textSem', selector, whats, 0, null, getBadWhat.toString()
41
+ page, catalog, withItems, 'textSem', selector, whats, 0, getBadWhat.toString()
43
42
  );
44
43
  };
package/testaro/title.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /*
2
2
  © 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jonathan Robert Pool.
3
4
 
4
- Licensed under the MIT License. See LICENSE file at the project root or
5
- https://opensource.org/license/mit/ for details.
5
+ Licensed under the MIT License. See LICENSE file at the project root or https://opensource.org/license/mit/ for details.
6
6
 
7
7
  SPDX-License-Identifier: MIT
8
8
  */
@@ -11,6 +11,8 @@
11
11
  title
12
12
  This test reports the page title.
13
13
  */
14
+
15
+ // Runs the test and returns the result.
14
16
  exports.reporter = async page => {
15
17
  const title = await page.title();
16
18
  return {
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  © 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
3
- © 2025 Jonathan Robert Pool.
3
+ © 2025–2026 Jonathan Robert Pool.
4
4
 
5
5
  Licensed under the MIT License. See LICENSE file at the project root or
6
6
  https://opensource.org/license/mit/ for details.
@@ -20,7 +20,7 @@ const {doTest} = require('../procs/testaro');
20
20
  // FUNCTIONS
21
21
 
22
22
  // Runs the test and returns the result.
23
- exports.reporter = async (page, withItems) => {
23
+ exports.reporter = async (page, catalog, withItems) => {
24
24
  const getBadWhat = element => {
25
25
  const elementType = element.tagName.toLowerCase();
26
26
  // Return a violation description.
@@ -29,6 +29,6 @@ exports.reporter = async (page, withItems) => {
29
29
  const selector = '[title]:not(iframe, link, style)';
30
30
  const whats = 'title attributes are used on elements they are likely ineffective on';
31
31
  return await doTest(
32
- page, withItems, 'titledEl', selector, whats, 0, null, getBadWhat.toString()
32
+ page, catalog, withItems, 'titledEl', selector, whats, 0, getBadWhat.toString()
33
33
  );
34
34
  };
package/testaro/zIndex.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  © 2021–2023 CVS Health and/or one of its affiliates. All rights reserved.
3
- © 2025 Jonathan Robert Pool
3
+ © 2025–2026 Jonathan Robert Pool
4
4
 
5
5
  Licensed under the MIT License. See LICENSE file at the project root or
6
6
  https://opensource.org/license/mit/ for details.
@@ -20,7 +20,7 @@ const {doTest} = require('../procs/testaro');
20
20
  // FUNCTIONS
21
21
 
22
22
  // Runs the test and returns the result.
23
- exports.reporter = async (page, withItems) => {
23
+ exports.reporter = async (page, catalog, withItems) => {
24
24
  const getBadWhat = element => {
25
25
  // Get whether the element violates the rule.
26
26
  const styleDec = window.getComputedStyle(element);
@@ -33,6 +33,6 @@ exports.reporter = async (page, withItems) => {
33
33
  };
34
34
  const whats = 'Elements have non-default Z indexes';
35
35
  return await doTest(
36
- page, withItems, 'zIndex', 'body *', whats, 0, null, getBadWhat.toString()
36
+ page, catalog, withItems, 'zIndex', 'body *', whats, 0, getBadWhat.toString()
37
37
  );
38
38
  };
package/tests/alfa.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  © 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
3
- © 2025 Jonathan Robert Pool.
3
+ © 2025–2026 Jonathan Robert Pool.
4
4
 
5
5
  Licensed under the MIT License. See LICENSE file at the project root or
6
6
  https://opensource.org/license/mit/ for details.
@@ -17,9 +17,7 @@
17
17
 
18
18
  let alfaRules = require('@siteimprove/alfa-rules').default;
19
19
  const {Audit} = require('@siteimprove/alfa-act');
20
- const {cap, tidy} = require('../procs/job');
21
- const {getIdentifiers} = require('../procs/standardize');
22
- const {getNormalizedXPath} = require('../procs/identify');
20
+ const {getNormalizedXPath, getXPathCatalogIndex} = require('../procs/xPath');
23
21
  const {Playwright} = require('@siteimprove/alfa-playwright');
24
22
 
25
23
  // FUNCTIONS
@@ -56,8 +54,6 @@ exports.reporter = async (page, report, actIndex) => {
56
54
  };
57
55
  }
58
56
  try {
59
- // Wait for a stable page to make the page and its alfa version consistent.
60
- await page.waitForLoadState('networkidle', {timeout: 2000});
61
57
  const doc = await page.evaluateHandle('document');
62
58
  const alfaPage = await Playwright.toPage(doc);
63
59
  // Test the page content with the specified rules.
@@ -65,13 +61,14 @@ exports.reporter = async (page, report, actIndex) => {
65
61
  // Get the evaluations.
66
62
  const evaluations = Array.from(await audit.evaluate());
67
63
  const {nativeResult, standardResult} = result;
64
+ const {catalog} = report;
68
65
  // For each of them:
69
66
  for (const index in evaluations) {
70
67
  const evaluation = evaluations[index];
71
68
  const targetClass = evaluation.target;
72
69
  // If it has a non-collection violator:
73
70
  if (targetClass && ! targetClass._members) {
74
- // Convert the evaluation to an item.
71
+ // Convert the evaluation to an element-specific item.
75
72
  const item = evaluation.toJSON();
76
73
  const {diagnostic, expectations, outcome, rule, target} = item;
77
74
  // If the outcome of the item is a failure or warning:
@@ -105,44 +102,16 @@ exports.reporter = async (page, report, actIndex) => {
105
102
  // If standard results are to be reported:
106
103
  if (standard) {
107
104
  const {requirements, uri} = rule;
108
- // Get properties required for a standard instance.
109
- const pathID = getNormalizedXPath(item.path.replace(/\/text\(\).*$/, ''));
110
- const {name} = target;
111
- let tagName = name?.toUpperCase();
112
- if (pathID && tagName?.startsWith('TEXT') || ! tagName) {
113
- tagName = pathID.split('/').pop().replace(/\[.+/, '').toUpperCase() || '';
114
- }
115
- let boxID = '';
116
- const targetLoc = page.locator(`xpath=${pathID}`);
117
- try {
118
- const box = await targetLoc.boundingBox({timeout: 50});
119
- if (box) {
120
- boxID = Object.values(box).join(':');
121
- }
122
- }
123
- catch(error) {}
124
- const text = [];
125
- try {
126
- const textRaw = await targetLoc.innerText({timeout: 50});
127
- const segments = textRaw?.trim().split(/[\t\n]+/).filter(segment => segment.length);
128
- if (segments?.length) {
129
- if (segments.length > 1) {
130
- text.push(segments[0], segments[segments.length - 1]);
131
- }
132
- else {
133
- text.push(segments[0]);
134
- }
135
- }
136
- }
137
- catch(error) {}
138
- // Get rule-specific properties of a standard instance.
105
+ // Get the rule ID of the item.
139
106
  let ruleID = uri.replace(/^.+-/, '');
140
- let what = tidy(expectations?.[0]?.[1]?.error?.message || '');
107
+ // Get the rule description of the item.
108
+ let what = (expectations?.[0]?.[1]?.error?.message || '').trim().replace(/\s+/g, ' ');
141
109
  if (! what) {
142
110
  if (requirements && requirements.length && requirements[0].title) {
143
111
  what = requirements[0].title;
144
112
  }
145
113
  }
114
+ // Get the ordinal severity of the item.
146
115
  let ordinalSeverity = 2;
147
116
  // If the outcome is untestability:
148
117
  if (outcome === 'cantTell') {
@@ -153,24 +122,29 @@ exports.reporter = async (page, report, actIndex) => {
153
122
  }
154
123
  // Increment the standard total.
155
124
  standardResult.totals[ordinalSeverity]++;
156
- // Add a standard instance to the standard result.
157
- standardResult.instances.push({
125
+ // Initialize a proto-instance.
126
+ const protoInstance = {
158
127
  ruleID,
159
128
  what,
160
129
  ordinalSeverity,
161
- count: 1,
162
- tagName,
163
- id: getIdentifiers(code)[1],
164
- location: {
165
- doc: 'dom',
166
- type: 'xpath',
167
- spec: pathID
168
- },
169
- excerpt: cap(tidy(item.code)),
170
- text,
171
- boxID,
172
- pathID
173
- });
130
+ count: 1
131
+ };
132
+ // Get the pathID of the element or, if none, the document pathID.
133
+ const pathID = getNormalizedXPath(item.path.replace(/\/text\(\).*$/, '')) || '/html';
134
+ // Use it to get the index of the element in the catalog.
135
+ const catalogIndex = getXPathCatalogIndex(catalog, pathID);
136
+ // If the acquisition succeeded:
137
+ if (catalogIndex) {
138
+ // Add the catalog index to the proto-instance.
139
+ protoInstance.catalogIndex = catalogIndex;
140
+ }
141
+ // Otherwise, i.e. if the acquisition failed:
142
+ else {
143
+ // Add the pathID to the proto-instance.
144
+ protoInstance.pathID = pathID;
145
+ }
146
+ // Add the proto-instance to the instances of the standard result.
147
+ standardResult.instances.push(protoInstance);
174
148
  }
175
149
  }
176
150
  }