ui5-test-runner 5.3.0 โ 5.3.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.
- package/package.json +2 -2
- package/src/add-test-pages.js +8 -5
- package/src/browsers.js +301 -297
- package/src/inject/post.js +10 -6
- package/src/inject/qunit-redirect.js +3 -2
- package/src/job.js +393 -392
- package/src/output.js +608 -604
- package/src/qunit-hooks.js +10 -4
package/src/job.js
CHANGED
|
@@ -1,392 +1,393 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { Command, Option, InvalidArgumentError } = require('commander')
|
|
4
|
-
const { statSync, accessSync, constants } = require('fs')
|
|
5
|
-
const { dirname, join, isAbsolute } = require('path')
|
|
6
|
-
const { name, description, version } = require(join(__dirname, '../package.json'))
|
|
7
|
-
const { getOutput } = require('./output')
|
|
8
|
-
const { $valueSources, $remoteOnLegacy } = require('./symbols')
|
|
9
|
-
const { buildAndCheckMode } = require('./job-mode')
|
|
10
|
-
const { boolean, integer, timeout, url, arrayOf, regex, percent, string } = require('./options')
|
|
11
|
-
|
|
12
|
-
const $status = Symbol('status')
|
|
13
|
-
|
|
14
|
-
function toLongName (name) {
|
|
15
|
-
return name.replace(/([A-Z])([a-z]+)/g, (match, firstLetter, reminder) => `-${firstLetter.toLowerCase()}${reminder}`)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function buildArgs (parameters) {
|
|
19
|
-
const before = []
|
|
20
|
-
const after = []
|
|
21
|
-
let browser = []
|
|
22
|
-
Object.keys(parameters).forEach(name => {
|
|
23
|
-
if (name === '--') {
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
const value = parameters[name]
|
|
27
|
-
let args
|
|
28
|
-
if (name.startsWith('!')) {
|
|
29
|
-
args = after
|
|
30
|
-
name = name.substring(1)
|
|
31
|
-
} else {
|
|
32
|
-
args = before
|
|
33
|
-
}
|
|
34
|
-
args.push(`--${toLongName(name)}`)
|
|
35
|
-
if (value !== null) {
|
|
36
|
-
if (Array.isArray(value)) {
|
|
37
|
-
args.push(...value)
|
|
38
|
-
} else {
|
|
39
|
-
args.push(value)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
if (parameters['--']) {
|
|
44
|
-
browser = parameters['--']
|
|
45
|
-
}
|
|
46
|
-
const stringify = args => args.map(value => value.toString())
|
|
47
|
-
return {
|
|
48
|
-
before: stringify(before),
|
|
49
|
-
after: stringify(after),
|
|
50
|
-
browser: stringify(browser)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function lib (value) {
|
|
55
|
-
if (value.includes('=')) {
|
|
56
|
-
const [relative, source] = value.split('=')
|
|
57
|
-
return { relative, source }
|
|
58
|
-
} else {
|
|
59
|
-
return { relative: '', source: value }
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function mapping (value) {
|
|
64
|
-
try {
|
|
65
|
-
const [, match, handler, mapping] = /([^=]*)=(file|url)\((.*)\)/.exec(value)
|
|
66
|
-
return {
|
|
67
|
-
match,
|
|
68
|
-
[handler]: mapping
|
|
69
|
-
}
|
|
70
|
-
} catch (e) {
|
|
71
|
-
throw new InvalidArgumentError('Invalid mapping')
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function getCommand (cwd) {
|
|
76
|
-
const command = new Command()
|
|
77
|
-
command.exitOverride()
|
|
78
|
-
|
|
79
|
-
const DEBUG_OPTION = '(๐ for debugging purpose)'
|
|
80
|
-
const EXPERIMENTAL_OPTION = '[โ ๏ธ experimental]'
|
|
81
|
-
|
|
82
|
-
command
|
|
83
|
-
.name(name)
|
|
84
|
-
.description(description)
|
|
85
|
-
.version(version)
|
|
86
|
-
|
|
87
|
-
.option('--capabilities', '๐งช Capabilities tester for browser')
|
|
88
|
-
.option('-u, --url <url...>', '๐ URL of the testsuite / page to test', arrayOf(url))
|
|
89
|
-
|
|
90
|
-
// Common to all modes
|
|
91
|
-
.addOption(
|
|
92
|
-
new Option('-c, --cwd <path>', '[๐ป๐๐งช] Set working directory')
|
|
93
|
-
.default(cwd, 'current working directory')
|
|
94
|
-
)
|
|
95
|
-
.option('--port <port>', '[๐ป๐๐งช] Port to use (0 to use any free one)', integer, 0)
|
|
96
|
-
.option('-r, --report-dir <path>', '[๐ป๐๐งช] Directory to output test reports (relative to cwd)', 'report')
|
|
97
|
-
.option('-pt, --page-timeout <timeout>', '[๐ป๐๐งช] Limit the page execution time, fails the page if it takes longer than the timeout (0 means no timeout)', timeout, 0)
|
|
98
|
-
.option('-f, --fail-fast [flag]', '[๐ป๐๐งช] Stop the execution after the first failing page', boolean, false)
|
|
99
|
-
.option('-fo, --fail-opa-fast [flag]', '[๐ป๐] Stop the OPA page execution after the first failing test', boolean, false)
|
|
100
|
-
.option('-k, --keep-alive [flag]', '[๐ป๐๐งช] Keep the server alive', boolean, false)
|
|
101
|
-
.option('-l, --log-server [flag]', '[๐ป๐๐งช] Log inner server traces', boolean, false)
|
|
102
|
-
.option('-p, --parallel <count>', '[๐ป๐๐งช] Number of parallel tests executions', integer, 2)
|
|
103
|
-
.option('-b, --browser <command>', '[๐ป๐๐งช] Browser instantiation command (relative to cwd or use $/ for provided ones)', '$/puppeteer.js')
|
|
104
|
-
.option('--browser-args <argument...>', '[๐ป๐๐งช] Browser instantiation command parameters (use -- instead)')
|
|
105
|
-
.option('--alternate-npm-path <path>', '[๐ป๐] Alternate NPM path to look for packages (priority: local, alternate, global)')
|
|
106
|
-
.option('--no-npm-install', '[๐ป๐๐งช] Prevent any NPM install (execution may fail if a dependency is missing)')
|
|
107
|
-
.option('-bt, --browser-close-timeout <timeout>', '[๐ป๐๐งช] Maximum waiting time for browser close', timeout, 2000)
|
|
108
|
-
.option('-br, --browser-retry <count>', '[๐ป๐๐งช] Browser instantiation retries : if the command fails unexpectedly, it is re-executed (0 means no retry)', 1)
|
|
109
|
-
.option('-oi, --output-interval <interval>', '[๐ป๐๐งช] Interval for reporting progress on non interactive output (CI/CD) (0 means no output)', timeout, 30000)
|
|
110
|
-
.option('--offline', '[๐ป๐๐งช] Limit network usage (implies --no-npm-install)', boolean, false)
|
|
111
|
-
|
|
112
|
-
// Common to legacy and url
|
|
113
|
-
.option('--webapp <path>', '[๐ป๐] Base folder of the web application (relative to cwd)', 'webapp')
|
|
114
|
-
.option('-pf, --page-filter <regexp>', '[๐ป๐] Filter out pages not matching the regexp')
|
|
115
|
-
.option('-pp, --page-params <params>', '[๐ป๐] Add parameters to page URL')
|
|
116
|
-
.option('--page-close-timeout <timeout>', '[๐ป๐] Maximum waiting time for page close', timeout, 250)
|
|
117
|
-
.option('-t, --global-timeout <timeout>', '[๐ป๐] Limit the pages execution time, fail the page if it takes longer than the timeout (0 means no timeout)', timeout, 0)
|
|
118
|
-
.option('--screenshot [flag]', '[๐ป๐] Take screenshots during the tests execution (if supported by the browser)', boolean, true)
|
|
119
|
-
.option('--no-screenshot', '[๐ป๐] Disable screenshots')
|
|
120
|
-
.option('-st, --screenshot-timeout <timeout>', '[๐ป๐] Maximum waiting time for browser screenshot', timeout, 5000)
|
|
121
|
-
.option('-so, --split-opa', '[๐ป๐] Split OPA tests using QUnit modules', boolean, false)
|
|
122
|
-
.option('-rg, --report-generator <path...>', '[๐ป๐] Report generator paths (relative to cwd or use $/ for provided ones)', ['$/report.js'])
|
|
123
|
-
.option('--progress-page <path>', '[๐ป๐] Progress page path (relative to cwd or use $/ for provided ones)', '$/report/default.html')
|
|
124
|
-
|
|
125
|
-
.option('--coverage [flag]', '[๐ป๐] Enable or disable code coverage', boolean)
|
|
126
|
-
.option('--no-coverage', '[๐ป๐] Disable code coverage')
|
|
127
|
-
.option('-cs, --coverage-settings <path>', '[๐ป๐] Path to a custom nyc.json file providing settings for instrumentation (relative to cwd or use $/ for provided ones)', '$/nyc.json')
|
|
128
|
-
.option('-ctd, --coverage-temp-dir <path>', '[๐ป๐] Directory to output raw coverage information to (relative to cwd)', '.nyc_output')
|
|
129
|
-
.option('-crd, --coverage-report-dir <path>', '[๐ป๐] Directory to store the coverage report files (relative to cwd)', 'coverage')
|
|
130
|
-
.option('-cr, --coverage-reporters <reporter...>', '[๐ป๐] List of nyc reporters to use (text is always used)', ['lcov', 'cobertura'])
|
|
131
|
-
.option('-ccb, --coverage-check-branches <percent>', '[๐ป๐] What % of branches must be covered', percent, 0)
|
|
132
|
-
.option('-ccf, --coverage-check-functions <percent>', '[๐ป๐] What % of functions must be covered', percent, 0)
|
|
133
|
-
.option('-ccl, --coverage-check-lines <percent>', '[๐ป๐] What % of lines must be covered', percent, 0)
|
|
134
|
-
.option('-ccs, --coverage-check-statements <percent>', '[๐ป๐] What % of statements must be covered', percent, 0)
|
|
135
|
-
.option('-crs, --coverage-remote-scanner <path>', '[๐ป๐] Scan for files when all coverage is requested', '$/scan-ui5.js')
|
|
136
|
-
.option('-s, --serve-only [flag]', '[๐ป๐] Serve only', boolean, false)
|
|
137
|
-
|
|
138
|
-
// Specific to legacy (and might be used with url if pointing to local project)
|
|
139
|
-
.option('--ui5 <url>', '[๐ป] UI5 url', url, 'https://ui5.sap.com')
|
|
140
|
-
.option('--disable-ui5', '[๐ป] Disable UI5 mapping (also disable libs)', boolean, false)
|
|
141
|
-
.option('--libs <lib...>', '[๐ป] Library mapping (<relative>=<path> or <path>)', arrayOf(lib))
|
|
142
|
-
.option('--mappings <mapping...>', '[๐ป] Custom mapping (<match>=<file|url>(<config>))', arrayOf(mapping))
|
|
143
|
-
.option('--cache <path>', '[๐ป] Cache UI5 resources locally in the given folder (empty to disable)')
|
|
144
|
-
.option('--preload <library...>', '[๐ป] Preload UI5 libraries in the cache folder (only if --cache is used)', arrayOf(string))
|
|
145
|
-
.option('--testsuite <path>', '[๐ป] Path of the testsuite file (relative to webapp, URL parameters are supported)', 'test/testsuite.qunit.html')
|
|
146
|
-
.option('-w, --watch [flag]', '[๐ป] Monitor the webapp folder and re-execute tests on change', boolean, false)
|
|
147
|
-
|
|
148
|
-
// Specific to coverage in url mode (experimental)
|
|
149
|
-
.option('-cp, --coverage-proxy [flag]', `[๐] ${EXPERIMENTAL_OPTION} use internal proxy to instrument remote files`, boolean, false)
|
|
150
|
-
.option('-cpi, --coverage-proxy-include <regexp>', `[๐] ${EXPERIMENTAL_OPTION} urls to instrument for coverage`, regex, '.*')
|
|
151
|
-
.option('-cpe, --coverage-proxy-exclude <regexp>', `[๐] ${EXPERIMENTAL_OPTION} urls to ignore for coverage`, regex, '/((test-)?resources|tests?)/')
|
|
152
|
-
|
|
153
|
-
.addOption(new Option('--debug-dev-mode', DEBUG_OPTION, boolean).hideHelp())
|
|
154
|
-
.addOption(new Option('--debug-probe-only', DEBUG_OPTION, boolean).hideHelp())
|
|
155
|
-
.addOption(new Option('--debug-keep-browser-open', DEBUG_OPTION, boolean).hideHelp())
|
|
156
|
-
.addOption(new Option('--debug-memory', DEBUG_OPTION, boolean).hideHelp())
|
|
157
|
-
.addOption(new Option('--debug-keep-report', DEBUG_OPTION, boolean).hideHelp())
|
|
158
|
-
.addOption(new Option('--debug-capabilities-test <name>', DEBUG_OPTION).hideHelp())
|
|
159
|
-
.addOption(new Option('--debug-capabilities-no-timeout', DEBUG_OPTION, boolean).hideHelp())
|
|
160
|
-
.addOption(new Option('--debug-
|
|
161
|
-
.addOption(new Option('--debug-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
//
|
|
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
|
-
.
|
|
227
|
-
|
|
228
|
-
'
|
|
229
|
-
|
|
230
|
-
.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
overrideDirIfNotSet('
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
debugger
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Command, Option, InvalidArgumentError } = require('commander')
|
|
4
|
+
const { statSync, accessSync, constants } = require('fs')
|
|
5
|
+
const { dirname, join, isAbsolute } = require('path')
|
|
6
|
+
const { name, description, version } = require(join(__dirname, '../package.json'))
|
|
7
|
+
const { getOutput } = require('./output')
|
|
8
|
+
const { $valueSources, $remoteOnLegacy } = require('./symbols')
|
|
9
|
+
const { buildAndCheckMode } = require('./job-mode')
|
|
10
|
+
const { boolean, integer, timeout, url, arrayOf, regex, percent, string } = require('./options')
|
|
11
|
+
|
|
12
|
+
const $status = Symbol('status')
|
|
13
|
+
|
|
14
|
+
function toLongName (name) {
|
|
15
|
+
return name.replace(/([A-Z])([a-z]+)/g, (match, firstLetter, reminder) => `-${firstLetter.toLowerCase()}${reminder}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildArgs (parameters) {
|
|
19
|
+
const before = []
|
|
20
|
+
const after = []
|
|
21
|
+
let browser = []
|
|
22
|
+
Object.keys(parameters).forEach(name => {
|
|
23
|
+
if (name === '--') {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
const value = parameters[name]
|
|
27
|
+
let args
|
|
28
|
+
if (name.startsWith('!')) {
|
|
29
|
+
args = after
|
|
30
|
+
name = name.substring(1)
|
|
31
|
+
} else {
|
|
32
|
+
args = before
|
|
33
|
+
}
|
|
34
|
+
args.push(`--${toLongName(name)}`)
|
|
35
|
+
if (value !== null) {
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
args.push(...value)
|
|
38
|
+
} else {
|
|
39
|
+
args.push(value)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
if (parameters['--']) {
|
|
44
|
+
browser = parameters['--']
|
|
45
|
+
}
|
|
46
|
+
const stringify = args => args.map(value => value.toString())
|
|
47
|
+
return {
|
|
48
|
+
before: stringify(before),
|
|
49
|
+
after: stringify(after),
|
|
50
|
+
browser: stringify(browser)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function lib (value) {
|
|
55
|
+
if (value.includes('=')) {
|
|
56
|
+
const [relative, source] = value.split('=')
|
|
57
|
+
return { relative, source }
|
|
58
|
+
} else {
|
|
59
|
+
return { relative: '', source: value }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mapping (value) {
|
|
64
|
+
try {
|
|
65
|
+
const [, match, handler, mapping] = /([^=]*)=(file|url)\((.*)\)/.exec(value)
|
|
66
|
+
return {
|
|
67
|
+
match,
|
|
68
|
+
[handler]: mapping
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
throw new InvalidArgumentError('Invalid mapping')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getCommand (cwd) {
|
|
76
|
+
const command = new Command()
|
|
77
|
+
command.exitOverride()
|
|
78
|
+
|
|
79
|
+
const DEBUG_OPTION = '(๐ for debugging purpose)'
|
|
80
|
+
const EXPERIMENTAL_OPTION = '[โ ๏ธ experimental]'
|
|
81
|
+
|
|
82
|
+
command
|
|
83
|
+
.name(name)
|
|
84
|
+
.description(description)
|
|
85
|
+
.version(version)
|
|
86
|
+
|
|
87
|
+
.option('--capabilities', '๐งช Capabilities tester for browser')
|
|
88
|
+
.option('-u, --url <url...>', '๐ URL of the testsuite / page to test', arrayOf(url))
|
|
89
|
+
|
|
90
|
+
// Common to all modes
|
|
91
|
+
.addOption(
|
|
92
|
+
new Option('-c, --cwd <path>', '[๐ป๐๐งช] Set working directory')
|
|
93
|
+
.default(cwd, 'current working directory')
|
|
94
|
+
)
|
|
95
|
+
.option('--port <port>', '[๐ป๐๐งช] Port to use (0 to use any free one)', integer, 0)
|
|
96
|
+
.option('-r, --report-dir <path>', '[๐ป๐๐งช] Directory to output test reports (relative to cwd)', 'report')
|
|
97
|
+
.option('-pt, --page-timeout <timeout>', '[๐ป๐๐งช] Limit the page execution time, fails the page if it takes longer than the timeout (0 means no timeout)', timeout, 0)
|
|
98
|
+
.option('-f, --fail-fast [flag]', '[๐ป๐๐งช] Stop the execution after the first failing page', boolean, false)
|
|
99
|
+
.option('-fo, --fail-opa-fast [flag]', '[๐ป๐] Stop the OPA page execution after the first failing test', boolean, false)
|
|
100
|
+
.option('-k, --keep-alive [flag]', '[๐ป๐๐งช] Keep the server alive', boolean, false)
|
|
101
|
+
.option('-l, --log-server [flag]', '[๐ป๐๐งช] Log inner server traces', boolean, false)
|
|
102
|
+
.option('-p, --parallel <count>', '[๐ป๐๐งช] Number of parallel tests executions', integer, 2)
|
|
103
|
+
.option('-b, --browser <command>', '[๐ป๐๐งช] Browser instantiation command (relative to cwd or use $/ for provided ones)', '$/puppeteer.js')
|
|
104
|
+
.option('--browser-args <argument...>', '[๐ป๐๐งช] Browser instantiation command parameters (use -- instead)')
|
|
105
|
+
.option('--alternate-npm-path <path>', '[๐ป๐] Alternate NPM path to look for packages (priority: local, alternate, global)')
|
|
106
|
+
.option('--no-npm-install', '[๐ป๐๐งช] Prevent any NPM install (execution may fail if a dependency is missing)')
|
|
107
|
+
.option('-bt, --browser-close-timeout <timeout>', '[๐ป๐๐งช] Maximum waiting time for browser close', timeout, 2000)
|
|
108
|
+
.option('-br, --browser-retry <count>', '[๐ป๐๐งช] Browser instantiation retries : if the command fails unexpectedly, it is re-executed (0 means no retry)', 1)
|
|
109
|
+
.option('-oi, --output-interval <interval>', '[๐ป๐๐งช] Interval for reporting progress on non interactive output (CI/CD) (0 means no output)', timeout, 30000)
|
|
110
|
+
.option('--offline', '[๐ป๐๐งช] Limit network usage (implies --no-npm-install)', boolean, false)
|
|
111
|
+
|
|
112
|
+
// Common to legacy and url
|
|
113
|
+
.option('--webapp <path>', '[๐ป๐] Base folder of the web application (relative to cwd)', 'webapp')
|
|
114
|
+
.option('-pf, --page-filter <regexp>', '[๐ป๐] Filter out pages not matching the regexp')
|
|
115
|
+
.option('-pp, --page-params <params>', '[๐ป๐] Add parameters to page URL')
|
|
116
|
+
.option('--page-close-timeout <timeout>', '[๐ป๐] Maximum waiting time for page close', timeout, 250)
|
|
117
|
+
.option('-t, --global-timeout <timeout>', '[๐ป๐] Limit the pages execution time, fail the page if it takes longer than the timeout (0 means no timeout)', timeout, 0)
|
|
118
|
+
.option('--screenshot [flag]', '[๐ป๐] Take screenshots during the tests execution (if supported by the browser)', boolean, true)
|
|
119
|
+
.option('--no-screenshot', '[๐ป๐] Disable screenshots')
|
|
120
|
+
.option('-st, --screenshot-timeout <timeout>', '[๐ป๐] Maximum waiting time for browser screenshot', timeout, 5000)
|
|
121
|
+
.option('-so, --split-opa', '[๐ป๐] Split OPA tests using QUnit modules', boolean, false)
|
|
122
|
+
.option('-rg, --report-generator <path...>', '[๐ป๐] Report generator paths (relative to cwd or use $/ for provided ones)', ['$/report.js'])
|
|
123
|
+
.option('--progress-page <path>', '[๐ป๐] Progress page path (relative to cwd or use $/ for provided ones)', '$/report/default.html')
|
|
124
|
+
|
|
125
|
+
.option('--coverage [flag]', '[๐ป๐] Enable or disable code coverage', boolean)
|
|
126
|
+
.option('--no-coverage', '[๐ป๐] Disable code coverage')
|
|
127
|
+
.option('-cs, --coverage-settings <path>', '[๐ป๐] Path to a custom nyc.json file providing settings for instrumentation (relative to cwd or use $/ for provided ones)', '$/nyc.json')
|
|
128
|
+
.option('-ctd, --coverage-temp-dir <path>', '[๐ป๐] Directory to output raw coverage information to (relative to cwd)', '.nyc_output')
|
|
129
|
+
.option('-crd, --coverage-report-dir <path>', '[๐ป๐] Directory to store the coverage report files (relative to cwd)', 'coverage')
|
|
130
|
+
.option('-cr, --coverage-reporters <reporter...>', '[๐ป๐] List of nyc reporters to use (text is always used)', ['lcov', 'cobertura'])
|
|
131
|
+
.option('-ccb, --coverage-check-branches <percent>', '[๐ป๐] What % of branches must be covered', percent, 0)
|
|
132
|
+
.option('-ccf, --coverage-check-functions <percent>', '[๐ป๐] What % of functions must be covered', percent, 0)
|
|
133
|
+
.option('-ccl, --coverage-check-lines <percent>', '[๐ป๐] What % of lines must be covered', percent, 0)
|
|
134
|
+
.option('-ccs, --coverage-check-statements <percent>', '[๐ป๐] What % of statements must be covered', percent, 0)
|
|
135
|
+
.option('-crs, --coverage-remote-scanner <path>', '[๐ป๐] Scan for files when all coverage is requested', '$/scan-ui5.js')
|
|
136
|
+
.option('-s, --serve-only [flag]', '[๐ป๐] Serve only', boolean, false)
|
|
137
|
+
|
|
138
|
+
// Specific to legacy (and might be used with url if pointing to local project)
|
|
139
|
+
.option('--ui5 <url>', '[๐ป] UI5 url', url, 'https://ui5.sap.com')
|
|
140
|
+
.option('--disable-ui5', '[๐ป] Disable UI5 mapping (also disable libs)', boolean, false)
|
|
141
|
+
.option('--libs <lib...>', '[๐ป] Library mapping (<relative>=<path> or <path>)', arrayOf(lib))
|
|
142
|
+
.option('--mappings <mapping...>', '[๐ป] Custom mapping (<match>=<file|url>(<config>))', arrayOf(mapping))
|
|
143
|
+
.option('--cache <path>', '[๐ป] Cache UI5 resources locally in the given folder (empty to disable)')
|
|
144
|
+
.option('--preload <library...>', '[๐ป] Preload UI5 libraries in the cache folder (only if --cache is used)', arrayOf(string))
|
|
145
|
+
.option('--testsuite <path>', '[๐ป] Path of the testsuite file (relative to webapp, URL parameters are supported)', 'test/testsuite.qunit.html')
|
|
146
|
+
.option('-w, --watch [flag]', '[๐ป] Monitor the webapp folder and re-execute tests on change', boolean, false)
|
|
147
|
+
|
|
148
|
+
// Specific to coverage in url mode (experimental)
|
|
149
|
+
.option('-cp, --coverage-proxy [flag]', `[๐] ${EXPERIMENTAL_OPTION} use internal proxy to instrument remote files`, boolean, false)
|
|
150
|
+
.option('-cpi, --coverage-proxy-include <regexp>', `[๐] ${EXPERIMENTAL_OPTION} urls to instrument for coverage`, regex, '.*')
|
|
151
|
+
.option('-cpe, --coverage-proxy-exclude <regexp>', `[๐] ${EXPERIMENTAL_OPTION} urls to ignore for coverage`, regex, '/((test-)?resources|tests?)/')
|
|
152
|
+
|
|
153
|
+
.addOption(new Option('--debug-dev-mode', DEBUG_OPTION, boolean).hideHelp())
|
|
154
|
+
.addOption(new Option('--debug-probe-only', DEBUG_OPTION, boolean).hideHelp())
|
|
155
|
+
.addOption(new Option('--debug-keep-browser-open', DEBUG_OPTION, boolean).hideHelp())
|
|
156
|
+
.addOption(new Option('--debug-memory', DEBUG_OPTION, boolean).hideHelp())
|
|
157
|
+
.addOption(new Option('--debug-keep-report', DEBUG_OPTION, boolean).hideHelp())
|
|
158
|
+
.addOption(new Option('--debug-capabilities-test <name>', DEBUG_OPTION).hideHelp())
|
|
159
|
+
.addOption(new Option('--debug-capabilities-no-timeout', DEBUG_OPTION, boolean).hideHelp())
|
|
160
|
+
.addOption(new Option('--debug-capabilities-no-script', DEBUG_OPTION, boolean).hideHelp())
|
|
161
|
+
.addOption(new Option('--debug-coverage-no-custom-fs', DEBUG_OPTION, boolean).hideHelp())
|
|
162
|
+
.addOption(new Option('--debug-verbose <module...>', DEBUG_OPTION, arrayOf(string), []).hideHelp())
|
|
163
|
+
|
|
164
|
+
return command
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parse (cwd, args) {
|
|
168
|
+
const command = getCommand(cwd)
|
|
169
|
+
|
|
170
|
+
command.parse(args, { from: 'user' })
|
|
171
|
+
const options = command.opts()
|
|
172
|
+
|
|
173
|
+
return Object.assign({
|
|
174
|
+
initialCwd: cwd,
|
|
175
|
+
browserArgs: command.args,
|
|
176
|
+
[$valueSources]: Object.keys(options).reduce((valueSources, name) => {
|
|
177
|
+
if (name !== 'browserArgs') {
|
|
178
|
+
valueSources[name] = command.getOptionValueSource(name)
|
|
179
|
+
}
|
|
180
|
+
return valueSources
|
|
181
|
+
}, {})
|
|
182
|
+
}, options)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function checkAccess ({ path, label, file /*, write */ }) {
|
|
186
|
+
try {
|
|
187
|
+
const mode = constants.R_OK
|
|
188
|
+
// if (write) {
|
|
189
|
+
// mode |= constants.W_OK
|
|
190
|
+
// }
|
|
191
|
+
accessSync(path, mode)
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new Error(`Unable to access ${label}, check your settings`)
|
|
194
|
+
}
|
|
195
|
+
const stat = statSync(path)
|
|
196
|
+
if (file) {
|
|
197
|
+
if (!stat.isFile()) {
|
|
198
|
+
throw new Error(`Unable to access ${label}, file expected`)
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
if (!stat.isDirectory()) {
|
|
202
|
+
throw new Error(`Unable to access ${label}, folder expected`)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function finalize (job) {
|
|
208
|
+
function toAbsolute (path, from = job.cwd) {
|
|
209
|
+
if (!isAbsolute(path)) {
|
|
210
|
+
return join(from, path)
|
|
211
|
+
}
|
|
212
|
+
return path
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function checkDefault (path) {
|
|
216
|
+
if (path.startsWith('$/')) {
|
|
217
|
+
return join(__dirname, './defaults', path.replace('$/', ''))
|
|
218
|
+
}
|
|
219
|
+
return path
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function updateToAbsolute (member, from = job.cwd) {
|
|
223
|
+
job[member] = toAbsolute(job[member], from)
|
|
224
|
+
}
|
|
225
|
+
'browser,coverageSettings,coverageRemoteScanner,progressPage'
|
|
226
|
+
.split(',')
|
|
227
|
+
.forEach(setting => { job[setting] = checkDefault(job[setting]) })
|
|
228
|
+
updateToAbsolute('cwd', job.initialCwd)
|
|
229
|
+
'webapp,browser,reportDir,coverageSettings,coverageTempDir,coverageReportDir'
|
|
230
|
+
.split(',')
|
|
231
|
+
.forEach(setting => updateToAbsolute(setting))
|
|
232
|
+
if (job.cache) {
|
|
233
|
+
updateToAbsolute('cache')
|
|
234
|
+
if (job.preload && job.offline) {
|
|
235
|
+
throw new Error('--preload cannot be used with --offline')
|
|
236
|
+
}
|
|
237
|
+
} else if (job.preload) {
|
|
238
|
+
throw new Error('--preload cannot be used without --cache')
|
|
239
|
+
}
|
|
240
|
+
if (job.alternateNpmPath) {
|
|
241
|
+
checkAccess({ path: job.alternateNpmPath, label: 'Alternate NPM path' })
|
|
242
|
+
}
|
|
243
|
+
job.mode = buildAndCheckMode(job)
|
|
244
|
+
if (job.mode === 'legacy') {
|
|
245
|
+
checkAccess({ path: job.webapp, label: 'webapp folder' })
|
|
246
|
+
|
|
247
|
+
const [, testsuiteFile] = job.testsuite.match(/([^?]*)(\?.*)?$/)
|
|
248
|
+
const testsuitePath = toAbsolute(testsuiteFile, job.webapp)
|
|
249
|
+
checkAccess({ path: testsuitePath, label: 'testsuite', file: true })
|
|
250
|
+
} else if (job.mode === 'url') {
|
|
251
|
+
if (job[$valueSources].coverage !== 'cli') {
|
|
252
|
+
job.coverage = false
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
checkAccess({ path: job.browser, label: 'browser command', file: true })
|
|
256
|
+
job.reportGenerator = job.reportGenerator.map(setting => {
|
|
257
|
+
const path = toAbsolute(checkDefault(setting), job.cwd)
|
|
258
|
+
checkAccess({ path, label: 'report generator', file: true })
|
|
259
|
+
return path
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
if (!job.libs) {
|
|
263
|
+
job.libs = []
|
|
264
|
+
} else {
|
|
265
|
+
job.libs.forEach(libMapping => {
|
|
266
|
+
libMapping.source = toAbsolute(libMapping.source)
|
|
267
|
+
let description
|
|
268
|
+
if (libMapping.relative) {
|
|
269
|
+
description = `lib mapping of ${libMapping.relative}`
|
|
270
|
+
} else {
|
|
271
|
+
description = 'generic lib mapping'
|
|
272
|
+
}
|
|
273
|
+
checkAccess({ path: libMapping.source, label: `${description} (${libMapping.source})` })
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const output = getOutput(job)
|
|
278
|
+
|
|
279
|
+
if (job.coverage) {
|
|
280
|
+
function overrideIfNotSet (option, valueFromSettings) {
|
|
281
|
+
if (valueFromSettings && job[$valueSources][option] !== 'cli') {
|
|
282
|
+
output.debug('coverage', `${option} extracted from nyc settings : ${valueFromSettings}`)
|
|
283
|
+
job[option] = valueFromSettings
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function overrideDirIfNotSet (option, valueFromSettings) {
|
|
288
|
+
if (valueFromSettings && !isAbsolute(valueFromSettings)) {
|
|
289
|
+
valueFromSettings = join(dirname(job.coverageSettings), valueFromSettings)
|
|
290
|
+
}
|
|
291
|
+
overrideIfNotSet(option, valueFromSettings)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
checkAccess({ path: job.coverageSettings, file: true, label: 'coverage settings' })
|
|
295
|
+
|
|
296
|
+
let settings
|
|
297
|
+
try {
|
|
298
|
+
settings = require(job.coverageSettings)
|
|
299
|
+
} catch (e) {
|
|
300
|
+
throw new Error(`Unable to read ${job.coverageSettings} as JSON`)
|
|
301
|
+
}
|
|
302
|
+
overrideDirIfNotSet('coverageReportDir', settings['report-dir'])
|
|
303
|
+
overrideDirIfNotSet('coverageTempDir', settings['temp-dir'])
|
|
304
|
+
overrideIfNotSet('coverageReporters', settings.reporter)
|
|
305
|
+
|
|
306
|
+
checkAccess({ path: job.coverageRemoteScanner, label: 'coverage remote scanner', file: true })
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (job.mode === 'url') {
|
|
310
|
+
const port = job.port.toString()
|
|
311
|
+
job[$remoteOnLegacy] = job.url.every(url => {
|
|
312
|
+
// ignore host name since the machine might be exposed with any name
|
|
313
|
+
const parsedUrl = new URL(url)
|
|
314
|
+
return parsedUrl.port === port
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
job[$status] = 'Starting'
|
|
319
|
+
Object.defineProperty(job, 'status', {
|
|
320
|
+
get () {
|
|
321
|
+
return job[$status]
|
|
322
|
+
},
|
|
323
|
+
set (value) {
|
|
324
|
+
job[$status] = value
|
|
325
|
+
output.status(value)
|
|
326
|
+
},
|
|
327
|
+
enumerable: false,
|
|
328
|
+
configurable: false
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
/* istanbul ignore next */
|
|
332
|
+
if (process.env.DEBUG_ON_FAILED) {
|
|
333
|
+
let failed
|
|
334
|
+
Object.defineProperty(job, 'failed', {
|
|
335
|
+
get () {
|
|
336
|
+
return failed
|
|
337
|
+
},
|
|
338
|
+
set (value) {
|
|
339
|
+
if (value) {
|
|
340
|
+
// eslint-disable-next-line no-debugger
|
|
341
|
+
debugger
|
|
342
|
+
}
|
|
343
|
+
failed = value
|
|
344
|
+
},
|
|
345
|
+
enumerable: true,
|
|
346
|
+
configurable: false
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function fromCmdLine (cwd, args) {
|
|
352
|
+
let job = parse(cwd, args)
|
|
353
|
+
|
|
354
|
+
let defaultPath = join(job.cwd, 'ui5-test-runner.json')
|
|
355
|
+
if (!isAbsolute(defaultPath)) {
|
|
356
|
+
defaultPath = join(job.initialCwd, defaultPath)
|
|
357
|
+
}
|
|
358
|
+
let hasDefaultSettings = false
|
|
359
|
+
try {
|
|
360
|
+
checkAccess({ path: defaultPath, file: true })
|
|
361
|
+
hasDefaultSettings = true
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// ignore
|
|
364
|
+
}
|
|
365
|
+
if (hasDefaultSettings) {
|
|
366
|
+
const defaults = require(defaultPath)
|
|
367
|
+
const { before, after, browser } = buildArgs(defaults)
|
|
368
|
+
const sep = args.indexOf('--')
|
|
369
|
+
if (sep === -1) {
|
|
370
|
+
args = [...before, ...args, ...after, '--', ...browser]
|
|
371
|
+
} else {
|
|
372
|
+
args = [...before, ...args.slice(0, sep), ...after, '--', ...browser, ...args.slice(sep + 1)]
|
|
373
|
+
}
|
|
374
|
+
job = parse(cwd, args)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
finalize(job)
|
|
378
|
+
return job
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function fromObject (cwd, parameters) {
|
|
382
|
+
const { before, browser } = buildArgs(parameters)
|
|
383
|
+
if (browser.length) {
|
|
384
|
+
return fromCmdLine(cwd, [...before, '--', ...browser])
|
|
385
|
+
}
|
|
386
|
+
return fromCmdLine(cwd, [...before])
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
module.exports = {
|
|
390
|
+
getCommand,
|
|
391
|
+
fromCmdLine,
|
|
392
|
+
fromObject
|
|
393
|
+
}
|