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
package/procs/testaro.js CHANGED
@@ -1,9 +1,8 @@
1
1
  /*
2
2
  © 2023–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
- 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
  */
@@ -15,49 +14,45 @@
15
14
 
16
15
  // ########## IMPORTS
17
16
 
18
- // Module to get locator data.
19
- const {getLocatorData} = require('../procs/getLocatorData');
20
- // Module to get element IDs.
21
- const {boxOf, boxToString} = require('./identify');
22
- // Module to get the XPath of an element.
23
- const {xPath} = require('playwright-dompath');
17
+ // Function to add a catalog index to a standard instance.
18
+ const {addCatalogIndex} = require('./catalog');
19
+ // Function to get a catalog index from an XPath.
20
+ const {getXPathCatalogIndex} = require('./xPath');
24
21
 
25
22
  // ########## FUNCTIONS
26
23
 
27
- // Performs a standard test.
24
+ // Tests for a testaro rule.
28
25
  exports.doTest = async (
29
26
  page,
27
+ catalog,
30
28
  withItems,
31
29
  ruleID,
32
30
  candidateSelector,
33
31
  whats,
34
32
  severity,
35
- summaryTagName,
36
33
  getBadWhatString
37
34
  ) => {
38
- // Return totals and standard instances for the rule.
39
- return await page.evaluate(async args => {
40
- // Get the arguments (summaryTagName must be upper-case or null).
35
+ const ruleData = await page.evaluate(async args => {
36
+ // Get the arguments.
41
37
  const [
42
38
  withItems,
43
- ruleID,
44
39
  candidateSelector,
45
- whats,
46
40
  severity,
47
- summaryTagName,
48
41
  getBadWhatString
49
42
  ] = args;
50
- // Get all candidates.
43
+ // Get all violator candidates.
51
44
  const candidates = document.body.querySelectorAll(candidateSelector);
52
45
  let violationCount = 0;
53
- const standardInstances = [];
54
- // Get a function that returns a violation description, if any, for the candidate.
46
+ // Initialize proto-instances.
47
+ const protoInstances = [];
48
+ // Parse the supplied string to get the classifier.
55
49
  const getBadWhat = eval(`(${getBadWhatString})`);
50
+ // Initialize data on the rule.
56
51
  let data = {};
57
52
  const totals = [0, 0, 0, 0];
58
53
  // For each candidate:
59
54
  for (const candidate of candidates) {
60
- // Get the violation description, if any.
55
+ // Classify it as and get a violation description if a violator or undefined if not.
61
56
  const violationWhat = await getBadWhat(candidate);
62
57
  // If the candidate violates the rule:
63
58
  if (violationWhat) {
@@ -65,7 +60,7 @@ exports.doTest = async (
65
60
  violationCount++;
66
61
  let ruleWhat;
67
62
  const violationType = typeof violationWhat;
68
- // If data on the violation were provided:
63
+ // If data on the violation were provided (unusual):
69
64
  if (violationType === 'object') {
70
65
  // Get the description and add the data to the rule data.
71
66
  ruleWhat = violationWhat.description;
@@ -76,57 +71,100 @@ exports.doTest = async (
76
71
  // Get it.
77
72
  ruleWhat = violationWhat;
78
73
  }
74
+ const ruleWhatStart = ruleWhat.slice(0, 2);
75
+ let ordinalSeverity = severity;
76
+ // If this violation has a custom severity:
77
+ if (/[0-3]:/.test(ruleWhatStart)) {
78
+ // Get it.
79
+ ordinalSeverity = Number(ruleWhat[0]);
80
+ // Remove it from the violation description.
81
+ ruleWhat = ruleWhat.slice(2);
82
+ }
83
+ // Increment the applicable rule-violation total.
84
+ totals[ordinalSeverity]++;
79
85
  // If itemization is required:
80
86
  if (withItems) {
81
- const ruleWhatStart = ruleWhat.slice(0, 2);
82
- let instanceSeverity = severity;
83
- // If this violation has a custom severity:
84
- if (/[0-3]:/.test(ruleWhatStart)) {
85
- // Get it.
86
- instanceSeverity = Number(ruleWhat[0]);
87
- // Remove it from the violation description.
88
- ruleWhat = ruleWhat.slice(2);
89
- }
90
- // Increment the violation totals.
91
- totals[instanceSeverity]++;
92
- // Add an instance to the instances.
93
- standardInstances.push(
94
- window.getInstance(candidate, ruleID, ruleWhat, 1, instanceSeverity)
95
- );
96
- }
97
- // Otherwise, i.e. if itemization is not required:
98
- else {
99
- // Increment the violation totals.
100
- totals[severity]++;
87
+ // Add a proto-instance to the proto-instances.
88
+ protoInstances.push({
89
+ what: ruleWhat,
90
+ ordinalSeverity,
91
+ pathID: window.getXPath(candidate)
92
+ });
101
93
  }
102
94
  }
103
95
  }
104
- // If there are any violations and itemization is not required:
105
- if (violationCount && ! withItems) {
106
- // Add a summary instance to the instances.
107
- standardInstances.push(
108
- window.getInstance(null, ruleID, whats, violationCount, severity, summaryTagName)
109
- );
110
- }
111
96
  return {
112
97
  data,
113
98
  totals,
114
- standardInstances
99
+ protoInstances
115
100
  }
116
101
  }, [
117
102
  withItems,
118
- ruleID,
119
103
  candidateSelector,
120
- whats,
121
104
  severity,
122
- summaryTagName,
123
105
  getBadWhatString
124
106
  ]
125
107
  );
108
+ // Initialize the standard instances.
109
+ let standardInstances = [];
110
+ const {data, totals, protoInstances} = ruleData;
111
+ // If itemization is required:
112
+ if (withItems) {
113
+ // For each proto-instance:
114
+ protoInstances.forEach(protoInstance => {
115
+ const {what, ordinalSeverity, pathID} = protoInstance;
116
+ // Initialize a standard instance.
117
+ const standardInstance = {
118
+ ruleID,
119
+ what,
120
+ ordinalSeverity,
121
+ count: 1
122
+ };
123
+ // If the proto-instance includes an XPath:
124
+ if (pathID) {
125
+ // Use it to get the catalog index of the element.
126
+ const catalogIndex = getXPathCatalogIndex(catalog, pathID);
127
+ // If the acquisition succeeded:
128
+ if (catalogIndex) {
129
+ // Add the catalog index to the standard instance.
130
+ standardInstance.catalogIndex = catalogIndex;
131
+ }
132
+ // Otherwise, i.e. if the acquisition failed:
133
+ else {
134
+ // Add the XPath to the standard instance as its path ID.
135
+ standardInstance.pathID = pathID;
136
+ }
137
+ }
138
+ // Add the standard instance to the standard instances.
139
+ standardInstances.push(standardInstance);
140
+ });
141
+ }
142
+ // Otherwise, i.e. if itemization is not required:
143
+ else {
144
+ // For each ordinal severity:
145
+ for (const index in totals) {
146
+ // If there were any violations at that severity:
147
+ if (totals[index]) {
148
+ // Add a summary standard instance to the standard instances.
149
+ standardInstances.push({
150
+ ruleID,
151
+ what: whats,
152
+ ordinalSeverity: index,
153
+ count: totals[index]
154
+ });
155
+ }
156
+ }
157
+ }
158
+ // Return the data, totals, and standard instances.
159
+ return {
160
+ data,
161
+ totals,
162
+ standardInstances
163
+ };
126
164
  };
127
- // Returns a result from a basic test.
165
+ // Tests for a doTest-ineligible Testaro rule.
128
166
  exports.getBasicResult = async (
129
- page, withItems, ruleID, ordinalSeverity, summaryTagName, whats, data, violations
167
+ catalog, withItems, ruleID, ordinalSeverity, whats, data, violations
130
168
  ) => {
131
169
  // If the test was prevented:
132
170
  if (data.prevented) {
@@ -146,22 +184,17 @@ exports.getBasicResult = async (
146
184
  // For each violation:
147
185
  for (const violation of violations) {
148
186
  const {loc, what} = violation;
149
- const elData = await getLocatorData(loc);
150
- // Get the bounding box of the element.
151
- const {tagName, id, location, excerpt} = elData;
152
- const box = location.type === 'box' ? location.spec : await boxOf(loc);
153
- // Add a standard instance to the instances.
154
- standardInstances.push({
187
+ // Initialize a standard instance.
188
+ const protoInstance = {
155
189
  ruleID,
156
190
  what,
157
191
  ordinalSeverity,
158
- tagName,
159
- id,
160
- location,
161
- excerpt,
162
- boxID: boxToString(box),
163
- pathID: tagName === 'HTML' ? '/html' : await xPath(loc)
164
- });
192
+ count: 1
193
+ };
194
+ // Add a catalog index or path ID to it.
195
+ addCatalogIndex(protoInstance, loc, catalog);
196
+ // Add the standard instance to the standard instances.
197
+ standardInstances.push(protoInstance);
165
198
  }
166
199
  }
167
200
  // Otherwise, i.e. if itemization is not required:
@@ -171,12 +204,7 @@ exports.getBasicResult = async (
171
204
  ruleID,
172
205
  what: whats,
173
206
  ordinalSeverity,
174
- summaryTagName,
175
- id: '',
176
- location: {},
177
- excerpt: '',
178
- boxID: '',
179
- pathID: ''
207
+ count: violations.length
180
208
  });
181
209
  }
182
210
  // Return the result.
@@ -186,54 +214,3 @@ exports.getBasicResult = async (
186
214
  standardInstances
187
215
  };
188
216
  };
189
- // Returns an awaited change in a visible element count.
190
- exports.getVisibleCountChange = async (
191
- rootLoc, elementCount0, timeLimit = 400, settleInterval = 75
192
- ) => {
193
- const startTime = Date.now();
194
- let timeout;
195
- let settleChecker;
196
- let elementCount1 = elementCount0;
197
- // Set a time limit on the change.
198
- const timeoutPromise = new Promise(resolve => {
199
- timeout = setTimeout(() => {
200
- clearInterval(settleChecker);
201
- resolve();
202
- }, timeLimit);
203
- });
204
- // Until the time limit expires, periodically:
205
- const settlePromise = new Promise(resolve => {
206
- settleChecker = setInterval(async () => {
207
- const visiblesLoc = await rootLoc.locator('*:visible');
208
- // Get the count.
209
- elementCount1 = await visiblesLoc.count();
210
- // If the count has changed:
211
- if (elementCount1 !== elementCount0) {
212
- // Stop.
213
- clearTimeout(timeout);
214
- clearInterval(settleChecker);
215
- resolve();
216
- }
217
- }, settleInterval);
218
- });
219
- // When a change occurs or the time limit expires:
220
- await Promise.race([timeoutPromise, settlePromise]);
221
- const elapsedTime = Math.round(Date.now() - startTime);
222
- // Return the change.
223
- return {
224
- change: elementCount1 - elementCount0,
225
- elapsedTime
226
- };
227
- };
228
- // Annotates every element on a page with a unique identifier.
229
- exports.addTestaroIDs = async page => {
230
- // Wait for the page to be fully loaded.
231
- await page.waitForLoadState('networkidle');
232
- await page.evaluate(() => {
233
- let serialID = 0;
234
- for (const element of Array.from(document.querySelectorAll('*'))) {
235
- const xPath = window.getXPath(element);
236
- element.setAttribute('data-testaro-id', `${serialID++}#${xPath}`);
237
- }
238
- });
239
- };
package/procs/xPath.js ADDED
@@ -0,0 +1,62 @@
1
+ /*
2
+ © 2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2026 Jonathan Robert Pool.
4
+
5
+ Licensed under the MIT License. See LICENSE file at the project root or
6
+ https://opensource.org/license/mit/ for details.
7
+
8
+ SPDX-License-Identifier: MIT
9
+ */
10
+
11
+ /*
12
+ xPath.js
13
+ Processes element XPaths.
14
+ */
15
+
16
+ // FUNCTIONS
17
+
18
+ // Normalizes an XPath.
19
+ exports.getNormalizedXPath = xPath => {
20
+ if (xPath) {
21
+ if (xPath === '/') {
22
+ xPath = '/html';
23
+ }
24
+ xPath = xPath.replace(/^\.\/\//, '/');
25
+ const segments = xPath.split('/');
26
+ // Initialize an array of normalized segments.
27
+ const normalizedSegments = [];
28
+ // For each segment of the XPath:
29
+ segments.forEach(segment => {
30
+ // If the segment is html[1] or body[1]:
31
+ if (/html\[1\]|body\[1\]/.test(segment)) {
32
+ // Add it without its subscript to the array.
33
+ normalizedSegments.push(segment.replace(/\[1\]/, ''));
34
+ }
35
+ // Otherwise, if the segment is empty or html or body or ends with a subscript:
36
+ else if (segment === '' || ['html', 'body'].includes(segment) || segment.endsWith(']')) {
37
+ // Add it to the array.
38
+ normalizedSegments.push(segment);
39
+ }
40
+ // Otherwise, i.e. if the segment is a tag name with no subscript:
41
+ else {
42
+ // Add it with a subscript 1 to the array.
43
+ normalizedSegments.push(`${segment}[1]`);
44
+ }
45
+ });
46
+ // Return the concatenated segments as the normalized XPath.
47
+ return normalizedSegments.join('/');
48
+ }
49
+ else {
50
+ return '';
51
+ }
52
+ };
53
+ // Gets an XPath from a data-xpath attribute in an HTML excerpt.
54
+ exports.getAttributeXPath = html => {
55
+ const match = html.match(/ data-xpath="([^" ]+)"/);
56
+ return match ? match[1] : '';
57
+ };
58
+ // Gets a catalog index as a string from an XPath.
59
+ exports.getXPathCatalogIndex = (catalog, xPath) => {
60
+ const index = catalog.pathID[xPath]?.[0] ?? '';
61
+ return index;
62
+ };