ui5-test-runner 1.1.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +32 -188
  2. package/index.js +47 -16
  3. package/package.json +28 -10
  4. package/src/add-test-pages.js +35 -0
  5. package/src/add-test-pages.spec.js +95 -0
  6. package/src/browser.spec.js +724 -0
  7. package/src/browsers.js +220 -59
  8. package/src/capabilities/index.js +194 -0
  9. package/src/capabilities/tests/basic/iframe.html +8 -0
  10. package/src/capabilities/tests/basic/index.html +12 -0
  11. package/src/capabilities/tests/basic/index.js +20 -0
  12. package/src/capabilities/tests/basic/ui5.html +24 -0
  13. package/src/capabilities/tests/dynamic-include/index.js +21 -0
  14. package/src/capabilities/tests/dynamic-include/mix.html +11 -0
  15. package/src/capabilities/tests/dynamic-include/one.html +11 -0
  16. package/src/capabilities/tests/dynamic-include/post.js +3 -0
  17. package/src/capabilities/tests/dynamic-include/test.js +1 -0
  18. package/src/capabilities/tests/dynamic-include/two.html +11 -0
  19. package/src/capabilities/tests/index.js +16 -0
  20. package/src/capabilities/tests/local-storage/index.html +16 -0
  21. package/src/capabilities/tests/local-storage/index.js +21 -0
  22. package/src/capabilities/tests/screenshot/index.html +13 -0
  23. package/src/capabilities/tests/screenshot/index.js +18 -0
  24. package/src/capabilities/tests/scripts/index.js +50 -0
  25. package/src/capabilities/tests/scripts/qunit.html +22 -0
  26. package/src/capabilities/tests/scripts/testsuite.html +10 -0
  27. package/src/capabilities/tests/scripts/testsuite.js +8 -0
  28. package/src/capabilities/tests/timeout/index.html +21 -0
  29. package/src/capabilities/tests/timeout/index.js +19 -0
  30. package/src/capabilities/tests/traces/index.html +18 -0
  31. package/src/capabilities/tests/traces/index.js +81 -0
  32. package/src/cors.js +1 -1
  33. package/src/cors.spec.js +41 -0
  34. package/src/coverage.js +30 -18
  35. package/src/coverage.spec.js +79 -0
  36. package/src/csv-reader.js +36 -0
  37. package/src/csv-reader.spec.js +42 -0
  38. package/src/csv-writer.js +52 -0
  39. package/src/csv-writer.spec.js +77 -0
  40. package/src/defaults/browser.js +144 -0
  41. package/src/defaults/jsdom/compatibility.js +95 -0
  42. package/src/defaults/jsdom/debug.js +23 -0
  43. package/src/defaults/jsdom/resource-loader.js +43 -0
  44. package/src/defaults/jsdom/sap.ui.test.matchers.visible.js +39 -0
  45. package/src/defaults/jsdom.js +64 -0
  46. package/src/defaults/junit-xml-report.js +64 -0
  47. package/src/defaults/puppeteer.js +111 -0
  48. package/src/defaults/report/common.js +38 -0
  49. package/src/defaults/report/default.html +84 -0
  50. package/src/defaults/report/main.js +44 -0
  51. package/src/defaults/report/progress.js +49 -0
  52. package/src/defaults/report/styles.css +66 -0
  53. package/src/defaults/report.js +69 -0
  54. package/src/defaults/selenium-webdriver/chrome.js +38 -0
  55. package/src/defaults/selenium-webdriver/edge.js +25 -0
  56. package/src/defaults/selenium-webdriver/firefox.js +31 -0
  57. package/src/defaults/selenium-webdriver.js +138 -0
  58. package/src/endpoints.js +70 -124
  59. package/src/error.js +52 -0
  60. package/src/error.spec.js +17 -0
  61. package/src/get-job-progress.js +69 -0
  62. package/src/get-job-progress.spec.js +175 -0
  63. package/src/inject/post.js +96 -0
  64. package/src/inject/post.spec.js +147 -0
  65. package/src/inject/qunit-hooks.js +6 -21
  66. package/src/inject/qunit-intercept.js +30 -0
  67. package/src/inject/qunit-redirect.js +15 -7
  68. package/src/job-mode.js +45 -0
  69. package/src/job.js +254 -108
  70. package/src/job.spec.js +413 -0
  71. package/src/npm.js +73 -0
  72. package/src/npm.spec.js +98 -0
  73. package/src/options.js +73 -0
  74. package/src/options.spec.js +125 -0
  75. package/src/output.js +450 -131
  76. package/src/qunit-hooks.js +116 -0
  77. package/src/qunit-hooks.spec.js +687 -0
  78. package/src/report.js +47 -0
  79. package/src/reserve.js +3 -4
  80. package/src/simulate.spec.js +466 -0
  81. package/src/symbols.js +8 -0
  82. package/src/tests.js +127 -84
  83. package/src/timeout.spec.js +39 -0
  84. package/src/tools.js +111 -4
  85. package/src/tools.spec.js +90 -0
  86. package/src/ui5.js +3 -3
  87. package/src/unhandled.js +6 -6
  88. package/src/unhandled.spec.js +63 -0
  89. package/defaults/chromium.js +0 -62
  90. package/src/progress.html +0 -71
  91. package/src/proxies.js +0 -8
  92. package/src/report.html +0 -202
  93. /package/{defaults → src/defaults}/nyc.json +0 -0
@@ -0,0 +1,724 @@
1
+ const { join } = require('path')
2
+ const { mock } = require('child_process')
3
+ const jobFactory = require('./job')
4
+ const { probe, start, stop, screenshot } = require('./browsers')
5
+ const { $browsers } = require('./symbols')
6
+ const { readFile, writeFile, stat } = require('fs/promises')
7
+ const { createDir, recreateDir, allocPromise, filename } = require('./tools')
8
+ const { UTRError } = require('./error')
9
+
10
+ const cwd = '/test/project'
11
+ const tmp = join(__dirname, '../tmp')
12
+
13
+ describe('src/browser', () => {
14
+ let job
15
+ let remainingChildProcess
16
+
17
+ beforeEach(async () => {
18
+ const reportDir = join(tmp, 'browser')
19
+ await recreateDir(reportDir)
20
+ job = jobFactory.fromObject(cwd, {
21
+ url: 'http://localhost:8080',
22
+ reportDir,
23
+ browserCloseTimeout: 100,
24
+ '--': ['argument1', 'argument2']
25
+ })
26
+ })
27
+
28
+ afterEach(async () => {
29
+ job.browserRetry = 0 // avoid retry in case of unexpected close
30
+ if (remainingChildProcess) {
31
+ await stop(job, '/test.html')
32
+ remainingChildProcess.close() // stop command not implemented
33
+ await remainingChildProcess.closed
34
+ }
35
+ })
36
+
37
+ describe('probe', () => {
38
+ it('starts the command with a specific config file', async () => {
39
+ let config
40
+ mock({
41
+ api: 'fork',
42
+ scriptPath: job.browser,
43
+ exec: async childProcess => {
44
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
45
+ await writeFile(config.capabilities, '{}')
46
+ }
47
+ })
48
+ await probe(job)
49
+ expect(config.url).toStrictEqual('about:blank')
50
+ expect(job.browserCapabilities.parallel).toStrictEqual(true)
51
+ })
52
+
53
+ it('fails if the browser does not generate capabilities', async () => {
54
+ mock({
55
+ api: 'fork',
56
+ scriptPath: job.browser,
57
+ exec: () => {}
58
+ })
59
+ await expect(probe(job)).rejects.toMatchObject({
60
+ name: 'UTRError:MISSING_OR_INVALID_BROWSER_CAPABILITIES'
61
+ })
62
+ })
63
+
64
+ it('reads and merge browser capabilities', async () => {
65
+ mock({
66
+ api: 'fork',
67
+ scriptPath: job.browser,
68
+ exec: async childProcess => {
69
+ const config = JSON.parse((await readFile(childProcess.args[0])).toString())
70
+ await writeFile(config.capabilities, JSON.stringify({
71
+ screenshot: false,
72
+ traces: ['console']
73
+ }))
74
+ }
75
+ })
76
+ await probe(job)
77
+ expect(job.browserCapabilities.traces).toStrictEqual(['console'])
78
+ expect(job.browserCapabilities.parallel).toStrictEqual(true)
79
+ })
80
+
81
+ it('probes only once', async () => {
82
+ let count = 0
83
+ mock({
84
+ api: 'fork',
85
+ scriptPath: job.browser,
86
+ exec: async childProcess => {
87
+ ++count
88
+ const config = JSON.parse((await readFile(childProcess.args[0])).toString())
89
+ await writeFile(config.capabilities, '{}')
90
+ }
91
+ })
92
+ await probe(job)
93
+ expect(count).toStrictEqual(1)
94
+ await probe(job)
95
+ expect(count).toStrictEqual(1)
96
+ })
97
+
98
+ it('handles failure during probe', async () => {
99
+ mock({
100
+ api: 'fork',
101
+ scriptPath: job.browser,
102
+ exec: async childProcess => {
103
+ childProcess.close(-1)
104
+ }
105
+ })
106
+ await expect(probe(job)).rejects.toThrowError(UTRError.BROWSER_PROBE_FAILED('-1'))
107
+ })
108
+
109
+ describe('dependent modules', () => {
110
+ const npmGlobal = join(tmp, 'npm/global')
111
+
112
+ it('handles dependent modules', async () => {
113
+ mock({
114
+ api: 'exec',
115
+ scriptPath: 'npm',
116
+ args: ['install', 'dependentModule', '-g'],
117
+ exec: async childProcess => {
118
+ await createDir(join(npmGlobal, 'dependentModule'))
119
+ await writeFile(join(npmGlobal, 'dependentModule', 'package.json'), `{
120
+ "version": "1.0.0"
121
+ }`)
122
+ childProcess.stdout.write('OK')
123
+ }
124
+ })
125
+ mock({
126
+ api: 'exec',
127
+ scriptPath: 'npm',
128
+ args: ['view', 'dependentModule', 'version'],
129
+ exec: async childProcess => {
130
+ childProcess.stdout.write('1.0.0\n')
131
+ }
132
+ })
133
+ mock({
134
+ api: 'fork',
135
+ scriptPath: job.browser,
136
+ exec: async childProcess => {
137
+ const config = JSON.parse((await readFile(childProcess.args[0])).toString())
138
+ await writeFile(config.capabilities, JSON.stringify({
139
+ modules: ['reserve', 'dependentModule']
140
+ }))
141
+ }
142
+ })
143
+ await probe(job)
144
+ expect(job.browserCapabilities.modules).toStrictEqual(['reserve', 'dependentModule'])
145
+ expect(job.browserModules.reserve).toStrictEqual(join(__dirname, '../node_modules/reserve'))
146
+ expect(job.browserModules.dependentModule).toStrictEqual(join(npmGlobal, 'dependentModule'))
147
+ })
148
+
149
+ it('fails if a dependent module cannot be installed', async () => {
150
+ mock({
151
+ api: 'exec',
152
+ scriptPath: 'npm',
153
+ args: ['install', 'dependentModuleNotInstallable', '-g'],
154
+ exec: childProcess => {
155
+ childProcess.stdout.write('KO')
156
+ childProcess.close(-1)
157
+ },
158
+ close: false
159
+ })
160
+ mock({
161
+ api: 'fork',
162
+ scriptPath: job.browser,
163
+ exec: async childProcess => {
164
+ const config = JSON.parse((await readFile(childProcess.args[0])).toString())
165
+ await writeFile(config.capabilities, JSON.stringify({
166
+ modules: ['reserve', 'dependentModuleNotInstallable']
167
+ }))
168
+ }
169
+ })
170
+ await expect(probe(job)).rejects.toMatchObject({
171
+ name: 'UTRError:NPM_FAILED'
172
+ })
173
+ })
174
+ })
175
+
176
+ describe('probe with modules', () => {
177
+ it('enables a second call if dependent modules were expected', async () => {
178
+ let config
179
+ let count = 0
180
+ mock({
181
+ api: 'fork',
182
+ scriptPath: job.browser,
183
+ exec: async childProcess => {
184
+ ++count
185
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
186
+ if (!config.modules) {
187
+ await writeFile(config.capabilities, JSON.stringify({
188
+ modules: ['reserve'],
189
+ 'probe-with-modules': true,
190
+ screenshot: true
191
+ }))
192
+ } else {
193
+ await writeFile(config.capabilities, JSON.stringify({
194
+ modules: ['reserve'],
195
+ screenshot: false
196
+ }))
197
+ }
198
+ }
199
+ })
200
+ await probe(job)
201
+ expect(count).toStrictEqual(2)
202
+ expect(config.modules.reserve).not.toBeUndefined()
203
+ expect(job.browserCapabilities.screenshot).toStrictEqual(false)
204
+ })
205
+
206
+ it('handles failure on second probe', async () => {
207
+ mock({
208
+ api: 'fork',
209
+ scriptPath: job.browser,
210
+ exec: async childProcess => {
211
+ const config = JSON.parse((await readFile(childProcess.args[0])).toString())
212
+ if (!config.modules) {
213
+ await writeFile(config.capabilities, JSON.stringify({
214
+ modules: ['reserve'],
215
+ 'probe-with-modules': true,
216
+ screenshot: true
217
+ }))
218
+ } else {
219
+ childProcess.close(-1)
220
+ }
221
+ }
222
+ })
223
+ await expect(probe(job)).rejects.toThrowError(UTRError.BROWSER_PROBE_FAILED('-1'))
224
+ })
225
+ })
226
+ })
227
+
228
+ describe('start and stop', () => {
229
+ const capabilities = {
230
+ whatever: 'is passed'
231
+ }
232
+
233
+ beforeEach(() => {
234
+ job.browserCapabilities = capabilities
235
+ })
236
+
237
+ it('returns a promise resolved on stop (even if the child process remains)', async () => {
238
+ mock({
239
+ api: 'fork',
240
+ scriptPath: job.browser,
241
+ exec: async childProcess => {
242
+ remainingChildProcess = childProcess
243
+ setTimeout(() => stop(job, '/test.html'), 0)
244
+ },
245
+ close: false
246
+ })
247
+ await start(job, '/test.html')
248
+ })
249
+
250
+ it('passes the provided capabilities', async () => {
251
+ let config
252
+ mock({
253
+ api: 'fork',
254
+ scriptPath: job.browser,
255
+ exec: async childProcess => {
256
+ remainingChildProcess = childProcess
257
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
258
+ setTimeout(() => stop(job, '/test.html'), 0)
259
+ },
260
+ close: false
261
+ })
262
+ await start(job, '/test.html')
263
+ expect(config.capabilities).toStrictEqual(capabilities)
264
+ })
265
+
266
+ it('passes URL to open', async () => {
267
+ let config
268
+ mock({
269
+ api: 'fork',
270
+ scriptPath: job.browser,
271
+ exec: async childProcess => {
272
+ remainingChildProcess = childProcess
273
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
274
+ setTimeout(() => stop(job, '/test.html'), 0)
275
+ },
276
+ close: false
277
+ })
278
+ await start(job, '/test.html')
279
+ expect(config.url).toStrictEqual('/test.html')
280
+ })
281
+
282
+ it('passes browser arguments', async () => {
283
+ let config
284
+ mock({
285
+ api: 'fork',
286
+ scriptPath: job.browser,
287
+ exec: async childProcess => {
288
+ remainingChildProcess = childProcess
289
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
290
+ setTimeout(() => stop(job, '/test.html'), 0)
291
+ },
292
+ close: false
293
+ })
294
+ await start(job, '/test.html')
295
+ expect(config.args).toEqual(['argument1', 'argument2'])
296
+ })
297
+
298
+ it('captures outputs', async () => {
299
+ mock({
300
+ api: 'fork',
301
+ scriptPath: job.browser,
302
+ exec: async childProcess => {
303
+ remainingChildProcess = childProcess
304
+ await childProcess.stdout.write('stdout')
305
+ await childProcess.stderr.write('stderr')
306
+ setTimeout(() => stop(job, '/test.html'), 0)
307
+ childProcess.on('message.received', message => {
308
+ if (message.command === 'stop') {
309
+ childProcess.close()
310
+ }
311
+ })
312
+ },
313
+ close: false
314
+ })
315
+ await start(job, '/test.html')
316
+ const stdout = (await readFile(remainingChildProcess.stdoutFilename)).toString()
317
+ expect(stdout).toStrictEqual('stdout')
318
+ const stderr = (await readFile(remainingChildProcess.stderrFilename)).toString()
319
+ expect(stderr).toStrictEqual('stderr')
320
+ })
321
+
322
+ it('stops automatically after a timeout', async () => {
323
+ const { promise: waitingForStop, resolve: stopReceived } = allocPromise()
324
+ mock({
325
+ api: 'fork',
326
+ scriptPath: job.browser,
327
+ exec: async childProcess => {
328
+ childProcess.on('message.received', message => {
329
+ if (message.command === 'stop') {
330
+ childProcess.close()
331
+ stopReceived()
332
+ }
333
+ })
334
+ },
335
+ close: false
336
+ })
337
+ job.pageTimeout = 100
338
+ await Promise.all([
339
+ start(job, '/test.html'),
340
+ waitingForStop
341
+ ])
342
+ })
343
+
344
+ it('retries on abnormal termination', async () => {
345
+ let config
346
+ mock({
347
+ api: 'fork',
348
+ scriptPath: job.browser,
349
+ exec: async childProcess => {
350
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
351
+ if (config.retry === 0) {
352
+ childProcess.close(-1)
353
+ } else {
354
+ remainingChildProcess = childProcess
355
+ setTimeout(() => stop(job, '/test.html'), 0)
356
+ }
357
+ },
358
+ close: false
359
+ })
360
+ await start(job, '/test.html')
361
+ expect(config.retry).toStrictEqual(1)
362
+ })
363
+
364
+ it('fails after all retries', async () => {
365
+ let config
366
+ mock({
367
+ api: 'fork',
368
+ scriptPath: job.browser,
369
+ exec: async childProcess => {
370
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
371
+ childProcess.close(-1)
372
+ },
373
+ close: false
374
+ })
375
+ await expect(start(job, '/test.html')).rejects.toMatchObject({
376
+ name: 'UTRError:BROWSER_FAILED'
377
+ })
378
+ expect(config.retry).toStrictEqual(1)
379
+ })
380
+
381
+ const parallels = [2, 3, 4, 6]
382
+ parallels.forEach(parallel =>
383
+ it(`can run more than one page simultaneously (${parallel})`, async () => {
384
+ let ready
385
+ let setReady
386
+ mock({
387
+ api: 'fork',
388
+ scriptPath: job.browser,
389
+ exec: async childProcess => {
390
+ childProcess.on('message.received', async message => {
391
+ if (message.command === 'stop') {
392
+ childProcess.close()
393
+ }
394
+ })
395
+ setReady()
396
+ },
397
+ close: false
398
+ })
399
+ const startPromises = []
400
+ for (let step = 0; step < parallel; ++step) {
401
+ const allocatedPromise = allocPromise()
402
+ ready = allocatedPromise.promise
403
+ setReady = allocatedPromise.resolve
404
+ const promise = start(job, `/test${step}.html`)
405
+ await ready
406
+ startPromises.push(promise)
407
+ }
408
+ for (let step = 0; step < parallel; ++step) {
409
+ const promise = startPromises[step]
410
+ await stop(job, `/test${step}.html`)
411
+ await promise
412
+ }
413
+ })
414
+ )
415
+
416
+ it('ignores unknown pages', async () => {
417
+ job[$browsers] = {}
418
+ await stop(job, '/unknown.html')
419
+ })
420
+
421
+ it('ignores unknown messages', async () => {
422
+ const { promise: waitingForReady, resolve: ready } = allocPromise()
423
+ mock({
424
+ api: 'fork',
425
+ scriptPath: job.browser,
426
+ exec: async childProcess => {
427
+ childProcess.emit('message', {
428
+ command: 'unknown'
429
+ })
430
+ childProcess.on('message.received', async message => {
431
+ if (message.command === 'stop') {
432
+ childProcess.close()
433
+ }
434
+ })
435
+ ready()
436
+ },
437
+ close: false
438
+ })
439
+ const started = start(job, '/test.html')
440
+ await waitingForReady
441
+ await stop(job, '/test.html')
442
+ await started
443
+ })
444
+ })
445
+
446
+ describe('script injection', () => {
447
+ beforeEach(() => {
448
+ job.browserCapabilities = {
449
+ scripts: true
450
+ }
451
+ })
452
+
453
+ it('does not use any script by default', async () => {
454
+ let config
455
+ mock({
456
+ api: 'fork',
457
+ scriptPath: job.browser,
458
+ exec: async childProcess => {
459
+ remainingChildProcess = childProcess
460
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
461
+ setTimeout(() => stop(job, '/test.html'), 0)
462
+ },
463
+ close: false
464
+ })
465
+ await start(job, '/test.html')
466
+ expect(config.scripts).toEqual([])
467
+ })
468
+
469
+ it('injects window[\'ui5-test-runner/base-host\'] before any script', async () => {
470
+ let config
471
+ mock({
472
+ api: 'fork',
473
+ scriptPath: job.browser,
474
+ exec: async childProcess => {
475
+ remainingChildProcess = childProcess
476
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
477
+ setTimeout(() => stop(job, '/test.html'), 0)
478
+ },
479
+ close: false
480
+ })
481
+ await start(job, '/test.html', ['whatever'])
482
+ expect(config.scripts.length).toEqual(2)
483
+ expect(config.scripts[0]).toEqual('window[\'ui5-test-runner/base-host\'] = \'http://localhost:0\'\n')
484
+ expect(config.scripts[1]).toEqual('whatever')
485
+ })
486
+
487
+ it('translates pre-defined scripts', async () => {
488
+ let config
489
+ mock({
490
+ api: 'fork',
491
+ scriptPath: job.browser,
492
+ exec: async childProcess => {
493
+ remainingChildProcess = childProcess
494
+ config = JSON.parse((await readFile(childProcess.args[0])).toString())
495
+ setTimeout(() => stop(job, '/test.html'), 0)
496
+ },
497
+ close: false
498
+ })
499
+ await start(job, '/test.html', ['post.js'])
500
+ expect(config.scripts.length).toEqual(2)
501
+ expect(config.scripts[1]).toMatch(/ui5-test-runner\/base-host/)
502
+ expect(config.scripts[1]).toMatch(/ui5-test-runner\/post/)
503
+ expect(config.scripts[1]).toMatch(/function post \(url, data\)/)
504
+ })
505
+ })
506
+
507
+ describe('screenshot', () => {
508
+ describe('supporting', () => {
509
+ beforeEach(() => {
510
+ job.browserCapabilities = {
511
+ screenshot: '.png'
512
+ }
513
+ })
514
+
515
+ it('should generate a file', async () => {
516
+ const { promise: ready, resolve: setReady } = allocPromise()
517
+ let fileName
518
+ mock({
519
+ api: 'fork',
520
+ scriptPath: job.browser,
521
+ exec: async childProcess => {
522
+ remainingChildProcess = childProcess
523
+ childProcess.on('message.received', async message => {
524
+ if (message.command === 'screenshot') {
525
+ fileName = message.filename
526
+ await writeFile(fileName, 'some random content to avoid empty file')
527
+ childProcess.emit('message', message)
528
+ }
529
+ })
530
+ setReady()
531
+ },
532
+ close: false
533
+ })
534
+ const started = start(job, '/test.html')
535
+ await ready
536
+ const result = await screenshot(job, '/test.html', 'screenshot')
537
+ expect(fileName).toMatch(/\bscreenshot\.png$/)
538
+ expect(result).toStrictEqual(fileName)
539
+ stop(job, '/test.html')
540
+ await started
541
+ })
542
+
543
+ it('fails if the file is missing', async () => {
544
+ const { promise: ready, resolve: setReady } = allocPromise()
545
+ mock({
546
+ api: 'fork',
547
+ scriptPath: job.browser,
548
+ exec: async childProcess => {
549
+ remainingChildProcess = childProcess
550
+ childProcess.on('message.received', async message => {
551
+ if (message.command === 'screenshot') {
552
+ childProcess.emit('message', message)
553
+ }
554
+ })
555
+ setReady()
556
+ },
557
+ close: false
558
+ })
559
+ start(job, '/test.html')
560
+ await ready
561
+ await expect(screenshot(job, '/test.html', 'screenshot')).rejects.toMatchObject({
562
+ name: 'UTRError:BROWSER_SCREENSHOT_FAILED'
563
+ })
564
+ })
565
+
566
+ it('fails if the file is empty', async () => {
567
+ const { promise: ready, resolve: setReady } = allocPromise()
568
+ mock({
569
+ api: 'fork',
570
+ scriptPath: job.browser,
571
+ exec: async childProcess => {
572
+ remainingChildProcess = childProcess
573
+ childProcess.on('message.received', async message => {
574
+ if (message.command === 'screenshot') {
575
+ await writeFile(message.filename, '')
576
+ childProcess.emit('message', message)
577
+ }
578
+ })
579
+ setReady()
580
+ },
581
+ close: false
582
+ })
583
+ start(job, '/test.html')
584
+ await ready
585
+ await expect(screenshot(job, '/test.html', 'screenshot')).rejects.toMatchObject({
586
+ name: 'UTRError:BROWSER_SCREENSHOT_FAILED'
587
+ })
588
+ })
589
+
590
+ it('fails after a timeout', async () => {
591
+ const { promise: ready, resolve: setReady } = allocPromise()
592
+ job.screenshotTimeout = 10
593
+ mock({
594
+ api: 'fork',
595
+ scriptPath: job.browser,
596
+ exec: async childProcess => {
597
+ remainingChildProcess = childProcess
598
+ setReady()
599
+ },
600
+ close: false
601
+ })
602
+ const started = start(job, '/test.html')
603
+ await ready
604
+ await expect(screenshot(job, '/test.html', 'screenshot')).rejects.toMatchObject({
605
+ name: 'UTRError:BROWSER_SCREENSHOT_TIMEOUT'
606
+ })
607
+ stop(job, '/test.html')
608
+ await started
609
+ })
610
+
611
+ describe('edge cases', () => {
612
+ it('ignores disconnected child processes', async () => {
613
+ job[$browsers] = {
614
+ '/disconnected.html': {
615
+ childProcess: {
616
+ connected: false
617
+ },
618
+ reportDir: job.reportDir
619
+ }
620
+ }
621
+ expect(await screenshot(job, '/disconnected.html')).toBeUndefined()
622
+ })
623
+ })
624
+ })
625
+
626
+ describe('not supporting', () => {
627
+ beforeEach(() => {
628
+ job.browserCapabilities = {}
629
+ })
630
+
631
+ it('fails', async () => {
632
+ const { promise: ready, resolve: setReady } = allocPromise()
633
+ mock({
634
+ api: 'fork',
635
+ scriptPath: job.browser,
636
+ exec: async childProcess => {
637
+ remainingChildProcess = childProcess
638
+ setReady()
639
+ },
640
+ close: false
641
+ })
642
+ const started = start(job, '/test.html')
643
+ await ready
644
+ await expect(screenshot(job, '/test.html', 'screenshot')).rejects.toThrowError(UTRError.BROWSER_SCREENSHOT_NOT_SUPPORTED())
645
+ stop(job, '/test.html')
646
+ await started
647
+ })
648
+ })
649
+ })
650
+
651
+ describe('console', () => {
652
+ it('aggregate console logs in a .jsonl file', async () => {
653
+ const { promise: ready, resolve: setReady } = allocPromise()
654
+ mock({
655
+ api: 'fork',
656
+ scriptPath: job.browser,
657
+ exec: async childProcess => {
658
+ childProcess.on('message.received', async message => {
659
+ if (message.command === 'stop') {
660
+ childProcess.close(0)
661
+ }
662
+ })
663
+ childProcess.emit('message', {
664
+ command: 'console',
665
+ t: Date.now(),
666
+ api: 'log',
667
+ args: ['Hello']
668
+ })
669
+ childProcess.emit('message', {
670
+ command: 'console',
671
+ t: Date.now(),
672
+ api: 'log',
673
+ args: ['World !']
674
+ })
675
+ setReady()
676
+ },
677
+ close: false
678
+ })
679
+ const started = start(job, '/test.html')
680
+ await ready
681
+ stop(job, '/test.html')
682
+ await started
683
+ const consoleFilename = join(job.reportDir, filename('/test.html'), 'console.jsonl')
684
+ const consoleStat = await stat(consoleFilename)
685
+ expect(consoleStat.isFile()).toStrictEqual(true)
686
+ const consoleContent = (await readFile(consoleFilename)).toString()
687
+ const [firstTrace, secondTrace] = consoleContent.split('\n')
688
+ expect(JSON.parse(firstTrace)).toMatchObject({
689
+ api: 'log',
690
+ args: ['Hello']
691
+ })
692
+ expect(JSON.parse(secondTrace)).toMatchObject({
693
+ api: 'log',
694
+ args: ['World !']
695
+ })
696
+ })
697
+
698
+ it('resets .jsonl file on retry', async () => {
699
+ mock({
700
+ api: 'fork',
701
+ scriptPath: job.browser,
702
+ exec: async childProcess => {
703
+ childProcess.on('message.received', async message => {
704
+ if (message.command === 'stop') {
705
+ childProcess.close(0)
706
+ }
707
+ })
708
+ childProcess.emit('message', {
709
+ command: 'console',
710
+ t: Date.now(),
711
+ api: 'log',
712
+ args: ['Hello World !']
713
+ })
714
+ childProcess.close(-1)
715
+ },
716
+ close: false
717
+ })
718
+ await expect(start(job, '/test.html')).rejects.toThrowError()
719
+ const consoleFilename = join(job.reportDir, filename('/test.html'), 'console.jsonl')
720
+ const consoleContent = (await readFile(consoleFilename)).toString()
721
+ expect(consoleContent.split('\n').length).toStrictEqual(2)
722
+ })
723
+ })
724
+ })