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/testaro/secHeading.js
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
29
|
+
// Make and save the screenshot.
|
|
29
30
|
pngPath = await shoot(page, 1);
|
|
30
31
|
}
|
|
31
32
|
// Return whether the screenshot was prevented.
|
package/testaro/styleDiff.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
//
|
|
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
|
|
457
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
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.
|
package/testaro/targetSmall.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
75
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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:
|
|
111
|
+
standardInstances: protoInstances
|
|
99
112
|
};
|
|
100
113
|
}, withItems);
|
|
114
|
+
// Return the result.
|
|
115
|
+
return protoResult;
|
|
101
116
|
};
|
package/testaro/textNodes.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
package/testaro/textSem.js
CHANGED
|
@@ -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,
|
|
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 {
|
package/testaro/titledEl.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
157
|
-
|
|
125
|
+
// Initialize a proto-instance.
|
|
126
|
+
const protoInstance = {
|
|
158
127
|
ruleID,
|
|
159
128
|
what,
|
|
160
129
|
ordinalSeverity,
|
|
161
|
-
count: 1
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
}
|