testaro 5.4.0 → 5.5.2
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/create.js +6 -3
- package/data/roles.txt +62 -0
- package/package.json +1 -1
- package/tests/focInd.js +1 -1
- package/tests/hover.js +33 -27
- package/tests/role.js +259 -10
- package/validation/executors/debug.js +62 -0
- package/validation/tests/scripts/hover.json +24 -10
- package/validation/tests/scripts/role.json +15 -6
- package/validation/tests/targets/hover/bad.html +2 -2
- package/validation/tests/targets/hover/good.html +1 -1
- package/validation/tests/targets/role/bad.html +25 -3
package/create.js
CHANGED
|
@@ -89,7 +89,8 @@ exports.runJob = async (scriptID, batchID) => {
|
|
|
89
89
|
// If there is no need to keep checking:
|
|
90
90
|
const reportNames = await fs.readdir(reportDir);
|
|
91
91
|
const timedOut = Date.now() - startTime > 1000 * timeLimit;
|
|
92
|
-
|
|
92
|
+
const reportWritten = reportNames.includes(`${id}.json`);
|
|
93
|
+
if (timedOut || reportWritten || ! childAlive) {
|
|
93
94
|
// Stop checking.
|
|
94
95
|
clearInterval(reCheck);
|
|
95
96
|
// If the cause is a timeout:
|
|
@@ -98,7 +99,7 @@ exports.runJob = async (scriptID, batchID) => {
|
|
|
98
99
|
timeoutHosts.push(id);
|
|
99
100
|
}
|
|
100
101
|
// Otherwise, if the cause is a child crash:
|
|
101
|
-
else if (! childAlive) {
|
|
102
|
+
else if (! (childAlive || reportWritten)) {
|
|
102
103
|
// Add the host to the array of crashed hosts.
|
|
103
104
|
crashHosts.push(id);
|
|
104
105
|
}
|
|
@@ -121,7 +122,9 @@ exports.runJob = async (scriptID, batchID) => {
|
|
|
121
122
|
console.log(`Reports not created:\n${JSON.stringify(timeoutHosts), null, 2}`);
|
|
122
123
|
}
|
|
123
124
|
if (crashHosts.length) {
|
|
124
|
-
console.log(
|
|
125
|
+
console.log(
|
|
126
|
+
`Hosts crashed with or without report:\n${JSON.stringify(crashHosts, null, 2)}`
|
|
127
|
+
);
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
};
|
package/data/roles.txt
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
input type=checkbox role=checkbox
|
|
2
|
+
td role=cell if the ancestor table element is exposed as a role=table
|
|
3
|
+
td role=gridcell if the ancestor table element is exposed as a role=grid or treegrid
|
|
4
|
+
th role=columnheader, rowheader or cell if the ancestor table element is exposed as a role=table
|
|
5
|
+
td role=columnheader, rowheader or gridcell if the ancestor table element is exposed as a role=grid or treegrid
|
|
6
|
+
dd role=definition
|
|
7
|
+
dt role=term
|
|
8
|
+
hr role=separator
|
|
9
|
+
li role=listitem
|
|
10
|
+
ol role=list
|
|
11
|
+
tr role=row
|
|
12
|
+
ul role=list
|
|
13
|
+
dfn role=term
|
|
14
|
+
nav role=navigation
|
|
15
|
+
SVG role=graphics-document
|
|
16
|
+
html role=document
|
|
17
|
+
main role=main
|
|
18
|
+
math role=math
|
|
19
|
+
menu role=list
|
|
20
|
+
aside role=complementary
|
|
21
|
+
table role=table
|
|
22
|
+
tbody role=rowgroup
|
|
23
|
+
tfoot role=rowgroup
|
|
24
|
+
thead role=rowgroup
|
|
25
|
+
button role=button
|
|
26
|
+
dialog role=dialog
|
|
27
|
+
figure role=figure
|
|
28
|
+
output role=status
|
|
29
|
+
article role=article
|
|
30
|
+
details role=group
|
|
31
|
+
section role=region if the section element has an accessible name
|
|
32
|
+
summary role=button
|
|
33
|
+
datalist role=listbox
|
|
34
|
+
fieldset role=group
|
|
35
|
+
h1 to h6 role=heading, aria-level = the number in the element's tag name
|
|
36
|
+
\
|
|
37
|
+
progress role=progressbar
|
|
38
|
+
textarea role=textbox
|
|
39
|
+
a with href role=link
|
|
40
|
+
area with href role=link
|
|
41
|
+
img with alt="" role=presentation
|
|
42
|
+
input type=image role=button
|
|
43
|
+
input type=radio role=radio
|
|
44
|
+
input type=range role=slider
|
|
45
|
+
input type=reset role=button
|
|
46
|
+
input type=button role=button
|
|
47
|
+
input type=number role=spinbutton
|
|
48
|
+
input type=submit role=button
|
|
49
|
+
img with alt="some text" role=img
|
|
50
|
+
img without an alt attribute role=img
|
|
51
|
+
input type=url with no list attribute role=textbox
|
|
52
|
+
input type=tel, with no list attribute role=textbox
|
|
53
|
+
input type=email with no list attribute role=textbox
|
|
54
|
+
input type=search, with no list attribute role=searchbox
|
|
55
|
+
form If the form element has an accessible name: role=form. Otherwise, no corresponding role.
|
|
56
|
+
input type=text or with a missing or invalid type, with no list attribute role=textbox
|
|
57
|
+
select (with a multiple attribute or a size attribute having value greater than 1) role=listbox
|
|
58
|
+
select (with NO multiple attribute and NO size attribute having value greater than 1) role=combobox
|
|
59
|
+
option element that is in a list of options or that represents a suggestion in a datalist role=option
|
|
60
|
+
footer If not a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region then role=contentinfo. Otherwise no corresponding role.
|
|
61
|
+
header If not a descendant of an article, aside, main, nav or section element, or an element with role=article, complementary, main, navigation or region then role=banner. Otherwise no corresponding role
|
|
62
|
+
input type=text, search, tel, url, email, or with a missing or invalid type, with a list attribute role=combobox
|
package/package.json
CHANGED
package/tests/focInd.js
CHANGED
|
@@ -138,7 +138,7 @@ exports.reporter = async (page, revealAll, allowedDelay, withItems) => {
|
|
|
138
138
|
const hasIndicator
|
|
139
139
|
= hasDiffOutline
|
|
140
140
|
|| hasDiffBorder
|
|
141
|
-
|| diff('
|
|
141
|
+
|| diff('boxShadow')
|
|
142
142
|
|| diff('fontSize')
|
|
143
143
|
|| diff('fontStyle')
|
|
144
144
|
|| diff('textDecorationLine')
|
package/tests/hover.js
CHANGED
|
@@ -28,10 +28,6 @@
|
|
|
28
28
|
value in the same location being the target.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
// CONSTANTS
|
|
32
|
-
|
|
33
|
-
const data = {};
|
|
34
|
-
|
|
35
31
|
// FUNCTIONS
|
|
36
32
|
|
|
37
33
|
// Samples a population and returns the sample.
|
|
@@ -62,14 +58,13 @@ const textOf = async (element, limit) => {
|
|
|
62
58
|
return text.trim().replace(/\s*/sg, '').slice(0, limit);
|
|
63
59
|
};
|
|
64
60
|
// Recursively reports impacts of hovering over triggers.
|
|
65
|
-
const find = async (withItems, page, region, sample, popRatio) => {
|
|
61
|
+
const find = async (data, withItems, page, region, sample, popRatio) => {
|
|
66
62
|
// If any potential triggers remain:
|
|
67
63
|
if (sample.length) {
|
|
68
64
|
// Identify the first of them.
|
|
69
65
|
const firstTrigger = sample[0];
|
|
70
66
|
const tagNameJSHandle = await firstTrigger.getProperty('tagName')
|
|
71
67
|
.catch(error => {
|
|
72
|
-
console.log(`ERROR getting trigger tag name (${error.message})`);
|
|
73
68
|
return '';
|
|
74
69
|
});
|
|
75
70
|
if (tagNameJSHandle) {
|
|
@@ -89,7 +84,7 @@ const find = async (withItems, page, region, sample, popRatio) => {
|
|
|
89
84
|
root = rootJSHandle.asElement();
|
|
90
85
|
}
|
|
91
86
|
// Identify all the descendants of the root.
|
|
92
|
-
const preDescendants = await root.$$('
|
|
87
|
+
const preDescendants = await root.$$(':visible');
|
|
93
88
|
// Identify their opacities.
|
|
94
89
|
const preOpacities = await page.evaluate(elements => elements.map(
|
|
95
90
|
element => window.getComputedStyle(element).opacity
|
|
@@ -102,8 +97,11 @@ const find = async (withItems, page, region, sample, popRatio) => {
|
|
|
102
97
|
});
|
|
103
98
|
// Repeatedly seeks impacts.
|
|
104
99
|
const getImpacts = async (interval, triesLeft) => {
|
|
100
|
+
// If the allowed trial count has not yet been exhausted:
|
|
105
101
|
if (triesLeft--) {
|
|
106
|
-
|
|
102
|
+
// Get the collection of descendants of the root.
|
|
103
|
+
const postDescendants = await root.$$(':visible');
|
|
104
|
+
// Identify the prior descandants of the root still in existence.
|
|
107
105
|
const remainerIndexes = await page.evaluate(args => {
|
|
108
106
|
const preDescendants = args[0];
|
|
109
107
|
const postDescendants = args[1];
|
|
@@ -112,6 +110,7 @@ const find = async (withItems, page, region, sample, popRatio) => {
|
|
|
112
110
|
.filter(index => index > -1);
|
|
113
111
|
return remainerIndexes;
|
|
114
112
|
}, [preDescendants, postDescendants]);
|
|
113
|
+
// Get the count of elements added by the hover event.
|
|
115
114
|
const additionCount = postDescendants.length - remainerIndexes.length;
|
|
116
115
|
const removalCount = preDescendants.length - remainerIndexes.length;
|
|
117
116
|
const remainers = [];
|
|
@@ -191,17 +190,22 @@ const find = async (withItems, page, region, sample, popRatio) => {
|
|
|
191
190
|
console.log(`ERROR hovering (${error.message.replace(/\n.+/s, '')})`);
|
|
192
191
|
data.totals.unhoverables++;
|
|
193
192
|
if (withItems) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
try {
|
|
194
|
+
const id = await firstTrigger.getAttribute('id');
|
|
195
|
+
data.items[region].unhoverables.push({
|
|
196
|
+
tagName,
|
|
197
|
+
id: id || '',
|
|
198
|
+
text: await textOf(firstTrigger, 50)
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch(error) {
|
|
202
|
+
console.log('ERROR itemizing unhoverable element');
|
|
203
|
+
}
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
}
|
|
203
207
|
// Process the remaining potential triggers.
|
|
204
|
-
await find(withItems, page, region, sample.slice(1), popRatio);
|
|
208
|
+
await find(data, withItems, page, region, sample.slice(1), popRatio);
|
|
205
209
|
}
|
|
206
210
|
};
|
|
207
211
|
// Performs the hover test and reports results.
|
|
@@ -209,16 +213,18 @@ exports.reporter = async (
|
|
|
209
213
|
page, headSize = 0, headSampleSize = -1, tailSampleSize = -1, withItems
|
|
210
214
|
) => {
|
|
211
215
|
// Initialize the result.
|
|
212
|
-
data
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
const data = {
|
|
217
|
+
totals: {
|
|
218
|
+
triggers: 0,
|
|
219
|
+
headTriggers: 0,
|
|
220
|
+
tailTriggers: 0,
|
|
221
|
+
impactTriggers: 0,
|
|
222
|
+
additions: 0,
|
|
223
|
+
removals: 0,
|
|
224
|
+
opacityChanges: 0,
|
|
225
|
+
opacityImpact: 0,
|
|
226
|
+
unhoverables: 0
|
|
227
|
+
}
|
|
222
228
|
};
|
|
223
229
|
// If details are to be reported:
|
|
224
230
|
if (withItems) {
|
|
@@ -255,10 +261,10 @@ exports.reporter = async (
|
|
|
255
261
|
const tailSample = tailSampleSize === -1 ? tailTriggers : getSample(tailTriggers, tailSampleSize);
|
|
256
262
|
// Find and document the impacts.
|
|
257
263
|
if (headSample.length) {
|
|
258
|
-
await find(withItems, page, 'head', headSample, headTriggerCount / headSample.length);
|
|
264
|
+
await find(data, withItems, page, 'head', headSample, headTriggerCount / headSample.length);
|
|
259
265
|
}
|
|
260
266
|
if (tailSample.length) {
|
|
261
|
-
await find(withItems, page, 'tail', tailSample, tailTriggerCount / tailSample.length);
|
|
267
|
+
await find(data, withItems, page, 'tail', tailSample, tailTriggerCount / tailSample.length);
|
|
262
268
|
}
|
|
263
269
|
// Round the reported totals.
|
|
264
270
|
Object.keys(data.totals).forEach(key => {
|
package/tests/role.js
CHANGED
|
@@ -133,12 +133,6 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
133
133
|
dt: 'term',
|
|
134
134
|
fieldset: 'group',
|
|
135
135
|
figure: 'figure',
|
|
136
|
-
h1: 'heading',
|
|
137
|
-
h2: 'heading',
|
|
138
|
-
h3: 'heading',
|
|
139
|
-
h4: 'heading',
|
|
140
|
-
h5: 'heading',
|
|
141
|
-
h6: 'heading',
|
|
142
136
|
hr: 'separator',
|
|
143
137
|
html: 'document',
|
|
144
138
|
li: 'listitem',
|
|
@@ -159,6 +153,207 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
159
153
|
tr: 'row',
|
|
160
154
|
ul: 'list'
|
|
161
155
|
};
|
|
156
|
+
const implicitAttributes = {
|
|
157
|
+
a: [
|
|
158
|
+
{
|
|
159
|
+
role: 'link',
|
|
160
|
+
attributes: {
|
|
161
|
+
href: /./
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
area: [
|
|
166
|
+
{
|
|
167
|
+
role: 'link',
|
|
168
|
+
attributes: {
|
|
169
|
+
href: /./
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
h1: [
|
|
174
|
+
{
|
|
175
|
+
role: 'heading',
|
|
176
|
+
attributes: {
|
|
177
|
+
'aria-level': /^1$/
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
h2: [
|
|
182
|
+
{
|
|
183
|
+
role: 'heading',
|
|
184
|
+
attributes: {
|
|
185
|
+
'aria-level': /^2$/
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
role: 'heading',
|
|
190
|
+
attributes: {
|
|
191
|
+
'aria-level': false
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
h3: [
|
|
196
|
+
{
|
|
197
|
+
role: 'heading',
|
|
198
|
+
attributes: {
|
|
199
|
+
'aria-level': /^3$/
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
h4: [
|
|
204
|
+
{
|
|
205
|
+
role: 'heading',
|
|
206
|
+
attributes: {
|
|
207
|
+
'aria-level': /^4$/
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
],
|
|
211
|
+
h5: [
|
|
212
|
+
{
|
|
213
|
+
role: 'heading',
|
|
214
|
+
attributes: {
|
|
215
|
+
'aria-level': /^5$/
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
],
|
|
219
|
+
h6: [
|
|
220
|
+
{
|
|
221
|
+
role: 'heading',
|
|
222
|
+
attributes: {
|
|
223
|
+
'aria-level': /^6$/
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
],
|
|
227
|
+
input: [
|
|
228
|
+
{
|
|
229
|
+
role: 'checkbox',
|
|
230
|
+
attributes: {
|
|
231
|
+
type: /^checkbox$/
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
role: 'button',
|
|
236
|
+
attributes: {
|
|
237
|
+
type: /^(?:button|image|reset|submit)$/
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
role: 'combobox',
|
|
242
|
+
attributes: {
|
|
243
|
+
type: /^(?:email|search|tel|text|url)$/,
|
|
244
|
+
list: true
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
role: 'combobox',
|
|
249
|
+
attributes: {
|
|
250
|
+
type: false,
|
|
251
|
+
list: true
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
role: 'radio',
|
|
256
|
+
attributes: {
|
|
257
|
+
type: /^radio$/
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
role: 'searchbox',
|
|
262
|
+
attributes: {
|
|
263
|
+
type: /^search$/,
|
|
264
|
+
list: false
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
role: 'slider',
|
|
269
|
+
attributes: {
|
|
270
|
+
type: /^range$/
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
role: 'spinbutton',
|
|
275
|
+
attributes: {
|
|
276
|
+
type: /^number$/
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
role: 'textbox',
|
|
281
|
+
attributes: {
|
|
282
|
+
type: /^(?:email|tel|text|url)$/,
|
|
283
|
+
list: false
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
role: 'textbox',
|
|
288
|
+
attributes: {
|
|
289
|
+
type: false,
|
|
290
|
+
list: false
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
role: 'checkbox',
|
|
295
|
+
attributes: {
|
|
296
|
+
type: /^checkbox$/
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
role: 'checkbox',
|
|
301
|
+
attributes: {
|
|
302
|
+
type: /^checkbox$/
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
img: [
|
|
307
|
+
{
|
|
308
|
+
role: 'presentation',
|
|
309
|
+
attributes: {
|
|
310
|
+
alt: /^$/
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
role: 'img',
|
|
315
|
+
attributes: {
|
|
316
|
+
alt: /./
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
role: 'img',
|
|
321
|
+
attributes: {
|
|
322
|
+
alt: false
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
],
|
|
326
|
+
select: [
|
|
327
|
+
{
|
|
328
|
+
role: 'listbox',
|
|
329
|
+
attributes: {
|
|
330
|
+
multiple: true
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
role: 'listbox',
|
|
335
|
+
attributes: {
|
|
336
|
+
size: /^(?:[2-9]|[1-9]\d+)$/
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
role: 'combobox',
|
|
341
|
+
attributes: {
|
|
342
|
+
multiple: false,
|
|
343
|
+
size: false
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
role: 'combobox',
|
|
348
|
+
attributes: {
|
|
349
|
+
multiple: false,
|
|
350
|
+
size: /^1$/
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
};
|
|
355
|
+
// Array of th and td elements with redundant roles.
|
|
356
|
+
const redundantCells = [];
|
|
162
357
|
// FUNCTIONS
|
|
163
358
|
const dataInit = (data, tagName, role) => {
|
|
164
359
|
if (! data.tagNames[tagName]) {
|
|
@@ -171,6 +366,17 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
171
366
|
};
|
|
172
367
|
}
|
|
173
368
|
};
|
|
369
|
+
const tallyTableRedundancy = (elements, okRoles, tagName) => {
|
|
370
|
+
elements.forEach(element => {
|
|
371
|
+
const role = element.getAttribute('role');
|
|
372
|
+
if (okRoles.includes(role)) {
|
|
373
|
+
dataInit(data, tagName, role);
|
|
374
|
+
data.redundantRoleElements++;
|
|
375
|
+
data.tagNames[tagName][role].redundant++;
|
|
376
|
+
redundantCells.push(element);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
};
|
|
174
380
|
// OPERATION
|
|
175
381
|
// Remove the deprecated roles from the non-abstract roles.
|
|
176
382
|
goodRoles.forEach(role => {
|
|
@@ -187,8 +393,25 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
187
393
|
redundantRoleElements: 0,
|
|
188
394
|
tagNames: {}
|
|
189
395
|
};
|
|
190
|
-
// Identify the elements with redundant roles
|
|
191
|
-
|
|
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
|
+
);
|
|
409
|
+
tallyTableRedundancy(gridHeaders, ['columnheader', 'rowheader', 'gridcell'], 'TH');
|
|
410
|
+
tallyTableRedundancy(gridCells, ['gridcell'], 'TD');
|
|
411
|
+
tallyTableRedundancy(tableHeaders, ['columnheader', 'rowheader', 'cell'], 'TH');
|
|
412
|
+
tallyTableRedundancy(tableCells, ['cell'], 'TD');
|
|
413
|
+
// Identify the additional elements with redundant roles and bad roles.
|
|
414
|
+
roleElements.filter(element => ! redundantCells.includes(element)).forEach(element => {
|
|
192
415
|
const role = element.getAttribute('role');
|
|
193
416
|
const tagName = element.tagName;
|
|
194
417
|
// If the role is not absolutely valid:
|
|
@@ -196,11 +419,37 @@ exports.reporter = async page => await page.$eval('body', body => {
|
|
|
196
419
|
// If it is bad or redundant:
|
|
197
420
|
if (badRoles.has(role)) {
|
|
198
421
|
dataInit(data, tagName, role);
|
|
199
|
-
|
|
200
|
-
|
|
422
|
+
const lcTagName = tagName.toLowerCase();
|
|
423
|
+
// If it is simply redundant:
|
|
424
|
+
if (role === implicitRoles[lcTagName]) {
|
|
425
|
+
data.redundantRoleElements++;
|
|
426
|
+
data.tagNames[tagName][role].redundant++;
|
|
427
|
+
}
|
|
428
|
+
// Otherwise, if it is attributionally redundant:
|
|
429
|
+
else if (
|
|
430
|
+
implicitAttributes[lcTagName] && implicitAttributes[lcTagName].some(
|
|
431
|
+
criterion => role === criterion.role && Object.keys(criterion.attributes).every(
|
|
432
|
+
attributeName => {
|
|
433
|
+
const rule = criterion.attributes[attributeName];
|
|
434
|
+
const exists = element.hasAttribute(attributeName);
|
|
435
|
+
const value = exists ? element.getAttribute(attributeName) : null;
|
|
436
|
+
if (rule === true) {
|
|
437
|
+
return exists;
|
|
438
|
+
}
|
|
439
|
+
else if (rule === false) {
|
|
440
|
+
return ! exists;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
return rule.test(value);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
) {
|
|
201
449
|
data.redundantRoleElements++;
|
|
202
450
|
data.tagNames[tagName][role].redundant++;
|
|
203
451
|
}
|
|
452
|
+
// Otherwise, i.e. if it is absolutely invalid:
|
|
204
453
|
else {
|
|
205
454
|
data.badRoleElements++;
|
|
206
455
|
data.tagNames[tagName][role].bad++;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// app.js
|
|
2
|
+
// Validator for Testaro tests.
|
|
3
|
+
|
|
4
|
+
const fs = require('fs').promises;
|
|
5
|
+
const {handleRequest} = require(`${__dirname}/../../run`);
|
|
6
|
+
const validateTests = async () => {
|
|
7
|
+
const totals = {
|
|
8
|
+
attempts: 0,
|
|
9
|
+
successes: 0
|
|
10
|
+
};
|
|
11
|
+
const scriptFileNames = await fs.readdir(`${__dirname}/../tests/scripts`);
|
|
12
|
+
for (const scriptFileName of ['hover.json']) {
|
|
13
|
+
const rawScriptJSON = await fs
|
|
14
|
+
.readFile(`${__dirname}/../tests/scripts/${scriptFileName}`, 'utf8');
|
|
15
|
+
const scriptJSON = rawScriptJSON
|
|
16
|
+
.replace(/__targets__/g, `file://${__dirname}/../tests/targets`);
|
|
17
|
+
const script = JSON.parse(scriptJSON);
|
|
18
|
+
const report = {script};
|
|
19
|
+
report.log = [];
|
|
20
|
+
report.acts = [];
|
|
21
|
+
await handleRequest(report);
|
|
22
|
+
const {log, acts} = report;
|
|
23
|
+
if (log.length === 2 && log[1].event === 'endTime' && /^\d{4}-.+$/.test(log[1].value)) {
|
|
24
|
+
console.log('Success: Log has been correctly populated');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log('Failure: Log empty or invalid');
|
|
28
|
+
console.log(JSON.stringify(log, null, 2));
|
|
29
|
+
}
|
|
30
|
+
if (
|
|
31
|
+
acts.length === script.commands.length
|
|
32
|
+
&& acts.every(
|
|
33
|
+
act => act.type && act.type === 'test'
|
|
34
|
+
? act.result && act.result.failureCount !== undefined
|
|
35
|
+
: true
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
totals.attempts++;
|
|
39
|
+
totals.successes++;
|
|
40
|
+
console.log('Success: Reports have been correctly populated');
|
|
41
|
+
if (acts.every(
|
|
42
|
+
act => act.type === 'test' ? act.result.failureCount === 0 : true
|
|
43
|
+
)) {
|
|
44
|
+
totals.attempts++;
|
|
45
|
+
totals.successes++;
|
|
46
|
+
console.log('Success: No failures');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
totals.attempts++;
|
|
50
|
+
console.log('Failure: At least one test has at least one failure');
|
|
51
|
+
console.log(JSON.stringify(acts, null, 2));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
totals.attempts++;
|
|
56
|
+
console.log('Failure: Reports empty or invalid');
|
|
57
|
+
console.log(JSON.stringify(acts, null, 2));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
console.log(`Grand totals: attempts ${totals.attempts}, successes ${totals.successes}`);
|
|
61
|
+
};
|
|
62
|
+
validateTests();
|
|
@@ -16,12 +16,19 @@
|
|
|
16
16
|
"type": "test",
|
|
17
17
|
"which": "hover",
|
|
18
18
|
"what": "hover",
|
|
19
|
-
"
|
|
19
|
+
"headSize": 3,
|
|
20
|
+
"headSampleSize": 3,
|
|
21
|
+
"tailSampleSize": 30,
|
|
22
|
+
"withItems": true,
|
|
20
23
|
"expect": [
|
|
21
|
-
["totals.triggers", "=",
|
|
22
|
-
["totals.
|
|
23
|
-
["totals.
|
|
24
|
-
["totals.
|
|
24
|
+
["totals.triggers", "=", 2],
|
|
25
|
+
["totals.headTriggers", "=", 2],
|
|
26
|
+
["totals.tailTriggers", "=", 0],
|
|
27
|
+
["totals.impactTriggers", "=", 0],
|
|
28
|
+
["totals.additions", "=", 0],
|
|
29
|
+
["totals.removals", "=", 0],
|
|
30
|
+
["totals.opacityChanges", "=", 0],
|
|
31
|
+
["totals.opacityImpact", "=", 0],
|
|
25
32
|
["totals.unhoverables", "=", 0]
|
|
26
33
|
]
|
|
27
34
|
},
|
|
@@ -34,12 +41,19 @@
|
|
|
34
41
|
"type": "test",
|
|
35
42
|
"which": "hover",
|
|
36
43
|
"what": "hover",
|
|
37
|
-
"
|
|
44
|
+
"headSize": 3,
|
|
45
|
+
"headSampleSize": 3,
|
|
46
|
+
"tailSampleSize": 30,
|
|
47
|
+
"withItems": true,
|
|
38
48
|
"expect": [
|
|
39
|
-
["totals.triggers", "=",
|
|
40
|
-
["totals.
|
|
41
|
-
["totals.
|
|
42
|
-
["totals.
|
|
49
|
+
["totals.triggers", "=", 4],
|
|
50
|
+
["totals.headTriggers", "=", 3],
|
|
51
|
+
["totals.tailTriggers", "=", 1],
|
|
52
|
+
["totals.impactTriggers", "=", 2],
|
|
53
|
+
["totals.additions", "=", 3],
|
|
54
|
+
["totals.removals", "=", 0],
|
|
55
|
+
["totals.opacityChanges", "=", 1],
|
|
56
|
+
["totals.opacityImpact", "=", 1],
|
|
43
57
|
["totals.unhoverables", "=", 1]
|
|
44
58
|
]
|
|
45
59
|
}
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"what": "role",
|
|
19
19
|
"expect": [
|
|
20
20
|
["roleElements", "=", 1],
|
|
21
|
-
["badRoleElements", "=", 0]
|
|
21
|
+
["badRoleElements", "=", 0],
|
|
22
|
+
["redundantRoleElements", "=", 0]
|
|
22
23
|
]
|
|
23
24
|
},
|
|
24
25
|
{
|
|
@@ -31,11 +32,19 @@
|
|
|
31
32
|
"which": "role",
|
|
32
33
|
"what": "role",
|
|
33
34
|
"expect": [
|
|
34
|
-
["roleElements", "=",
|
|
35
|
-
["badRoleElements", "=",
|
|
36
|
-
["
|
|
37
|
-
["tagNames.SECTION.
|
|
38
|
-
["tagNames.
|
|
35
|
+
["roleElements", "=", 9],
|
|
36
|
+
["badRoleElements", "=", 6],
|
|
37
|
+
["redundantRoleElements", "=", 3],
|
|
38
|
+
["tagNames.SECTION.main.bad", "=", 1],
|
|
39
|
+
["tagNames.SECTION.section.bad", "=", 1],
|
|
40
|
+
["tagNames.H2.heading.bad", "=", 0],
|
|
41
|
+
["tagNames.H2.heading.redundant", "=", 1],
|
|
42
|
+
["tagNames.H3.heading.bad", "=", 2],
|
|
43
|
+
["tagNames.H3.heading.redundant", "=", 1],
|
|
44
|
+
["tagNames.INPUT.spinbutton.redundant", "=", 1],
|
|
45
|
+
["tagNames.INPUT.textbox.bad", "=", 1],
|
|
46
|
+
["tagNames.INPUT.textbox.redundant", "=", 0],
|
|
47
|
+
["tagNames.INPUT.combobox.bad", "=", 1]
|
|
39
48
|
]
|
|
40
49
|
}
|
|
41
50
|
]
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
<body>
|
|
10
10
|
<main>
|
|
11
11
|
<h1>Page with deviant hover behavior</h1>
|
|
12
|
-
<p>This page contains a link to <a href="https://en.wikipedia.org" onmouseover="document.getElementById('translucent').style.opacity = 1">information</a
|
|
12
|
+
<p>This page contains a link to <a href="https://en.wikipedia.org" onmouseover="document.getElementById('translucent').style.opacity = 1">information</a> and a <button type="button" onmouseover="document.getElementById('hiddenP').style.display = 'block'">bothersome button</button>. Both elements can be hovered over.</p>
|
|
13
13
|
<p id="hiddenP" style="display: none">The button, when hovered over, makes this paragraph, and therefore this <a href="https://en.wikipedia.org/wiki/Web_accessibility">link on web accessibility</a> and this <button type="button">new button</button>, visible.</p>
|
|
14
14
|
<p id="translucent" style="opacity: 0.4">The first link, when hovered over, changes the opacity of this paragraph from 0.4 to 1. That indirectly changes the opacity of this <span>word</span>, too.</p>
|
|
15
|
-
<p>The small button is mostly covered by a large one
|
|
15
|
+
<p>The small button is mostly covered by a large one here, preventing the small button from receiving a hover event.</p>
|
|
16
16
|
<p style="position: relative"><button style="position: absolute; left: 10rem">button</button><button style="position: absolute; left: 11rem; top: -0.5rem; font-size: x-large">bigger button</button></p>
|
|
17
17
|
</main>
|
|
18
18
|
</body>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<body>
|
|
10
10
|
<main>
|
|
11
11
|
<h1>Page with standard hover behavior</h1>
|
|
12
|
-
<p>This paragraph contains a link to <a href="https://en.wikipedia.org">information</a
|
|
12
|
+
<p>This paragraph contains a link to <a href="https://en.wikipedia.org">information</a> and a <button type="button">button</button>. Both of them can be hovered over, and hovering over either of them does not trigger any change in content.</p>
|
|
13
13
|
</main>
|
|
14
14
|
</body>
|
|
15
15
|
</html>
|
|
@@ -11,15 +11,37 @@
|
|
|
11
11
|
<h1>Page with deviant role elements</h1>
|
|
12
12
|
<section role="section">
|
|
13
13
|
<h2>Abstraction</h2>
|
|
14
|
-
<p>This section has an abstract role.</p>
|
|
14
|
+
<p>This section has an abstract role, so it is bad.</p>
|
|
15
15
|
</section>
|
|
16
16
|
<section>
|
|
17
17
|
<h2 role="heading">Redundancy</h2>
|
|
18
|
-
<p>
|
|
18
|
+
<p>The heading of this section has a redundant role, with an inferred level.</p>
|
|
19
|
+
<section>
|
|
20
|
+
<h3 role="heading" aria-level="3">Redundancy with explicit level</h3>
|
|
21
|
+
<p>The heading of this section has a redundant role because the stated level is 3 and the implicit level is 3.</p>
|
|
22
|
+
</section>
|
|
23
|
+
<section>
|
|
24
|
+
<h3 role="heading">Failed redundancy for missing level</h3>
|
|
25
|
+
<p>The heading of this section has a heading that fails redundancy because the inferred level is 2 but the explicit level is 3. So the element role is bad.</p>
|
|
26
|
+
</section>
|
|
27
|
+
<section>
|
|
28
|
+
<h3 role="heading" aria-level="4">Failed redundancy for wrong level</h3>
|
|
29
|
+
<p>The heading of this section has a heading that fails redundancy because the attributional level is 4 but the explicit level is 3. So the element role is bad.</p>
|
|
30
|
+
</section>
|
|
19
31
|
</section>
|
|
20
32
|
<section>
|
|
21
33
|
<h2>Conflict</h2>
|
|
22
|
-
<p>The parent section of these sections has an unnecessary explicit role
|
|
34
|
+
<p>The parent section of these sections has an unnecessary explicit role <code>main</code>, instead of a <code>main</code> element with the same implicit role.</p>
|
|
35
|
+
</section>
|
|
36
|
+
<section>
|
|
37
|
+
<h2>Attributes</h2>
|
|
38
|
+
<h3>Valid redundancy</h3>
|
|
39
|
+
<p>This paragraph contains an input for a number. It has an implicit <code>spinbutton</code> role and the same redundant explicit role. <input type="number" role="spinbutton"></p>
|
|
40
|
+
<h3>Failed redundancy</h3>
|
|
41
|
+
<p>This paragraph contains an input for a number. It has an implicit <code>spinbutton</code> role but an explicit <code>textbox</code> role, so its role is bad. <input type="number" role="textbox"></p>
|
|
42
|
+
<h3>Attribute existence</h3>
|
|
43
|
+
<datalist id="options"><option value="a"></option><option value="b"></option></datalist>
|
|
44
|
+
<p>This input is identical, except that it omits the <code>list</code> attribute. That makes its implicit role <code>textbox</code>, so the explicit role of <code>combobox</code> is bad. <input role="combobox"></p>
|
|
23
45
|
</section>
|
|
24
46
|
</section>
|
|
25
47
|
</body>
|