testaro 67.0.0 → 68.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -16
- package/README.md +10 -2
- package/UPGRADES.md +1 -1
- package/dirWatch.js +2 -3
- package/ed11y/editoria11y.min.js +109 -690
- package/ed11y/editoria11y210.min.js +747 -0
- package/netWatch.js +6 -6
- package/package.json +1 -1
- package/procs/aslint.js +2 -2
- package/procs/catalog.js +190 -0
- package/procs/{dateOf.js → dateTime.js} +6 -4
- package/procs/doActs.js +1227 -0
- package/procs/doTestAct.js +63 -29
- package/procs/error.js +53 -0
- package/procs/job.js +64 -38
- package/procs/launch.js +596 -0
- package/procs/nu.js +3 -18
- package/procs/shoot.js +18 -2
- package/procs/testaro.js +102 -125
- package/procs/xPath.js +62 -0
- package/run.js +42 -1938
- package/scratch/README.md +9 -0
- package/testaro/adbID.js +3 -3
- package/testaro/allCaps.js +4 -5
- package/testaro/allHidden.js +19 -18
- package/testaro/allSlanted.js +4 -5
- package/testaro/altScheme.js +3 -3
- package/testaro/attVal.js +19 -35
- package/testaro/autocomplete.js +65 -62
- package/testaro/bulk.js +21 -20
- package/testaro/buttonMenu.js +112 -33
- package/testaro/captionLoc.js +3 -3
- package/testaro/datalistRef.js +4 -5
- package/testaro/distortion.js +3 -3
- package/testaro/docType.js +6 -9
- package/testaro/dupAtt.js +12 -25
- package/testaro/elements.js +4 -3
- package/testaro/embAc.js +4 -2
- package/testaro/focAll.js +6 -13
- package/testaro/focAndOp.js +3 -3
- package/testaro/focInd.js +3 -3
- package/testaro/focVis.js +4 -3
- package/testaro/headEl.js +5 -12
- package/testaro/headingAmb.js +45 -88
- package/testaro/hovInd.js +5 -5
- package/testaro/hover.js +44 -8
- package/testaro/hr.js +4 -4
- package/testaro/imageLink.js +3 -3
- package/testaro/labClash.js +3 -3
- package/testaro/legendLoc.js +3 -3
- package/testaro/lineHeight.js +3 -3
- package/testaro/linkAmb.js +25 -17
- package/testaro/linkExt.js +5 -5
- package/testaro/linkOldAtt.js +4 -3
- package/testaro/linkTo.js +4 -3
- package/testaro/linkUl.js +4 -5
- package/testaro/miniText.js +4 -3
- package/testaro/motion.js +3 -22
- package/testaro/nonTable.js +4 -5
- package/testaro/optRoleSel.js +3 -3
- package/testaro/phOnly.js +3 -3
- package/testaro/pseudoP.js +5 -5
- package/testaro/radioSet.js +4 -5
- package/testaro/role.js +4 -5
- package/testaro/secHeading.js +4 -5
- package/testaro/shoot0.js +3 -2
- package/testaro/shoot1.js +3 -2
- package/testaro/styleDiff.js +5 -12
- package/testaro/tabNav.js +30 -118
- package/testaro/targetSmall.js +30 -15
- package/testaro/textNodes.js +3 -1
- package/testaro/textSem.js +4 -5
- package/testaro/title.js +4 -2
- package/testaro/titledEl.js +3 -3
- package/testaro/zIndex.js +3 -3
- package/tests/alfa.js +28 -54
- package/tests/aslint.js +20 -53
- package/tests/axe.js +76 -13
- package/tests/ed11y.js +69 -141
- package/tests/htmlcs.js +69 -38
- package/tests/ibm.js +54 -9
- package/tests/nuVal.js +65 -12
- package/tests/nuVnu.js +76 -26
- package/tests/qualWeb.js +89 -44
- package/tests/testaro.js +288 -273
- package/tests/wave.js +142 -117
- package/tests/wax.js +61 -42
- package/procs/getLocatorData.js +0 -192
- package/procs/identify.js +0 -250
- package/procs/isInlineLink.js +0 -42
- package/procs/screenShot.js +0 -32
- package/procs/standardize.js +0 -524
- package/procs/target.js +0 -90
- package/procs/tellServer.js +0 -43
- package/scripts/dumpAlts.js +0 -28
package/tests/testaro.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Jonathan Robert Pool.
|
|
3
|
+
© 2025–2026 Jonathan Robert Pool.
|
|
4
4
|
|
|
5
5
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
6
|
https://opensource.org/license/mit/ for details.
|
|
@@ -16,387 +16,436 @@
|
|
|
16
16
|
// IMPORTS
|
|
17
17
|
|
|
18
18
|
// Function to launch a browser.
|
|
19
|
-
const {launch} = require('../
|
|
19
|
+
const {launch} = require('../procs/launch');
|
|
20
20
|
|
|
21
21
|
// CONSTANTS
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/*
|
|
24
|
+
Metadata of all rules in default execution order.
|
|
25
|
+
Property needsLaunch is true if the rule is first or the previous one contaminates the page.
|
|
26
|
+
*/
|
|
24
27
|
const allRules = [
|
|
25
28
|
{
|
|
26
29
|
id: 'shoot0',
|
|
27
30
|
what: 'first page screenshot',
|
|
28
|
-
|
|
31
|
+
contaminates: false,
|
|
32
|
+
needsAccessibleName: false,
|
|
29
33
|
timeOut: 5,
|
|
30
34
|
defaultOn: true
|
|
31
35
|
},
|
|
32
36
|
{
|
|
33
37
|
id: 'adbID',
|
|
34
38
|
what: 'elements with ambiguous or missing referenced descriptions',
|
|
35
|
-
|
|
39
|
+
contaminates: false,
|
|
40
|
+
needsAccessibleName: false,
|
|
36
41
|
timeOut: 5,
|
|
37
42
|
defaultOn: true
|
|
38
43
|
},
|
|
39
44
|
{
|
|
40
45
|
id: 'allCaps',
|
|
41
46
|
what: 'leaf elements with entirely upper-case text longer than 7 characters',
|
|
42
|
-
|
|
47
|
+
contaminates: false,
|
|
48
|
+
needsAccessibleName: false,
|
|
43
49
|
timeOut: 5,
|
|
44
50
|
defaultOn: true
|
|
45
51
|
},
|
|
46
52
|
{
|
|
47
53
|
id: 'allHidden',
|
|
48
54
|
what: 'page that is entirely or mostly hidden',
|
|
49
|
-
|
|
55
|
+
contaminates: false,
|
|
56
|
+
needsAccessibleName: false,
|
|
50
57
|
timeOut: 5,
|
|
51
58
|
defaultOn: true
|
|
52
59
|
},
|
|
53
60
|
{
|
|
54
61
|
id: 'allSlanted',
|
|
55
62
|
what: 'leaf elements with entirely italic or oblique text longer than 39 characters',
|
|
56
|
-
|
|
63
|
+
contaminates: false,
|
|
64
|
+
needsAccessibleName: false,
|
|
57
65
|
timeOut: 5,
|
|
58
66
|
defaultOn: true
|
|
59
67
|
},
|
|
60
68
|
{
|
|
61
69
|
id: 'altScheme',
|
|
62
70
|
what: 'img elements with alt attributes having URLs as their entire values',
|
|
63
|
-
|
|
71
|
+
contaminates: false,
|
|
72
|
+
needsAccessibleName: false,
|
|
64
73
|
timeOut: 5,
|
|
65
74
|
defaultOn: true
|
|
66
75
|
},
|
|
67
76
|
{
|
|
68
77
|
id: 'attVal',
|
|
69
78
|
what: 'elements with attributes having illicit values',
|
|
70
|
-
|
|
79
|
+
contaminates: false,
|
|
80
|
+
needsAccessibleName: false,
|
|
71
81
|
timeOut: 5,
|
|
72
82
|
defaultOn: false
|
|
73
83
|
},
|
|
74
|
-
{
|
|
75
|
-
id: 'dupAtt',
|
|
76
|
-
what: 'duplicate attribute values',
|
|
77
|
-
launchRole: 'sharer',
|
|
78
|
-
timeOut: 5,
|
|
79
|
-
defaultOn: true
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
id: 'autocomplete',
|
|
83
|
-
what: 'name and email inputs without autocomplete attributes',
|
|
84
|
-
launchRole: 'sharer',
|
|
85
|
-
timeOut: 5,
|
|
86
|
-
defaultOn: true
|
|
87
|
-
},
|
|
88
84
|
{
|
|
89
85
|
id: 'bulk',
|
|
90
86
|
what: 'large count of visible elements',
|
|
91
|
-
|
|
87
|
+
contaminates: false,
|
|
88
|
+
needsAccessibleName: false,
|
|
92
89
|
timeOut: 5,
|
|
93
90
|
defaultOn: true
|
|
94
91
|
},
|
|
95
92
|
{
|
|
96
93
|
id: 'captionLoc',
|
|
97
94
|
what: 'caption elements that are not first children of table elements',
|
|
98
|
-
|
|
95
|
+
contaminates: false,
|
|
96
|
+
needsAccessibleName: false,
|
|
99
97
|
timeOut: 5,
|
|
100
98
|
defaultOn: true
|
|
101
99
|
},
|
|
102
100
|
{
|
|
103
101
|
id: 'datalistRef',
|
|
104
102
|
what: 'elements with ambiguous or missing referenced datalist elements',
|
|
105
|
-
|
|
103
|
+
contaminates: false,
|
|
104
|
+
needsAccessibleName: false,
|
|
106
105
|
timeOut: 5,
|
|
107
106
|
defaultOn: true
|
|
108
107
|
},
|
|
109
108
|
{
|
|
110
109
|
id: 'distortion',
|
|
111
110
|
what: 'distorted text',
|
|
112
|
-
|
|
111
|
+
contaminates: false,
|
|
112
|
+
needsAccessibleName: false,
|
|
113
113
|
timeOut: 5,
|
|
114
114
|
defaultOn: true
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
id: 'docType',
|
|
118
118
|
what: 'document without a doctype property',
|
|
119
|
-
|
|
119
|
+
contaminates: false,
|
|
120
|
+
needsAccessibleName: false,
|
|
120
121
|
timeOut: 5,
|
|
121
122
|
defaultOn: true
|
|
122
123
|
},
|
|
123
124
|
{
|
|
124
125
|
id: 'dupAtt',
|
|
125
|
-
what: '
|
|
126
|
-
|
|
126
|
+
what: 'duplicate attribute values',
|
|
127
|
+
contaminates: false,
|
|
128
|
+
needsAccessibleName: false,
|
|
127
129
|
timeOut: 5,
|
|
128
130
|
defaultOn: true
|
|
129
131
|
},
|
|
130
132
|
{
|
|
131
133
|
id: 'embAc',
|
|
132
134
|
what: 'active elements embedded in links or buttons',
|
|
133
|
-
|
|
135
|
+
contaminates: false,
|
|
136
|
+
needsAccessibleName: false,
|
|
134
137
|
timeOut: 5,
|
|
135
138
|
defaultOn: true
|
|
136
139
|
},
|
|
137
140
|
{
|
|
138
141
|
id: 'headEl',
|
|
139
142
|
what: 'invalid elements within the head',
|
|
140
|
-
|
|
143
|
+
contaminates: false,
|
|
144
|
+
needsAccessibleName: false,
|
|
141
145
|
timeOut: 5,
|
|
142
146
|
defaultOn: true
|
|
143
147
|
},
|
|
144
148
|
{
|
|
145
149
|
id: 'headingAmb',
|
|
146
150
|
what: 'same-level sibling headings with identical texts',
|
|
147
|
-
|
|
151
|
+
contaminates: false,
|
|
152
|
+
needsAccessibleName: false,
|
|
148
153
|
timeOut: 5,
|
|
149
154
|
defaultOn: true
|
|
150
155
|
},
|
|
151
156
|
{
|
|
152
157
|
id: 'hr',
|
|
153
158
|
what: 'hr element instead of styles used for vertical segmentation',
|
|
154
|
-
|
|
159
|
+
contaminates: false,
|
|
160
|
+
needsAccessibleName: false,
|
|
155
161
|
timeOut: 5,
|
|
156
162
|
defaultOn: true
|
|
157
163
|
},
|
|
158
164
|
{
|
|
159
165
|
id: 'imageLink',
|
|
160
166
|
what: 'links with image files as their destinations',
|
|
161
|
-
|
|
167
|
+
contaminates: false,
|
|
168
|
+
needsAccessibleName: false,
|
|
162
169
|
timeOut: 5,
|
|
163
170
|
defaultOn: true
|
|
164
171
|
},
|
|
165
172
|
{
|
|
166
173
|
id: 'labClash',
|
|
167
174
|
what: 'labeling inconsistencies',
|
|
168
|
-
|
|
175
|
+
contaminates: false,
|
|
176
|
+
needsAccessibleName: false,
|
|
169
177
|
timeOut: 5,
|
|
170
178
|
defaultOn: true
|
|
171
179
|
},
|
|
172
180
|
{
|
|
173
181
|
id: 'legendLoc',
|
|
174
182
|
what: 'legend elements that are not first children of fieldset elements',
|
|
175
|
-
|
|
183
|
+
contaminates: false,
|
|
184
|
+
needsAccessibleName: false,
|
|
176
185
|
timeOut: 5,
|
|
177
186
|
defaultOn: true
|
|
178
187
|
},
|
|
179
188
|
{
|
|
180
189
|
id: 'lineHeight',
|
|
181
190
|
what: 'text with a line height less than 1.5 times its font size',
|
|
182
|
-
|
|
191
|
+
contaminates: false,
|
|
192
|
+
needsAccessibleName: false,
|
|
183
193
|
timeOut: 5,
|
|
184
194
|
defaultOn: true
|
|
185
195
|
},
|
|
186
196
|
{
|
|
187
197
|
id: 'linkAmb',
|
|
188
198
|
what: 'links with identical texts but different destinations',
|
|
189
|
-
|
|
199
|
+
contaminates: false,
|
|
200
|
+
needsAccessibleName: false,
|
|
190
201
|
timeOut: 20,
|
|
191
202
|
defaultOn: true
|
|
192
203
|
},
|
|
193
204
|
{
|
|
194
205
|
id: 'linkExt',
|
|
195
206
|
what: 'links that automatically open new windows',
|
|
196
|
-
|
|
207
|
+
contaminates: false,
|
|
208
|
+
needsAccessibleName: false,
|
|
197
209
|
timeOut: 5,
|
|
198
210
|
defaultOn: true
|
|
199
211
|
},
|
|
200
212
|
{
|
|
201
213
|
id: 'linkOldAtt',
|
|
202
214
|
what: 'links with deprecated attributes',
|
|
203
|
-
|
|
215
|
+
contaminates: false,
|
|
216
|
+
needsAccessibleName: false,
|
|
204
217
|
timeOut: 5,
|
|
205
218
|
defaultOn: true
|
|
206
219
|
},
|
|
207
220
|
{
|
|
208
221
|
id: 'linkTo',
|
|
209
222
|
what: 'links without destinations',
|
|
210
|
-
|
|
223
|
+
contaminates: false,
|
|
224
|
+
needsAccessibleName: false,
|
|
211
225
|
timeOut: 5,
|
|
212
226
|
defaultOn: true
|
|
213
227
|
},
|
|
214
228
|
{
|
|
215
229
|
id: 'linkUl',
|
|
216
230
|
what: 'missing underlines on inline links',
|
|
217
|
-
|
|
231
|
+
contaminates: false,
|
|
232
|
+
needsAccessibleName: false,
|
|
218
233
|
timeOut: 5,
|
|
219
234
|
defaultOn: true
|
|
220
235
|
},
|
|
221
236
|
{
|
|
222
237
|
id: 'miniText',
|
|
223
238
|
what: 'text smaller than 11 pixels',
|
|
224
|
-
|
|
239
|
+
contaminates: false,
|
|
240
|
+
needsAccessibleName: false,
|
|
225
241
|
timeOut: 5,
|
|
226
242
|
defaultOn: true
|
|
227
243
|
},
|
|
228
244
|
{
|
|
229
245
|
id: 'nonTable',
|
|
230
246
|
what: 'table elements used for layout',
|
|
231
|
-
|
|
247
|
+
contaminates: false,
|
|
248
|
+
needsAccessibleName: false,
|
|
232
249
|
timeOut: 5,
|
|
233
250
|
defaultOn: true
|
|
234
251
|
},
|
|
235
252
|
{
|
|
236
253
|
id: 'optRoleSel',
|
|
237
254
|
what: 'Non-option elements with option roles that have no aria-selected attributes',
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
defaultOn: true
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
id: 'phOnly',
|
|
244
|
-
what: 'input elements with placeholders but no accessible names',
|
|
245
|
-
launchRole: 'sharer',
|
|
255
|
+
contaminates: false,
|
|
256
|
+
needsAccessibleName: false,
|
|
246
257
|
timeOut: 5,
|
|
247
258
|
defaultOn: true
|
|
248
259
|
},
|
|
249
260
|
{
|
|
250
261
|
id: 'pseudoP',
|
|
251
262
|
what: 'adjacent br elements suspected of nonsemantically simulating p elements',
|
|
252
|
-
|
|
263
|
+
contaminates: false,
|
|
264
|
+
needsAccessibleName: false,
|
|
253
265
|
timeOut: 5,
|
|
254
266
|
defaultOn: true
|
|
255
267
|
},
|
|
256
268
|
{
|
|
257
269
|
id: 'radioSet',
|
|
258
270
|
what: 'radio buttons not grouped into standard field sets',
|
|
259
|
-
|
|
271
|
+
contaminates: false,
|
|
272
|
+
needsAccessibleName: false,
|
|
260
273
|
timeOut: 5,
|
|
261
274
|
defaultOn: true
|
|
262
275
|
},
|
|
263
276
|
{
|
|
264
277
|
id: 'role',
|
|
265
278
|
what: 'native-replacing explicit roles',
|
|
266
|
-
|
|
279
|
+
contaminates: false,
|
|
280
|
+
needsAccessibleName: false,
|
|
267
281
|
timeOut: 20,
|
|
268
282
|
defaultOn: true
|
|
269
283
|
},
|
|
270
284
|
{
|
|
271
285
|
id: 'secHeading',
|
|
272
286
|
what: 'headings that violate the logical level order in their sectioning containers',
|
|
273
|
-
|
|
287
|
+
contaminates: false,
|
|
288
|
+
needsAccessibleName: false,
|
|
274
289
|
timeOut: 5,
|
|
275
290
|
defaultOn: true
|
|
276
291
|
},
|
|
277
292
|
{
|
|
278
293
|
id: 'styleDiff',
|
|
279
294
|
what: 'style inconsistencies',
|
|
280
|
-
|
|
295
|
+
contaminates: false,
|
|
296
|
+
needsAccessibleName: false,
|
|
281
297
|
timeOut: 5,
|
|
282
298
|
defaultOn: true
|
|
283
299
|
},
|
|
284
300
|
{
|
|
285
301
|
id: 'targetSmall',
|
|
286
302
|
what: 'labels, buttons, inputs, and links too near each other',
|
|
287
|
-
|
|
303
|
+
contaminates: false,
|
|
304
|
+
needsAccessibleName: false,
|
|
288
305
|
timeOut: 5,
|
|
289
306
|
defaultOn: true
|
|
290
307
|
},
|
|
291
308
|
{
|
|
292
309
|
id: 'textSem',
|
|
293
310
|
what: 'semantically vague elements i, b, and/or small',
|
|
294
|
-
|
|
311
|
+
contaminates: false,
|
|
312
|
+
needsAccessibleName: false,
|
|
295
313
|
timeOut: 5,
|
|
296
314
|
defaultOn: true
|
|
297
315
|
},
|
|
298
316
|
{
|
|
299
317
|
id: 'title',
|
|
300
318
|
what: 'page title',
|
|
301
|
-
|
|
319
|
+
contaminates: false,
|
|
320
|
+
needsAccessibleName: false,
|
|
302
321
|
timeOut: 5,
|
|
303
322
|
defaultOn: false
|
|
304
323
|
},
|
|
305
324
|
{
|
|
306
325
|
id: 'titledEl',
|
|
307
326
|
what: 'title attributes on inappropriate elements',
|
|
308
|
-
|
|
327
|
+
contaminates: false,
|
|
328
|
+
needsAccessibleName: false,
|
|
309
329
|
timeOut: 5,
|
|
310
330
|
defaultOn: true
|
|
311
331
|
},
|
|
312
332
|
{
|
|
313
333
|
id: 'zIndex',
|
|
314
334
|
what: 'non-default Z indexes',
|
|
315
|
-
|
|
335
|
+
contaminates: false,
|
|
336
|
+
needsAccessibleName: false,
|
|
316
337
|
timeOut: 5,
|
|
317
338
|
defaultOn: true
|
|
318
339
|
},
|
|
319
340
|
{
|
|
320
341
|
id: 'shoot1',
|
|
321
342
|
what: 'second page screenshot',
|
|
322
|
-
|
|
343
|
+
contaminates: false,
|
|
344
|
+
needsAccessibleName: false,
|
|
323
345
|
timeOut: 5,
|
|
324
346
|
defaultOn: true
|
|
325
347
|
},
|
|
326
348
|
{
|
|
327
349
|
id: 'motion',
|
|
328
350
|
what: 'motion without user request, measured across tests',
|
|
329
|
-
|
|
351
|
+
contaminates: false,
|
|
352
|
+
needsAccessibleName: false,
|
|
353
|
+
timeOut: 5,
|
|
354
|
+
defaultOn: true
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: 'autocomplete',
|
|
358
|
+
what: 'name and email inputs without autocomplete attributes',
|
|
359
|
+
contaminates: false,
|
|
360
|
+
needsAccessibleName: true,
|
|
361
|
+
timeOut: 5,
|
|
362
|
+
defaultOn: true
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: 'phOnly',
|
|
366
|
+
what: 'input elements with placeholders but no accessible names',
|
|
367
|
+
contaminates: false,
|
|
368
|
+
needsAccessibleName: true,
|
|
330
369
|
timeOut: 5,
|
|
331
370
|
defaultOn: true
|
|
332
371
|
},
|
|
333
372
|
{
|
|
334
373
|
id: 'buttonMenu',
|
|
335
374
|
what: 'nonstandard keyboard navigation between items of button-controlled menus',
|
|
336
|
-
|
|
375
|
+
contaminates: true,
|
|
376
|
+
needsAccessibleName: false,
|
|
337
377
|
timeOut: 15,
|
|
338
378
|
defaultOn: true
|
|
339
379
|
},
|
|
340
380
|
{
|
|
341
381
|
id: 'elements',
|
|
342
382
|
what: 'data on specified elements',
|
|
343
|
-
|
|
383
|
+
contaminates: true,
|
|
384
|
+
needsAccessibleName: false,
|
|
344
385
|
timeOut: 10,
|
|
345
386
|
defaultOn: false
|
|
346
387
|
},
|
|
347
388
|
{
|
|
348
389
|
id: 'focAll',
|
|
349
390
|
what: 'discrepancies between focusable and Tab-focused elements',
|
|
350
|
-
|
|
391
|
+
contaminates: true,
|
|
392
|
+
needsAccessibleName: false,
|
|
351
393
|
timeOut: 10,
|
|
352
394
|
defaultOn: true
|
|
353
395
|
},
|
|
354
396
|
{
|
|
355
397
|
id: 'focAndOp',
|
|
356
398
|
what: 'Tab-focusable elements that are not operable or vice versa',
|
|
357
|
-
|
|
399
|
+
contaminates: true,
|
|
400
|
+
needsAccessibleName: false,
|
|
358
401
|
timeOut: 5,
|
|
359
402
|
defaultOn: true
|
|
360
403
|
},
|
|
361
404
|
{
|
|
362
405
|
id: 'focInd',
|
|
363
406
|
what: 'missing and nonstandard focus indicators',
|
|
364
|
-
|
|
407
|
+
contaminates: true,
|
|
408
|
+
needsAccessibleName: false,
|
|
365
409
|
timeOut: 10,
|
|
366
410
|
defaultOn: true
|
|
367
411
|
},
|
|
368
412
|
{
|
|
369
413
|
id: 'focVis',
|
|
370
414
|
what: 'links that are not entirely visible when focused',
|
|
371
|
-
|
|
415
|
+
contaminates: true,
|
|
416
|
+
needsAccessibleName: false,
|
|
372
417
|
timeOut: 10,
|
|
373
418
|
defaultOn: true
|
|
374
419
|
},
|
|
375
420
|
{
|
|
376
421
|
id: 'hover',
|
|
377
422
|
what: 'hover-caused content changes',
|
|
378
|
-
|
|
423
|
+
contaminates: true,
|
|
424
|
+
needsAccessibleName: false,
|
|
379
425
|
timeOut: 20,
|
|
380
426
|
defaultOn: true
|
|
381
427
|
},
|
|
382
428
|
{
|
|
383
429
|
id: 'hovInd',
|
|
384
430
|
what: 'hover indication nonstandard',
|
|
385
|
-
|
|
431
|
+
contaminates: true,
|
|
432
|
+
needsAccessibleName: false,
|
|
386
433
|
timeOut: 10,
|
|
387
434
|
defaultOn: true
|
|
388
435
|
},
|
|
389
436
|
{
|
|
390
437
|
id: 'tabNav',
|
|
391
438
|
what: 'nonstandard keyboard navigation between elements with the tab role',
|
|
392
|
-
|
|
439
|
+
contaminates: true,
|
|
440
|
+
needsAccessibleName: false,
|
|
393
441
|
timeOut: 10,
|
|
394
442
|
defaultOn: true
|
|
395
443
|
},
|
|
396
444
|
{
|
|
397
445
|
id: 'textNodes',
|
|
398
446
|
what: 'data on specified text nodes',
|
|
399
|
-
|
|
447
|
+
contaminates: true,
|
|
448
|
+
needsAccessibleName: false,
|
|
400
449
|
timeOut: 10,
|
|
401
450
|
defaultOn: false
|
|
402
451
|
}
|
|
@@ -426,244 +475,210 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
426
475
|
const url = target.url;
|
|
427
476
|
const browserID = act.launch ? act.launch.browserID || report.browserID : report.browserID;
|
|
428
477
|
const argRules = args ? Object.keys(args) : null;
|
|
429
|
-
// Get the specification of rules to be tested for.
|
|
478
|
+
// Get the specification of rules to be tested for or, by default, all rules with defaultOn true.
|
|
430
479
|
const ruleSpec = act.rules
|
|
431
480
|
|| ['y', ... allRules.filter(rule => rule.defaultOn).map(rule => rule.id)];
|
|
432
|
-
// Initialize the act data
|
|
481
|
+
// Initialize the act data.
|
|
433
482
|
const data = {
|
|
434
483
|
prevented: false,
|
|
435
484
|
error: '',
|
|
436
|
-
rulePreventions:
|
|
437
|
-
rulePreventionMessages: {},
|
|
485
|
+
rulePreventions: {},
|
|
438
486
|
rulesInvalid: [],
|
|
439
|
-
ruleTestTimes:
|
|
487
|
+
ruleTestTimes: [],
|
|
440
488
|
ruleData: {}
|
|
441
489
|
};
|
|
442
|
-
|
|
490
|
+
// Initialize the act result.
|
|
491
|
+
const result = {
|
|
492
|
+
nativeResult: {},
|
|
493
|
+
standardResult: {
|
|
494
|
+
prevented: false,
|
|
495
|
+
totals: [0, 0, 0, 0],
|
|
496
|
+
instances: []
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
const {standardResult} = result;
|
|
443
500
|
const allRuleIDs = allRules.map(rule => rule.id);
|
|
444
|
-
// If the rule specification is
|
|
445
|
-
if (
|
|
501
|
+
// If the rule specification is valid:
|
|
502
|
+
if (
|
|
446
503
|
ruleSpec.length > 1
|
|
447
504
|
&& ['y', 'n'].includes(ruleSpec[0])
|
|
448
505
|
&& ruleSpec.slice(1).every(ruleID => allRuleIDs.includes(ruleID))
|
|
449
|
-
)
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const ruleIndex = Number.parseInt(ruleIndexString);
|
|
470
|
-
const rule = jobRules[ruleIndex];
|
|
471
|
-
const ruleID = rule.id;
|
|
472
|
-
console.log(`Starting rule ${ruleID}`);
|
|
473
|
-
// Make the browser emulate headedness in all cases, because performance does not suffer.
|
|
474
|
-
const headEmulation = ruleID.startsWith('shoot') ? 'high' : 'high';
|
|
475
|
-
// Get whether it needs a new browser launched.
|
|
476
|
-
const needsLaunch = ruleIndex
|
|
477
|
-
&& jobRules[ruleIndex - 1].launchRole !== 'sharer'
|
|
478
|
-
&& rule.launchRole !== 'owner'
|
|
479
|
-
|| ! ruleIndex;
|
|
480
|
-
const pageClosed = page && page.isClosed();
|
|
481
|
-
// If it does, or if the page has closed:
|
|
482
|
-
if (needsLaunch || pageClosed) {
|
|
483
|
-
// If the page has closed when it is expected to be open:
|
|
484
|
-
if (pageClosed && ! needsLaunch) {
|
|
485
|
-
// Report this.
|
|
486
|
-
console.log(`WARNING: Relaunching browser for test ${rule} after abnormal closure`);
|
|
487
|
-
}
|
|
488
|
-
// Replace the browser and the page and navigate to the target.
|
|
489
|
-
await launch(
|
|
490
|
-
report,
|
|
491
|
-
actIndex,
|
|
492
|
-
headEmulation,
|
|
493
|
-
browserID,
|
|
494
|
-
url
|
|
495
|
-
);
|
|
496
|
-
page = require('../run').page;
|
|
497
|
-
}
|
|
498
|
-
// Get the current browser.
|
|
499
|
-
const {browser} = require('../run');
|
|
500
|
-
// Report crashes and disconnections during this test.
|
|
501
|
-
let crashHandler;
|
|
502
|
-
let disconnectHandler;
|
|
503
|
-
if (page && ! page.isClosed()) {
|
|
504
|
-
crashHandler = () => {
|
|
505
|
-
console.log(`ERROR: Page crashed during ${rule} test`);
|
|
506
|
+
) {
|
|
507
|
+
// Wait 1 second to prevent out-of-order logging with granular reporting.
|
|
508
|
+
await wait(1000);
|
|
509
|
+
// Get the rules to be tested for and their execution order.
|
|
510
|
+
const jobRuleIDs = ruleSpec[0] === 'y'
|
|
511
|
+
? ruleSpec.slice(1)
|
|
512
|
+
: allRules.filter(rule => rule.defaultOn && ! allRuleIDs.includes(rule.id));
|
|
513
|
+
const jobRules = allRules.filter(rule => jobRuleIDs.includes(rule.id));
|
|
514
|
+
// For each rule to be tested for:
|
|
515
|
+
for (let ruleIndex = 0; ruleIndex < jobRules.length; ruleIndex++) {
|
|
516
|
+
const rule = jobRules[ruleIndex];
|
|
517
|
+
// Initialize the rule result.
|
|
518
|
+
const ruleResult = {
|
|
519
|
+
id: rule.id,
|
|
520
|
+
prevented: false,
|
|
521
|
+
error: '',
|
|
522
|
+
data: {},
|
|
523
|
+
totals: [0, 0, 0, 0],
|
|
524
|
+
instances: [],
|
|
525
|
+
elapsedTime: 0
|
|
506
526
|
};
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
527
|
+
console.log(`Starting rule ${ruleResult.id}`);
|
|
528
|
+
// Make the browser emulate headedness in all cases, because performance does not suffer.
|
|
529
|
+
const headEmulation = ruleResult.id.startsWith('shoot') ? 'high' : 'high';
|
|
530
|
+
// Get whether the rule needs a new browser launched.
|
|
531
|
+
const needsLaunch = ruleIndex === 0
|
|
532
|
+
|| jobRules[ruleIndex - 1].contaminates
|
|
533
|
+
|| jobRules[ruleIndex].needsAccessibleName && ! jobRules[ruleIndex - 1].needsAccessibleName;
|
|
534
|
+
const pageClosed = page && page.isClosed();
|
|
535
|
+
// If it does, or if the page has closed:
|
|
536
|
+
if (needsLaunch || pageClosed) {
|
|
537
|
+
// If the page has closed when it is expected to be open:
|
|
538
|
+
if (pageClosed && ! needsLaunch) {
|
|
539
|
+
// Report this.
|
|
540
|
+
console.log(`WARNING: Relaunching browser for test ${rule} after abnormal closure`);
|
|
541
|
+
}
|
|
542
|
+
// Create a browser, replace the page, and visit the target, retrying twice if necessary.
|
|
543
|
+
page = await launch({
|
|
544
|
+
report,
|
|
545
|
+
actIndex,
|
|
546
|
+
tempBrowserID: browserID,
|
|
547
|
+
tempURL: url,
|
|
548
|
+
xPathNeed: 'script',
|
|
549
|
+
needsAccessibleName: jobRules[ruleIndex].needsAccessibleName,
|
|
550
|
+
retries: 2
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
// Report crashes and disconnections during this test.
|
|
554
|
+
let crashHandler;
|
|
555
|
+
let disconnectHandler;
|
|
556
|
+
if (page && ! page.isClosed()) {
|
|
557
|
+
crashHandler = () => {
|
|
558
|
+
console.log(`ERROR: Page crashed during ${rule} test`);
|
|
559
|
+
};
|
|
560
|
+
page.on('crash', crashHandler);
|
|
561
|
+
}
|
|
562
|
+
const browser = page.context().browser();
|
|
563
|
+
if (browser) {
|
|
564
|
+
disconnectHandler = () => {
|
|
565
|
+
console.log(`ERROR: Browser disconnected during ${rule} test`);
|
|
566
|
+
};
|
|
567
|
+
browser.on('disconnected', disconnectHandler);
|
|
568
|
+
}
|
|
569
|
+
// Initialize an argument array for the reporter.
|
|
570
|
+
const ruleArgs = [page, report.catalog, withItems];
|
|
571
|
+
// If the rule has extra arguments:
|
|
572
|
+
if (argRules?.includes(ruleResult.id)) {
|
|
573
|
+
// Add them to the argument array.
|
|
574
|
+
ruleArgs.push(... args[ruleResult.id]);
|
|
575
|
+
}
|
|
576
|
+
const startTime = Date.now();
|
|
577
|
+
let timer;
|
|
532
578
|
try {
|
|
533
579
|
// Apply a time limit to the test.
|
|
534
580
|
const timeLimit = 1000 * timeoutMultiplier * rule.timeOut;
|
|
581
|
+
let timeout;
|
|
535
582
|
// If the time limit expires during the test:
|
|
536
|
-
|
|
583
|
+
timer = new Promise(resolve => {
|
|
537
584
|
timeout = setTimeout(() => {
|
|
538
|
-
// Add data about the
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
data.rulePreventionMessages[ruleID] = 'Timeout';
|
|
543
|
-
ruleResult.totals = [0, 0, 0, 0];
|
|
544
|
-
ruleResult.standardInstances = [];
|
|
545
|
-
console.log(`ERROR: Test of testaro rule ${ruleID} timed out`);
|
|
585
|
+
// Add data about the timeout to the rule result.
|
|
586
|
+
ruleResult.prevented = true;
|
|
587
|
+
ruleResult.error = 'Timeout';
|
|
588
|
+
console.log(`ERROR: Test of testaro rule ${ruleResult.id} timed out`);
|
|
546
589
|
resolve({timedOut: true});
|
|
547
590
|
}, timeLimit);
|
|
548
591
|
});
|
|
549
|
-
//
|
|
550
|
-
const
|
|
551
|
-
// Get
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
// Add this to the tool result.
|
|
564
|
-
data.rulePreventions.push(ruleID);
|
|
565
|
-
data.rulePreventionMessages[ruleID] = ruleResult.data.error;
|
|
592
|
+
// Try to perform the test and get a test report.
|
|
593
|
+
const testReport = require(`../testaro/${ruleResult.id}`).reporter(... ruleArgs);
|
|
594
|
+
// Get a test or timeout report.
|
|
595
|
+
const ruleReport = await Promise.race([timer, testReport]);
|
|
596
|
+
clearTimeout(timeout);
|
|
597
|
+
// If it was a test report:
|
|
598
|
+
if (! ruleReport.timedOut) {
|
|
599
|
+
// Add the rule-report properties to the rule result.
|
|
600
|
+
ruleResult.data = ruleReport.data;
|
|
601
|
+
ruleResult.totals = ruleReport.totals;
|
|
602
|
+
ruleResult.instances = ruleReport.standardInstances;
|
|
603
|
+
// Add the rule-result properties to the result.
|
|
604
|
+
if (Object.keys(ruleReport.data).length) {
|
|
605
|
+
data.ruleData[ruleResult.id] = ruleResult.data;
|
|
566
606
|
}
|
|
567
|
-
// If the result includes totals:
|
|
568
607
|
if (ruleResult.totals) {
|
|
569
|
-
|
|
570
|
-
|
|
608
|
+
ruleResult.totals.forEach((total, index) => {
|
|
609
|
+
standardResult.totals[index] += Math.round(total);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
if (ruleResult.instances) {
|
|
613
|
+
standardResult.instances.push(... ruleResult.instances);
|
|
571
614
|
}
|
|
572
|
-
const ruleDataMiscKeys = Object
|
|
573
|
-
.keys(ruleResult.data)
|
|
574
|
-
.filter(key => ! ['prevented', 'error'].includes(key));
|
|
575
|
-
// For any other property of the rule report data object:
|
|
576
|
-
ruleDataMiscKeys.forEach(key => {
|
|
577
|
-
data.ruleData[ruleID] ??= {};
|
|
578
|
-
// Add it to the tool result.
|
|
579
|
-
data.ruleData[ruleID][key] = ruleResult.data[key];
|
|
580
|
-
});
|
|
581
|
-
// Prevent a retry of the test.
|
|
582
|
-
testSuccess = true;
|
|
583
615
|
// If testing is to stop after a failure and the page failed the test:
|
|
584
|
-
if (stopOnFail &&
|
|
585
|
-
//
|
|
616
|
+
if (stopOnFail && ruleReport.totals?.some(total => total)) {
|
|
617
|
+
// Test for no more rules.
|
|
586
618
|
break;
|
|
587
619
|
}
|
|
588
620
|
}
|
|
589
|
-
// Otherwise, i.e. if the test timed out:
|
|
590
|
-
else {
|
|
591
|
-
// Report this.
|
|
592
|
-
data.rulePreventions.push(ruleID);
|
|
593
|
-
data.rulePreventionMessages[ruleID] = 'Timeout';
|
|
594
|
-
// Stop retrying the test.
|
|
595
|
-
break;
|
|
596
|
-
}
|
|
597
621
|
}
|
|
598
622
|
// If an error is thrown by the test:
|
|
599
623
|
catch(error) {
|
|
600
624
|
const isPageClosed = ['closed', 'Protocol error', 'Target page'].some(phrase =>
|
|
601
625
|
error.message.includes(phrase)
|
|
602
626
|
);
|
|
603
|
-
// If the page has closed
|
|
604
|
-
if (isPageClosed
|
|
605
|
-
// Report this
|
|
606
|
-
console.log(
|
|
607
|
-
`WARNING: Retry ${3 - testRetries--} of test ${ruleID} starting after page closed`
|
|
608
|
-
);
|
|
609
|
-
await wait(2000);
|
|
610
|
-
// Replace the browser and the page in the run module and navigate to the target.
|
|
611
|
-
await launch(
|
|
612
|
-
report,
|
|
613
|
-
actIndex,
|
|
614
|
-
headEmulation,
|
|
615
|
-
report.browserID,
|
|
616
|
-
url
|
|
617
|
-
);
|
|
618
|
-
page = require('../run').page;
|
|
619
|
-
// If the page replacement failed:
|
|
620
|
-
if (! page) {
|
|
621
|
-
// Report this.
|
|
622
|
-
console.log(`ERROR: Browser relaunch to retry test ${ruleID} failed`);
|
|
623
|
-
data.rulePreventions.push(ruleID);
|
|
624
|
-
data.rulePreventionMessages[ruleID] = 'Retry failure due to browser relaunch failure';
|
|
625
|
-
// Stop retrying the test.
|
|
626
|
-
break;
|
|
627
|
-
}
|
|
628
|
-
// Update the rule arguments with the current page.
|
|
629
|
-
ruleArgs[0] = page;
|
|
627
|
+
// If the page has closed:
|
|
628
|
+
if (isPageClosed) {
|
|
629
|
+
// Report this.
|
|
630
|
+
console.log(`ERROR: Test ${ruleResult.id} failed because page closed`);
|
|
630
631
|
}
|
|
631
|
-
// Otherwise, i.e. if the page is open
|
|
632
|
+
// Otherwise, i.e. if the page is open:
|
|
632
633
|
else {
|
|
633
|
-
//
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
console.log(
|
|
637
|
-
|
|
638
|
-
|
|
634
|
+
// Add this to the rule result.
|
|
635
|
+
ruleResult.prevented = true;
|
|
636
|
+
ruleResult.error = error.message;
|
|
637
|
+
console.log(
|
|
638
|
+
`ERROR: Test of testaro rule ${ruleResult.id} prevented (${error.message})`
|
|
639
|
+
);
|
|
639
640
|
}
|
|
640
641
|
}
|
|
641
642
|
finally {
|
|
642
|
-
//
|
|
643
|
-
|
|
643
|
+
// Add the elapsed time to the rule result.
|
|
644
|
+
ruleResult.elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
|
645
|
+
// Add the elapsed time to the data.
|
|
646
|
+
data.ruleTestTimes.push([ruleResult.id, ruleResult.elapsedTime]);
|
|
647
|
+
// If the test timed out or otherwise failed:
|
|
648
|
+
if (ruleResult.prevented) {
|
|
649
|
+
// Add this and the error to the data.
|
|
650
|
+
data.rulePreventions[ruleResult.id] = ruleResult.error;
|
|
651
|
+
}
|
|
644
652
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
page.
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (browser && disconnectHandler) {
|
|
652
|
-
browser.off('disconnected', disconnectHandler);
|
|
653
|
-
disconnectHandler = null;
|
|
654
|
-
}
|
|
655
|
-
// Force a garbage collection.
|
|
656
|
-
try {
|
|
657
|
-
if (global.gc) {
|
|
658
|
-
global.gc();
|
|
653
|
+
// Sort the rule test times.
|
|
654
|
+
data.ruleTestTimes.sort((a, b) => b[1] - a[1]);
|
|
655
|
+
// Clear the error listeners.
|
|
656
|
+
if (page && ! page.isClosed() && crashHandler) {
|
|
657
|
+
page.off('crash', crashHandler);
|
|
658
|
+
crashHandler = null;
|
|
659
659
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
660
|
+
if (browser && disconnectHandler) {
|
|
661
|
+
browser.off('disconnected', disconnectHandler);
|
|
662
|
+
disconnectHandler = null;
|
|
663
|
+
}
|
|
664
|
+
// Force a garbage collection.
|
|
665
|
+
try {
|
|
666
|
+
if (global.gc) {
|
|
667
|
+
global.gc();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
catch(error) {}
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
// Otherwise, i.e. if the rule specification is invalid:
|
|
674
|
+
else {
|
|
675
|
+
// Report this and stop testing.
|
|
676
|
+
standardResult.prevented = true;
|
|
677
|
+
data.prevented = true;
|
|
678
|
+
const message = 'ERROR: Testaro rule specification invalid';
|
|
679
|
+
data.error = message;
|
|
680
|
+
console.log(message);
|
|
681
|
+
}
|
|
667
682
|
return {
|
|
668
683
|
data,
|
|
669
684
|
result
|