ui5-test-runner 3.3.5 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +9 -16
  2. package/package.json +17 -18
  3. package/src/browsers.js +9 -1
  4. package/src/capabilities/index.js +6 -6
  5. package/src/capabilities/tests/screenshot/index.html +13 -3
  6. package/src/capabilities/tests/screenshot/index.js +11 -5
  7. package/src/capabilities/tests/traces/index.js +1 -1
  8. package/src/coverage.js +51 -42
  9. package/src/defaults/puppeteer.js +10 -20
  10. package/src/defaults/report/progress.js +11 -0
  11. package/src/endpoints.js +8 -0
  12. package/src/get-job-progress.js +9 -0
  13. package/src/inject/opa-iframe-coverage.js +0 -1
  14. package/src/inject/post.js +4 -4
  15. package/src/inject/qunit-hooks.js +18 -2
  16. package/src/inject/qunit-intercept.js +6 -0
  17. package/src/inject/ui5-coverage.js +6 -0
  18. package/src/job-mode.js +0 -1
  19. package/src/job.js +10 -10
  20. package/src/options.js +4 -0
  21. package/src/output.js +56 -24
  22. package/src/qunit-hooks.js +43 -29
  23. package/src/symbols.js +2 -1
  24. package/src/tests.js +6 -1
  25. package/src/add-test-pages.spec.js +0 -95
  26. package/src/browser.spec.js +0 -724
  27. package/src/cors.spec.js +0 -41
  28. package/src/coverage.spec.js +0 -120
  29. package/src/csv-reader.spec.js +0 -42
  30. package/src/csv-writer.spec.js +0 -77
  31. package/src/error.spec.js +0 -17
  32. package/src/get-job-progress.spec.js +0 -175
  33. package/src/inject/post.spec.js +0 -147
  34. package/src/job.spec.js +0 -418
  35. package/src/npm.spec.js +0 -98
  36. package/src/options.spec.js +0 -141
  37. package/src/qunit-hooks.spec.js +0 -747
  38. package/src/simulate.spec.js +0 -547
  39. package/src/timeout.spec.js +0 -39
  40. package/src/tools.spec.js +0 -90
  41. package/src/unhandled.spec.js +0 -63
package/README.md CHANGED
@@ -17,10 +17,9 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
17
17
 
18
18
  ## 📚 [Documentation](https://arnaudbuchholz.github.io/ui5-test-runner/)
19
19
 
20
-
21
20
  ## 💿 How to install
22
21
 
23
- * Works with [Node.js](https://nodejs.org/en/download/) >= 16
22
+ * Works with [Node.js](https://nodejs.org/en/download/) >= 18
24
23
  * Local installation
25
24
  * `npm install --save-dev ui5-test-runner`
26
25
  * Trigger either with `npx ui5-test-runner` or through an npm script invoking `ui5-test-runner`
@@ -30,25 +29,14 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
30
29
 
31
30
  **NOTE** : additional packages might be needed during the execution (`puppeteer`, `selenium-webdriver`, `nyc`...) . If they are found installed **locally** in the tested project, they are used. Otherwise, they are installed **globally**.
32
31
 
33
- ## 🖥️ How to demo
34
-
35
- * Clone the project [training-ui5con18-opa](https://github.com/ArnaudBuchholz/training-ui5con18-opa) and run `npm install`
36
- * Use `npm run karma` to test with the karma runner
37
- * *Serving the application (a.k.a. legacy mode)*
38
- * `npx ui5-test-runner --port 8081 --ui5 https://ui5.sap.com/1.109.0/ --cache .ui5 --keep-alive`
39
- * Follow the progress of the test executions using http://localhost:8081/_/progress.html
40
- * When the tests are completed, check the code coverage with http://localhost:8081/_/coverage/lcov-report/index.html
41
- * *Serving the application with `@ui5/cli`*
42
- * Use `npm start` to serve the application with `@ui5/cli`
43
- * `npx ui5-test-runner --port 8081 --url http://localhost:8080/test/testsuite.qunit.html --keep-alive`
44
- * Follow the progress of the test executions using http://localhost:8081/_/progress.html
45
-
46
-
47
32
  ## ⚖️ License
48
33
  [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner?ref=badge_large)
49
34
 
50
35
  ## ⚠️ Breaking changes
51
36
 
37
+ ### v4
38
+ * Dropping support of Node.js 16
39
+
52
40
  ### v3
53
41
  * Dropping support of Node.js 14
54
42
 
@@ -58,3 +46,8 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
58
46
  * Dependencies are installed **on demand**
59
47
  * Browser instantiation command evolved in an **incompatible way** (see [documentation](https://arnaudbuchholz.github.io/ui5-test-runner/browser.html)).
60
48
  * Output is different (report, traces)
49
+
50
+ ## ✒ Contributors
51
+
52
+ * [Marian Zeis](https://github.com/marianfoo): Documentation page revamp [PR #54](https://github.com/ArnaudBuchholz/ui5-test-runner/pull/54)
53
+ * [Raj Singh](https://github.com/rajxsingh): Basic HTTP Authentication in Puppeteer [PR #71](https://github.com/ArnaudBuchholz/ui5-test-runner/pull/71)
package/package.json CHANGED
@@ -1,26 +1,22 @@
1
1
  {
2
2
  "name": "ui5-test-runner",
3
- "version": "3.3.5",
3
+ "version": "4.1.0",
4
4
  "description": "Standalone test runner for UI5",
5
5
  "main": "index.js",
6
- "files": [
7
- "defaults/*",
8
- "src/**/*",
9
- "*.js"
10
- ],
11
6
  "bin": {
12
7
  "ui5-test-runner": "./index.js"
13
8
  },
14
9
  "engines": {
15
- "node": ">=16"
10
+ "node": ">=18"
16
11
  },
17
12
  "scripts": {
18
13
  "lint": "standard --fix",
19
14
  "test": "npm run test:unit && npm run test:browsers && npm run test:samples",
20
15
  "test:browsers": "npm run test:integration:jsdom && npm run test:integration:puppeteer && npm run test:integration:selenium-webdriver-chrome && npm run test:integration:playwright",
21
- "test:samples": "npm run test:samples:js && npm run test:samples:ts",
16
+ "test:samples": "npm run test:samples:js && npm run test:samples:ts && npm run test:auth-sample",
22
17
  "test:samples:js": "npm run test:sample:js:legacy && npm run test:sample:js:coverage:legacy && npm run test:sample:js:legacy-remote && npm run test:sample:js:coverage:legacy-remote && npm run test:sample:js:remote && npm run test:sample:js:coverage:remote",
23
18
  "test:samples:ts": "npm run test:sample:ts:remote && npm run test:sample:ts:coverage:remote",
19
+ "test:auth-sample": "npm run test:auth-sample:remote",
24
20
  "test:coverall": "rimraf .nyc_output && jest --coverageDirectory .nyc_output --coverageReporters json && nyc --silent --no-clean npm run test:integration:jsdom && nyc --silent --no-clean npm run test:integration:puppeteer && nyc --silent --no-clean npm run test:integration:selenium-webdriver-chrome && nyc --silent --no-clean npm run test:integration:playwright && nyc merge .nyc_output .nyc_output/final/coverage.json && nyc report --temp-dir .nyc_output/final/ --report-dir coverage --branches 80 --functions 80 --lines 80 --statements 80",
25
21
  "test:unit": "jest",
26
22
  "test:unit:debug": "jest --runInBand",
@@ -41,7 +37,10 @@
41
37
  "serve:sample:ts": "cd ./test/sample.ts && node ui5.cjs serve",
42
38
  "test:sample:ts:coverage:remote": "start-server-and-test 'npm run serve:sample:ts:coverage' http://localhost:8080 'node . --url http://localhost:8080/test/testsuite.qunit.html --coverage --coverage-check-statements 67'",
43
39
  "serve:sample:ts:coverage": "cd ./test/sample.ts && node ui5.cjs serve --config ui5-coverage.yaml",
44
- "build:doc": "node build/doc"
40
+ "test:auth-sample:remote": "start-server-and-test 'npm run serve:auth-sample' http://localhost:8080 'node . --url http://localhost:8080/test/testsuite.qunit.html --browser $/puppeteer.js --browser-args --basic-auth-username testUsername --browser-args --basic-auth-password testPassword'",
41
+ "serve:auth-sample": "cd ./test/auth_sample.js && reserve --config ./reserve.json",
42
+ "build:doc": "node build/doc",
43
+ "clean": "npm uninstall -g ui5-test-runner puppeteer nyc selenium-webdriver playwright webdriverio"
45
44
  },
46
45
  "repository": {
47
46
  "type": "git",
@@ -63,23 +62,23 @@
63
62
  },
64
63
  "homepage": "https://github.com/ArnaudBuchholz/ui5-test-runner#readme",
65
64
  "dependencies": {
66
- "commander": "^11.0.0",
65
+ "commander": "^12.0.0",
67
66
  "mime": "^3.0.0",
68
67
  "punybind": "^1.2.1",
69
68
  "punyexpr": "^1.0.4",
70
- "reserve": "^1.15.4"
69
+ "reserve": "^1.15.6"
71
70
  },
72
71
  "devDependencies": {
73
- "@openui5/types": "^1.119.0",
74
- "@ui5/cli": "^3.6.1",
75
- "@ui5/middleware-code-coverage": "^1.1.0",
72
+ "@openui5/types": "^1.120.7",
73
+ "@ui5/cli": "^3.9.1",
74
+ "@ui5/middleware-code-coverage": "^1.1.1",
76
75
  "jest": "^29.7.0",
77
- "nock": "^13.3.3",
76
+ "nock": "^13.5.1",
78
77
  "nyc": "^15.1.0",
79
78
  "standard": "^17.1.0",
80
- "start-server-and-test": "^2.0.1",
81
- "typescript": "^5.2.2",
82
- "ui5-tooling-transpile": "^3.2.6"
79
+ "start-server-and-test": "^2.0.3",
80
+ "typescript": "^5.3.3",
81
+ "ui5-tooling-transpile": "^3.3.3"
83
82
  },
84
83
  "optionalDependencies": {
85
84
  "fsevents": "^2.3.3"
package/src/browsers.js CHANGED
@@ -214,13 +214,16 @@ async function screenshot (job, url, filename) {
214
214
  if (!job.browserCapabilities.screenshot) {
215
215
  throw UTRError.BROWSER_SCREENSHOT_NOT_SUPPORTED()
216
216
  }
217
+ const output = getOutput(job)
218
+ const id = ++lastScreenshotId
217
219
  try {
218
220
  const { childProcess, reportDir } = job[$browsers][url]
219
221
  const absoluteFilename = join(reportDir, filename + job.browserCapabilities.screenshot)
220
222
  if (childProcess.connected) {
221
- const id = ++lastScreenshotId
223
+ output.debug('screenshot', id, url, absoluteFilename)
222
224
  const { promise, resolve, reject } = allocPromise()
223
225
  screenshots[id] = resolve
226
+ output.debug('screenshot', id, 'sending command')
224
227
  childProcess.send({
225
228
  id,
226
229
  command: 'screenshot',
@@ -229,15 +232,20 @@ async function screenshot (job, url, filename) {
229
232
  const timeoutId = setTimeout(() => {
230
233
  reject(UTRError.BROWSER_SCREENSHOT_TIMEOUT())
231
234
  }, job.screenshotTimeout)
235
+ output.debug('screenshot', id, 'command sent, waiting for answer')
232
236
  await promise
237
+ output.debug('screenshot', id, 'answer received')
233
238
  clearTimeout(timeoutId)
234
239
  const result = await stat(absoluteFilename)
240
+ output.debug('screenshot', id, 'file size :', result.size)
235
241
  if (!result.isFile() || result.size === 0) {
236
242
  throw new Error('File expected')
237
243
  }
244
+ output.debug('screenshot', id, 'done')
238
245
  return absoluteFilename
239
246
  }
240
247
  } catch (e) {
248
+ output.debug('screenshot', id, e.message)
241
249
  if (e.code === UTRError.BROWSER_SCREENSHOT_TIMEOUT_CODE) {
242
250
  throw e
243
251
  }
@@ -4,7 +4,6 @@ const { check, serve, body } = require('reserve')
4
4
  const { probe, start, stop } = require('../browsers')
5
5
  const { join } = require('path')
6
6
  const { getOutput } = require('../output')
7
- const EventEmitter = require('events')
8
7
  const { performance } = require('perf_hooks')
9
8
  const { cleanDir, allocPromise, filename } = require('../tools')
10
9
  const { $browsers } = require('../symbols')
@@ -60,13 +59,16 @@ async function capabilities (job) {
60
59
  const { referer, 'x-page-url': xPageUrl } = request.headers
61
60
  const listenerIndex = (xPageUrl || referer).match(/\blistener=(\d+)/)[1]
62
61
  const listener = listeners[listenerIndex]
63
- listener.emit('endpoint', {
62
+ await listener({
64
63
  endpoint,
65
64
  body: JSON.parse(await body(request))
66
65
  })
67
66
  response.writeHead(200)
68
67
  response.end()
69
68
  }
69
+ }, {
70
+ match: '^/inject/(.*)',
71
+ file: join(__dirname, '../inject/$1')
70
72
  }, {
71
73
  match: '^/(.*)',
72
74
  file: join(__dirname, '$1')
@@ -105,8 +107,6 @@ async function capabilities (job) {
105
107
  const { label, url, scripts, endpoint = () => { } } = filteredTests.shift()
106
108
 
107
109
  const listenerIndex = listeners.length
108
- const listener = new EventEmitter()
109
- listeners.push(listener)
110
110
  let pageUrl
111
111
  if (url.startsWith('http')) {
112
112
  pageUrl = url
@@ -151,7 +151,7 @@ async function capabilities (job) {
151
151
  const context = {
152
152
  job
153
153
  }
154
- listener.on('endpoint', async data => {
154
+ listeners[listenerIndex] = async data => {
155
155
  try {
156
156
  if (await endpoint.call(context, data, pageUrl) !== false) {
157
157
  done()
@@ -159,7 +159,7 @@ async function capabilities (job) {
159
159
  } catch (e) {
160
160
  done(e)
161
161
  }
162
- })
162
+ }
163
163
 
164
164
  start(job, pageUrl, scripts)
165
165
  .catch(reason => done(reason))
@@ -3,11 +3,21 @@
3
3
  <body>
4
4
  <H1>screenshot</H1>
5
5
  <p>Checks if the browser supports screenshot</p>
6
+ <input type="text" id="first" value="first">
7
+ <input type="text" id="second" value="second">
6
8
  <span style="font-size: 32rem;">&#128521;</span>
9
+ <script src="/inject/post.js"></script>
7
10
  <script>
8
- const xhr = new XMLHttpRequest()
9
- xhr.open('POST', '/_/log')
10
- xhr.send('{}')
11
+ document.getElementById('first').focus()
12
+ const post = window['ui5-test-runner/post']
13
+ post('/_/log', { step:'screenshot' })
14
+ .then(() => {
15
+ const current = document.activeElement
16
+ return post('/_/log', {
17
+ step: 'focus',
18
+ current: current ? current.id : ''
19
+ })
20
+ })
11
21
  </script>
12
22
  </body>
13
23
  </html>
@@ -8,11 +8,17 @@ module.exports = {
8
8
  label: 'Screenshot',
9
9
  for: capabilities => !!capabilities.screenshot,
10
10
  url: 'screenshot/index.html',
11
- endpoint: async function (_, url) {
11
+ endpoint: async function ({ body: { step, current } }, url) {
12
12
  const { job } = this
13
- const fileName = await screenshot(job, url, 'screenshot')
14
- const fileInfo = await stat(fileName)
15
- assert.ok(fileInfo.isFile(), 'The file was generated')
16
- assert.ok(fileInfo.size > 1024, 'The file contains something')
13
+ if (step === 'screenshot') {
14
+ const fileName = await screenshot(job, url, 'screenshot')
15
+ const fileInfo = await stat(fileName)
16
+ assert.ok(fileInfo.isFile(), 'The file was generated')
17
+ assert.ok(fileInfo.size > 1024, 'The file contains something')
18
+ return false
19
+ } else {
20
+ assert.strictEqual(current, 'first')
21
+ return true
22
+ }
17
23
  }
18
24
  }
@@ -17,7 +17,7 @@ const expectedLogs = [{
17
17
  type: 'log',
18
18
  text: /^"?complex parameters"? 1 true / // Not sure how objects are handled
19
19
  }, {
20
- type: 'warning',
20
+ type: /warning|warn/,
21
21
  text: 'A warning'
22
22
  }, {
23
23
  type: 'error',
package/src/coverage.js CHANGED
@@ -86,8 +86,31 @@ async function instrument (job) {
86
86
  await nyc(job, 'instrument', job.webapp, join(job.coverageTempDir, 'instrumented'), '--nycrc-path', job[$nycSettingsPath])
87
87
  }
88
88
 
89
+ async function getReadableSource (job, pathOrUrl) {
90
+ if (isAbsolute(pathOrUrl)) {
91
+ try {
92
+ await access(pathOrUrl, constants.R_OK)
93
+ return pathOrUrl
94
+ } catch (e) {}
95
+ }
96
+ try {
97
+ const filePath = join(job.webapp, pathOrUrl)
98
+ await access(filePath, constants.R_OK)
99
+ return filePath
100
+ } catch (e) {}
101
+ try {
102
+ // Assuming all files are coming from the same server
103
+ const { origin } = new URL(job.testPageUrls[0])
104
+ const filePath = join(job.coverageTempDir, 'sources', pathOrUrl)
105
+ await download(origin + pathOrUrl, filePath)
106
+ return filePath
107
+ } catch (e) {}
108
+ }
109
+
89
110
  async function generateCoverageReport (job) {
90
111
  job.status = 'Generating coverage report'
112
+ const output = getOutput(job)
113
+ output.debug('coverage', 'Generating coverage report...')
91
114
  await cleanDir(job.coverageReportDir)
92
115
  const coverageMergedDir = join(job.coverageTempDir, 'merged')
93
116
  await createDir(coverageMergedDir)
@@ -95,25 +118,17 @@ async function generateCoverageReport (job) {
95
118
  await nyc(job, 'merge', job.coverageTempDir, coverageFilename)
96
119
  if (job[$coverageRemote] && !job.coverageProxy) {
97
120
  job.status = 'Checking remote source files'
98
- // Assuming all files are coming from the same server
99
- const { origin } = new URL(job.testPageUrls[0])
100
- const sourcesBasePath = join(job.coverageTempDir, 'sources')
121
+ output.debug('coverage', 'Checking remote source files...')
101
122
  const coverageData = require(coverageFilename)
102
123
  const filenames = Object.keys(coverageData)
103
124
  let changes = 0
104
125
  for (const filename of filenames) {
105
126
  const fileData = coverageData[filename]
106
- const { path } = fileData
107
- if (isAbsolute(path)) {
108
- try {
109
- await access(path, constants.R_OK)
110
- continue
111
- } catch (e) {}
127
+ const filePath = await getReadableSource(job, fileData.path)
128
+ if (filePath && filePath !== fileData.path) {
129
+ fileData.path = filePath
130
+ ++changes
112
131
  }
113
- const filePath = join(sourcesBasePath, path)
114
- fileData.path = filePath
115
- await download(origin + path, filePath)
116
- ++changes
117
132
  }
118
133
  if (changes > 0) {
119
134
  await writeFile(coverageFilename, JSON.stringify(coverageData))
@@ -151,9 +166,7 @@ module.exports = {
151
166
  async collect (job, url, coverageData) {
152
167
  job[$coverageFileIndex] = (job[$coverageFileIndex] || 0) + 1
153
168
  const coverageFileName = join(job.coverageTempDir, `${filename(url)}_${job[$coverageFileIndex]}.json`)
154
- if (job.debugCoverage) {
155
- getOutput(job).wrap(() => console.log('coverage', coverageFileName))
156
- }
169
+ getOutput(job).debug('coverage', `saved coverage in '${coverageFileName}'`)
157
170
  await writeFile(coverageFileName, JSON.stringify(coverageData))
158
171
  },
159
172
  generateCoverageReport: job => job.coverage ? generateCoverageReport(job) : Promise.resolve(),
@@ -175,8 +188,8 @@ module.exports = {
175
188
  }
176
189
  if (job.mode === 'url' && job.coverageProxy) {
177
190
  await setupNyc(job)
191
+ // Assuming all files are coming from the same server
178
192
  const { origin } = new URL(job.url[0])
179
- const sourcesBasePath = join(job.coverageTempDir, 'sources')
180
193
  const { createInstrumenter } = require(join(await nycInstallationPath, 'node_modules/istanbul-lib-instrument'))
181
194
  const instrumenter = createInstrumenter({
182
195
  produceSourceMap: true,
@@ -188,34 +201,30 @@ module.exports = {
188
201
  return [{
189
202
  match: /(.*\.js)(\?.*)?$/,
190
203
  custom: async (request, response, url) => {
191
- if (!url.match(job.coverageProxyInclude) || url.match(job.coverageProxyExclude)) {
192
- if (job.debugCoverage) {
193
- getOutput(job).wrap(() => console.log('coverage_proxy ignore', url))
194
- }
195
- return // Ignore
204
+ if (!url.match(job.coverageProxyInclude) || url.match(/\bresources\b/) || url.match(job.coverageProxyExclude)) {
205
+ getOutput(job).debug('coverage', 'coverage_proxy ignore', url)
206
+ return
196
207
  }
197
- const sourcePath = join(sourcesBasePath, url)
208
+ const instrumentedSourcePath = join(instrumentedBasePath, url)
198
209
  try {
199
- await access(sourcePath, constants.R_OK)
200
- } catch (e) {
201
- try {
202
- if (sources[url]) {
203
- await sources[url]
204
- } else {
205
- if (job.debugCoverage) {
206
- getOutput(job).wrap(() => console.log('coverage_proxy instrument', url))
207
- }
208
- sources[url] = await download(origin + url, sourcePath)
209
- }
210
- } catch (statusCode) {
211
- return statusCode
212
- }
210
+ await access(instrumentedSourcePath, constants.R_OK)
211
+ return
212
+ } catch (e) {}
213
+ const instrumenting = sources[url]
214
+ if (instrumenting) {
215
+ await instrumenting
216
+ return // ok
213
217
  }
214
- const source = (await readFile(sourcePath)).toString()
215
- const instrumentedSource = await instrument(source, sourcePath)
216
- const instrumentedSourcePath = join(instrumentedBasePath, url)
217
- await createDir(dirname(instrumentedSourcePath))
218
- await writeFile(instrumentedSourcePath, instrumentedSource)
218
+ sources[url] = (async () => {
219
+ const sourcePath = await getReadableSource(job, url)
220
+ getOutput(job).debug('coverage', 'coverage_proxy instrument', url, sourcePath)
221
+ if (sourcePath) {
222
+ const source = (await readFile(sourcePath)).toString()
223
+ const instrumentedSource = await instrument(source, sourcePath)
224
+ await createDir(dirname(instrumentedSourcePath))
225
+ await writeFile(instrumentedSourcePath, instrumentedSource)
226
+ }
227
+ })()
219
228
  }
220
229
  },
221
230
  instrumentedMapping,
@@ -11,27 +11,13 @@ require('./browser')({
11
11
  ['-w, --viewport-width <width>', 'Viewport width', 1920],
12
12
  ['-h, --viewport-height <height>', 'Viewport height', 1080],
13
13
  ['-l, --language <lang...>', 'Language(s)', ['en-US']],
14
- ['-u, --unsecure', 'Disable security features', false]
15
- ] // ,
16
- // TODO restore when Node16 is no more supported
17
- // capabilities: {
18
- // modules: ['puppeteer'],
19
- // screenshot: '.png',
20
- // scripts: true,
21
- // traces: ['console', 'network']
22
- // }
23
- },
24
-
25
- // TODO remove when Node16 is no more supported
26
- async capabilities () {
27
- const version = process.version.match(/^v(\d+\.\d+)/)[1]
28
- let screenshot
29
- if (!version.startsWith('16')) {
30
- screenshot = '.png'
31
- }
32
- return {
14
+ ['-u, --unsecure', 'Disable security features', false],
15
+ ['--basic-auth-username <username>', 'Username for basic authentication', ''],
16
+ ['--basic-auth-password <password>', 'Password for basic authentication', '']
17
+ ],
18
+ capabilities: {
33
19
  modules: ['puppeteer'],
34
- screenshot,
20
+ screenshot: '.png',
35
21
  scripts: true,
36
22
  traces: ['console', 'network']
37
23
  }
@@ -94,6 +80,10 @@ require('./browser')({
94
80
  await page.setBypassCSP(true)
95
81
  }
96
82
 
83
+ if (options.basicAuthUsername || options.basicAuthPassword) {
84
+ await page.authenticate({ username: options.basicAuthUsername, password: options.basicAuthPassword })
85
+ }
86
+
97
87
  page
98
88
  .on('console', message => consoleWriter.append({
99
89
  type: message.type(),
@@ -4,6 +4,16 @@
4
4
  report.ready.then(update => {
5
5
  let lastState = {}
6
6
 
7
+ async function retry () {
8
+ try {
9
+ await fetch('/_/progress', { method: 'INFO' })
10
+ location.hash = ''
11
+ refresh()
12
+ } catch (e) {
13
+ setTimeout(retry, 250)
14
+ }
15
+ }
16
+
7
17
  async function refresh () {
8
18
  const [, page, test] = location.hash.match(/#?([^-]*)(?:-(.*))?/)
9
19
  let url = '/_/progress'
@@ -22,6 +32,7 @@
22
32
  ...lastState,
23
33
  disconnected: true
24
34
  })
35
+ retry()
25
36
  return
26
37
  }
27
38
  if (test) {
package/src/endpoints.js CHANGED
@@ -141,6 +141,14 @@ module.exports = job => {
141
141
  match: '^/_/punyexpr.js',
142
142
  file: punyexprBinPath
143
143
  }, {
144
+ // Endpoint to retry on progress
145
+ method: 'INFO',
146
+ match: '^/_/progress',
147
+ custom: (request, response) => {
148
+ response.writeHead(204)
149
+ response.end()
150
+ }
151
+ }, {
144
152
  // Endpoint to follow progress
145
153
  match: '^/_/progress(?:\\?page=([^&]*)(?:&test=([^&]*))?)?',
146
154
  custom: (request, response, pageId, testId) => getJobProgress(job, request, response, pageId, testId)
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { $proxifiedUrls } = require('./symbols')
4
+
3
5
  const send = (response, obj) => {
4
6
  let json
5
7
  if (typeof obj !== 'string') {
@@ -60,6 +62,13 @@ module.exports = {
60
62
  ...job,
61
63
  status: job.status
62
64
  }, (key, value) => {
65
+ if (key === 'qunitPages' && job[$proxifiedUrls]) {
66
+ const unproxifiedQunitPages = {}
67
+ for (const url of Object.keys(job.qunitPages)) {
68
+ unproxifiedQunitPages[job[$proxifiedUrls][url] || url] = job.qunitPages[url]
69
+ }
70
+ return unproxifiedQunitPages
71
+ }
63
72
  if (key === 'modules') {
64
73
  return undefined
65
74
  }
@@ -2,7 +2,6 @@
2
2
  'use strict'
3
3
 
4
4
  const MODULE = 'ui5-test-runner/opa-iframe-coverage'
5
-
6
5
  if (window[MODULE]) {
7
6
  return // already installed
8
7
  }
@@ -1,9 +1,9 @@
1
1
  (function () {
2
2
  'use strict'
3
3
 
4
- const POST = 'ui5-test-runner/post'
5
- if (window[POST]) {
6
- return
4
+ const MODULE = 'ui5-test-runner/post'
5
+ if (window[MODULE]) {
6
+ return // already installed
7
7
  }
8
8
 
9
9
  const base = window['ui5-test-runner/base-host'] || ''
@@ -71,7 +71,7 @@
71
71
 
72
72
  window['ui5-test-runner/stringify'] = stringify
73
73
 
74
- window[POST] = function post (url, data) {
74
+ window[MODULE] = function post (url, data) {
75
75
  function request () {
76
76
  return new Promise(function (resolve, reject) {
77
77
  const xhr = new XMLHttpRequest()
@@ -18,18 +18,34 @@
18
18
  }
19
19
  }
20
20
 
21
+ function getModules () {
22
+ if (QUnit.config && QUnit.config.modules) {
23
+ return QUnit.config.modules.map(({ name, tests }) => ({
24
+ name,
25
+ tests: tests.map(({ name, testId, skip }) => ({ name, testId, skip }))
26
+ }))
27
+ }
28
+ return []
29
+ }
30
+
31
+ function extend (details) {
32
+ details.isOpa = isOpa()
33
+ details.modules = getModules()
34
+ return details
35
+ }
36
+
21
37
  QUnit.begin(function (details) {
22
38
  details.isOpa = isOpa()
23
39
  return post('QUnit/begin', details)
24
40
  })
25
41
 
26
42
  QUnit.testStart(function (details) {
27
- return post('QUnit/testStart', details)
43
+ return post('QUnit/testStart', extend(details))
28
44
  })
29
45
 
30
46
  QUnit.log(function (log) {
31
47
  let ready = false
32
- post('QUnit/log', log)
48
+ post('QUnit/log', extend(log))
33
49
  .then(undefined, function () {
34
50
  console.error('Failed to POST to QUnit/log (no timestamp)', log)
35
51
  })
@@ -2,6 +2,12 @@
2
2
  (function () {
3
3
  'use strict'
4
4
 
5
+ const MODULE = 'ui5-test-runner/qunit-intercept'
6
+ if (window[MODULE]) {
7
+ return // already installed
8
+ }
9
+ window[MODULE] = true
10
+
5
11
  const callbacks = {}
6
12
  const mock = new Proxy({}, {
7
13
  get: function (instance, property) {
@@ -1,6 +1,12 @@
1
1
  (function () {
2
2
  'use strict'
3
3
 
4
+ const MODULE = 'ui5-test-runner/ui5-coverage'
5
+ if (window[MODULE]) {
6
+ return // already installed
7
+ }
8
+ window[MODULE] = true
9
+
4
10
  // inspired from ui5/resources/sap/ui/qunit/qunit-coverage-istanbul-dbg.js
5
11
 
6
12
  function appendUrlParameter (url) {
package/src/job-mode.js CHANGED
@@ -39,7 +39,6 @@ function buildAndCheckMode (job) {
39
39
  'libs',
40
40
  'mappings',
41
41
  'cache',
42
- 'webapp',
43
42
  'watch',
44
43
  'testsuite'
45
44
  ])