testaro 60.14.0 → 60.15.1
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/package.json +2 -1
- package/procs/isInlineLink.js +1 -0
- package/procs/testaro.js +1 -0
- package/run.js +5 -4
- package/testaro/adbID.js +1 -1
- package/testaro/altScheme.js +1 -1
- package/testaro/captionLoc.js +1 -1
- package/testaro/datalistRef.js +1 -1
- package/testaro/hover.js +2 -2
- package/testaro/imageLink.js +1 -1
- package/testaro/legendLoc.js +1 -1
- package/testaro/linkAmb.js +60 -37
- package/testaro/miniText.js +1 -1
- package/testaro/motion.js +1 -1
- package/testaro/optRoleSel.js +1 -1
- package/testaro/phOnly.js +24 -34
- package/testaro/pseudoP.json +10 -0
- package/testaro/radioSet.js +57 -89
- package/testaro/role.js +21 -61
- package/testaro/secHeading.js +37 -27
- package/testaro/targetSmall.js +86 -28
- package/testaro/textSem.js +1 -1
- package/tests/testaro.js +3 -10
- package/validation/tests/targets/linkAmb/index.html +1 -1
- package/validation/tests/targets/phOnly/index.html +1 -1
- package/validation/tests/targets/secHeading/index.html +4 -4
- package/testaro/pseudoP.js +0 -51
- package/testaro/targetTiny.js +0 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testaro",
|
|
3
|
-
"version": "60.
|
|
3
|
+
"version": "60.15.1",
|
|
4
4
|
"description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@siteimprove/alfa-rules": "*",
|
|
40
40
|
"@wally-ax/wax-dev": "npm:@jrpool/wax-dev@^1.0.0",
|
|
41
41
|
"accessibility-checker": "*",
|
|
42
|
+
"aria-query": "*",
|
|
42
43
|
"aslint-testaro": "*",
|
|
43
44
|
"axe-playwright": "*",
|
|
44
45
|
"dotenv": "*",
|
package/procs/isInlineLink.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
A link is classified as inline unless its declared or effective display is block.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
// Returns whether a locator references an inline link.
|
|
17
18
|
exports.isInlineLink = async loc => await loc.evaluate(element => {
|
|
18
19
|
// Returns the normalized text content of an element.
|
|
19
20
|
const realTextOf = element => element ? element.textContent.replace(/\s/g, '') : '';
|
package/procs/testaro.js
CHANGED
package/run.js
CHANGED
|
@@ -494,7 +494,7 @@ const launch = exports.launch = async (
|
|
|
494
494
|
window.getInstance = (
|
|
495
495
|
element, ruleID, what, count = 1, ordinalSeverity, summaryTagName = ''
|
|
496
496
|
) => {
|
|
497
|
-
// If
|
|
497
|
+
// If an element has been specified:
|
|
498
498
|
if (element) {
|
|
499
499
|
// Get its properties.
|
|
500
500
|
const boxData = element.getBoundingClientRect();
|
|
@@ -503,8 +503,9 @@ const launch = exports.launch = async (
|
|
|
503
503
|
});
|
|
504
504
|
const {x, y, width, height} = boxData;
|
|
505
505
|
const {tagName, id = ''} = element;
|
|
506
|
-
const rawExcerpt = element.textContent.trim() || element.outerHTML.trim()
|
|
507
|
-
|
|
506
|
+
const rawExcerpt = (element.textContent.trim() || element.outerHTML.trim())
|
|
507
|
+
.replace(/\s+/g, ' ');
|
|
508
|
+
const excerpt = rawExcerpt.slice(0, 200);
|
|
508
509
|
// Return an itemized instance.
|
|
509
510
|
return {
|
|
510
511
|
ruleID,
|
|
@@ -528,7 +529,7 @@ const launch = exports.launch = async (
|
|
|
528
529
|
pathID: window.getXPath(element)
|
|
529
530
|
};
|
|
530
531
|
}
|
|
531
|
-
// Otherwise, i.e. if no element
|
|
532
|
+
// Otherwise, i.e. if no element has been specified, return a summary instance.
|
|
532
533
|
return {
|
|
533
534
|
ruleID,
|
|
534
535
|
what,
|
package/testaro/adbID.js
CHANGED
package/testaro/altScheme.js
CHANGED
package/testaro/captionLoc.js
CHANGED
package/testaro/datalistRef.js
CHANGED
package/testaro/hover.js
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
the rule is considered violated.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// IMPORTS
|
|
21
21
|
|
|
22
22
|
// Module to perform common operations.
|
|
23
23
|
const {getBasicResult, getVisibleCountChange} = require('../procs/testaro');
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// FUNCTIONS
|
|
26
26
|
|
|
27
27
|
// Gets a violation description.
|
|
28
28
|
const getViolationDescription = (change, elapsedTime) =>
|
package/testaro/imageLink.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Juan S. Casado.
|
|
3
|
+
© 2025 Juan S. Casado.
|
|
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.
|
package/testaro/legendLoc.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Juan S. Casado.
|
|
3
|
+
© 2025 Juan S. Casado.
|
|
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.
|
package/testaro/linkAmb.js
CHANGED
|
@@ -15,46 +15,69 @@
|
|
|
15
15
|
Text contents are compared case-insensitively.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
20
|
-
// Module to get locator data.
|
|
21
|
-
const {getLocatorData} = require('../procs/getLocatorData');
|
|
22
|
-
|
|
23
|
-
// ########## FUNCTIONS
|
|
18
|
+
// FUNCTIONS
|
|
24
19
|
|
|
25
20
|
// Runs the test and returns the result.
|
|
26
21
|
exports.reporter = async (page, withItems) => {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
22
|
+
// Return totals and standard instances for the rule.
|
|
23
|
+
return await page.evaluate(withItems => {
|
|
24
|
+
// Get all links.
|
|
25
|
+
const allLinks = Array.from(document.body.getElementsByTagName('a'));
|
|
26
|
+
// Get the visible ones.
|
|
27
|
+
const visibleLinks = allLinks.filter(link => link.checkVisibility({
|
|
28
|
+
contentVisibilityAuto: true,
|
|
29
|
+
opacityProperty: true,
|
|
30
|
+
visibilityProperty: true
|
|
31
|
+
}));
|
|
32
|
+
// Initialize the data.
|
|
33
|
+
const linksData = {
|
|
34
|
+
elementData: [],
|
|
35
|
+
textTotals: {}
|
|
36
|
+
};
|
|
37
|
+
// For each visible link:
|
|
38
|
+
visibleLinks.forEach(element => {
|
|
39
|
+
// Get its trimmed and lowercased text.
|
|
40
|
+
const text = element.textContent.trim().toLowerCase();
|
|
41
|
+
// Get its destination.
|
|
42
|
+
const href = element.getAttribute('href');
|
|
43
|
+
// Add to the data.
|
|
44
|
+
linksData.elementData.push([text, href]);
|
|
45
|
+
linksData.textTotals[text] ??= {
|
|
46
|
+
linkCount: 0,
|
|
47
|
+
hrefs: new Set()
|
|
48
|
+
};
|
|
49
|
+
const linkData = linksData.textTotals[text];
|
|
50
|
+
linkData.linkCount++;
|
|
51
|
+
linkData.hrefs.add(href);
|
|
52
|
+
});
|
|
53
|
+
let violationCount = 0;
|
|
54
|
+
const instances = [];
|
|
55
|
+
// For each visible link:
|
|
56
|
+
visibleLinks.forEach((element, index) => {
|
|
57
|
+
const text = linksData.elementData[index][0];
|
|
58
|
+
const {linkCount, hrefs} = linksData.textTotals[text];
|
|
59
|
+
// If it violates the rule:
|
|
60
|
+
if (hrefs.size > 1) {
|
|
61
|
+
// Increment the violation count.
|
|
62
|
+
violationCount++;
|
|
63
|
+
// If itemization is required:
|
|
64
|
+
if (withItems) {
|
|
65
|
+
const what = `${linkCount} links with this text have ${hrefs.size} different destinations`;
|
|
66
|
+
// Add an instance to the instances.
|
|
67
|
+
instances.push(window.getInstance(element, 'linkAmb', what, 1, 2));
|
|
68
|
+
}
|
|
51
69
|
}
|
|
70
|
+
});
|
|
71
|
+
// If there were any violations and itemization is not required:
|
|
72
|
+
if (violationCount && ! withItems) {
|
|
73
|
+
const what = 'Links have the same text but different destinations';
|
|
74
|
+
// Add a summary instance to the instances.
|
|
75
|
+
instances.push(window.getInstance(null, 'linkAmb', what, violationCount, 2, 'A'));
|
|
52
76
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return await getRuleResult(withItems, all, 'linkAmb', whats, 2);
|
|
77
|
+
return {
|
|
78
|
+
data: {},
|
|
79
|
+
totals: [0, 0, violationCount, 0],
|
|
80
|
+
standardInstances: instances
|
|
81
|
+
};
|
|
82
|
+
}, withItems);
|
|
60
83
|
};
|
package/testaro/miniText.js
CHANGED
|
@@ -47,6 +47,6 @@ exports.reporter = async (page, withItems) => {
|
|
|
47
47
|
};
|
|
48
48
|
const whats = 'Visible elements have font sizes smaller than 11 pixels';
|
|
49
49
|
return await doTest(
|
|
50
|
-
page, withItems, 'miniText', 'body *:not(script, style)', whats, 2,
|
|
50
|
+
page, withItems, 'miniText', 'body *:not(script, style)', whats, 2, null, getBadWhat.toString()
|
|
51
51
|
);
|
|
52
52
|
};
|
package/testaro/motion.js
CHANGED
|
@@ -45,7 +45,7 @@ exports.reporter = async page => {
|
|
|
45
45
|
// Get the screenshot PNG buffers made by the shoot0 and shoot1 tests.
|
|
46
46
|
let shoot0PNGBuffer = await fs.readFile(`${tmpDir}/testaro-shoot-0.png`);
|
|
47
47
|
let shoot1PNGBuffer = await fs.readFile(`${tmpDir}/testaro-shoot-1.png`);
|
|
48
|
-
// Delete the
|
|
48
|
+
// Delete the buffer files.
|
|
49
49
|
await fs.unlink(`${tmpDir}/testaro-shoot-0.png`);
|
|
50
50
|
await fs.unlink(`${tmpDir}/testaro-shoot-1.png`);
|
|
51
51
|
// If both buffers exist:
|
package/testaro/optRoleSel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Juan S. Casado.
|
|
3
|
+
© 2025 Juan S. Casado.
|
|
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.
|
package/testaro/phOnly.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Juan S. Casado.
|
|
3
|
+
© 2025 Juan S. Casado.
|
|
4
|
+
© 2025 Jonathan Robert Pool.
|
|
4
5
|
|
|
5
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -10,42 +11,31 @@
|
|
|
10
11
|
|
|
11
12
|
/*
|
|
12
13
|
phOnly
|
|
13
|
-
Clean-room rule: input elements that have
|
|
14
|
+
Clean-room rule: This test reports input elements that have placeholders but no accessible names.
|
|
15
|
+
The standard for accessible name computation is employed; it accepts title attributes as
|
|
16
|
+
sources for accessible names. Thus, this test does not report reliance on title attributes for
|
|
17
|
+
accessible names, although such reliance is generally considered a poor practice.
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
// IMPORTS
|
|
17
21
|
|
|
18
|
-
const
|
|
19
|
-
return await loc.evaluate(el => {
|
|
20
|
-
// Quick accessible name checks: aria-label, aria-labelledby, associated <label>
|
|
21
|
-
// NOTE: `title` is intentionally NOT considered a reliable accessible name here
|
|
22
|
-
// because the validation targets expect placeholders with title attributes to be flagged.
|
|
23
|
-
if (el.hasAttribute('aria-label')) return true;
|
|
24
|
-
if (el.hasAttribute('aria-labelledby')) return true;
|
|
25
|
-
// check for label[for]
|
|
26
|
-
const id = el.getAttribute('id');
|
|
27
|
-
if (id) {
|
|
28
|
-
if (document.querySelector(`label[for="${CSS.escape(id)}"]`)) return true;
|
|
29
|
-
}
|
|
30
|
-
// check implicit ancestor label
|
|
31
|
-
let parent = el.parentElement;
|
|
32
|
-
while (parent) {
|
|
33
|
-
if (parent.tagName && parent.tagName.toUpperCase() === 'LABEL') return true;
|
|
34
|
-
parent = parent.parentElement;
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
});
|
|
38
|
-
};
|
|
22
|
+
const {doTest} = require('../procs/testaro');
|
|
39
23
|
|
|
24
|
+
// FUNCTIONS
|
|
25
|
+
|
|
26
|
+
// Runs the test and returns the result.
|
|
40
27
|
exports.reporter = async (page, withItems) => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
const getBadWhat = element => {
|
|
29
|
+
// Get the accessible name of the element.
|
|
30
|
+
const accessibleName = window.getAccessibleName(element);
|
|
31
|
+
// If there is none:
|
|
32
|
+
if (! accessibleName) {
|
|
33
|
+
// Return a violation description.
|
|
34
|
+
return 'Element has a placeholder but no accessible name';
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const whats = 'input elements have placeholders but no accessible names';
|
|
38
|
+
return await doTest(
|
|
39
|
+
page, withItems, 'phOnly', 'input[placeholder]', whats, 2, 'INPUT', getBadWhat.toString()
|
|
40
|
+
);
|
|
51
41
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ruleID": "pseudoP",
|
|
3
|
+
"selector": "body br + br",
|
|
4
|
+
"complaints": {
|
|
5
|
+
"instance": "br element follows another br element, likely acting as a pseudo-paragraph",
|
|
6
|
+
"summary": "br elements follow other br elements, likely acting as pseudo-paragraphs"
|
|
7
|
+
},
|
|
8
|
+
"ordinalSeverity": 0,
|
|
9
|
+
"summaryTagName": "BR"
|
|
10
|
+
}
|
package/testaro/radioSet.js
CHANGED
|
@@ -1,5 +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
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.
|
|
@@ -9,101 +10,68 @@
|
|
|
9
10
|
|
|
10
11
|
/*
|
|
11
12
|
radioSet
|
|
12
|
-
This test reports nonstandard
|
|
13
|
-
that two or more radio buttons with the same name, and no other radio buttons, be grouped
|
|
14
|
-
|
|
13
|
+
This test reports nonstandard groupings of radio buttons. It defines a standard grouping to
|
|
14
|
+
require that two or more radio buttons with the same name, and no other radio buttons, be grouped
|
|
15
|
+
in a fieldset element with a valid legend element.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
//
|
|
18
|
+
// IMPORTS
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
20
|
+
const {doTest} = require('../procs/testaro');
|
|
21
21
|
|
|
22
|
-
//
|
|
22
|
+
// FUNCTIONS
|
|
23
23
|
|
|
24
24
|
// Runs the test and returns the result.
|
|
25
25
|
exports.reporter = async (page, withItems) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
.querySelectorAll(`input[type=radio][name=${elName}]`)
|
|
58
|
-
.length;
|
|
59
|
-
// If the count is at least 2:
|
|
60
|
-
if (nameGroupSize > 1) {
|
|
61
|
-
// Get the count of radio buttons in the field set.
|
|
62
|
-
const groupSize = elFS.querySelectorAll('input[type=radio]').length;
|
|
63
|
-
// If it is the same:
|
|
64
|
-
if (groupSize === nameGroupSize) {
|
|
65
|
-
// Get the count of radio buttons with the same name in the document.
|
|
66
|
-
const nameDocSize = document
|
|
67
|
-
.querySelectorAll(`input[type=radio][name=${elName}]`)
|
|
68
|
-
.length;
|
|
69
|
-
// If none of them is outside the field set:
|
|
70
|
-
if (nameDocSize === nameGroupSize) {
|
|
71
|
-
// Return rule conformance.
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
return 'nameLeak';
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
return 'fsMixed';
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
return 'only1RB';
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
return 'legendBad';
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
return 'noFS';
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
return 'noName';
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
// If it does:
|
|
99
|
-
if (howBad) {
|
|
100
|
-
// Add the locator to the array of violators.
|
|
101
|
-
all.locs.push([loc, params[howBad]]);
|
|
26
|
+
const getBadWhat = element => {
|
|
27
|
+
// Get the name of the element.
|
|
28
|
+
const elName = element.name;
|
|
29
|
+
// If it has none:
|
|
30
|
+
if (! elName) {
|
|
31
|
+
// Return a violation description.
|
|
32
|
+
return 'radio button has no name attribute';
|
|
33
|
+
}
|
|
34
|
+
// Identify the field set of the element.
|
|
35
|
+
const elFS = element.closest('fieldset');
|
|
36
|
+
// If it has none:
|
|
37
|
+
if (! elFS) {
|
|
38
|
+
// Return a violation description.
|
|
39
|
+
return 'radio button is not in a field set';
|
|
40
|
+
}
|
|
41
|
+
// Get the first child element of the field set.
|
|
42
|
+
const fsChild1 = elFS.firstElementChild;
|
|
43
|
+
// Get whether the child is a legend with text content.
|
|
44
|
+
const legendOK = fsChild1.tagName === 'LEGEND'
|
|
45
|
+
&& fsChild1.textContent.replace(/\s/g, '').length;
|
|
46
|
+
// If it is not:
|
|
47
|
+
if (! legendOK) {
|
|
48
|
+
// Return a violation description.
|
|
49
|
+
return 'radio button is in a field set without a valid legend';
|
|
50
|
+
}
|
|
51
|
+
// Get the count of radio buttons with the same name in the field set.
|
|
52
|
+
const nameGroupSize = elFS.querySelectorAll(`input[type=radio][name=${elName}]`).length;
|
|
53
|
+
// If the count is only 1:
|
|
54
|
+
if (nameGroupSize === 1) {
|
|
55
|
+
// Return a violation description.
|
|
56
|
+
return 'radio button is the only one with its name in its field set';
|
|
102
57
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
58
|
+
// Get the count of radio buttons in the field set.
|
|
59
|
+
const groupSize = elFS.querySelectorAll('input[type=radio]').length;
|
|
60
|
+
// If it is greater than the count of radio buttons with the same name in the field set:
|
|
61
|
+
if (groupSize > nameGroupSize) {
|
|
62
|
+
// Return a violation description.
|
|
63
|
+
return 'radio button shares a field set with differently named others';
|
|
64
|
+
}
|
|
65
|
+
// Get the count of radio buttons with the same name in the document.
|
|
66
|
+
const nameDocSize = document.querySelectorAll(`input[type=radio][name=${elName}]`).length;
|
|
67
|
+
// If it is greater, and thus some such radio button is outside the field set:
|
|
68
|
+
if (nameDocSize > nameGroupSize) {
|
|
69
|
+
// Return a violation description.
|
|
70
|
+
return 'radio button shares a name with others outside its field set';
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const whats = 'Radio buttons are not validly grouped in fieldsets with legends';
|
|
74
|
+
return await doTest(
|
|
75
|
+
page, withItems, 'radioSet', 'input[type=radio]', whats, 2, 'INPUT', getBadWhat.toString()
|
|
76
|
+
);
|
|
109
77
|
};
|
package/testaro/role.js
CHANGED
|
@@ -15,75 +15,35 @@
|
|
|
15
15
|
|
|
16
16
|
// IMPORTS
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const {
|
|
18
|
+
const {elementRoles} = require('aria-query');
|
|
19
|
+
const {getBasicResult} = require('../procs/testaro');
|
|
20
20
|
|
|
21
21
|
// CONSTANTS
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
article: 'article',
|
|
26
|
-
aside: 'complementary',
|
|
27
|
-
button: 'button',
|
|
28
|
-
datalist: 'listbox',
|
|
29
|
-
dd: 'definition',
|
|
30
|
-
details: 'group',
|
|
31
|
-
dfn: 'term',
|
|
32
|
-
dialog: 'dialog',
|
|
33
|
-
dt: 'term',
|
|
34
|
-
fieldset: 'group',
|
|
35
|
-
figure: 'figure',
|
|
36
|
-
h1: 'heading',
|
|
37
|
-
h2: 'heading',
|
|
38
|
-
h3: 'heading',
|
|
39
|
-
h4: 'heading',
|
|
40
|
-
h5: 'heading',
|
|
41
|
-
h6: 'heading',
|
|
42
|
-
hr: 'separator',
|
|
43
|
-
html: 'document',
|
|
44
|
-
'input[type=number]': 'spinbutton',
|
|
45
|
-
'input[type=text]': 'textbox',
|
|
46
|
-
'input[type=text, list]': 'combobox',
|
|
47
|
-
li: 'listitem',
|
|
48
|
-
main: 'main',
|
|
49
|
-
math: 'math',
|
|
50
|
-
menu: 'list',
|
|
51
|
-
nav: 'navigation',
|
|
52
|
-
ol: 'list',
|
|
53
|
-
output: 'status',
|
|
54
|
-
progress: 'progressbar',
|
|
55
|
-
summary: 'button',
|
|
56
|
-
SVG: 'graphics-document',
|
|
57
|
-
table: 'table',
|
|
58
|
-
tbody: 'rowgroup',
|
|
59
|
-
textarea: 'textbox',
|
|
60
|
-
tfoot: 'rowgroup',
|
|
61
|
-
thead: 'rowgroup',
|
|
62
|
-
tr: 'row',
|
|
63
|
-
ul: 'list'
|
|
64
|
-
};
|
|
65
|
-
const implicitRoles = new Set(Object.values(roleImplications));
|
|
23
|
+
// Implicit roles
|
|
24
|
+
const implicitRoles = new Set(Array.from(elementRoles.values()).flat());
|
|
66
25
|
|
|
67
26
|
// FUNCTIONS
|
|
68
27
|
|
|
69
28
|
// Runs the test and returns the result.
|
|
70
29
|
exports.reporter = async (page, withItems) => {
|
|
71
|
-
// Get locators for
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
30
|
+
// Get locators for the elements with explicit roles.
|
|
31
|
+
const loc = page.locator('[role]');
|
|
32
|
+
const locs = await loc.all();
|
|
33
|
+
const violations = [];
|
|
34
|
+
// Get data on those with roles that are also implicit.
|
|
35
|
+
for (const loc of locs) {
|
|
36
|
+
const roleSpec = await loc.getAttribute('role');
|
|
37
|
+
const roles = roleSpec.split(/\s+/);
|
|
38
|
+
const badRole = roles.find(role => implicitRoles.has(role));
|
|
39
|
+
if (badRole) {
|
|
40
|
+
violations.push({
|
|
41
|
+
loc,
|
|
42
|
+
what: `Explicit ${badRole} role of the element is also an implicit HTML element role`
|
|
43
|
+
});
|
|
81
44
|
}
|
|
82
45
|
}
|
|
83
|
-
//
|
|
84
|
-
const whats =
|
|
85
|
-
|
|
86
|
-
'Elements have roles assigned that are also implicit roles of HTML elements'
|
|
87
|
-
];
|
|
88
|
-
return await getRuleResult(withItems, all, 'role', whats, 0);
|
|
46
|
+
// Get and return a result.
|
|
47
|
+
const whats = 'Elements have roles assigned that are also implicit HTML element roles';
|
|
48
|
+
return await getBasicResult(page, withItems, 'role', 0, null, whats, {}, violations);
|
|
89
49
|
};
|
package/testaro/secHeading.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Juan S. Casado.
|
|
3
|
+
© 2025 Juan S. Casado.
|
|
4
|
+
© 2025 Jonathan Robert Pool.
|
|
4
5
|
|
|
5
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -10,35 +11,44 @@
|
|
|
10
11
|
|
|
11
12
|
/*
|
|
12
13
|
secHeading
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
This test reports sectioning containers that have child headings of which the first has a depth
|
|
15
|
+
(i.e. level) lower than at least one of the others. An example is a section element whose first
|
|
16
|
+
heading child is an h3 element and whose subsequent heading children include an h2 element. The
|
|
17
|
+
first child heading is presumed the principal heading of the container, so this pattern merits
|
|
18
|
+
scrutiny.
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
// IMPORTS
|
|
18
22
|
|
|
23
|
+
const {doTest} = require('../procs/testaro');
|
|
24
|
+
|
|
25
|
+
// FUNCTIONS
|
|
26
|
+
|
|
27
|
+
// Runs the test and returns the result.
|
|
19
28
|
exports.reporter = async (page, withItems) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
const getBadWhat = element => {
|
|
30
|
+
// Get the children of the element.
|
|
31
|
+
const children = Array.from(element.children);
|
|
32
|
+
// Get the headings among them.
|
|
33
|
+
const headingChildren = children.filter(child => /^H[1-6]$/.test(child.tagName));
|
|
34
|
+
// Get their depths.
|
|
35
|
+
const headingChildDepths = headingChildren.map(child => Number(child.tagName.slice(1)));
|
|
36
|
+
// If there are 2 or more heading children:
|
|
37
|
+
if (headingChildren.length > 1) {
|
|
38
|
+
// Get the depth of the first of them.
|
|
39
|
+
const firstHeadingDepth = headingChildDepths[0];
|
|
40
|
+
// Get the minimum of their depths.
|
|
41
|
+
const minHeadingDepth = Math.min(...headingChildDepths);
|
|
42
|
+
// If any heading is less deep than the first:
|
|
43
|
+
if (minHeadingDepth < firstHeadingDepth) {
|
|
44
|
+
// Return a violation description.
|
|
45
|
+
return `First child heading is H${firstHeadingDepth}, but another is H${minHeadingDepth}`;
|
|
27
46
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return curLevel < prevLevel;
|
|
36
|
-
});
|
|
37
|
-
if (isBad) all.locs.push(loc);
|
|
38
|
-
}
|
|
39
|
-
const whats = [
|
|
40
|
-
'Element violates the logical level order in its sectioning container',
|
|
41
|
-
'Heading elements violate the logical level order in their sectioning containers'
|
|
42
|
-
];
|
|
43
|
-
return await getRuleResult(withItems, all, 'secHeading', whats, 1);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const selector = 'SECTION, ARTICLE, NAV, ASIDE, MAIN';
|
|
50
|
+
const whats = 'First child headings of sectioning containers are deeper than others';
|
|
51
|
+
return await doTest(
|
|
52
|
+
page, withItems, 'secHeading', selector, whats, 0, null, getBadWhat.toString()
|
|
53
|
+
);
|
|
44
54
|
};
|
package/testaro/targetSmall.js
CHANGED
|
@@ -10,38 +10,96 @@
|
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
12
|
targetSmall
|
|
13
|
-
Related to Tenon rule 152
|
|
14
|
-
This test reports visible buttons, inputs, and
|
|
15
|
-
|
|
13
|
+
Related to Tenon rule 152.
|
|
14
|
+
This test reports visible pointer targets, i.e. labels, buttons, inputs, and links, that are
|
|
15
|
+
small enough or near enough to other targets to make pointer interaction difficult. This test
|
|
16
|
+
relates to WCAG 2.2 Success Criteria 2.5.5 and 2.5.8, but does not attempt to implement either
|
|
17
|
+
of them precisely. For example, the test reports a small pointer target that is far from all
|
|
18
|
+
other targets, although it conforms to the Success Criteria.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
// Module to perform common operations.
|
|
21
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
22
|
-
// Module to classify links.
|
|
23
|
-
const {isTooSmall} = require('../procs/target');
|
|
24
|
-
|
|
25
|
-
// ########## FUNCTIONS
|
|
21
|
+
// FUNCTIONS
|
|
26
22
|
|
|
27
23
|
// Runs the test and returns the result.
|
|
28
24
|
exports.reporter = async (page, withItems) => {
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Get
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
// Return totals and standard instances for the rule.
|
|
26
|
+
return await page.evaluate(withItems => {
|
|
27
|
+
// Get all pointer targets.
|
|
28
|
+
const allPTs = Array.from(document.body.querySelectorAll('label, button, input, a'));
|
|
29
|
+
// Get the visible ones.
|
|
30
|
+
const visiblePTs = allPTs.filter(pt => pt.checkVisibility({
|
|
31
|
+
contentVisibilityAuto: true,
|
|
32
|
+
opacityProperty: true,
|
|
33
|
+
visibilityProperty: true
|
|
34
|
+
}));
|
|
35
|
+
// Initialize the data.
|
|
36
|
+
const ptsData = [];
|
|
37
|
+
// For each visible pointer target:
|
|
38
|
+
visiblePTs.forEach(element => {
|
|
39
|
+
// Get the coordinates of its centerpoint.
|
|
40
|
+
const rect = element.getBoundingClientRect();
|
|
41
|
+
const centerX = rect.left + rect.width / 2;
|
|
42
|
+
const centerY = rect.top + rect.height / 2;
|
|
43
|
+
// Add them to the data.
|
|
44
|
+
ptsData.push([centerX, centerY]);
|
|
45
|
+
});
|
|
46
|
+
// Initialize the counts of minor and major violations.
|
|
47
|
+
let violationCounts = [0, 0];
|
|
48
|
+
const instances = [];
|
|
49
|
+
// For each visible pointer target:
|
|
50
|
+
visiblePTs.forEach((element, index) => {
|
|
51
|
+
// Get the minimum of the vertical distances of its centerpoint from those of the others.
|
|
52
|
+
const minYDiff = ptsData[index][1] - Math.abs(
|
|
53
|
+
Math.min(...ptsData.splice(index, 1).map(ptData => ptData[1]))
|
|
54
|
+
);
|
|
55
|
+
// If it is close enough to make a violation possible:
|
|
56
|
+
if (minYDiff < 44) {
|
|
57
|
+
// Get the centerpoint coordinates of those within that vertical distance.
|
|
58
|
+
const yNearPTsData = ptsData.splice(index, 1).filter(
|
|
59
|
+
ptData => Math.abs(ptData[1] - ptsData[index][1]) < 44
|
|
60
|
+
);
|
|
61
|
+
// Get the minimum of their planar distances.
|
|
62
|
+
const minPlanarDistance = Math.min(...yNearPTsData.map(ptData => {
|
|
63
|
+
const planarDistance = Math.sqrt(
|
|
64
|
+
Math.pow(centerX - ptData[0], 2) + Math.pow(centerY - ptData[1], 2)
|
|
65
|
+
);
|
|
66
|
+
return planarDistance;
|
|
67
|
+
}));
|
|
68
|
+
// If the minimum planar distance is less than 44px:
|
|
69
|
+
if (minPlanarDistance < 44) {
|
|
70
|
+
const isVeryNear = minPlanarDistance < 24;
|
|
71
|
+
// Get the ordinal severity of the violation.
|
|
72
|
+
const ordinalSeverity = isVeryNear ? 3 : 2;
|
|
73
|
+
// Increment the applicable violation count.
|
|
74
|
+
violationCounts[isVeryNear ? 1 : 0]++;
|
|
75
|
+
// If itemization is required:
|
|
76
|
+
if (withItems) {
|
|
77
|
+
const what = `Pointer-target centerpoint is only ${Math.round(minPlanarDistance)}px from another one`;
|
|
78
|
+
// Add an instance to the instances.
|
|
79
|
+
instances.push(window.getInstance(element, 'targetSmall', what, 1, ordinalSeverity));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// If itemization is not required:
|
|
85
|
+
if (! withItems) {
|
|
86
|
+
// If there were any major violations:
|
|
87
|
+
if (violationCounts[1]) {
|
|
88
|
+
const what = 'Pointer-target centerpoints are less than 24px from others';
|
|
89
|
+
// Add a summary instance to the instances.
|
|
90
|
+
instances.push(window.getInstance(null, 'targetSmall', what, violationCounts[1], 1));
|
|
91
|
+
}
|
|
92
|
+
// If there were any minor violations:
|
|
93
|
+
if (violationCounts[0]) {
|
|
94
|
+
const what = 'Pointer-target centerpoints are less than 44px from others';
|
|
95
|
+
// Add a summary instance to the instances.
|
|
96
|
+
instances.push(window.getInstance(null, 'targetSmall', what, violationCounts[0], 0));
|
|
97
|
+
}
|
|
39
98
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return await getRuleResult(withItems, all, 'targetSmall', whats, 0);
|
|
99
|
+
return {
|
|
100
|
+
data: {},
|
|
101
|
+
totals: [...violationCounts, 0, 0],
|
|
102
|
+
standardInstances: instances
|
|
103
|
+
};
|
|
104
|
+
}, withItems);
|
|
47
105
|
};
|
package/testaro/textSem.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Juan S. Casado.
|
|
3
|
+
© 2025 Juan S. Casado.
|
|
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.
|
package/tests/testaro.js
CHANGED
|
@@ -191,7 +191,7 @@ const allRules = [
|
|
|
191
191
|
id: 'linkAmb',
|
|
192
192
|
what: 'links with identical texts but different destinations',
|
|
193
193
|
launchRole: 'sharer',
|
|
194
|
-
timeOut:
|
|
194
|
+
timeOut: 50,
|
|
195
195
|
defaultOn: true
|
|
196
196
|
},
|
|
197
197
|
{
|
|
@@ -212,7 +212,7 @@ const allRules = [
|
|
|
212
212
|
id: 'linkTitle',
|
|
213
213
|
what: 'links with title attributes repeating text content',
|
|
214
214
|
launchRole: 'sharer',
|
|
215
|
-
timeOut:
|
|
215
|
+
timeOut: 10,
|
|
216
216
|
defaultOn: true
|
|
217
217
|
},
|
|
218
218
|
{
|
|
@@ -294,14 +294,7 @@ const allRules = [
|
|
|
294
294
|
},
|
|
295
295
|
{
|
|
296
296
|
id: 'targetSmall',
|
|
297
|
-
what: 'buttons, inputs, and
|
|
298
|
-
launchRole: 'sharer',
|
|
299
|
-
timeOut: 5,
|
|
300
|
-
defaultOn: true
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
id: 'targetTiny',
|
|
304
|
-
what: 'buttons, inputs, and non-inline links smaller than 24 pixels wide and high',
|
|
297
|
+
what: 'labels, buttons, inputs, and links too near each other',
|
|
305
298
|
launchRole: 'sharer',
|
|
306
299
|
timeOut: 5,
|
|
307
300
|
defaultOn: true
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<li><a id="basqueInfoWP0" href="https://eus.wikipedia.org">Basque information</a></li>
|
|
25
25
|
<li><a id="basqueInfoWP1" href="https://eus.wikipedia.org">Basque information</a></li>
|
|
26
26
|
</ul>
|
|
27
|
-
<p>However, the
|
|
27
|
+
<p>However, they are made ambiguous by the second link below. As a result, there are 4 ambiguous links, with 2 different destinations.</p>
|
|
28
28
|
<ul>
|
|
29
29
|
<li><a id="basqueInfoWP2" href="https://eus.wikipedia.org">Basque information</a></li>
|
|
30
30
|
<li><a id="basqueInfoICB" href="https://www.eke.eus/en/kultura/basque-country">Basque information</a></li>
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<h3>Input with no label</h3>
|
|
56
56
|
<p>Enter the name of a company</p>
|
|
57
57
|
<p><input size="30" maxlength="30" name="company2" placeholder="company"></p>
|
|
58
|
-
<p>The last
|
|
58
|
+
<p>The last input violates the phOnly rule.</p>
|
|
59
59
|
</main>
|
|
60
60
|
</body>
|
|
61
61
|
</html>
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
<p>More detailed information about the blip of the blah.</p>
|
|
33
33
|
<h3>This heads a second part of the same section.</h3>
|
|
34
34
|
<p>More detailed information about the swax of the blah.</p>
|
|
35
|
-
<h2>This heading is illogical</h2>
|
|
36
|
-
<p>This
|
|
35
|
+
<h2>This heading is not quite illogical</h2>
|
|
36
|
+
<p>This would have started its own section if each section started with a uniquely top-level heading.</p>
|
|
37
37
|
</section>
|
|
38
38
|
<section>
|
|
39
|
-
<h2>This heads
|
|
39
|
+
<h2>This heads a truly illogical section.</h2>
|
|
40
40
|
<p>Information about blah.</p>
|
|
41
41
|
<h1>This heading is fundamentally illogical</h1>
|
|
42
42
|
<p>It is not obvious what information belongs here.</p>
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
<p>More detailed information about the blip of the blah.</p>
|
|
57
57
|
<h3>This heads a second part of the same article.</h3>
|
|
58
58
|
<p>More detailed information about the swax of the blah.</p>
|
|
59
|
-
<
|
|
59
|
+
<h1>This article heading is illogical</h1>
|
|
60
60
|
<p>This should have been its own article.</p>
|
|
61
61
|
</article>
|
|
62
62
|
</main>
|
package/testaro/pseudoP.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
|
|
4
|
-
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
|
-
https://opensource.org/license/mit/ for details.
|
|
6
|
-
|
|
7
|
-
SPDX-License-Identifier: MIT
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
pseudoP
|
|
12
|
-
Related to Tenon rule 242.
|
|
13
|
-
This test reports 2 or more sequential br elements. They may be inferior substitutes for a
|
|
14
|
-
p element.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// ########## IMPORTS
|
|
18
|
-
|
|
19
|
-
// Module to perform common operations.
|
|
20
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
21
|
-
|
|
22
|
-
// ########## FUNCTIONS
|
|
23
|
-
|
|
24
|
-
// Runs the test and returns the result.
|
|
25
|
-
exports.reporter = async (page, withItems) => {
|
|
26
|
-
// Initialize the locators and result.
|
|
27
|
-
const all = await init(100, page, 'body br + br');
|
|
28
|
-
// For each locator:
|
|
29
|
-
for (const loc of all.allLocs) {
|
|
30
|
-
// Return whether the second br element violates the rule.
|
|
31
|
-
const parentTagNameIfBad = await loc.evaluate(el => {
|
|
32
|
-
el.parentElement.normalize();
|
|
33
|
-
const previousSib = el.previousSibling;
|
|
34
|
-
return previousSib.nodeType === Node.ELEMENT_NODE
|
|
35
|
-
|| previousSib.nodeType === Node.TEXT_NODE && /^\s+$/.test(previousSib)
|
|
36
|
-
? el.parentElement.tagName
|
|
37
|
-
: false;
|
|
38
|
-
});
|
|
39
|
-
// If it does:
|
|
40
|
-
if (parentTagNameIfBad) {
|
|
41
|
-
// Add the locator to the array of violators.
|
|
42
|
-
all.locs.push([loc, parentTagNameIfBad]);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// Populate and return the result.
|
|
46
|
-
const whats = [
|
|
47
|
-
'Adjacent BR elements within a __param__ element may be pseudo-paragraphs',
|
|
48
|
-
'Elements contain 2 or more adjacent br elements that may be pseudo-paragraphs'
|
|
49
|
-
];
|
|
50
|
-
return await getRuleResult(withItems, all, 'pseudoP', whats, 0, 'br');
|
|
51
|
-
};
|
package/testaro/targetTiny.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 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
|
-
targetTiny
|
|
13
|
-
Related to Tenon rule 152.
|
|
14
|
-
This test reports visible buttons, inputs, and non-inline links with widths or heights smaller
|
|
15
|
-
than 24 pixels.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
// ########## IMPORTS
|
|
19
|
-
|
|
20
|
-
// Module to perform common operations.
|
|
21
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
22
|
-
// Module to classify links.
|
|
23
|
-
const {isTooSmall} = require('../procs/target');
|
|
24
|
-
|
|
25
|
-
// ########## FUNCTIONS
|
|
26
|
-
|
|
27
|
-
// Runs the test and returns the result.
|
|
28
|
-
exports.reporter = async (page, withItems) => {
|
|
29
|
-
// Initialize the locators and result.
|
|
30
|
-
const all = await init(100, page, 'a:visible, button:visible, input:visible');
|
|
31
|
-
// For each locator:
|
|
32
|
-
for (const loc of all.allLocs) {
|
|
33
|
-
// Get data on it if illicitly small.
|
|
34
|
-
const sizeData = await isTooSmall(loc, 24);
|
|
35
|
-
// If it is:
|
|
36
|
-
if (sizeData) {
|
|
37
|
-
// Add the locator to the array of violators.
|
|
38
|
-
all.locs.push([loc, `${sizeData.width} wide by ${sizeData.height} high`]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Populate and return the result.
|
|
42
|
-
const whats = [
|
|
43
|
-
'Interactive element pixel size (__param__) is less than 24 by 24',
|
|
44
|
-
'Interactive elements are smaller than 24 pixels wide and high'
|
|
45
|
-
];
|
|
46
|
-
return await getRuleResult(withItems, all, 'targetTiny', whats, 1);
|
|
47
|
-
};
|