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/job.js CHANGED
@@ -1,57 +1,186 @@
1
1
  'use strict'
2
2
 
3
- const { accessSync } = require('fs')
3
+ const { Command, Option, InvalidArgumentError } = require('commander')
4
+ const { statSync, accessSync, constants } = require('fs')
4
5
  const { join, isAbsolute } = require('path')
5
- const output = require('./output')
6
+ const { name, description, version } = require(join(__dirname, '../package.json'))
7
+ const { getOutput } = require('./output')
8
+ const { $valueSources } = require('./symbols')
9
+ const { buildAndCheckMode } = require('./job-mode')
10
+ const { boolean, integer, timeout, url, arrayOf } = require('./options')
6
11
 
7
- function allocate (cwd) {
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())
8
47
  return {
9
- initialCwd: cwd,
10
- cwd,
11
- port: 0,
12
- ui5: 'https://ui5.sap.com',
13
- libs: [],
14
- cache: '',
15
- webapp: 'webapp',
16
- testsuite: 'test/testsuite.qunit.html',
17
- pageFilter: '',
18
- pageParams: '',
19
- pageTimeout: 0,
20
- globalTimeout: 0,
21
- failFast: false,
22
- keepAlive: false,
23
- watch: false,
24
- logServer: false,
25
-
26
- browser: join(__dirname, '../defaults/chromium.js'),
27
- browserRetry: 1,
28
- noScreenshot: false,
29
- args: '__URL__ __REPORT__',
30
-
31
- parallel: 2,
32
- tstReportDir: 'report',
33
-
34
- coverage: true,
35
- covSettings: join(__dirname, '../defaults/nyc.json'),
36
- covTempDir: '.nyc_output',
37
- covReportDir: 'coverage',
38
- covReporters: 'lcov,cobertura'
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 }
39
60
  }
40
61
  }
41
62
 
42
- function checkAccess (path, label) {
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
+
81
+ command
82
+ .name(name)
83
+ .description(description)
84
+ .version(version)
85
+
86
+ .option('--capabilities', '🧪 Capabilities tester for browser')
87
+ .option('-u, --url <url...>', '🔗 URL of the testsuite / page to test', arrayOf(url))
88
+
89
+ // Common to all modes
90
+ .addOption(
91
+ new Option('-c, --cwd <path>', '[💻🔗🧪] Set working directory')
92
+ .default(cwd, 'current working directory')
93
+ )
94
+ .option('--port <port>', '[💻🔗🧪] Port to use (0 to use any free one)', integer, 0)
95
+ .option('-r, --report-dir <path>', '[💻🔗🧪] Directory to output test reports (relative to cwd)', 'report')
96
+ .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)
97
+ .option('-f, --fail-fast [flag]', '[💻🔗🧪] Stop the execution after the first failing page', boolean, false)
98
+ .option('-k, --keep-alive [flag]', '[💻🔗🧪] Keep the server alive', boolean, false)
99
+ .option('-l, --log-server [flag]', '[💻🔗🧪] Log inner server traces', boolean, false)
100
+ .option('-p, --parallel <count>', '[💻🔗🧪] Number of parallel tests executions', 2)
101
+ .option('-b, --browser <command>', '[💻🔗🧪] Browser instantiation command (relative to cwd or use $/ for provided ones)', '$/puppeteer.js')
102
+ .option('--browser-args <argument...>', '[💻🔗🧪] Browser instantiation command parameters (use -- instead)')
103
+ .option('--no-npm-install', '[💻🔗🧪] Prevent any NPM install (execution may fail if a dependency is missing)')
104
+ .option('-bt, --browser-close-timeout <timeout>', '[💻🔗🧪] Maximum waiting time for browser close', timeout, 2000)
105
+ .option('-br, --browser-retry <count>', '[💻🔗🧪] Browser instantiation retries : if the command fails unexpectedly, it is re-executed (0 means no retry)', 1)
106
+
107
+ // Common to legacy and testing
108
+ .option('-pf, --page-filter <regexp>', '[💻🔗] Filter out pages not matching the regexp')
109
+ .option('-pp, --page-params <params>', '[💻🔗] Add parameters to page URL')
110
+ .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)
111
+ .option('--screenshot [flag]', '[💻🔗] Take screenshots during the tests execution (if supported by the browser)', boolean, true)
112
+ .option('--no-screenshot', '[💻🔗] Disable screenshots')
113
+ .option('-st, --screenshot-timeout <timeout>', '[💻🔗] Maximum waiting time for browser screenshot', timeout, 5000)
114
+ .option('-rg, --report-generator <path...>', '[💻🔗] Report generator paths (relative to cwd or use $/ for provided ones)', ['$/report.js'])
115
+ .option('-pp, --progress-page <path>', '[💻🔗] progress page path (relative to cwd or use $/ for provided ones)', '$/report/default.html')
116
+
117
+ // Specific to legacy
118
+ .option('--ui5 <url>', '[💻] UI5 url', url, 'https://ui5.sap.com')
119
+ .option('--libs <lib...>', '[💻] Library mapping (<relative>=<path> or <path>)', arrayOf(lib))
120
+ .option('--mappings <mapping...>', '[💻] Custom mapping (<match>=<file|url>(<config>))', arrayOf(mapping))
121
+ .option('--cache <path>', '[💻] Cache UI5 resources locally in the given folder (empty to disable)')
122
+ .option('--webapp <path>', '[💻] Base folder of the web application (relative to cwd)', 'webapp')
123
+ .option('--testsuite <path>', '[💻] Path of the testsuite file (relative to webapp)', 'test/testsuite.qunit.html')
124
+ .option('-s, --serve-only [flag]', '[💻] Serve only', boolean, false)
125
+ .option('-w, --watch [flag]', '[💻] Monitor the webapp folder and re-execute tests on change', boolean, false)
126
+ .option('--coverage [flag]', '[💻] Enable or disable code coverage', boolean)
127
+ .option('--no-coverage', '[💻] Disable code coverage')
128
+ .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')
129
+ .option('-ct, --coverage-temp-dir <path>', '[💻] Directory to output raw coverage information to (relative to cwd)', '.nyc_output')
130
+ .option('-cr, --coverage-report-dir <path>', '[💻] Directory to store the coverage report files (relative to cwd)', 'coverage')
131
+ .option('-cr, --coverage-reporters <reporter...>', '[💻] List of nyc reporters to use', ['lcov', 'cobertura'])
132
+
133
+ .addOption(new Option('--debug-probe-only', DEBUG_OPTION, boolean).hideHelp())
134
+ .addOption(new Option('--debug-keep-browser-open', DEBUG_OPTION, boolean).hideHelp())
135
+ .addOption(new Option('--debug-memory', DEBUG_OPTION, boolean).hideHelp())
136
+ .addOption(new Option('--debug-keep-report', DEBUG_OPTION, boolean).hideHelp())
137
+ .addOption(new Option('--debug-capabilities-test <name>', DEBUG_OPTION).hideHelp())
138
+ .addOption(new Option('--debug-capabilities-no-timeout', DEBUG_OPTION, boolean).hideHelp())
139
+
140
+ return command
141
+ }
142
+
143
+ function parse (cwd, args) {
144
+ const command = getCommand(cwd)
145
+
146
+ command.parse(args, { from: 'user' })
147
+ const options = command.opts()
148
+
149
+ return Object.assign({
150
+ initialCwd: cwd,
151
+ browserArgs: command.args,
152
+ [$valueSources]: Object.keys(options).reduce((valueSources, name) => {
153
+ if (name !== 'browserArgs') {
154
+ valueSources[name] = command.getOptionValueSource(name)
155
+ }
156
+ return valueSources
157
+ }, {})
158
+ }, options)
159
+ }
160
+
161
+ function checkAccess ({ path, label, file /*, write */ }) {
43
162
  try {
44
- accessSync(path)
163
+ const mode = constants.R_OK
164
+ // if (write) {
165
+ // mode |= constants.W_OK
166
+ // }
167
+ accessSync(path, mode)
45
168
  } catch (error) {
46
169
  throw new Error(`Unable to access ${label}, check your settings`)
47
170
  }
171
+ const stat = statSync(path)
172
+ if (file) {
173
+ if (!stat.isFile()) {
174
+ throw new Error(`Unable to access ${label}, file expected`)
175
+ }
176
+ } else {
177
+ if (!stat.isDirectory()) {
178
+ throw new Error(`Unable to access ${label}, folder expected`)
179
+ }
180
+ }
48
181
  }
49
182
 
50
183
  function finalize (job) {
51
- Object.keys(job)
52
- .filter(name => name.startsWith('!'))
53
- .forEach(name => { job[name.substring(1)] = job[name] })
54
-
55
184
  function toAbsolute (path, from = job.cwd) {
56
185
  if (!isAbsolute(path)) {
57
186
  return join(from, path)
@@ -59,93 +188,110 @@ function finalize (job) {
59
188
  return path
60
189
  }
61
190
 
191
+ function checkDefault (path) {
192
+ if (path.startsWith('$/')) {
193
+ return join(__dirname, './defaults', path.replace('$/', ''))
194
+ }
195
+ return path
196
+ }
197
+
62
198
  function updateToAbsolute (member, from = job.cwd) {
63
199
  job[member] = toAbsolute(job[member], from)
64
200
  }
65
-
201
+ 'browser,coverageSettings,progressPage'
202
+ .split(',')
203
+ .forEach(setting => { job[setting] = checkDefault(job[setting]) })
66
204
  updateToAbsolute('cwd', job.initialCwd)
67
- 'webapp,browser,tstReportDir,covSettings,covTempDir,covReportDir'
205
+ 'webapp,browser,reportDir,coverageSettings,coverageTempDir,coverageReportDir'
68
206
  .split(',')
69
207
  .forEach(setting => updateToAbsolute(setting))
70
- checkAccess(job.webapp, 'webapp folder')
71
- checkAccess(job.browser, 'browser command')
72
-
73
- const testsuitePath = toAbsolute(job.testsuite, job.webapp)
74
- checkAccess(testsuitePath, 'testsuite')
75
-
76
- job.libs.forEach(libMapping => {
77
- libMapping.source = toAbsolute(libMapping.source)
78
- checkAccess(libMapping.source, `lib mapping of ${libMapping.relative}`)
208
+ if (job.cache) {
209
+ updateToAbsolute('cache')
210
+ }
211
+ job.mode = buildAndCheckMode(job)
212
+ if (job.mode === 'legacy') {
213
+ checkAccess({ path: job.webapp, label: 'webapp folder' })
214
+ const testsuitePath = toAbsolute(job.testsuite, job.webapp)
215
+ checkAccess({ path: testsuitePath, label: 'testsuite', file: true })
216
+ } else if (job.mode === 'url') {
217
+ if (job[$valueSources].coverage !== 'cli') {
218
+ job.coverage = false
219
+ }
220
+ }
221
+ checkAccess({ path: job.browser, label: 'browser command', file: true })
222
+ job.reportGenerator = job.reportGenerator.map(setting => {
223
+ const path = toAbsolute(checkDefault(setting), job.cwd)
224
+ checkAccess({ path, label: 'report generator', file: true })
225
+ return path
79
226
  })
80
227
 
81
- if (job.parallel <= 0) {
82
- job.keepAlive = true
228
+ if (!job.libs) {
229
+ job.libs = []
230
+ } else {
231
+ job.libs.forEach(libMapping => {
232
+ libMapping.source = toAbsolute(libMapping.source)
233
+ let description
234
+ if (libMapping.relative) {
235
+ description = `lib mapping of ${libMapping.relative}`
236
+ } else {
237
+ description = 'generic lib mapping'
238
+ }
239
+ checkAccess({ path: libMapping.source, label: `${description} (${libMapping.source})` })
240
+ })
83
241
  }
84
242
 
85
- if (job.browserRetry < 0) {
86
- output.unexpectedOptionValue('browserRetry', 'defaulting to 1')
87
- job.browserRetry = 1
88
- }
243
+ const output = getOutput(job)
244
+ job[$status] = 'Starting'
245
+ Object.defineProperty(job, 'status', {
246
+ get () {
247
+ return job[$status]
248
+ },
249
+ set (value) {
250
+ job[$status] = value
251
+ output.status(value)
252
+ },
253
+ enumerable: false,
254
+ configurable: false
255
+ })
89
256
  }
90
257
 
91
- function parseJobParam (job, arg) {
92
- const valueParsers = {
93
- boolean: (value, defaultValue) => value === undefined ? !defaultValue : value === 'true',
94
- number: value => parseInt(value, 10),
95
- default: value => { if (!value) { throw new Error('must not be empty') } return value }
96
- }
258
+ function fromCmdLine (cwd, args) {
259
+ let job = parse(cwd, args)
97
260
 
98
- const paramParser = {
99
- libs: (value, defaultValue) => {
100
- if (value.includes('=')) {
101
- const [relative, source] = value.split('=')
102
- defaultValue.push({ relative, source })
103
- } else {
104
- defaultValue.push({ relative: '', source: value })
105
- }
106
- return defaultValue
261
+ const defaultPath = join(job.cwd, 'ui5-test-runner.json')
262
+ let hasDefaultSettings = false
263
+ try {
264
+ checkAccess({ path: defaultPath, file: true })
265
+ hasDefaultSettings = true
266
+ } catch (e) {
267
+ // ignore
268
+ }
269
+ if (hasDefaultSettings) {
270
+ const defaults = require(defaultPath)
271
+ const { before, after, browser } = buildArgs(defaults)
272
+ const sep = args.indexOf('--')
273
+ if (sep === -1) {
274
+ args = [...before, ...args, ...after, '--', ...browser]
275
+ } else {
276
+ args = [...before, ...args.slice(0, sep), ...after, '--', ...browser, ...args.slice(sep + 1)]
107
277
  }
278
+ job = parse(cwd, args)
108
279
  }
109
280
 
110
- const parsed = /-(\w+)(?::(.*))?/.exec(arg)
111
- if (parsed) {
112
- const [, name, value] = parsed
113
- if (Object.prototype.hasOwnProperty.call(job, name)) {
114
- const valueParser = paramParser[name] || valueParsers[typeof job[name]] || valueParsers.default
115
- try {
116
- job[name] = valueParser(value, job[name])
117
- } catch (error) {
118
- output.unexpectedOptionValue(name, error.message)
119
- }
120
- }
281
+ finalize(job)
282
+ return job
283
+ }
284
+
285
+ function fromObject (cwd, parameters) {
286
+ const { before, browser } = buildArgs(parameters)
287
+ if (browser.length) {
288
+ return fromCmdLine(cwd, [...before, '--', ...browser])
121
289
  }
290
+ return fromCmdLine(cwd, [...before])
122
291
  }
123
292
 
124
293
  module.exports = {
125
- fromCmdLine (cwd, argv) {
126
- const job = allocate(cwd)
127
- try {
128
- const defaults = require(join(cwd, 'ui5-test-runner.json'))
129
- Object.assign(job, defaults)
130
- } catch (e) {
131
- // ignore
132
- }
133
- let defaultHasLibs = job.libs.length !== 0
134
- let inBrowserParams = false
135
- argv.forEach(arg => {
136
- if (inBrowserParams) {
137
- job.args += ` ${arg}`
138
- } else if (arg === '--') {
139
- inBrowserParams = true
140
- } else {
141
- if (arg.toString().startsWith('-libs:') && defaultHasLibs) {
142
- defaultHasLibs = false
143
- job.libs = []
144
- }
145
- parseJobParam(job, arg)
146
- }
147
- })
148
- finalize(job)
149
- return job
150
- }
294
+ getCommand,
295
+ fromCmdLine,
296
+ fromObject
151
297
  }