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.
- package/LICENSE +4 -16
- package/README.md +10 -2
- package/UPGRADES.md +1 -1
- package/dirWatch.js +2 -3
- package/ed11y/editoria11y.min.js +109 -690
- package/ed11y/editoria11y210.min.js +747 -0
- package/netWatch.js +6 -6
- package/package.json +1 -1
- package/procs/aslint.js +2 -2
- package/procs/catalog.js +190 -0
- package/procs/{dateOf.js → dateTime.js} +6 -4
- package/procs/doActs.js +1227 -0
- package/procs/doTestAct.js +63 -29
- package/procs/error.js +53 -0
- package/procs/job.js +64 -38
- package/procs/launch.js +596 -0
- package/procs/nu.js +3 -18
- package/procs/shoot.js +18 -2
- package/procs/testaro.js +102 -125
- package/procs/xPath.js +62 -0
- package/run.js +42 -1938
- package/scratch/README.md +9 -0
- package/testaro/adbID.js +3 -3
- package/testaro/allCaps.js +4 -5
- package/testaro/allHidden.js +19 -18
- package/testaro/allSlanted.js +4 -5
- package/testaro/altScheme.js +3 -3
- package/testaro/attVal.js +19 -35
- package/testaro/autocomplete.js +65 -62
- package/testaro/bulk.js +21 -20
- package/testaro/buttonMenu.js +112 -33
- package/testaro/captionLoc.js +3 -3
- package/testaro/datalistRef.js +4 -5
- package/testaro/distortion.js +3 -3
- package/testaro/docType.js +6 -9
- package/testaro/dupAtt.js +12 -25
- package/testaro/elements.js +4 -3
- package/testaro/embAc.js +4 -2
- package/testaro/focAll.js +6 -13
- package/testaro/focAndOp.js +3 -3
- package/testaro/focInd.js +3 -3
- package/testaro/focVis.js +4 -3
- package/testaro/headEl.js +5 -12
- package/testaro/headingAmb.js +45 -88
- package/testaro/hovInd.js +5 -5
- package/testaro/hover.js +44 -8
- package/testaro/hr.js +4 -4
- package/testaro/imageLink.js +3 -3
- package/testaro/labClash.js +3 -3
- package/testaro/legendLoc.js +3 -3
- package/testaro/lineHeight.js +3 -3
- package/testaro/linkAmb.js +25 -17
- package/testaro/linkExt.js +5 -5
- package/testaro/linkOldAtt.js +4 -3
- package/testaro/linkTo.js +4 -3
- package/testaro/linkUl.js +4 -5
- package/testaro/miniText.js +4 -3
- package/testaro/motion.js +3 -22
- package/testaro/nonTable.js +4 -5
- package/testaro/optRoleSel.js +3 -3
- package/testaro/phOnly.js +3 -3
- package/testaro/pseudoP.js +5 -5
- package/testaro/radioSet.js +4 -5
- package/testaro/role.js +4 -5
- package/testaro/secHeading.js +4 -5
- package/testaro/shoot0.js +3 -2
- package/testaro/shoot1.js +3 -2
- package/testaro/styleDiff.js +5 -12
- package/testaro/tabNav.js +30 -118
- package/testaro/targetSmall.js +30 -15
- package/testaro/textNodes.js +3 -1
- package/testaro/textSem.js +4 -5
- package/testaro/title.js +4 -2
- package/testaro/titledEl.js +3 -3
- package/testaro/zIndex.js +3 -3
- package/tests/alfa.js +28 -54
- package/tests/aslint.js +20 -53
- package/tests/axe.js +76 -13
- package/tests/ed11y.js +69 -141
- package/tests/htmlcs.js +69 -38
- package/tests/ibm.js +54 -9
- package/tests/nuVal.js +65 -12
- package/tests/nuVnu.js +76 -26
- package/tests/qualWeb.js +89 -44
- package/tests/testaro.js +288 -273
- package/tests/wave.js +142 -117
- package/tests/wax.js +61 -42
- package/procs/getLocatorData.js +0 -192
- package/procs/identify.js +0 -250
- package/procs/isInlineLink.js +0 -42
- package/procs/screenShot.js +0 -32
- package/procs/standardize.js +0 -524
- package/procs/target.js +0 -90
- package/procs/tellServer.js +0 -43
- 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
|
-
//
|
|
19
|
-
const {
|
|
20
|
-
//
|
|
21
|
-
const {
|
|
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
|
-
//
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
165
|
+
// Tests for a doTest-ineligible Testaro rule.
|
|
128
166
|
exports.getBasicResult = async (
|
|
129
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
+
};
|