testaro 5.6.3 → 5.7.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/README.md +11 -3
- package/commands.js +30 -0
- package/package.json +1 -1
- package/run.js +7 -0
- package/samples/scripts/tp14.json +2 -2
- package/samples/scripts/tp15.json +173 -0
- package/tests/docType.js +12 -0
- package/tests/focVis.js +29 -0
- package/tests/linkTo.js +25 -0
- package/tests/miniText.js +48 -0
- package/tests/nonTable.js +55 -0
- package/tests/nuVal.js +64 -0
- package/tests/role.js +40 -26
- package/tests/titledEl.js +29 -0
- package/tests/wave.js +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Federated accessibility test automation
|
|
|
6
6
|
|
|
7
7
|
Testaro is a collection of collections of web accessibility tests.
|
|
8
8
|
|
|
9
|
-
The purpose of Testaro is to provide programmatic access to
|
|
9
|
+
The purpose of Testaro is to provide programmatic access to 1228 accessibility tests defined in several test packages and in Testaro itself.
|
|
10
10
|
|
|
11
11
|
## System requirements
|
|
12
12
|
|
|
@@ -26,6 +26,9 @@ Testaro includes some of its own accessibility tests. In addition, it performs t
|
|
|
26
26
|
- [axe-playwright](https://www.npmjs.com/package/axe-playwright) (Deque Axe-core)
|
|
27
27
|
- [Tenon](https://tenon.io/documentation/what-tenon-tests.php) (Level Access)
|
|
28
28
|
- [WAVE API](https://wave.webaim.org/api/) (WebAIM WAVE)
|
|
29
|
+
- [Nu Html Checker](https://github.com/validator/validator)
|
|
30
|
+
|
|
31
|
+
Some of the Testaro tests are derived from tests performed by the [BBC Accessibility Standards Checker](https://github.com/bbc/bbc-a11y).
|
|
29
32
|
|
|
30
33
|
As of this version, the counts of tests in the packages referenced above were:
|
|
31
34
|
- Alfa: 103
|
|
@@ -35,9 +38,10 @@ As of this version, the counts of tests in the packages referenced above were:
|
|
|
35
38
|
- HTML CodeSniffer: 98
|
|
36
39
|
- Tenon: 180
|
|
37
40
|
- WAVE: 110
|
|
41
|
+
- Nu Html Checker: 147
|
|
38
42
|
- subtotal: 612
|
|
39
|
-
- Testaro tests:
|
|
40
|
-
- grand total:
|
|
43
|
+
- Testaro tests: 22
|
|
44
|
+
- grand total: 1228
|
|
41
45
|
|
|
42
46
|
## Code organization
|
|
43
47
|
|
|
@@ -330,6 +334,10 @@ The changes in `htmlcs/HTMLCS.js` are:
|
|
|
330
334
|
> );
|
|
331
335
|
```
|
|
332
336
|
|
|
337
|
+
###### BBC Accessibility Standards Checker
|
|
338
|
+
|
|
339
|
+
The BBC Accessibility Standards Checker has obsolete dependencies with security vulnerabilities. Therefore, it is not used as a dependency of Testaro. Instead, 6 of its tests were reimplemented, in some case with revisions, as Testaro tests. They were drawn from the 18 automated tests of the Checker. The other 12 tests were found too duplicative of other tests to justify reimplementation.
|
|
340
|
+
|
|
333
341
|
##### Branching
|
|
334
342
|
|
|
335
343
|
An example of a **branching** command is:
|
package/commands.js
CHANGED
|
@@ -170,6 +170,12 @@ exports.commands = {
|
|
|
170
170
|
withItems: [true, 'boolean']
|
|
171
171
|
}
|
|
172
172
|
],
|
|
173
|
+
focVis: [
|
|
174
|
+
'Perform a focVis test',
|
|
175
|
+
{
|
|
176
|
+
withItems: [true, 'boolean']
|
|
177
|
+
}
|
|
178
|
+
],
|
|
173
179
|
hover: [
|
|
174
180
|
'Perform a hover test',
|
|
175
181
|
{
|
|
@@ -196,6 +202,12 @@ exports.commands = {
|
|
|
196
202
|
withItems: [true, 'boolean']
|
|
197
203
|
}
|
|
198
204
|
],
|
|
205
|
+
linkTo: [
|
|
206
|
+
'Perform a linkTo test',
|
|
207
|
+
{
|
|
208
|
+
withItems: [true, 'boolean']
|
|
209
|
+
}
|
|
210
|
+
],
|
|
199
211
|
linkUl: [
|
|
200
212
|
'Perform a linkUl test',
|
|
201
213
|
{
|
|
@@ -208,6 +220,12 @@ exports.commands = {
|
|
|
208
220
|
withItems: [true, 'boolean']
|
|
209
221
|
}
|
|
210
222
|
],
|
|
223
|
+
miniText: [
|
|
224
|
+
'Perform a miniText test',
|
|
225
|
+
{
|
|
226
|
+
withItems: [true, 'boolean']
|
|
227
|
+
}
|
|
228
|
+
],
|
|
211
229
|
motion: [
|
|
212
230
|
'Perform a motion test',
|
|
213
231
|
{
|
|
@@ -216,6 +234,12 @@ exports.commands = {
|
|
|
216
234
|
count: [true, 'number', '', 'count of screen shots to make']
|
|
217
235
|
}
|
|
218
236
|
],
|
|
237
|
+
nonTable: [
|
|
238
|
+
'Perform a nonTable test',
|
|
239
|
+
{
|
|
240
|
+
withItems: [true, 'boolean']
|
|
241
|
+
}
|
|
242
|
+
],
|
|
219
243
|
radioSet: [
|
|
220
244
|
'Perform a radioSet test',
|
|
221
245
|
{
|
|
@@ -240,6 +264,12 @@ exports.commands = {
|
|
|
240
264
|
id: [true, 'string', 'hasLength', 'ID of the requested test instance']
|
|
241
265
|
}
|
|
242
266
|
],
|
|
267
|
+
titledEl: [
|
|
268
|
+
'Perform a titledEl test',
|
|
269
|
+
{
|
|
270
|
+
withItems: [true, 'boolean']
|
|
271
|
+
}
|
|
272
|
+
],
|
|
243
273
|
wave: [
|
|
244
274
|
'Perform a WebAIM WAVE test',
|
|
245
275
|
{
|
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -33,22 +33,29 @@ const tests = {
|
|
|
33
33
|
axe: 'Axe',
|
|
34
34
|
bulk: 'count of visible elements',
|
|
35
35
|
continuum: 'Level Access Continuum, community edition',
|
|
36
|
+
docType: 'document without a doctype property',
|
|
36
37
|
embAc: 'active elements embedded in links or buttons',
|
|
37
38
|
focAll: 'focusable and Tab-focused elements',
|
|
38
39
|
focInd: 'focus indicators',
|
|
39
40
|
focOp: 'focusability and operability',
|
|
41
|
+
focVis: 'links that are invisible when focused',
|
|
40
42
|
hover: 'hover-caused content changes',
|
|
41
43
|
htmlcs: 'HTML CodeSniffer WCAG 2.1 AA ruleset',
|
|
42
44
|
ibm: 'IBM Accessibility Checker',
|
|
43
45
|
labClash: 'labeling inconsistencies',
|
|
46
|
+
linkTo: 'links without destinations',
|
|
44
47
|
linkUl: 'adjacent-link underlining',
|
|
45
48
|
menuNav: 'keyboard navigation between focusable menu items',
|
|
49
|
+
miniText: 'text smaller than 11 pixels',
|
|
46
50
|
motion: 'motion',
|
|
51
|
+
nonTable: 'table elements used for layout',
|
|
52
|
+
nuVal: 'failures to pass the Nu Html Checker',
|
|
47
53
|
radioSet: 'fieldset grouping of radio buttons',
|
|
48
54
|
role: 'roles',
|
|
49
55
|
styleDiff: 'style inconsistencies',
|
|
50
56
|
tabNav: 'keyboard navigation between tab elements',
|
|
51
57
|
tenon: 'Tenon',
|
|
58
|
+
titledEl: 'title attributes on inappropriate elements',
|
|
52
59
|
wave: 'WAVE',
|
|
53
60
|
zIndex: 'z indexes'
|
|
54
61
|
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "tp15",
|
|
3
|
+
"what": "Alfa, Axe, Continuum, HTML CodeSniffer, IBM, Nu Html Checker, Tenon, WAVE, and 22 custom tests",
|
|
4
|
+
"strict": true,
|
|
5
|
+
"timeLimit": 500,
|
|
6
|
+
"commands": [
|
|
7
|
+
{
|
|
8
|
+
"type": "launch",
|
|
9
|
+
"which": "webkit",
|
|
10
|
+
"what": "Webkit browser"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "url",
|
|
14
|
+
"which": "https://*",
|
|
15
|
+
"what": "any page"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"type": "tenonRequest",
|
|
19
|
+
"id": "a",
|
|
20
|
+
"withNewContent": true,
|
|
21
|
+
"what": "Tenon API version 2 test request"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"type": "test",
|
|
25
|
+
"which": "motion",
|
|
26
|
+
"what": "spontaneous change of content; requires webkit",
|
|
27
|
+
"delay": 2500,
|
|
28
|
+
"interval": 2500,
|
|
29
|
+
"count": 5
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "launch",
|
|
33
|
+
"which": "chromium",
|
|
34
|
+
"what": "Chromium browser"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"type": "url",
|
|
38
|
+
"which": "https://*",
|
|
39
|
+
"what": "any page"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "test",
|
|
43
|
+
"which": "bulk",
|
|
44
|
+
"what": "count of visible elements"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "test",
|
|
48
|
+
"which": "embAc",
|
|
49
|
+
"withItems": true,
|
|
50
|
+
"what": "active elements incorrectly embedded in each other"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"type": "test",
|
|
54
|
+
"which": "focAll",
|
|
55
|
+
"what": "Tab-focusability"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "test",
|
|
59
|
+
"which": "focInd",
|
|
60
|
+
"revealAll": false,
|
|
61
|
+
"allowedDelay": 250,
|
|
62
|
+
"withItems": true,
|
|
63
|
+
"what": "focus indicators"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "test",
|
|
67
|
+
"which": "focOp",
|
|
68
|
+
"withItems": true,
|
|
69
|
+
"what": "focusability and operability of elements"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"type": "test",
|
|
73
|
+
"which": "hover",
|
|
74
|
+
"headSize": 40,
|
|
75
|
+
"headSampleSize": 20,
|
|
76
|
+
"tailSampleSize": 15,
|
|
77
|
+
"withItems": true,
|
|
78
|
+
"what": "hover impacts"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"type": "test",
|
|
82
|
+
"which": "labClash",
|
|
83
|
+
"withItems": true,
|
|
84
|
+
"what": "unlabeled and mislabeled form controls"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"type": "test",
|
|
88
|
+
"which": "linkUl",
|
|
89
|
+
"withItems": true,
|
|
90
|
+
"what": "underlining of inline links"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"type": "test",
|
|
94
|
+
"which": "menuNav",
|
|
95
|
+
"withItems": true,
|
|
96
|
+
"what": "keyboard navigation within true-focus menus"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"type": "test",
|
|
100
|
+
"which": "radioSet",
|
|
101
|
+
"withItems": true,
|
|
102
|
+
"what": "grouping of radio buttons in fieldsets"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"type": "test",
|
|
106
|
+
"which": "role",
|
|
107
|
+
"what": "validity and necessity of role assignments"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"type": "test",
|
|
111
|
+
"which": "styleDiff",
|
|
112
|
+
"withItems": true,
|
|
113
|
+
"what": "style consistency of headings, buttons, and links"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "test",
|
|
117
|
+
"which": "tabNav",
|
|
118
|
+
"withItems": true,
|
|
119
|
+
"what": "keyboard navigation within tab lists"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"type": "test",
|
|
123
|
+
"which": "zIndex",
|
|
124
|
+
"withItems": true,
|
|
125
|
+
"what": "elements with non-auto z indexes"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"type": "test",
|
|
129
|
+
"which": "alfa",
|
|
130
|
+
"what": "Siteimprove alfa"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"type": "test",
|
|
134
|
+
"which": "axe",
|
|
135
|
+
"detailLevel": 2,
|
|
136
|
+
"rules": [],
|
|
137
|
+
"what": "Axe core, all rules"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"type": "test",
|
|
141
|
+
"which": "continuum",
|
|
142
|
+
"what": "Continuum"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"type": "test",
|
|
146
|
+
"which": "htmlcs",
|
|
147
|
+
"what": "HTML CodeSniffer"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"type": "test",
|
|
151
|
+
"which": "ibm",
|
|
152
|
+
"withItems": true,
|
|
153
|
+
"what": "IBM Accessibility Checker, with page content and again with URL"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"type": "test",
|
|
157
|
+
"which": "nuVal",
|
|
158
|
+
"what": "Nu Html Checker"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"type": "test",
|
|
162
|
+
"which": "wave",
|
|
163
|
+
"reportType": 4,
|
|
164
|
+
"what": "WAVE, report-type 4"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"type": "test",
|
|
168
|
+
"which": "tenon",
|
|
169
|
+
"id": "a",
|
|
170
|
+
"what": "Tenon API version 2 result retrieval"
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
package/tests/docType.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
docType
|
|
3
|
+
Derived from the bbc-a11y allDocumentsMustHaveAW3cRecommendedDoctype test.
|
|
4
|
+
This test reports a failure to equip the page document with a W3C-recommended doctype.
|
|
5
|
+
*/
|
|
6
|
+
exports.reporter = async page => {
|
|
7
|
+
// Identify the visible links without href attributes.
|
|
8
|
+
const docType = await page.evaluate(() => document.doctype);
|
|
9
|
+
return {result: {
|
|
10
|
+
docHasType: Boolean(docType)
|
|
11
|
+
}};
|
|
12
|
+
};
|
package/tests/focVis.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
focVis
|
|
3
|
+
Derived from the bbc-a11y elementsMustBeVisibleOnFocus test.
|
|
4
|
+
This test reports links that are off the display when focused.
|
|
5
|
+
*/
|
|
6
|
+
exports.reporter = async (page, withItems) => {
|
|
7
|
+
// Identify the initially visible links.
|
|
8
|
+
const badLinks = await page.$$eval('a:visible', links => {
|
|
9
|
+
// FUNCTION DEFINITION START
|
|
10
|
+
// Returns a space-minimized copy of a string.
|
|
11
|
+
const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
12
|
+
// FUNCTION DEFINITION END
|
|
13
|
+
const badLinks = [];
|
|
14
|
+
links.forEach(link => {
|
|
15
|
+
link.focus();
|
|
16
|
+
if (link.offsetTop + link.offsetHeight <= 0 || link.offsetLeft + link.offsetWidth <= 0) {
|
|
17
|
+
badLinks.push(compact(link.textContent));
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return badLinks;
|
|
21
|
+
});
|
|
22
|
+
const data = {
|
|
23
|
+
totals: badLinks.length
|
|
24
|
+
};
|
|
25
|
+
if (withItems) {
|
|
26
|
+
data.items = badLinks;
|
|
27
|
+
}
|
|
28
|
+
return {result: data};
|
|
29
|
+
};
|
package/tests/linkTo.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
linkTo
|
|
3
|
+
Derived from the bbc-a11y anchorsMustHaveHrefs test.
|
|
4
|
+
This test reports failures to equip links with destinations.
|
|
5
|
+
*/
|
|
6
|
+
exports.reporter = async (page, withItems) => {
|
|
7
|
+
// Identify the visible links without href attributes.
|
|
8
|
+
const badLinkTexts = await page.$$eval(
|
|
9
|
+
'a:not([href]):visible',
|
|
10
|
+
badLinks => {
|
|
11
|
+
// FUNCTION DEFINITION START
|
|
12
|
+
// Returns a space-minimized copy of a string.
|
|
13
|
+
const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
14
|
+
// FUNCTION DEFINITION END
|
|
15
|
+
return badLinks.map(link => compact(link.textContent));
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
const data = {
|
|
19
|
+
totals: badLinkTexts.length
|
|
20
|
+
};
|
|
21
|
+
if (withItems) {
|
|
22
|
+
data.items = badLinkTexts;
|
|
23
|
+
}
|
|
24
|
+
return {result: data};
|
|
25
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
miniText
|
|
3
|
+
Derived from the bbc-a11y textCannotBeTooSmall test.
|
|
4
|
+
This test reports text nodes smaller than 11 pixels.
|
|
5
|
+
*/
|
|
6
|
+
exports.reporter = async (page, withItems) => {
|
|
7
|
+
// Identify the text nodes smaller than 11 pixels.
|
|
8
|
+
const miniTexts = await page.$$eval(
|
|
9
|
+
'body *:not(script, style):visible',
|
|
10
|
+
elements => {
|
|
11
|
+
// FUNCTION DEFINITION START
|
|
12
|
+
// Returns a space-minimized copy of a string.
|
|
13
|
+
const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
14
|
+
// FUNCTION DEFINITION END
|
|
15
|
+
const textParents = new Set();
|
|
16
|
+
const miniTexts = [];
|
|
17
|
+
elements.forEach(element => {
|
|
18
|
+
element.childNodes.forEach(node => {
|
|
19
|
+
if (node.nodeType === 3) {
|
|
20
|
+
const nodeText = compact(node.textContent);
|
|
21
|
+
if (nodeText) {
|
|
22
|
+
textParents.add(element);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
textParents.forEach(textParent => {
|
|
28
|
+
const {fontSize} = window.getComputedStyle(textParent);
|
|
29
|
+
const pixels = Number.parseInt(fontSize);
|
|
30
|
+
if (pixels < 11) {
|
|
31
|
+
textParent.childNodes.forEach(node => {
|
|
32
|
+
if (node.nodeType === 3 && compact(node.textContent)) {
|
|
33
|
+
miniTexts.push(compact(node.textContent));
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return miniTexts;
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
const data = {
|
|
42
|
+
totals: miniTexts.length
|
|
43
|
+
};
|
|
44
|
+
if (withItems) {
|
|
45
|
+
data.items = miniTexts;
|
|
46
|
+
}
|
|
47
|
+
return {result: data};
|
|
48
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
nonTable
|
|
3
|
+
Derived from the bbc-a11y useTablesForData test. Crude heuristics omitted.
|
|
4
|
+
This test reports tables used for layout.
|
|
5
|
+
*/
|
|
6
|
+
exports.reporter = async (page, withItems) => {
|
|
7
|
+
// Identify the visible links without href attributes.
|
|
8
|
+
const badTableTexts = await page.$$eval('table', tables => {
|
|
9
|
+
const badTableTexts = [];
|
|
10
|
+
// FUNCTION DEFINITIONS START
|
|
11
|
+
// Returns a space-minimized copy of a string.
|
|
12
|
+
const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
13
|
+
const addBad = table => {
|
|
14
|
+
badTableTexts.push(compact(table.outerHTML).slice(0, 100));
|
|
15
|
+
};
|
|
16
|
+
// FUNCTION DEFINITIONS END
|
|
17
|
+
tables.forEach(table => {
|
|
18
|
+
const role = table.getAttribute('role');
|
|
19
|
+
if (
|
|
20
|
+
table.caption
|
|
21
|
+
|| ['grid', 'treegrid'].includes(role)
|
|
22
|
+
|| table.querySelector('col, colgroup, tfoot, thead, th')
|
|
23
|
+
) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
else if (table.querySelector('table')) {
|
|
27
|
+
addBad(table);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
else if (
|
|
31
|
+
table.querySelectorAll('tr').length === 1
|
|
32
|
+
|| Math.max(
|
|
33
|
+
... Array
|
|
34
|
+
.from(table.querySelectorAll('tr'))
|
|
35
|
+
.map(row => Array.from(row.querySelectorAll('td')).length)
|
|
36
|
+
) === 1
|
|
37
|
+
) {
|
|
38
|
+
addBad(table);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
else if (table.querySelector('object, embed, applet, audio, video')) {
|
|
42
|
+
addBad(table);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return badTableTexts;
|
|
47
|
+
});
|
|
48
|
+
const data = {
|
|
49
|
+
totals: badTableTexts.length
|
|
50
|
+
};
|
|
51
|
+
if (withItems) {
|
|
52
|
+
data.items = badTableTexts;
|
|
53
|
+
}
|
|
54
|
+
return {result: data};
|
|
55
|
+
};
|
package/tests/nuVal.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
nuVal
|
|
3
|
+
This test subjects a page to the Nu Html Checker.
|
|
4
|
+
*/
|
|
5
|
+
const https = require('https');
|
|
6
|
+
exports.reporter = async page => {
|
|
7
|
+
const pageContent = await page.content();
|
|
8
|
+
// Get the data from a Nu validation.
|
|
9
|
+
const data = await new Promise((resolve, reject) => {
|
|
10
|
+
try {
|
|
11
|
+
const request = https.request(
|
|
12
|
+
{
|
|
13
|
+
// Alternatives: host=validator.w3.org; path=/nu/?parser=html@out=json
|
|
14
|
+
host: 'validator.nu',
|
|
15
|
+
path: '/?parser=html&out=json',
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'User-Agent': 'Mozilla/5.0',
|
|
19
|
+
'Content-Type': 'text/html; charset=utf-8'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
response => {
|
|
23
|
+
let report = '';
|
|
24
|
+
response.on('data', chunk => {
|
|
25
|
+
report += chunk;
|
|
26
|
+
});
|
|
27
|
+
// When the data arrive:
|
|
28
|
+
response.on('end', async () => {
|
|
29
|
+
try {
|
|
30
|
+
// Delete unnecessary properties.
|
|
31
|
+
const result = JSON.parse(report);
|
|
32
|
+
return resolve(result);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.log(`Validation failed (${error.message})`);
|
|
36
|
+
return resolve({
|
|
37
|
+
prevented: true,
|
|
38
|
+
error: error.message,
|
|
39
|
+
report
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
request.write(pageContent);
|
|
46
|
+
request.end();
|
|
47
|
+
request.on('error', error => {
|
|
48
|
+
console.log(error.message);
|
|
49
|
+
return reject({
|
|
50
|
+
prevented: true,
|
|
51
|
+
error: error.message
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch(error) {
|
|
56
|
+
console.log(error.message);
|
|
57
|
+
return reject({
|
|
58
|
+
prevented: true,
|
|
59
|
+
error: error.message
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return {result: data};
|
|
64
|
+
};
|
package/tests/role.js
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
role
|
|
3
3
|
This test reports role assignment that violate either an applicable standard or an applicable
|
|
4
4
|
recommendation from WAI-ARIA. Invalid roles include those that are abstract and thus prohibited
|
|
5
|
-
from direct use, and those that are implicit in HTML elements and thus advised against.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
from direct use, and those that are implicit in HTML elements and thus advised against. Roles
|
|
6
|
+
that explicitly confirm implicit roles are deemed redundant and can be scored as less serious
|
|
7
|
+
than roles that override implicit roles. The math role has been removed, because of poor
|
|
8
|
+
adoption and exclusion from HTML5. The img role has accessibility uses, so is not classified
|
|
9
|
+
as deprecated. See:
|
|
8
10
|
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Role_Img
|
|
9
11
|
https://www.w3.org/TR/html-aria/
|
|
10
12
|
https://www.w3.org/TR/wai-aria/#roles_categorization
|
|
11
13
|
*/
|
|
12
14
|
exports.reporter = async page => await page.$eval('body', body => {
|
|
15
|
+
|
|
13
16
|
// CONSTANTS
|
|
17
|
+
|
|
14
18
|
const badRoles = new Set([
|
|
15
19
|
'article',
|
|
16
20
|
'banner',
|
|
@@ -354,7 +358,32 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
354
358
|
};
|
|
355
359
|
// Array of th and td elements with redundant roles.
|
|
356
360
|
const redundantCells = [];
|
|
361
|
+
// Elements with role attributes.
|
|
362
|
+
const roleElements = Array.from(body.querySelectorAll('[role]'));
|
|
363
|
+
// th and td elements with redundant roles.
|
|
364
|
+
const gridHeaders = Array.from(
|
|
365
|
+
document.body.querySelectorAll('table[role=grid] th, table[role=treegrid] th')
|
|
366
|
+
);
|
|
367
|
+
const gridCells = Array.from(
|
|
368
|
+
document.body.querySelectorAll('table[role=grid] td, table[role=treegrid] td')
|
|
369
|
+
);
|
|
370
|
+
const tableHeaders = Array.from(
|
|
371
|
+
document.body.querySelectorAll('table[role=table] th, table:not([role]) th')
|
|
372
|
+
);
|
|
373
|
+
const tableCells = Array.from(
|
|
374
|
+
document.body.querySelectorAll('table[role=table] td, table:not([role]) td')
|
|
375
|
+
);
|
|
376
|
+
// Initialized result.
|
|
377
|
+
const data = {
|
|
378
|
+
roleElements: roleElements.length,
|
|
379
|
+
badRoleElements: 0,
|
|
380
|
+
redundantRoleElements: 0,
|
|
381
|
+
tagNames: {}
|
|
382
|
+
};
|
|
383
|
+
|
|
357
384
|
// FUNCTIONS
|
|
385
|
+
|
|
386
|
+
// Initializes the results.
|
|
358
387
|
const dataInit = (data, tagName, role) => {
|
|
359
388
|
if (! data.tagNames[tagName]) {
|
|
360
389
|
data.tagNames[tagName] = {};
|
|
@@ -366,6 +395,7 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
366
395
|
};
|
|
367
396
|
}
|
|
368
397
|
};
|
|
398
|
+
//
|
|
369
399
|
const tallyTableRedundancy = (elements, okRoles, tagName) => {
|
|
370
400
|
elements.forEach(element => {
|
|
371
401
|
const role = element.getAttribute('role');
|
|
@@ -377,35 +407,16 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
377
407
|
}
|
|
378
408
|
});
|
|
379
409
|
};
|
|
410
|
+
|
|
380
411
|
// OPERATION
|
|
412
|
+
|
|
381
413
|
// Remove the deprecated roles from the non-abstract roles.
|
|
382
414
|
goodRoles.forEach(role => {
|
|
383
415
|
if (badRoles.has(role)) {
|
|
384
416
|
goodRoles.delete(role);
|
|
385
417
|
}
|
|
386
418
|
});
|
|
387
|
-
// Identify
|
|
388
|
-
const roleElements = Array.from(body.querySelectorAll('[role]'));
|
|
389
|
-
// Initialize the result.
|
|
390
|
-
const data = {
|
|
391
|
-
roleElements: roleElements.length,
|
|
392
|
-
badRoleElements: 0,
|
|
393
|
-
redundantRoleElements: 0,
|
|
394
|
-
tagNames: {}
|
|
395
|
-
};
|
|
396
|
-
// Identify the th and td elements with redundant roles.
|
|
397
|
-
const gridHeaders = Array.from(
|
|
398
|
-
document.body.querySelectorAll('table[role=grid] th, table[role=treegrid] th')
|
|
399
|
-
);
|
|
400
|
-
const gridCells = Array.from(
|
|
401
|
-
document.body.querySelectorAll('table[role=grid] td, table[role=treegrid] td')
|
|
402
|
-
);
|
|
403
|
-
const tableHeaders = Array.from(
|
|
404
|
-
document.body.querySelectorAll('table[role=table] th, table:not([role]) th')
|
|
405
|
-
);
|
|
406
|
-
const tableCells = Array.from(
|
|
407
|
-
document.body.querySelectorAll('table[role=table] td, table:not([role]) td')
|
|
408
|
-
);
|
|
419
|
+
// Identify the table elements with redundant roles.
|
|
409
420
|
tallyTableRedundancy(gridHeaders, ['columnheader', 'rowheader', 'gridcell'], 'TH');
|
|
410
421
|
tallyTableRedundancy(gridCells, ['gridcell'], 'TD');
|
|
411
422
|
tallyTableRedundancy(tableHeaders, ['columnheader', 'rowheader', 'cell'], 'TH');
|
|
@@ -422,6 +433,7 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
422
433
|
const lcTagName = tagName.toLowerCase();
|
|
423
434
|
// If it is simply redundant:
|
|
424
435
|
if (role === implicitRoles[lcTagName]) {
|
|
436
|
+
// Update the results.
|
|
425
437
|
data.redundantRoleElements++;
|
|
426
438
|
data.tagNames[tagName][role].redundant++;
|
|
427
439
|
}
|
|
@@ -446,18 +458,20 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
446
458
|
)
|
|
447
459
|
)
|
|
448
460
|
) {
|
|
461
|
+
// Update the results.
|
|
449
462
|
data.redundantRoleElements++;
|
|
450
463
|
data.tagNames[tagName][role].redundant++;
|
|
451
464
|
}
|
|
452
465
|
// Otherwise, i.e. if it is absolutely invalid:
|
|
453
466
|
else {
|
|
467
|
+
// Update the results.
|
|
454
468
|
data.badRoleElements++;
|
|
455
469
|
data.tagNames[tagName][role].bad++;
|
|
456
470
|
}
|
|
457
471
|
}
|
|
458
472
|
// Otherwise, i.e. if it is absolutely invalid:
|
|
459
473
|
else {
|
|
460
|
-
//
|
|
474
|
+
// Update the results.
|
|
461
475
|
data.badRoleElements++;
|
|
462
476
|
dataInit(data, tagName, role);
|
|
463
477
|
data.tagNames[tagName][role].bad++;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
titledEl
|
|
3
|
+
Derived from the bbc-a11y titleAttributesOnlyOnInputs test.
|
|
4
|
+
This test reports title attributes on inappropriate elements.
|
|
5
|
+
*/
|
|
6
|
+
exports.reporter = async (page, withItems) => {
|
|
7
|
+
// Identify the inappropriate elements with title attributes.
|
|
8
|
+
const badTitleElements = await page.$$eval(
|
|
9
|
+
'[title]:not(input, button, textarea, select, iframe):visible',
|
|
10
|
+
badTitleElements => {
|
|
11
|
+
// FUNCTION DEFINITION START
|
|
12
|
+
// Returns a space-minimized copy of a string.
|
|
13
|
+
const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
14
|
+
// FUNCTION DEFINITION END
|
|
15
|
+
return badTitleElements.map(element => ({
|
|
16
|
+
tagName: element.tagName,
|
|
17
|
+
text: compact(element.textContent),
|
|
18
|
+
title: compact(element.title)
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
const data = {
|
|
23
|
+
totals: badTitleElements.length
|
|
24
|
+
};
|
|
25
|
+
if (withItems) {
|
|
26
|
+
data.items = badTitleElements;
|
|
27
|
+
}
|
|
28
|
+
return {result: data};
|
|
29
|
+
};
|