testaro 60.3.0 → 60.4.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/package.json +1 -1
- package/procs/screenShot.js +32 -0
- package/procs/testaro.js +4 -2
- package/procs/visChange.js +7 -25
- package/run.js +118 -43
- package/testaro/adbID.js +2 -2
- package/testaro/altScheme.js +2 -2
- package/testaro/attVal.js +2 -2
- package/testaro/autocomplete.js +2 -2
- package/testaro/captionLoc.js +2 -2
- package/testaro/datalistRef.js +2 -2
- package/testaro/embAc.js +2 -2
- package/testaro/focInd.js +2 -2
- package/testaro/focOp.js +2 -2
- package/testaro/focVis.js +2 -2
- package/testaro/hover.js +2 -2
- package/testaro/labClash.js +2 -2
- package/testaro/lineHeight.js +2 -2
- package/testaro/linkAmb.js +2 -2
- package/testaro/miniText.js +2 -2
- package/testaro/motion.js +67 -59
- package/testaro/motionSolo.js +94 -0
- package/testaro/opFoc.js +2 -2
- package/testaro/phOnly.js +2 -2
- package/testaro/pseudoP.js +2 -2
- package/testaro/radioSet.js +2 -2
- package/testaro/role.js +2 -2
- package/testaro/secHeading.js +2 -2
- package/testaro/shoot.js +57 -0
- package/testaro/targetSmall.js +2 -2
- package/testaro/targetTiny.js +2 -2
- package/testaro/textSem.js +2 -2
- package/testaro/zIndex.js +2 -2
- package/tests/testaro.js +608 -296
package/tests/testaro.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// IMPORTS
|
|
32
32
|
|
|
33
33
|
// Module to perform common operations.
|
|
34
|
-
const {init,
|
|
34
|
+
const {init, getRuleResult} = require('../procs/testaro');
|
|
35
35
|
// Function to launch a browser.
|
|
36
36
|
const {launch} = require('../run');
|
|
37
37
|
// Module to handle files.
|
|
@@ -39,100 +39,415 @@ const fs = require('fs/promises');
|
|
|
39
39
|
|
|
40
40
|
// CONSTANTS
|
|
41
41
|
|
|
42
|
-
//
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
42
|
+
// Metadata of all rules in default execution order.
|
|
43
|
+
const allRules = [
|
|
44
|
+
{
|
|
45
|
+
id: 'shoot',
|
|
46
|
+
what: 'page screenshot',
|
|
47
|
+
contaminator: false,
|
|
48
|
+
timeOut: 5,
|
|
49
|
+
defaultOn: true
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'adbID',
|
|
53
|
+
what: 'elements with ambiguous or missing referenced descriptions',
|
|
54
|
+
contaminator: false,
|
|
55
|
+
timeOut: 5,
|
|
56
|
+
defaultOn: true
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'allCaps',
|
|
60
|
+
what: 'leaf elements with entirely upper-case text longer than 7 characters',
|
|
61
|
+
contaminator: false,
|
|
62
|
+
timeOut: 10,
|
|
63
|
+
defaultOn: true
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'allHidden',
|
|
67
|
+
what: 'page that is entirely or mostly hidden',
|
|
68
|
+
contaminator: false,
|
|
69
|
+
timeOut: 5,
|
|
70
|
+
defaultOn: true
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'allSlanted',
|
|
74
|
+
what: 'leaf elements with entirely italic or oblique text longer than 39 characters',
|
|
75
|
+
contaminator: false,
|
|
76
|
+
timeOut: 5,
|
|
77
|
+
defaultOn: true
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'altScheme',
|
|
81
|
+
what: 'img elements with alt attributes having URLs as their entire values',
|
|
82
|
+
contaminator: false,
|
|
83
|
+
timeOut: 5,
|
|
84
|
+
defaultOn: true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'attVal',
|
|
88
|
+
what: 'elements with attributes having illicit values',
|
|
89
|
+
contaminator: false,
|
|
90
|
+
timeOut: 5,
|
|
91
|
+
defaultOn: false
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'dupAtt',
|
|
95
|
+
what: 'duplicate attribute values',
|
|
96
|
+
contaminator: false,
|
|
97
|
+
timeOut: 5,
|
|
98
|
+
defaultOn: true
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'autocomplete',
|
|
102
|
+
what: 'name and email inputs without autocomplete attributes',
|
|
103
|
+
contaminator: false,
|
|
104
|
+
timeOut: 5,
|
|
105
|
+
defaultOn: true
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'bulk',
|
|
109
|
+
what: 'large count of visible elements',
|
|
110
|
+
contaminator: false,
|
|
111
|
+
timeOut: 5,
|
|
112
|
+
defaultOn: true
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'captionLoc',
|
|
116
|
+
what: 'caption elements that are not first children of table elements',
|
|
117
|
+
contaminator: false,
|
|
118
|
+
timeOut: 5,
|
|
119
|
+
defaultOn: true
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'datalistRef',
|
|
123
|
+
what: 'elements with ambiguous or missing referenced datalist elements',
|
|
124
|
+
contaminator: false,
|
|
125
|
+
timeOut: 5,
|
|
126
|
+
defaultOn: true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'distortion',
|
|
130
|
+
what: 'distorted text',
|
|
131
|
+
contaminator: false,
|
|
132
|
+
timeOut: 10,
|
|
133
|
+
defaultOn: true
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'docType',
|
|
137
|
+
what: 'document without a doctype property',
|
|
138
|
+
contaminator: false,
|
|
139
|
+
timeOut: 10,
|
|
140
|
+
defaultOn: true
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'dupAtt',
|
|
144
|
+
what: 'elements with duplicate attributes',
|
|
145
|
+
contaminator: false,
|
|
146
|
+
timeOut: 5,
|
|
147
|
+
defaultOn: true
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'embAc',
|
|
151
|
+
what: 'active elements embedded in links or buttons',
|
|
152
|
+
contaminator: false,
|
|
153
|
+
timeOut: 5,
|
|
154
|
+
defaultOn: true
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'headEl',
|
|
158
|
+
what: 'invalid elements within the head',
|
|
159
|
+
contaminator: false,
|
|
160
|
+
timeOut: 5,
|
|
161
|
+
defaultOn: true
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'headingAmb',
|
|
165
|
+
what: 'same-level sibling headings with identical texts',
|
|
166
|
+
contaminator: false,
|
|
167
|
+
timeOut: 5,
|
|
168
|
+
defaultOn: true
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'hr',
|
|
172
|
+
what: 'hr element instead of styles used for vertical segmentation',
|
|
173
|
+
contaminator: false,
|
|
174
|
+
timeOut: 5,
|
|
175
|
+
defaultOn: true
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'imageLink',
|
|
179
|
+
what: 'links with image files as their destinations',
|
|
180
|
+
contaminator: false,
|
|
181
|
+
timeOut: 5,
|
|
182
|
+
defaultOn: true
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'labClash',
|
|
186
|
+
what: 'labeling inconsistencies',
|
|
187
|
+
contaminator: false,
|
|
188
|
+
timeOut: 10,
|
|
189
|
+
defaultOn: true
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 'legendLoc',
|
|
193
|
+
what: 'legend elements that are not first children of fieldset elements',
|
|
194
|
+
contaminator: false,
|
|
195
|
+
timeOut: 5,
|
|
196
|
+
defaultOn: true
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 'lineHeight',
|
|
200
|
+
what: 'text with a line height less than 1.5 times its font size',
|
|
201
|
+
contaminator: false,
|
|
202
|
+
timeOut: 10,
|
|
203
|
+
defaultOn: true
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: 'linkAmb',
|
|
207
|
+
what: 'links with identical texts but different destinations',
|
|
208
|
+
contaminator: false,
|
|
209
|
+
timeOut: 5,
|
|
210
|
+
defaultOn: true
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'linkExt',
|
|
214
|
+
what: 'links that automatically open new windows',
|
|
215
|
+
contaminator: false,
|
|
216
|
+
timeOut: 5,
|
|
217
|
+
defaultOn: true
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: 'linkOldAtt',
|
|
221
|
+
what: 'links with deprecated attributes',
|
|
222
|
+
contaminator: false,
|
|
223
|
+
timeOut: 5,
|
|
224
|
+
defaultOn: true
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 'linkTitle',
|
|
228
|
+
what: 'links with title attributes repeating text content',
|
|
229
|
+
contaminator: false,
|
|
230
|
+
timeOut: 5,
|
|
231
|
+
defaultOn: true
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'linkTo',
|
|
235
|
+
what: 'links without destinations',
|
|
236
|
+
contaminator: false,
|
|
237
|
+
timeOut: 5,
|
|
238
|
+
defaultOn: true
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'linkUl',
|
|
242
|
+
what: 'missing underlines on inline links',
|
|
243
|
+
contaminator: false,
|
|
244
|
+
timeOut: 10,
|
|
245
|
+
defaultOn: true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'miniText',
|
|
249
|
+
what: 'text smaller than 11 pixels',
|
|
250
|
+
contaminator: false,
|
|
251
|
+
timeOut: 5,
|
|
252
|
+
defaultOn: true
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: 'nonTable',
|
|
256
|
+
what: 'table elements used for layout',
|
|
257
|
+
contaminator: false,
|
|
258
|
+
timeOut: 5,
|
|
259
|
+
defaultOn: true
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: 'optRoleSel',
|
|
263
|
+
what: 'Non-option elements with option roles that have no aria-selected attributes',
|
|
264
|
+
contaminator: false,
|
|
265
|
+
timeOut: 5,
|
|
266
|
+
defaultOn: true
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
id: 'phOnly',
|
|
270
|
+
what: 'input elements with placeholders but no accessible names',
|
|
271
|
+
contaminator: false,
|
|
272
|
+
timeOut: 5,
|
|
273
|
+
defaultOn: true
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 'pseudoP',
|
|
277
|
+
what: 'adjacent br elements suspected of nonsemantically simulating p elements',
|
|
278
|
+
contaminator: false,
|
|
279
|
+
timeOut: 5,
|
|
280
|
+
defaultOn: true
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 'radioSet',
|
|
284
|
+
what: 'radio buttons not grouped into standard field sets',
|
|
285
|
+
contaminator: false,
|
|
286
|
+
timeOut: 5,
|
|
287
|
+
defaultOn: true
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: 'role',
|
|
291
|
+
what: 'native-replacing explicit roles',
|
|
292
|
+
contaminator: false,
|
|
293
|
+
timeOut: 5,
|
|
294
|
+
defaultOn: true
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: 'secHeading',
|
|
298
|
+
what: 'headings that violate the logical level order in their sectioning containers',
|
|
299
|
+
contaminator: false,
|
|
300
|
+
timeOut: 5,
|
|
301
|
+
defaultOn: true
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: 'styleDiff',
|
|
305
|
+
what: 'style inconsistencies',
|
|
306
|
+
contaminator: false,
|
|
307
|
+
timeOut: 5,
|
|
308
|
+
defaultOn: true
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: 'targetSmall',
|
|
312
|
+
what: 'buttons, inputs, and non-inline links smaller than 44 pixels wide and high',
|
|
313
|
+
contaminator: false,
|
|
314
|
+
timeOut: 5,
|
|
315
|
+
defaultOn: true
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: 'targetTiny',
|
|
319
|
+
what: 'buttons, inputs, and non-inline links smaller than 24 pixels wide and high',
|
|
320
|
+
contaminator: false,
|
|
321
|
+
timeOut: 5,
|
|
322
|
+
defaultOn: true
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'textSem',
|
|
326
|
+
what: 'semantically vague elements i, b, and/or small',
|
|
327
|
+
contaminator: false,
|
|
328
|
+
timeOut: 10,
|
|
329
|
+
defaultOn: true
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: 'title',
|
|
333
|
+
what: 'page title',
|
|
334
|
+
contaminator: false,
|
|
335
|
+
timeOut: 5,
|
|
336
|
+
defaultOn: false
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: 'titledEl',
|
|
340
|
+
what: 'title attributes on inappropriate elements',
|
|
341
|
+
contaminator: false,
|
|
342
|
+
timeOut: 5,
|
|
343
|
+
defaultOn: true
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: 'zIndex',
|
|
347
|
+
what: 'non-default Z indexes',
|
|
348
|
+
contaminator: false,
|
|
349
|
+
timeOut: 5,
|
|
350
|
+
defaultOn: true
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: 'shoot',
|
|
354
|
+
what: 'page screenshot',
|
|
355
|
+
contaminator: false,
|
|
356
|
+
timeOut: 5,
|
|
357
|
+
defaultOn: true
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: 'motion',
|
|
361
|
+
what: 'motion without user request, measured across tests',
|
|
362
|
+
contaminator: false,
|
|
363
|
+
timeOut: 5,
|
|
364
|
+
defaultOn: true
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: 'buttonMenu',
|
|
368
|
+
what: 'nonstandard keyboard navigation between items of button-controlled menus',
|
|
369
|
+
contaminator: true,
|
|
370
|
+
timeOut: 15,
|
|
371
|
+
defaultOn: true
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: 'elements',
|
|
375
|
+
what: 'data on specified elements',
|
|
376
|
+
contaminator: true,
|
|
377
|
+
timeOut: 10,
|
|
378
|
+
defaultOn: false
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: 'focAll',
|
|
382
|
+
what: 'discrepancies between focusable and Tab-focused elements',
|
|
383
|
+
contaminator: true,
|
|
384
|
+
timeOut: 10,
|
|
385
|
+
defaultOn: true
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: 'focInd',
|
|
389
|
+
what: 'missing and nonstandard focus indicators',
|
|
390
|
+
contaminator: true,
|
|
391
|
+
timeOut: 10,
|
|
392
|
+
defaultOn: true
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
id: 'focOp',
|
|
396
|
+
what: 'Tab-focusable elements that are not operable',
|
|
397
|
+
contaminator: true,
|
|
398
|
+
timeOut: 5,
|
|
399
|
+
defaultOn: true
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
id: 'focVis',
|
|
403
|
+
what: 'links that are not entirely visible when focused',
|
|
404
|
+
contaminator: true,
|
|
405
|
+
timeOut: 10,
|
|
406
|
+
defaultOn: true
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: 'hover',
|
|
410
|
+
what: 'hover-caused content changes',
|
|
411
|
+
contaminator: true,
|
|
412
|
+
timeOut: 10,
|
|
413
|
+
defaultOn: true
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: 'hovInd',
|
|
417
|
+
what: 'hover indication nonstandard',
|
|
418
|
+
contaminator: true,
|
|
419
|
+
timeOut: 10,
|
|
420
|
+
defaultOn: true
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: 'motionSolo',
|
|
424
|
+
what: 'motion without user request, measured within this test',
|
|
425
|
+
contaminator: true,
|
|
426
|
+
timeOut: 15,
|
|
427
|
+
defaultOn: false
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: 'opFoc',
|
|
431
|
+
what: 'operable elements that are not Tab-focusable',
|
|
432
|
+
contaminator: true,
|
|
433
|
+
timeOut: 10,
|
|
434
|
+
defaultOn: true
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: 'tabNav',
|
|
438
|
+
what: 'nonstandard keyboard navigation between elements with the tab role',
|
|
439
|
+
contaminator: true,
|
|
440
|
+
timeOut: 10,
|
|
441
|
+
defaultOn: true
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
id: 'textNodes',
|
|
445
|
+
what: 'data on specified text nodes',
|
|
446
|
+
contaminator: true,
|
|
447
|
+
timeOut: 10,
|
|
448
|
+
defaultOn: false
|
|
449
|
+
}
|
|
116
450
|
];
|
|
117
|
-
// Extraordinary time limits on rules.
|
|
118
|
-
const slowTestLimits = {
|
|
119
|
-
allCaps: 10,
|
|
120
|
-
buttonMenu: 15,
|
|
121
|
-
distortion: 10,
|
|
122
|
-
docType: 10,
|
|
123
|
-
focAll: 10,
|
|
124
|
-
focInd: 10,
|
|
125
|
-
focVis: 10,
|
|
126
|
-
hover: 10,
|
|
127
|
-
hovInd: 10,
|
|
128
|
-
labClash: 10,
|
|
129
|
-
lineHeight: 10,
|
|
130
|
-
linkUl: 10,
|
|
131
|
-
motion: 15,
|
|
132
|
-
opFoc: 10,
|
|
133
|
-
tabNav: 10,
|
|
134
|
-
textSem: 10
|
|
135
|
-
};
|
|
136
451
|
const timeoutMultiplier = Number.parseFloat(process.env.TIMEOUT_MULTIPLIER) || 1;
|
|
137
452
|
|
|
138
453
|
// ERROR HANDLER
|
|
@@ -156,7 +471,7 @@ const jsonTest = async (ruleID, ruleArgs) => {
|
|
|
156
471
|
ruleObj.complaints.instance,
|
|
157
472
|
ruleObj.complaints.summary
|
|
158
473
|
];
|
|
159
|
-
return await
|
|
474
|
+
return await getRuleResult(
|
|
160
475
|
withItems, all, ruleObj.ruleID, whats, ruleObj.ordinalSeverity, ruleObj.summaryTagName
|
|
161
476
|
);
|
|
162
477
|
};
|
|
@@ -170,13 +485,16 @@ const wait = ms => {
|
|
|
170
485
|
};
|
|
171
486
|
// Conducts and reports Testaro tests.
|
|
172
487
|
exports.reporter = async (page, report, actIndex) => {
|
|
173
|
-
// Report page crashes.
|
|
174
488
|
const url = await page.url();
|
|
175
489
|
const act = report.acts[actIndex];
|
|
176
490
|
const {args, stopOnFail, withItems} = act;
|
|
491
|
+
const launchOptions = act.launch;
|
|
492
|
+
const browserID = launchOptions ? launchOptions.browserID || report.browserID : report.browserID;
|
|
177
493
|
const argRules = args ? Object.keys(args) : null;
|
|
178
|
-
|
|
179
|
-
|
|
494
|
+
// Get the specification of rules to be tested for.
|
|
495
|
+
const ruleSpec = act.rules
|
|
496
|
+
|| ['y', ... allRules.filter(rule => rule.defaultOn).map(rule => rule.id)];
|
|
497
|
+
// Initialize the act data and result.
|
|
180
498
|
const data = {
|
|
181
499
|
prevented: false,
|
|
182
500
|
error: '',
|
|
@@ -185,222 +503,216 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
185
503
|
rulesInvalid: [],
|
|
186
504
|
ruleTestTimes: {}
|
|
187
505
|
};
|
|
188
|
-
const result = {};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
&&
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
// Replace the browser and the page and navigate to the target.
|
|
227
|
-
await launch(
|
|
228
|
-
report,
|
|
229
|
-
process.env.DEBUG === 'true',
|
|
230
|
-
Number.parseInt(process.env.WAITS) || 0,
|
|
231
|
-
report.browserID,
|
|
232
|
-
url
|
|
233
|
-
);
|
|
234
|
-
page = require('../run').page;
|
|
235
|
-
}
|
|
236
|
-
// If it is a contaminator, ensure that future tests use new browsers.
|
|
237
|
-
if (isContaminator) {
|
|
238
|
-
contaminatorsStarted = true;
|
|
239
|
-
}
|
|
240
|
-
// Report crashes and disconnections during this test.
|
|
241
|
-
let crashHandler;
|
|
242
|
-
let disconnectHandler;
|
|
243
|
-
const {browser} = require('../run');
|
|
244
|
-
if (page && ! page.isClosed()) {
|
|
245
|
-
crashHandler = () => {
|
|
246
|
-
console.log(`ERROR: Page crashed during ${rule} test`);
|
|
247
|
-
};
|
|
248
|
-
page.on('crash', crashHandler);
|
|
506
|
+
const result = exports.result = {};
|
|
507
|
+
const allRuleIDs = allRules.map(rule => rule.id);
|
|
508
|
+
// If the rule specification is invalid:
|
|
509
|
+
if (! (
|
|
510
|
+
ruleSpec.length > 1
|
|
511
|
+
&& ['y', 'n'].includes(ruleSpec[0])
|
|
512
|
+
&& ruleSpec.slice(1).every(ruleID => allRuleIDs.includes(ruleID))
|
|
513
|
+
)) {
|
|
514
|
+
// Report this and stop testing.
|
|
515
|
+
data.prevented = true;
|
|
516
|
+
data.error = 'ERROR: Testaro rule specification invalid';
|
|
517
|
+
console.log('ERROR: Testaro rule specification invalid');
|
|
518
|
+
return {
|
|
519
|
+
data,
|
|
520
|
+
result
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
// Wait 1 second to prevent out-of-order logging with granular reporting.
|
|
524
|
+
await wait(1000);
|
|
525
|
+
// Get the rules to be tested for and their execution order.
|
|
526
|
+
const jobRuleIDs = ruleSpec[0] === 'y'
|
|
527
|
+
? ruleSpec.slice(1)
|
|
528
|
+
: allRules.filter(rule => rule.defaultOn && ! allRuleIDs.includes(rule.id));
|
|
529
|
+
const jobRules = allRules.filter(rule => jobRuleIDs.includes(rule.id));
|
|
530
|
+
const testTimes = [];
|
|
531
|
+
let contaminatorsStarted = false;
|
|
532
|
+
// For each rule to be tested for:
|
|
533
|
+
for (const rule of jobRules) {
|
|
534
|
+
const ruleID = rule.id;
|
|
535
|
+
console.log(`Starting rule ${ruleID}`);
|
|
536
|
+
const pageClosed = page ? page.isClosed() : true;
|
|
537
|
+
const isContaminator = rule.contaminator;
|
|
538
|
+
// If it is a contaminator other than the first one or the page has closed:
|
|
539
|
+
if (contaminatorsStarted || pageClosed) {
|
|
540
|
+
// If the page has closed:
|
|
541
|
+
if (pageClosed) {
|
|
542
|
+
// Report this.
|
|
543
|
+
console.log(`WARNING: Relaunching browser for test ${rule} after abnormal closure`);
|
|
249
544
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
545
|
+
// Replace the browser and the page and navigate to the target.
|
|
546
|
+
await launch(
|
|
547
|
+
report,
|
|
548
|
+
process.env.DEBUG === 'true',
|
|
549
|
+
Number.parseInt(process.env.WAITS) || 0,
|
|
550
|
+
browserID,
|
|
551
|
+
url
|
|
552
|
+
);
|
|
553
|
+
page = require('../run').page;
|
|
554
|
+
}
|
|
555
|
+
// If the rule is a contaminator, ensure that future tests use new browsers.
|
|
556
|
+
if (isContaminator) {
|
|
557
|
+
contaminatorsStarted = true;
|
|
558
|
+
}
|
|
559
|
+
// Report crashes and disconnections during this test.
|
|
560
|
+
let crashHandler;
|
|
561
|
+
let disconnectHandler;
|
|
562
|
+
const {browser} = require('../run');
|
|
563
|
+
if (page && ! page.isClosed()) {
|
|
564
|
+
crashHandler = () => {
|
|
565
|
+
console.log(`ERROR: Page crashed during ${rule} test`);
|
|
566
|
+
};
|
|
567
|
+
page.on('crash', crashHandler);
|
|
568
|
+
}
|
|
569
|
+
if (browser) {
|
|
570
|
+
disconnectHandler = () => {
|
|
571
|
+
console.log(`ERROR: Browser disconnected during ${rule} test`);
|
|
572
|
+
};
|
|
573
|
+
browser.on('disconnected', disconnectHandler);
|
|
574
|
+
}
|
|
575
|
+
// Initialize an argument array.
|
|
576
|
+
const ruleArgs = [page, withItems];
|
|
577
|
+
const ruleFileNames = await fs.readdir(`${__dirname}/../testaro`);
|
|
578
|
+
const isJS = ruleFileNames.includes(`${ruleID}.js`);
|
|
579
|
+
const isJSON = ruleFileNames.includes(`${ruleID}.json`);
|
|
580
|
+
// If the rule is defined with JavaScript or JSON but not both:
|
|
581
|
+
if ((isJS || isJSON) && ! (isJS && isJSON)) {
|
|
582
|
+
// If with JavaScript and it has extra arguments:
|
|
583
|
+
if (isJS && argRules && argRules.includes(ruleID)) {
|
|
584
|
+
// Add them to the argument array.
|
|
585
|
+
ruleArgs.push(... args[ruleID]);
|
|
255
586
|
}
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
const what = evalRules[rule] || etcRules[rule];
|
|
273
|
-
result[rule].what = what;
|
|
274
|
-
const startTime = Date.now();
|
|
275
|
-
let timeout;
|
|
276
|
-
let testRetries = 2;
|
|
277
|
-
let testSuccess = false;
|
|
278
|
-
while (testRetries > 0 && ! testSuccess) {
|
|
279
|
-
try {
|
|
280
|
-
// Apply a time limit to the test.
|
|
281
|
-
const timeLimit = 1000 * timeoutMultiplier * (slowTestLimits[rule] ?? 5);
|
|
282
|
-
// If the time limit expires during the test:
|
|
283
|
-
const timer = new Promise(resolve => {
|
|
284
|
-
timeout = setTimeout(() => {
|
|
285
|
-
// Add data about the test, including its prevention, to the result.
|
|
286
|
-
const endTime = Date.now();
|
|
287
|
-
testTimes.push([rule, Math.round((endTime - startTime) / 1000)]);
|
|
288
|
-
data.rulePreventions.push(rule);
|
|
289
|
-
data.rulePreventionMessages[rule] = 'Timeout';
|
|
290
|
-
result[rule].totals = [0, 0, 0, 0];
|
|
291
|
-
result[rule].standardInstances = [];
|
|
292
|
-
console.log(`ERROR: Test of testaro rule ${rule} timed out`);
|
|
293
|
-
resolve({timedOut: true});
|
|
294
|
-
}, timeLimit);
|
|
295
|
-
});
|
|
296
|
-
// Perform the test, subject to the time limit.
|
|
297
|
-
const ruleReport = isJS
|
|
298
|
-
? require(`../testaro/${rule}`).reporter(... ruleArgs)
|
|
299
|
-
: jsonTest(rule, ruleArgs);
|
|
300
|
-
// Get the test result or a timeout result.
|
|
301
|
-
const ruleOrTimeoutReport = await Promise.race([timer, ruleReport]);
|
|
302
|
-
// If the test was completed:
|
|
303
|
-
if (! ruleOrTimeoutReport.timedOut) {
|
|
304
|
-
// Add data from the test to the result.
|
|
587
|
+
result[ruleID] ??= {};
|
|
588
|
+
const {what} = rule;
|
|
589
|
+
result[ruleID].what = what || '';
|
|
590
|
+
const startTime = Date.now();
|
|
591
|
+
let timeout;
|
|
592
|
+
let testRetries = 2;
|
|
593
|
+
let testSuccess = false;
|
|
594
|
+
while (testRetries > 0 && ! testSuccess) {
|
|
595
|
+
try {
|
|
596
|
+
// Apply a time limit to the test.
|
|
597
|
+
const timeLimit = 1000 * timeoutMultiplier * rule.timeOut;
|
|
598
|
+
// If the time limit expires during the test:
|
|
599
|
+
const timer = new Promise(resolve => {
|
|
600
|
+
timeout = setTimeout(() => {
|
|
601
|
+
// Add data about the test, including its prevention, to the result.
|
|
305
602
|
const endTime = Date.now();
|
|
306
603
|
testTimes.push([rule, Math.round((endTime - startTime) / 1000)]);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
result[
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
604
|
+
data.rulePreventions.push(rule);
|
|
605
|
+
data.rulePreventionMessages[ruleID] = 'Timeout';
|
|
606
|
+
result[ruleID].totals = [0, 0, 0, 0];
|
|
607
|
+
result[ruleID].standardInstances = [];
|
|
608
|
+
console.log(`ERROR: Test of testaro rule ${ruleID} timed out`);
|
|
609
|
+
resolve({timedOut: true});
|
|
610
|
+
}, timeLimit);
|
|
611
|
+
});
|
|
612
|
+
// Perform the test, subject to the time limit.
|
|
613
|
+
const ruleReport = isJS
|
|
614
|
+
? require(`../testaro/${ruleID}`).reporter(... ruleArgs)
|
|
615
|
+
: jsonTest(ruleID, ruleArgs);
|
|
616
|
+
// Get the test result or a timeout result.
|
|
617
|
+
const ruleOrTimeoutReport = await Promise.race([timer, ruleReport]);
|
|
618
|
+
// If the test was completed:
|
|
619
|
+
if (! ruleOrTimeoutReport.timedOut) {
|
|
620
|
+
// Add data from the test to the result.
|
|
621
|
+
const endTime = Date.now();
|
|
622
|
+
testTimes.push([ruleID, Math.round((endTime - startTime) / 1000)]);
|
|
623
|
+
Object.keys(ruleOrTimeoutReport).forEach(key => {
|
|
624
|
+
result[ruleID][key] = ruleOrTimeoutReport[key];
|
|
625
|
+
});
|
|
626
|
+
// If the result includes totals:
|
|
627
|
+
if (result[ruleID].totals) {
|
|
628
|
+
// Round them.
|
|
629
|
+
result[ruleID].totals = result[ruleID].totals.map(total => Math.round(total));
|
|
318
630
|
}
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
631
|
+
// Prevent a retry of the test.
|
|
632
|
+
testSuccess = true;
|
|
633
|
+
// If testing is to stop after a failure and the page failed the test:
|
|
634
|
+
if (stopOnFail && ruleOrTimeoutReport.totals.some(total => total)) {
|
|
635
|
+
// Stop testing.
|
|
322
636
|
break;
|
|
323
637
|
}
|
|
324
638
|
}
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
639
|
+
// Otherwise, i.e. if the test timed out:
|
|
640
|
+
else {
|
|
641
|
+
// Stop retrying the test.
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
// If an error is thrown by the test:
|
|
646
|
+
catch(error) {
|
|
647
|
+
const isPageClosed = ['closed', 'Protocol error', 'Target page'].some(phrase =>
|
|
648
|
+
error.message.includes(phrase)
|
|
649
|
+
);
|
|
650
|
+
// If the page has closed and there are retries left:
|
|
651
|
+
if (isPageClosed && testRetries) {
|
|
652
|
+
// Report this and decrement the allowed retry count.
|
|
653
|
+
console.log(
|
|
654
|
+
`WARNING: Retry ${3 - testRetries--} of test ${ruleID} starting after page closed`
|
|
329
655
|
);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
//
|
|
347
|
-
if (! page) {
|
|
348
|
-
// Report this.
|
|
349
|
-
console.log(`ERROR: Browser relaunch to retry test ${rule} failed`);
|
|
350
|
-
data.rulePreventions.push(rule);
|
|
351
|
-
data.rulePreventionMessages[rule] = 'Retry failure due to browser relaunch failure';
|
|
352
|
-
// Stop retrying the test.
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
// Update the rule arguments with the current page.
|
|
356
|
-
ruleArgs[0] = page;
|
|
357
|
-
}
|
|
358
|
-
// Otherwise, i.e. if the page is open or it is closed but no retries are left:
|
|
359
|
-
else {
|
|
360
|
-
// Treat the test as prevented.
|
|
361
|
-
data.rulePreventions.push(rule);
|
|
362
|
-
data.rulePreventionMessages[rule] = error.message;
|
|
363
|
-
console.log(`ERROR: Test of testaro rule ${rule} prevented (${error.message})`);
|
|
364
|
-
// Do not retry the test even if retries are left.
|
|
656
|
+
await wait(2000);
|
|
657
|
+
// Replace the browser and the page and navigate to the target.
|
|
658
|
+
await launch(
|
|
659
|
+
report,
|
|
660
|
+
process.env.DEBUG === 'true',
|
|
661
|
+
Number.parseInt(process.env.WAITS) || 0,
|
|
662
|
+
report.browserID,
|
|
663
|
+
url
|
|
664
|
+
);
|
|
665
|
+
page = require('../run').page;
|
|
666
|
+
// If the page replacement failed:
|
|
667
|
+
if (! page) {
|
|
668
|
+
// Report this.
|
|
669
|
+
console.log(`ERROR: Browser relaunch to retry test ${ruleID} failed`);
|
|
670
|
+
data.rulePreventions.push(ruleID);
|
|
671
|
+
data.rulePreventionMessages[ruleID] = 'Retry failure due to browser relaunch failure';
|
|
672
|
+
// Stop retrying the test.
|
|
365
673
|
break;
|
|
366
674
|
}
|
|
675
|
+
// Update the rule arguments with the current page.
|
|
676
|
+
ruleArgs[0] = page;
|
|
367
677
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
678
|
+
// Otherwise, i.e. if the page is open or it is closed but no retries are left:
|
|
679
|
+
else {
|
|
680
|
+
// Treat the test as prevented.
|
|
681
|
+
data.rulePreventions.push(ruleID);
|
|
682
|
+
data.rulePreventionMessages[ruleID] = error.message;
|
|
683
|
+
console.log(`ERROR: Test of testaro rule ${ruleID} prevented (${error.message})`);
|
|
684
|
+
// Do not retry the test even if retries are left.
|
|
685
|
+
break;
|
|
371
686
|
}
|
|
372
687
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
if (browser && disconnectHandler) {
|
|
378
|
-
browser.off('disconnected', disconnectHandler);
|
|
688
|
+
finally {
|
|
689
|
+
// Clear the timeout.
|
|
690
|
+
clearTimeout(timeout);
|
|
379
691
|
}
|
|
380
692
|
}
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (page && ! page.isClosed()) {
|
|
388
|
-
page.off('crash', crashHandler);
|
|
389
|
-
}
|
|
693
|
+
// Clear the error listeners.
|
|
694
|
+
if (page && ! page.isClosed() && crashHandler) {
|
|
695
|
+
page.off('crash', crashHandler);
|
|
696
|
+
}
|
|
697
|
+
if (browser && disconnectHandler) {
|
|
698
|
+
browser.off('disconnected', disconnectHandler);
|
|
390
699
|
}
|
|
391
700
|
}
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
701
|
+
// Otherwise, i.e. if the rule is undefined or doubly defined:
|
|
702
|
+
else {
|
|
703
|
+
// Report this.
|
|
704
|
+
data.rulesInvalid.push(rule);
|
|
705
|
+
console.log(`ERROR: Rule ${rule.id} not validly defined`);
|
|
706
|
+
// Clear the crash listener.
|
|
707
|
+
if (page && ! page.isClosed()) {
|
|
708
|
+
page.off('crash', crashHandler);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
// Record the test times in descending order.
|
|
713
|
+
testTimes.sort((a, b) => b[1] - a[1]).forEach(pair => {
|
|
714
|
+
data.ruleTestTimes[pair[0]] = pair[1];
|
|
715
|
+
});
|
|
404
716
|
return {
|
|
405
717
|
data,
|
|
406
718
|
result
|