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/scratch/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
# scratch
|
|
2
2
|
|
|
3
3
|
This directory is used for temporary files produced during execution of jobs.
|
|
4
|
+
|
|
5
|
+
## License
|
|
6
|
+
|
|
7
|
+
© 2021–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
8
|
+
© 2025–2026 Jonathan Robert Pool.
|
|
9
|
+
|
|
10
|
+
Licensed under the [MIT License](https://opensource.org/license/mit/). See [LICENSE](../../LICENSE) file at the project root for details.
|
|
11
|
+
|
|
12
|
+
SPDX-License-Identifier: MIT
|
package/testaro/adbID.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
7
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -22,7 +22,7 @@ const {doTest} = require('../procs/testaro');
|
|
|
22
22
|
// FUNCTIONS
|
|
23
23
|
|
|
24
24
|
// Runs the test and returns the result.
|
|
25
|
-
exports.reporter = async (page, withItems) => {
|
|
25
|
+
exports.reporter = async (page, catalog, withItems) => {
|
|
26
26
|
const getBadWhat = element => {
|
|
27
27
|
// Get the IDs in the aria-describedby attribute of the element.
|
|
28
28
|
const IDs = element.getAttribute('aria-describedby').trim().split(/\s+/).filter(Boolean);
|
|
@@ -57,6 +57,6 @@ exports.reporter = async (page, withItems) => {
|
|
|
57
57
|
};
|
|
58
58
|
const whats = 'Elements have aria-describedby attributes with missing or invalid id values';
|
|
59
59
|
return await doTest(
|
|
60
|
-
page, withItems, 'adbID', '[aria-describedby]', whats, 3,
|
|
60
|
+
page, catalog, withItems, 'adbID', '[aria-describedby]', whats, 3, getBadWhat.toString()
|
|
61
61
|
);
|
|
62
62
|
};
|
package/testaro/allCaps.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 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
|
-
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
|
*/
|
|
@@ -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 child text nodes of the element.
|
|
27
26
|
const childTextNodes = Array.from(element.childNodes).filter(
|
|
@@ -52,6 +51,6 @@ exports.reporter = async (page, withItems) => {
|
|
|
52
51
|
const selector = 'body *:not(style, script, svg)';
|
|
53
52
|
const whats = 'Elements have all-capital text';
|
|
54
53
|
return await doTest(
|
|
55
|
-
page, withItems, 'allCaps', selector, whats, 0,
|
|
54
|
+
page, catalog, withItems, 'allCaps', selector, whats, 0, getBadWhat.toString()
|
|
56
55
|
);
|
|
57
56
|
};
|
package/testaro/allHidden.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 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
|
-
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
|
*/
|
|
@@ -19,22 +18,24 @@
|
|
|
19
18
|
exports.reporter = async page => {
|
|
20
19
|
// Get a count of elements deemed visible by Playwright.
|
|
21
20
|
const visibleElementCount = await page.locator('body :visible').count();
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const instances = [];
|
|
26
|
-
// If no element is visible:
|
|
27
|
-
if (! visibleElementCount) {
|
|
28
|
-
// Increment the violation count.
|
|
29
|
-
violationCount = 1;
|
|
30
|
-
const what = `The entire page body is hidden or empty`;
|
|
31
|
-
// Add a summary instance to the instances.
|
|
32
|
-
instances.push(window.getInstance(null, 'allHidden', what, 1, 3));
|
|
33
|
-
}
|
|
21
|
+
// If no element is visible:
|
|
22
|
+
if (! visibleElementCount) {
|
|
23
|
+
// Return data, totals, and a summary standard instance.
|
|
34
24
|
return {
|
|
35
25
|
data: {},
|
|
36
|
-
totals: [0, 0, 0,
|
|
37
|
-
standardInstances:
|
|
26
|
+
totals: [0, 0, 0, 1],
|
|
27
|
+
standardInstances: [{
|
|
28
|
+
ruleID: 'allHidden',
|
|
29
|
+
what: 'The entire page body is hidden or empty',
|
|
30
|
+
ordinalSeverity: 3,
|
|
31
|
+
count: 1
|
|
32
|
+
}]
|
|
38
33
|
};
|
|
39
|
-
}
|
|
34
|
+
}
|
|
35
|
+
// Otherwise, return data, totals, and an empty array of standard instances.
|
|
36
|
+
return {
|
|
37
|
+
data: {},
|
|
38
|
+
totals: [0, 0, 0, 0],
|
|
39
|
+
standardInstances: []
|
|
40
|
+
};
|
|
40
41
|
};
|
package/testaro/allSlanted.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 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
|
-
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
|
*/
|
|
@@ -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
|
const styleDec = window.getComputedStyle(element);
|
|
27
26
|
const {textContent} = element;
|
|
@@ -34,6 +33,6 @@ exports.reporter = async (page, withItems) => {
|
|
|
34
33
|
const selector = 'body *:not(style, script, svg)';
|
|
35
34
|
const whats = 'Elements contain all-slanted text';
|
|
36
35
|
return await doTest(
|
|
37
|
-
page, withItems, 'allSlanted', selector, whats, 0,
|
|
36
|
+
page, catalog, withItems, 'allSlanted', selector, whats, 0, getBadWhat.toString()
|
|
38
37
|
);
|
|
39
38
|
};
|
package/testaro/altScheme.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
7
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -21,7 +21,7 @@ const {doTest} = require('../procs/testaro');
|
|
|
21
21
|
// FUNCTIONS
|
|
22
22
|
|
|
23
23
|
// Runs the test and returns the result.
|
|
24
|
-
exports.reporter = async (page, withItems) => {
|
|
24
|
+
exports.reporter = async (page, catalog, withItems) => {
|
|
25
25
|
const getBadWhat = element => {
|
|
26
26
|
// Get the value of the alt attribute of the element.
|
|
27
27
|
const alt = (element.getAttribute('alt') || '').trim();
|
|
@@ -41,6 +41,6 @@ exports.reporter = async (page, withItems) => {
|
|
|
41
41
|
};
|
|
42
42
|
const whats = 'img elements have alt attributes with URL or filename values';
|
|
43
43
|
return await doTest(
|
|
44
|
-
page, withItems, 'altScheme', 'img[alt]', whats, 1,
|
|
44
|
+
page, catalog, withItems, 'altScheme', 'img[alt]', whats, 1, getBadWhat.toString()
|
|
45
45
|
);
|
|
46
46
|
};
|
package/testaro/attVal.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 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
|
*/
|
|
@@ -12,41 +12,25 @@
|
|
|
12
12
|
This test reports elements with illicit values of an attribute.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
// IMPORTS
|
|
16
|
+
|
|
17
|
+
const {doTest} = require('../procs/testaro');
|
|
18
|
+
|
|
15
19
|
// FUNCTIONS
|
|
16
20
|
|
|
17
21
|
// Runs the test and returns the result.
|
|
18
|
-
exports.reporter = async (page, withItems, attributeName, areLicit, values) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// For each candidate:
|
|
27
|
-
candidates.forEach(element => {
|
|
28
|
-
const value = element.getAttribute(attributeName);
|
|
29
|
-
// If it violates the rule:
|
|
30
|
-
if (areLicit !== values.includes(value)) {
|
|
31
|
-
// Increment the violation count.
|
|
32
|
-
violationCount++;
|
|
33
|
-
// If itemization is required:
|
|
34
|
-
if (withItems) {
|
|
35
|
-
const what = `Element has attribute ${attributeName} with illicit value ${value}`;
|
|
36
|
-
instances.push(window.getInstance(element, 'attVal', what, 1, 2));
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
// If there were any violations and itemization is not required:
|
|
41
|
-
if (violationCount && ! withItems) {
|
|
42
|
-
const what = `Elements have attribute ${attributeName} with illicit values`;
|
|
43
|
-
// Add a summary instance to the instances.
|
|
44
|
-
instances.push(window.getInstance(null, 'attVal', what, violationCount, 2));
|
|
22
|
+
exports.reporter = async (page, catalog, withItems, attributeName, areLicit, values) => {
|
|
23
|
+
const getBadWhat = element => {
|
|
24
|
+
// Get the value of the attribute.
|
|
25
|
+
const value = element.getAttribute(attributeName);
|
|
26
|
+
// If it violates the rule:
|
|
27
|
+
if (areLicit !== values.includes(value)) {
|
|
28
|
+
// Return a violation description.
|
|
29
|
+
return `Element has attribute ${attributeName} with illicit value ${value}`;
|
|
45
30
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}, [withItems, attributeName, areLicit, values]);
|
|
31
|
+
};
|
|
32
|
+
const whats = `Elements have attribute ${attributeName} with illicit values`;
|
|
33
|
+
return await doTest(
|
|
34
|
+
page, catalog, withItems, 'attVal', `[${attributeName}]`, whats, 2, getBadWhat.toString()
|
|
35
|
+
);
|
|
52
36
|
};
|
package/testaro/autocomplete.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 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.
|
|
@@ -13,74 +13,77 @@
|
|
|
13
13
|
This test reports failures to equip name and email inputs with correct autocomplete attributes.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
// IMPORTS
|
|
17
|
+
|
|
18
|
+
const {doTest} = require('../procs/testaro');
|
|
19
|
+
|
|
16
20
|
// FUNCTIONS
|
|
17
21
|
|
|
18
22
|
// Runs the test and returns the result.
|
|
19
23
|
exports.reporter = async (
|
|
20
24
|
page,
|
|
25
|
+
catalog,
|
|
21
26
|
withItems,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
labels = {
|
|
28
|
+
name: ['your name', 'full name', 'first and last name'],
|
|
29
|
+
email: ['email'],
|
|
30
|
+
given: ['first name', 'forename', 'given name'],
|
|
31
|
+
family: ['last name', 'surname', 'family name']
|
|
32
|
+
}
|
|
26
33
|
) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (requiredAuto && ! (actualAuto && actualAuto.includes(requiredAuto))) {
|
|
64
|
-
// Increment the violation count.
|
|
65
|
-
violationCount++;
|
|
66
|
-
// If itemization is required:
|
|
67
|
-
if (withItems) {
|
|
68
|
-
const what = `input has no autocomplete="${requiredAuto}" attribute`;
|
|
69
|
-
// Add an instance to the instances.
|
|
70
|
-
instances.push(window.getInstance(candidate, 'autocomplete', what, 1, 2));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
// If there were any violations and itemization is not required:
|
|
75
|
-
if (violationCount && ! withItems) {
|
|
76
|
-
const what = 'Inputs are missing required autocomplete attributes';
|
|
77
|
-
// Add a summary instance to the instances.
|
|
78
|
-
instances.push(window.getInstance(null, 'autocomplete', what, violationCount, 2));
|
|
34
|
+
const getBadWhat = element => {
|
|
35
|
+
// Get the lower-cased accessible name of the element.
|
|
36
|
+
const accessibleName = window.getAccessibleName(element).toLowerCase();
|
|
37
|
+
const {type} = element;
|
|
38
|
+
let requiredAuto = '';
|
|
39
|
+
const labels = {
|
|
40
|
+
name: ['__nameLabels__'],
|
|
41
|
+
email: ['__emailLabels__'],
|
|
42
|
+
given: ['__givenLabels__'],
|
|
43
|
+
family: ['__familyLabels__']
|
|
44
|
+
};
|
|
45
|
+
// Get its required autocomplete value.
|
|
46
|
+
if (
|
|
47
|
+
type === 'email'
|
|
48
|
+
|| accessibleName && labels.email.some(label => accessibleName.includes(label))
|
|
49
|
+
) {
|
|
50
|
+
requiredAuto = 'email';
|
|
51
|
+
}
|
|
52
|
+
else if (
|
|
53
|
+
accessibleName && type === 'text' && labels.name.some(label => accessibleName.includes(label))
|
|
54
|
+
) {
|
|
55
|
+
requiredAuto = 'name';
|
|
56
|
+
}
|
|
57
|
+
else if (
|
|
58
|
+
accessibleName
|
|
59
|
+
&& type === 'text'
|
|
60
|
+
&& labels.given.some(label => accessibleName.includes(label))
|
|
61
|
+
) {
|
|
62
|
+
requiredAuto = 'given-name';
|
|
63
|
+
}
|
|
64
|
+
else if (
|
|
65
|
+
accessibleName
|
|
66
|
+
&& type === 'text'
|
|
67
|
+
&& labels.family.some(label => accessibleName.includes(label))
|
|
68
|
+
) {
|
|
69
|
+
requiredAuto = 'family-name';
|
|
79
70
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
// Get its actual autocomplete value.
|
|
72
|
+
const actualAuto = element.getAttribute('autocomplete');
|
|
73
|
+
// If an autocomplete value is required but not present:
|
|
74
|
+
if (requiredAuto && ! (actualAuto && actualAuto.includes(requiredAuto))) {
|
|
75
|
+
// Return a violation description.
|
|
76
|
+
return `input has no autocomplete="${requiredAuto}" attribute`;
|
|
84
77
|
}
|
|
85
|
-
}
|
|
78
|
+
};
|
|
79
|
+
const selector = 'input[type=text], input[type=email], input:not([type])';
|
|
80
|
+
const whats = 'Inputs are missing required autocomplete attributes';
|
|
81
|
+
const placeHolders = Object.keys(labels).map(key => `__${key}Labels__`);
|
|
82
|
+
const replacers = Object.values(labels).map(value => JSON.stringify(value));
|
|
83
|
+
// Create a stringified getBadWhat, with placeholders replaced with the specified label arrays.
|
|
84
|
+
let getBadWhatString = getBadWhat.toString();
|
|
85
|
+
[0, 1, 2, 3].forEach(index => {
|
|
86
|
+
getBadWhatString = getBadWhatString.replace(placeHolders[index], replacers[index]);
|
|
87
|
+
});
|
|
88
|
+
return doTest(page, catalog, withItems, 'autocomplete', selector, whats, 2, getBadWhatString);
|
|
86
89
|
};
|
package/testaro/bulk.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.
|
|
@@ -12,32 +12,33 @@
|
|
|
12
12
|
bulk
|
|
13
13
|
This test reports the count of visible elements. The test assumes that simplicity and compactness, with one page having one purpose, is an accessibility virtue. Users with visual, motor, and cognitive disabilities often have trouble finding what they want or understanding the purpose of a page if the page is cluttered with content.
|
|
14
14
|
*/
|
|
15
|
+
|
|
16
|
+
// Runs the test and returns the result.
|
|
15
17
|
exports.reporter = async page => {
|
|
16
18
|
// Get a count of elements deemed visible by Playwright.
|
|
17
|
-
const visibleElementCount = await page.locator(':visible').count();
|
|
18
|
-
//
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
if (severity > -1) {
|
|
26
|
-
totals[severity] = 1;
|
|
27
|
-
const what = `Page contains ${visibleElementCount} visible elements`;
|
|
28
|
-
// Create an instance reporting it.
|
|
29
|
-
instances.push(window.getInstance(document.documentElement, 'bulk', what, 1, severity));
|
|
30
|
-
}
|
|
19
|
+
const visibleElementCount = await page.locator('body :visible').count();
|
|
20
|
+
// Convert the count to a severity level, treating up to 400 as non-reportable.
|
|
21
|
+
const severity = Math.min(4, Math.round(visibleElementCount / 400)) - 1;
|
|
22
|
+
const totals = [0, 0, 0, 0];
|
|
23
|
+
// If the severity is reportable:
|
|
24
|
+
if (severity > -1) {
|
|
25
|
+
totals[severity] = 1;
|
|
26
|
+
// Return data, totals, and a summary standard instance.
|
|
31
27
|
return {
|
|
28
|
+
data: {},
|
|
32
29
|
totals,
|
|
33
|
-
|
|
30
|
+
standardInstances: [{
|
|
31
|
+
ruleID: 'bulk',
|
|
32
|
+
what: `Page contains ${visibleElementCount} visible elements`,
|
|
33
|
+
ordinalSeverity: severity,
|
|
34
|
+
count: 1
|
|
35
|
+
}]
|
|
34
36
|
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Return the result.
|
|
37
|
+
}
|
|
38
|
+
// Otherwise, return data, totals, and an empty array of standard instances.
|
|
38
39
|
return {
|
|
39
40
|
data: {},
|
|
40
41
|
totals,
|
|
41
|
-
standardInstances:
|
|
42
|
+
standardInstances: []
|
|
42
43
|
};
|
|
43
44
|
};
|
package/testaro/buttonMenu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
5
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
6
|
https://opensource.org/license/mit/ for details.
|
|
@@ -13,13 +13,99 @@
|
|
|
13
13
|
This test reports nonstandard navigation among menu items of button-controlled menus. Standards are based on https://www.w3.org/TR/wai-aria-practices-1.1/#menu. The trialKeys argument is an array of strings, each of which may be 'Home', 'End', '+', or '-'. The '+' string represents the ArrowDown or ArrowRight key, and the '-' string represents the ArrowUp or ArrowLeft key, depending on the orientation of the current menu. When the trialKeys argument is missing or is an empty array, 12 keys are selected at random.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
// ########## IMPORTS
|
|
17
|
-
|
|
18
|
-
// Module to get locator data.
|
|
19
|
-
const {getLocatorData} = require('../procs/getLocatorData');
|
|
20
|
-
|
|
21
16
|
// ########## FUNCTIONS
|
|
22
17
|
|
|
18
|
+
// Returns data about the element referenced by a locator.
|
|
19
|
+
const getLocatorData = async loc => {
|
|
20
|
+
const locCount = await loc.count();
|
|
21
|
+
// If the locator identifies exactly 1 element:
|
|
22
|
+
if (locCount === 1) {
|
|
23
|
+
// Get the facts obtainable from the browser.
|
|
24
|
+
const data = await loc.evaluate(element => {
|
|
25
|
+
// Tag name.
|
|
26
|
+
const tagName = element.tagName;
|
|
27
|
+
// ID.
|
|
28
|
+
const id = element.id || '';
|
|
29
|
+
// Texts.
|
|
30
|
+
const {textContent} = element;
|
|
31
|
+
const alts = Array.from(element.querySelectorAll('img[alt]:not([alt=""])'));
|
|
32
|
+
const altTexts = alts.map(alt => alt.getAttribute('alt'));
|
|
33
|
+
const altsText = altTexts.join(' ');
|
|
34
|
+
const ariaLabelText = element.ariaLabel || '';
|
|
35
|
+
const refLabelID = element.getAttribute('aria-labelledby');
|
|
36
|
+
const refLabel = refLabelID ? document.getElementById(refLabelID) : '';
|
|
37
|
+
const refLabelText = refLabel ? refLabel.textContent : '';
|
|
38
|
+
let labelsText = '';
|
|
39
|
+
if (tagName === 'INPUT') {
|
|
40
|
+
const labels = element.labels || [];
|
|
41
|
+
const labelTexts = [];
|
|
42
|
+
labels.forEach(label => {
|
|
43
|
+
labelTexts.push(label.textContent);
|
|
44
|
+
});
|
|
45
|
+
labelsText = labelTexts.join(' ');
|
|
46
|
+
}
|
|
47
|
+
let text = [textContent, altsText, ariaLabelText, refLabelText, labelsText]
|
|
48
|
+
.join(' ')
|
|
49
|
+
.replace(/\s+/g, ' ')
|
|
50
|
+
.trim();
|
|
51
|
+
if (! text) {
|
|
52
|
+
text = element.outerHTML.replace(/\s+/g, ' ').trim();
|
|
53
|
+
}
|
|
54
|
+
if (/^<[^<>]+>$/.test(text)) {
|
|
55
|
+
text = element.parentElement.outerHTML.replace(/\s+/g, ' ').trim();
|
|
56
|
+
}
|
|
57
|
+
// Location.
|
|
58
|
+
let location = {
|
|
59
|
+
doc: 'dom',
|
|
60
|
+
type: 'box',
|
|
61
|
+
spec: {}
|
|
62
|
+
};
|
|
63
|
+
if (id) {
|
|
64
|
+
location.type = 'selector';
|
|
65
|
+
location.spec = `#${id}`;
|
|
66
|
+
}
|
|
67
|
+
// Return the data.
|
|
68
|
+
return {
|
|
69
|
+
tagName,
|
|
70
|
+
id,
|
|
71
|
+
location,
|
|
72
|
+
excerpt: text
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
// If an ID-based selector could not be defined:
|
|
76
|
+
if (data.location.type === 'box') {
|
|
77
|
+
// Define a bounding-box-based location.
|
|
78
|
+
const rawSpec = await loc.boundingBox();
|
|
79
|
+
// If there is a bounding box (i.e. the element is visible):
|
|
80
|
+
if (rawSpec) {
|
|
81
|
+
// Populate the location.
|
|
82
|
+
Object.keys(rawSpec).forEach(specName => {
|
|
83
|
+
data.location.spec[specName] = Math.round(rawSpec[specName]);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Otherwise, i.e. if there is no bounding box:
|
|
87
|
+
else {
|
|
88
|
+
// Empty the location.
|
|
89
|
+
data.location.doc = '';
|
|
90
|
+
data.location.type = '';
|
|
91
|
+
data.location.spec = '';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// If the text is long:
|
|
95
|
+
if (data.excerpt.length > 400) {
|
|
96
|
+
// Truncate its middle.
|
|
97
|
+
data.excerpt = `${data.excerpt.slice(0, 200)} … ${data.excerpt.slice(-200)}`;
|
|
98
|
+
}
|
|
99
|
+
// Return the data.
|
|
100
|
+
return data;
|
|
101
|
+
}
|
|
102
|
+
// Otherwise, i.e. if it does not identify exactly 1 element:
|
|
103
|
+
else {
|
|
104
|
+
// Report this.
|
|
105
|
+
console.log(`ERROR: Locator count to get data from is ${locCount} instead of 1`);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
23
109
|
// Returns an adjacent index, with wrapping.
|
|
24
110
|
const getAdjacentIndexWithWrap = (groupSize, startIndex, increment) => {
|
|
25
111
|
let newIndex = startIndex + increment;
|
|
@@ -90,7 +176,7 @@ const focusSuccess = async (miLocsDir, priorIndex, key, isPseudo) => {
|
|
|
90
176
|
return result;
|
|
91
177
|
};
|
|
92
178
|
// Performs the test and reports the result.
|
|
93
|
-
exports.reporter = async (page, withItems, trialKeySpecs = []) => {
|
|
179
|
+
exports.reporter = async (page, catalog, withItems, trialKeySpecs = []) => {
|
|
94
180
|
// Initialize the result.
|
|
95
181
|
const data = {};
|
|
96
182
|
const totals = [0, 0, 0, 0];
|
|
@@ -99,8 +185,8 @@ exports.reporter = async (page, withItems, trialKeySpecs = []) => {
|
|
|
99
185
|
const mbLocAll = page.locator(
|
|
100
186
|
'button[aria-controls][aria-expanded][aria-haspopup=true], button[aria-controls][aria-expanded][aria-haspopup=menu]'
|
|
101
187
|
);
|
|
102
|
-
// For each menu button:
|
|
103
188
|
const mbLocsAll = await mbLocAll.all();
|
|
189
|
+
// For each menu button:
|
|
104
190
|
for (const mbLoc of mbLocsAll) {
|
|
105
191
|
// Get a locator for its menu.
|
|
106
192
|
const menuID = await mbLoc.getAttribute('aria-controls');
|
|
@@ -206,17 +292,19 @@ exports.reporter = async (page, withItems, trialKeySpecs = []) => {
|
|
|
206
292
|
totals[2]++;
|
|
207
293
|
// If itemization is required:
|
|
208
294
|
if (withItems) {
|
|
209
|
-
//
|
|
210
|
-
|
|
295
|
+
// Create a proto-instance.
|
|
296
|
+
const protoInstance = {
|
|
211
297
|
ruleID: 'buttonMenu',
|
|
212
298
|
what: `Menu responds nonstandardly to the ${key} key`,
|
|
213
299
|
ordinalSeverity: 2,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
300
|
+
count: 1
|
|
301
|
+
};
|
|
302
|
+
// Add a catalog index or XPath to it if possible.
|
|
303
|
+
addCatalogIndex(protoInstance, mbLoc, catalog);
|
|
304
|
+
// Add the proto-instance to the standard instances.
|
|
305
|
+
standardInstances.push(protoInstance);
|
|
219
306
|
}
|
|
307
|
+
// Stop testing the menu button.
|
|
220
308
|
break;
|
|
221
309
|
}
|
|
222
310
|
}
|
|
@@ -240,18 +328,17 @@ exports.reporter = async (page, withItems, trialKeySpecs = []) => {
|
|
|
240
328
|
totals[2]++;
|
|
241
329
|
// If itemization is required:
|
|
242
330
|
if (withItems) {
|
|
243
|
-
//
|
|
244
|
-
const
|
|
245
|
-
// Add an instance to the result.
|
|
246
|
-
standardInstances.push({
|
|
331
|
+
// Create a proto-instance.
|
|
332
|
+
const protoInstance = {
|
|
247
333
|
ruleID: 'buttonMenu',
|
|
248
|
-
what:
|
|
334
|
+
what: 'Menu button does not control exactly 1 menu',
|
|
249
335
|
ordinalSeverity: 2,
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
336
|
+
count: 1
|
|
337
|
+
};
|
|
338
|
+
// Add a catalog index or XPath to it if possible.
|
|
339
|
+
addCatalogIndex(protoInstance, mbLoc, catalog);
|
|
340
|
+
// Add the proto-instance to the standard instances.
|
|
341
|
+
standardInstances.push(protoInstance);
|
|
255
342
|
}
|
|
256
343
|
}
|
|
257
344
|
}
|
|
@@ -263,14 +350,6 @@ exports.reporter = async (page, withItems, trialKeySpecs = []) => {
|
|
|
263
350
|
what: 'Menu buttons and menus behave nonstandardly',
|
|
264
351
|
count: totals[2],
|
|
265
352
|
ordinalSeverity: 2,
|
|
266
|
-
tagName: '',
|
|
267
|
-
id: '',
|
|
268
|
-
location: {
|
|
269
|
-
doc: '',
|
|
270
|
-
type: '',
|
|
271
|
-
spec: ''
|
|
272
|
-
},
|
|
273
|
-
excerpt: ''
|
|
274
353
|
});
|
|
275
354
|
}
|
|
276
355
|
return {
|