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
package/src/output.js CHANGED
@@ -1,18 +1,44 @@
1
1
  'use strict'
2
2
 
3
- const nativeConsole = console
4
- const mockConsole = {}
5
- const interactive = process.stdout.columns !== undefined
6
- let lastTick = 0
7
- const ticks = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
8
- let job
9
- let lines = 1
10
-
11
- function write (...parts) {
12
- parts.forEach(part => process.stdout.write(part))
3
+ const { readFileSync, writeFileSync } = require('fs')
4
+ const { join } = require('path')
5
+ const { memoryUsage } = require('process')
6
+ const {
7
+ $browsers,
8
+ $probeUrlsStarted,
9
+ $probeUrlsCompleted,
10
+ $testPagesCompleted
11
+ } = require('./symbols')
12
+ const { noop, pad } = require('./tools')
13
+
14
+ const inJest = typeof jest !== 'undefined'
15
+ const interactive = process.stdout.columns !== undefined && !inJest
16
+ const $output = Symbol('output')
17
+
18
+ if (!interactive) {
19
+ const UTF8_BOM_CODE = '\ufeff'
20
+ process.stdout.write(UTF8_BOM_CODE)
21
+ }
22
+
23
+ let cons
24
+ if (inJest) {
25
+ cons = {
26
+ log: noop,
27
+ warn: noop,
28
+ error: noop
29
+ }
30
+ } else {
31
+ cons = console
13
32
  }
14
33
 
15
- function clean () {
34
+ const write = (...parts) => parts.forEach(part => process.stdout.write(part))
35
+
36
+ function clean (job) {
37
+ const { lines } = job[$output]
38
+ if (!lines) {
39
+ return
40
+ }
41
+ write('\x1b[?12l')
16
42
  write(`\x1b[${lines.toString()}F`)
17
43
  for (let line = 0; line < lines; ++line) {
18
44
  if (line > 1) {
@@ -27,54 +53,67 @@ function clean () {
27
53
  }
28
54
  }
29
55
 
30
- const width = 10
56
+ const BAR_WIDTH = 10
31
57
 
32
58
  function bar (ratio, msg) {
33
59
  write('[')
34
60
  if (typeof ratio === 'string') {
35
- if (ratio.length > width) {
36
- write(ratio.substring(0, width - 3), '...')
61
+ if (ratio.length > BAR_WIDTH) {
62
+ write(ratio.substring(0, BAR_WIDTH - 3), '...')
37
63
  } else {
38
- const padded = ratio.padStart(width - Math.floor((width - ratio.length) / 2), '-').padEnd(width, '-')
64
+ const padded = ratio.padStart(BAR_WIDTH - Math.floor((BAR_WIDTH - ratio.length) / 2), '-').padEnd(BAR_WIDTH, '-')
39
65
  write(padded)
40
66
  }
41
67
  write('] ')
42
68
  } else {
43
- const filled = Math.floor(width * ratio)
44
- write(''.padEnd(filled, '\u2588'), ''.padEnd(width - filled, '\u2591'))
69
+ const filled = Math.floor(BAR_WIDTH * ratio)
70
+ write(''.padEnd(filled, '\u2588'), ''.padEnd(BAR_WIDTH - filled, '\u2591'))
45
71
  const percent = Math.floor(100 * ratio).toString().padStart(3, ' ')
46
72
  write('] ', percent, '%')
47
73
  }
48
- write(' ', msg, '\n')
74
+ write(' ')
75
+ const spaceLeft = process.stdout.columns - BAR_WIDTH - 14
76
+ if (msg.length > spaceLeft) {
77
+ write('...', msg.substring(msg.length - spaceLeft - 3))
78
+ } else {
79
+ write(msg)
80
+ }
81
+ write('\n')
49
82
  }
50
83
 
51
- function progress (cleanFirst = true) {
84
+ const TICKS = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
85
+
86
+ function progress (job, cleanFirst = true) {
52
87
  if (cleanFirst) {
53
- clean()
88
+ clean(job)
54
89
  }
55
- lines = 1
90
+ const output = job[$output]
91
+ output.lines = 1
56
92
  let progressRatio
57
- if (job.testPageUrls && job.testPages && job.parallel > 0) {
58
- const total = job.testPageUrls.length
59
- const done = Object.keys(job.testPages)
60
- .filter(pageUrl => !!job.testPages[pageUrl].report)
61
- .length
62
- if (done < total) {
63
- progressRatio = done / total
93
+ if (job.debugMemory) {
94
+ ++output.lines
95
+ const { rss, heapTotal, heapUsed, external, arrayBuffers } = memoryUsage()
96
+ const fmt = size => `${(size / (1024 * 1024)).toFixed(2)}M`
97
+ write(`MEM r:${fmt(rss)}, h:${fmt(heapUsed)}/${fmt(heapTotal)}, x:${fmt(external)}, a:${fmt(arrayBuffers)}\n`)
98
+ }
99
+ if (job[$probeUrlsStarted]) {
100
+ const total = job.url.length + job.testPageUrls.length
101
+ if (job[$testPagesCompleted] !== total) {
102
+ progressRatio = (job[$probeUrlsCompleted] + (job[$testPagesCompleted] || 0)) / total
64
103
  }
65
104
  }
66
- if (job.browsers) {
67
- const runningPages = Object.keys(job.browsers)
68
- lines += runningPages.length
105
+ if (job[$browsers]) {
106
+ const runningPages = Object.keys(job[$browsers])
107
+ output.lines += runningPages.length
69
108
  runningPages.forEach(pageUrl => {
70
109
  let starting = true
71
- if (job.testPages) {
72
- const page = job.testPages[pageUrl]
110
+ if (job.qunitPages) {
111
+ const page = job.qunitPages[pageUrl]
73
112
  if (page) {
74
- const { total, passed, failed } = page
75
- if (total) {
113
+ const { count, passed, failed } = page
114
+ if (count) {
76
115
  const progress = passed + failed
77
- bar(progress / total, pageUrl)
116
+ bar(progress / count, pageUrl)
78
117
  starting = false
79
118
  }
80
119
  }
@@ -84,7 +123,7 @@ function progress (cleanFirst = true) {
84
123
  }
85
124
  })
86
125
  }
87
- const status = `${ticks[++lastTick % ticks.length]} ${job.status}`
126
+ const status = `${TICKS[++output.lastTick % TICKS.length]} ${job.status}`
88
127
  if (progressRatio !== undefined) {
89
128
  bar(progressRatio, status)
90
129
  } else {
@@ -92,108 +131,388 @@ function progress (cleanFirst = true) {
92
131
  }
93
132
  }
94
133
 
95
- function wrap (write) {
96
- if (job) {
97
- clean()
98
- }
99
- write()
100
- if (job) {
101
- progress(false)
134
+ function output (job, ...texts) {
135
+ writeFileSync(join(job.reportDir, 'output.txt'), texts.map(t => t.toString()).join(' ') + '\n', {
136
+ encoding: 'utf-8',
137
+ flag: 'a'
138
+ })
139
+ }
140
+
141
+ function log (job, ...texts) {
142
+ cons.log(...texts)
143
+ output(job, ...texts)
144
+ }
145
+
146
+ function warn (job, ...texts) {
147
+ cons.warn(...texts)
148
+ output(job, ...texts)
149
+ }
150
+
151
+ function err (job, ...texts) {
152
+ cons.error(...texts)
153
+ output(job, ...texts)
154
+ }
155
+
156
+ const p80 = () => pad(process.stdout.columns || 80)
157
+
158
+ function browserIssue (job, { type, url, code, dir }) {
159
+ const p = p80()
160
+ log(job, p`┌──────────${pad.x('─')}┐`)
161
+ log(job, p`│ BROWSER ${type.toUpperCase()} ${pad.x(' ')} │`)
162
+ log(job, p`├──────┬─${pad.x('─')}──┤`)
163
+ log(job, p`│ url │ ${pad.lt(url)} │`)
164
+ log(job, p`├──────┼─${pad.x('─')}──┤`)
165
+ const unsignedCode = new Uint32Array([code])[0]
166
+ log(job, p`│ code │ 0x${unsignedCode.toString(16).toUpperCase()}${pad.x(' ')} │`)
167
+ log(job, p`├──────┼─${pad.x('─')}──┤`)
168
+ log(job, p`│ dir │ ${pad.lt(dir)} │`)
169
+ log(job, p`├──────┴─${pad.x('─')}──┤`)
170
+
171
+ const stderr = readFileSync(join(dir, 'stderr.txt')).toString().trim()
172
+ if (stderr.length !== 0) {
173
+ log(job, p`│ Error output (${stderr.length}) ${pad.x(' ')} │`)
174
+ log(job, p`│ ${pad.w(stderr)} │`)
175
+ } else {
176
+ const stdout = readFileSync(join(dir, 'stdout.txt')).toString()
177
+ if (stdout.length !== 0) {
178
+ log(job, p`│ Standard output (${stderr.length}), last 10 lines... ${pad.x(' ')} │`)
179
+ log(job, p`│ ${pad.w('...')} │`)
180
+ log(job, p`│ ${pad.w(stdout.split(/\r?\n/).slice(-10).join('\n'))} │`)
181
+ } else {
182
+ log(job, p`│ No output ${pad.x(' ')} │`)
183
+ }
102
184
  }
185
+ log(job, p`└──────────${pad.x('─')}┘`)
103
186
  }
104
187
 
105
- Object.getOwnPropertyNames(console).forEach(name => {
106
- mockConsole[name] = function (...args) {
107
- wrap(() => nativeConsole[name](...args))
188
+ function build (job) {
189
+ let wrap
190
+ if (interactive) {
191
+ wrap = method => function () {
192
+ clean(job)
193
+ try {
194
+ method.call(this, ...arguments)
195
+ } finally {
196
+ progress(job, false)
197
+ }
198
+ }
199
+ } else {
200
+ wrap = method => method
108
201
  }
109
- })
110
202
 
111
- if (interactive) {
112
- global.console = mockConsole
203
+ return {
204
+ lastTick: 0,
205
+ reportIntervalId: undefined,
206
+ lines: 0,
207
+
208
+ wrap: wrap(callback => callback()),
209
+
210
+ version: () => {
211
+ const { name, version } = require(join(__dirname, '../package.json'))
212
+ log(job, p80()`${name}@${version}`)
213
+ },
214
+
215
+ serving: url => {
216
+ log(job, p80()`Server running at ${pad.lt(url)}`)
217
+ },
218
+
219
+ redirected: wrap(({ method, url, statusCode, timeSpent }) => {
220
+ if (url.startsWith('/_/progress')) {
221
+ return // avoids pollution
222
+ }
223
+ let statusText
224
+ if (!statusCode) {
225
+ statusText = 'N/A'
226
+ } else {
227
+ statusText = statusCode
228
+ }
229
+ log(job, p80()`${method.padEnd(7, ' ')} ${pad.lt(url)} ${statusText} ${timeSpent.toString().padStart(4, ' ')}ms`)
230
+ }),
231
+
232
+ status (status) {
233
+ let method
234
+ if (interactive) {
235
+ method = output
236
+ } else {
237
+ method = log
238
+ }
239
+ method(job, '')
240
+ method(job, status)
241
+ method(job, ''.padStart(status.length, '─'))
242
+ },
243
+
244
+ watching: wrap(path => {
245
+ log(job, p80()`Watching changes on ${pad.lt(path)}`)
246
+ }),
247
+
248
+ changeDetected: wrap((eventType, filename) => {
249
+ log(job, p80()`${eventType} ${pad.lt(filename)}`)
250
+ }),
251
+
252
+ reportOnJobProgress () {
253
+ if (interactive) {
254
+ this.reportIntervalId = setInterval(progress.bind(null, job), 250)
255
+ }
256
+ },
257
+
258
+ browserCapabilities: wrap(capabilities => {
259
+ log(job, p80()`Browser capabilities :`)
260
+ const { modules } = capabilities
261
+ if (modules.length) {
262
+ log(job, p80()` ├─ modules`)
263
+ modules.forEach((module, index) => {
264
+ let prefix
265
+ if (index === modules.length - 1) {
266
+ prefix = ' │ └─ '
267
+ } else {
268
+ prefix = ' │ ├─'
269
+ }
270
+ log(job, p80()`${prefix} ${pad.lt(module)}`)
271
+ })
272
+ }
273
+ Object.keys(capabilities)
274
+ .filter(key => key !== 'modules')
275
+ .forEach((key, index, keys) => {
276
+ let prefix
277
+ if (index === keys.length - 1) {
278
+ prefix = ' └─'
279
+ } else {
280
+ prefix = ' ├─'
281
+ }
282
+ log(job, p80()`${prefix} ${key}: ${JSON.stringify(capabilities[key])}`)
283
+ })
284
+ }),
285
+
286
+ resolvedPackage (name, path, version) {
287
+ if (!name.match(/@\d+\.\d+\.\d+$/)) {
288
+ name += `@${version}`
289
+ }
290
+ wrap(() => log(job, p80()`${name} in ${pad.lt(path)}`))()
291
+ },
292
+
293
+ packageNotLatest (name, latestVersion) {
294
+ wrap(() => log(job, `/!\\ latest version of ${name} is ${latestVersion}`))()
295
+ },
296
+
297
+ browserStart (url) {
298
+ if (interactive) {
299
+ output(job, '>>', url)
300
+ } else {
301
+ wrap(() => log(job, p80()`>> ${pad.lt(url)}`))()
302
+ }
303
+ },
304
+
305
+ browserStopped (url) {
306
+ if (interactive) {
307
+ output(job, '<<', url)
308
+ } else {
309
+ wrap(() => log(job, p80()`<< ${pad.lt(url)}`))()
310
+ }
311
+ },
312
+
313
+ browserClosed: wrap((url, code, dir) => {
314
+ browserIssue(job, { type: 'unexpected closed', url, code, dir })
315
+ }),
316
+
317
+ browserRetry (url, retry) {
318
+ if (interactive) {
319
+ output(job, '>>', url)
320
+ } else {
321
+ wrap(() => log(job, p80()`>> RETRY ${retry} ${pad.lt(url)}`))()
322
+ }
323
+ },
324
+
325
+ browserTimeout: wrap((url, dir) => {
326
+ browserIssue(job, { type: 'timeout', url, code: 0, dir })
327
+ }),
328
+
329
+ browserFailed: wrap((url, code, dir) => {
330
+ browserIssue(job, { type: 'failed', url, code, dir })
331
+ }),
332
+
333
+ startFailed: wrap((url, error) => {
334
+ const p = p80()
335
+ log(job, p`┌──────────${pad.x('─')}┐`)
336
+ log(job, p`│ UNABLE TO START THE URL ${pad.x(' ')} │`)
337
+ log(job, p`├──────┬─${pad.x('─')}──┤`)
338
+ log(job, p`│ url │ ${pad.lt(url)} │`)
339
+ log(job, p`├──────┴─${pad.x('─')}──┤`)
340
+ if (error.stack) {
341
+ log(job, p`│ ${pad.w(error.stack)} │`)
342
+ } else {
343
+ log(job, p`│ ${pad.w(error.toString())} │`)
344
+ }
345
+ log(job, p`└──────────${pad.x('─')}┘`)
346
+ }),
347
+
348
+ monitor (childProcess) {
349
+ ['stdout', 'stderr'].forEach(channel => {
350
+ const defaults = {
351
+ stdout: { buffer: [], method: log },
352
+ stderr: { buffer: [], method: err }
353
+ }
354
+ childProcess[channel].on('data', chunk => {
355
+ const { buffer, method } = defaults[channel]
356
+ const text = chunk.toString()
357
+ if (!text.includes('\n')) {
358
+ buffer.push(text)
359
+ return
360
+ }
361
+ const cached = buffer.join('')
362
+ const last = text.split('\n').slice(-1)
363
+ buffer.length = 0
364
+ if (last) {
365
+ buffer.push(last)
366
+ }
367
+ wrap(() => method(job, cached + text.split('\n').slice(0, -1).join('\n')))()
368
+ })
369
+ childProcess.on('close', () => {
370
+ ['stdout', 'stderr'].forEach(channel => {
371
+ const { buffer, method } = defaults[channel]
372
+ if (buffer.length) {
373
+ method(job, buffer.join(''))
374
+ }
375
+ })
376
+ })
377
+ })
378
+ },
379
+
380
+ nyc: wrap((...args) => {
381
+ log(job, p80()`nyc ${args.map(arg => arg.toString()).join(' ')}`)
382
+ }),
383
+
384
+ endpointError: wrap(({ api, url, data, error }) => {
385
+ const p = p80()
386
+ log(job, p`┌──────────${pad.x('─')}┐`)
387
+ log(job, p`│ UNEXPECTED ENDPOINT ERROR ${pad.x(' ')} │`)
388
+ log(job, p`├──────┬─${pad.x('─')}──┤`)
389
+ log(job, p`│ api │ ${pad.lt(api)} │`)
390
+ log(job, p`├──────┼─${pad.x('─')}──┤`)
391
+ log(job, p`│ from │ ${pad.lt(url)} │`)
392
+ log(job, p`├──────┴─${pad.x('─')}──┤`)
393
+ log(job, p`│ data (${JSON.stringify(data).length}) ${pad.x(' ')} │`)
394
+ log(job, p`│ ${pad.w(JSON.stringify(data, undefined, 2))} │`)
395
+ log(job, p`├────────${pad.x('─')}──┤`)
396
+ if (error.stack) {
397
+ log(job, p`│ ${pad.w(error.stack)} │`)
398
+ } else {
399
+ log(job, p`│ ${pad.w(error.toString())} │`)
400
+ }
401
+ log(job, p`└──────────${pad.x('─')}┘`)
402
+ }),
403
+
404
+ serverError: wrap(({ method, url, reason }) => {
405
+ const p = p80()
406
+ log(job, p`┌──────────${pad.x('─')}┐`)
407
+ log(job, p`│ UNEXPECTED SERVER ERROR ${pad.x(' ')} │`)
408
+ log(job, p`├──────┬─${pad.x('─')}──┤`)
409
+ log(job, p`│ verb │ ${pad.lt(method)} │`)
410
+ log(job, p`├──────┼─${pad.x('─')}──┤`)
411
+ log(job, p`│ url │ ${pad.lt(url)} │`)
412
+ log(job, p`├──────┴─${pad.x('─')}──┤`)
413
+ if (reason.stack) {
414
+ log(job, p`│ ${pad.w(reason.stack)} │`)
415
+ } else {
416
+ log(job, p`│ ${pad.w(reason.toString())} │`)
417
+ }
418
+ log(job, p`└──────────${pad.x('─')}┘`)
419
+ }),
420
+
421
+ globalTimeout: wrap(url => {
422
+ log(job, p80()`!! TIMEOUT ${pad.lt(url)}`)
423
+ }),
424
+
425
+ failFast: wrap(url => {
426
+ log(job, p80()`!! FAILFAST ${pad.lt(url)}`)
427
+ }),
428
+
429
+ timeSpent: wrap((start, end = new Date()) => {
430
+ log(job, p80()`Time spent: ${end - start}ms`)
431
+ }),
432
+
433
+ noTestPageFound: wrap(() => {
434
+ err(job, p80()`No test page found (or all filtered out)`)
435
+ }),
436
+
437
+ failedToCacheUI5resource: wrap((path, statusCode) => {
438
+ err(job, p80()`Unable to cache '${pad.lt(path)}' (status ${statusCode})`)
439
+ }),
440
+
441
+ genericError: wrap((error, url) => {
442
+ const p = p80()
443
+ log(job, p`┌──────────${pad.x('─')}┐`)
444
+ log(job, p`│ UNEXPECTED ERROR ${pad.x(' ')} │`)
445
+ if (url) {
446
+ log(job, p`├──────┬─${pad.x('─')}──┤`)
447
+ log(job, p`│ url │ ${pad.lt(url)} │`)
448
+ log(job, p`├──────┴─${pad.x('─')}──┤`)
449
+ } else {
450
+ log(job, p`├────────${pad.x('─')}──┤`)
451
+ }
452
+ if (error.stack) {
453
+ log(job, p`│ ${pad.w(error.stack)} │`)
454
+ } else {
455
+ log(job, p`│ ${pad.w(error.toString())} │`)
456
+ }
457
+ log(job, p`└──────────${pad.x('─')}┘`)
458
+ }),
459
+
460
+ unhandled: wrap(() => {
461
+ warn(job, p80()`Some requests are not handled properly, check the unhandled.txt report for more info`)
462
+ }),
463
+
464
+ results: wrap(() => {
465
+ const p = p80()
466
+ log(job, p`┌──────────${pad.x('─')}┐`)
467
+ log(job, p`│ RESULTS ${pad.x(' ')} │`)
468
+ log(job, p`├─────┬─${pad.x('─')}──┤`)
469
+ const messages = []
470
+ function result (url) {
471
+ const page = job.qunitPages && job.qunitPages[url]
472
+ let message
473
+ if (!page || !page.report) {
474
+ message = 'Unable to run the page'
475
+ } else if (page.report.failed > 1) {
476
+ message = `${page.report.failed} tests failed`
477
+ } else if (page.report.failed === 1) {
478
+ message = '1 test failed'
479
+ }
480
+ if (message) {
481
+ log(job, p`│ ${(messages.length + 1).toString().padStart(3, ' ')} │ ${pad.lt(url)} │`)
482
+ messages.push(message)
483
+ } else {
484
+ log(job, p`│ OK │ ${pad.lt(url)} │`)
485
+ }
486
+ }
487
+ job.testPageUrls.forEach(result)
488
+ Object.keys(job.qunitPages || []).forEach(url => {
489
+ if (!job.testPageUrls.includes(url)) {
490
+ result(url)
491
+ }
492
+ })
493
+ log(job, p`└─────┴───${pad.x('─')}┘`)
494
+ messages.forEach((message, index) => {
495
+ log(job, p`${(index + 1).toString().padStart(3, ' ')}: ${message}`)
496
+ })
497
+ if (!messages.length) {
498
+ log(job, p`Success !`)
499
+ }
500
+ }),
501
+
502
+ stop () {
503
+ if (this.reportIntervalId) {
504
+ clearInterval(this.reportIntervalId)
505
+ clean(job)
506
+ }
507
+ }
508
+ }
113
509
  }
114
510
 
115
511
  module.exports = {
116
- serving (url) {
117
- console.log(`Server running at ${url}`)
118
- },
119
- watching (path) {
120
- console.log('Watching changes on', path)
121
- },
122
- changeDetected (eventType, filename) {
123
- console.log(eventType, filename)
124
- },
125
- report (newJob) {
126
- job = newJob
127
- if (interactive) {
128
- process.stdout.write('\n')
129
- setInterval(progress, 250)
130
- }
131
- },
132
- browserStart (url) {
133
- if (!interactive) {
134
- console.log('>>', url)
135
- }
136
- },
137
- browserCapabilities (capabilities) {
138
- console.log('Browser capabilities :', capabilities)
139
- },
140
- browserStopped (url) {
141
- if (!interactive) {
142
- console.log('<<', url)
512
+ getOutput (job) {
513
+ if (!job[$output]) {
514
+ job[$output] = build(job)
143
515
  }
144
- },
145
- browserClosed (url) {
146
- console.log('!! BROWSER CLOSED', url)
147
- },
148
- browserRetry (url, retry) {
149
- console.log('>> RETRY', retry, url)
150
- },
151
- browserTimeout (url) {
152
- console.log('!! TIMEOUT', url)
153
- },
154
- monitor (childProcess) {
155
- ['stdout', 'stderr'].forEach(channel => {
156
- childProcess[channel].on('data', chunk => {
157
- wrap(() => process[channel].write(chunk))
158
- })
159
- })
160
- },
161
- nyc (...args) {
162
- console.log('nyc', ...args)
163
- },
164
- endpoint (url, data) {
165
- console.log(url, data)
166
- },
167
- endpointError (url, data, error) {
168
- console.error(`Exception when processing ${url}`)
169
- console.error(data)
170
- console.error(error)
171
- },
172
- globalTimeout (url) {
173
- console.log('!! TIMEOUT', url)
174
- },
175
- failFast (url) {
176
- console.log('!! FAILFAST', url)
177
- },
178
- timeSpent (start, end = new Date()) {
179
- console.log(`Time spent: ${end - start}ms`)
180
- },
181
- unexpectedOptionValue (optionName, message) {
182
- console.error(`Unexpected value for option ${optionName} : ${message}`)
183
- },
184
- noTestPageFound () {
185
- console.error('No test page found (or all filtered out)')
186
- },
187
- failedToCacheUI5resource (path, statusCode) {
188
- console.error(`Unable to cache '${path}' (status ${statusCode})`)
189
- },
190
- genericError (error) {
191
- console.error('An unexpected error occurred :', error.message || error)
192
- },
193
- unhandled () {
194
- console.warn('Some requests are not handled properly, check the unhandled.txt report for more info')
195
- },
196
- results (pages) {
197
- console.table(pages)
516
+ return job[$output]
198
517
  }
199
518
  }