ui5-test-runner 4.4.0 → 4.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui5-test-runner",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "description": "Standalone test runner for UI5",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -11,34 +11,15 @@
11
11
  },
12
12
  "scripts": {
13
13
  "lint": "standard --fix",
14
- "test": "npm run test:unit && npm run test:browsers && npm run test:samples",
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",
16
- "test:samples": "npm run test:samples:js && npm run test:samples:ts && npm run test:auth-sample",
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",
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",
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",
14
+ "test": "npm run test:unit && npm run test:e2e",
15
+ "test:samples": "npm run test:samples:js && npm run test:samples:ts",
16
+ "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 && npm run test:sample:js:basic-authent && npm run test:sample:js:legacy:split-opa && npm run test:sample:js:remote:split-opa",
17
+ "test:coverall": "rimraf .nyc_output && jest --coverageDirectory .nyc_output --coverageReporters json && nyc --silent --no-clean npm run test:e2e && 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",
21
18
  "test:unit": "jest",
22
- "test:unit:debug": "jest --runInBand",
23
- "test:integration:puppeteer": "node . --capabilities --browser $/puppeteer.js",
24
- "test:integration:selenium-webdriver-chrome": "node . --capabilities --browser $/selenium-webdriver.js -- --browser chrome",
25
- "test:integration:jsdom": "node . --capabilities --browser $/jsdom.js",
26
- "test:integration:playwright": "node . --capabilities --browser $/playwright.js",
19
+ "test:unit:debug": "node --inspect node_modules/jest/bin/jest.js --runInBand --no-coverage",
20
+ "test:e2e": "node test/e2e",
27
21
  "test:report": "node ./src/defaults/report.js ./test/report && reserve --config ./test/report/reserve.json",
28
22
  "test:text-report": "node ./src/defaults/text-report.js ./test/report",
29
- "test:sample:js:legacy": "node . --cwd ./test/sample.js",
30
- "test:sample:js:coverage:legacy": "node . --cwd ./test/sample.js --coverage --coverage-settings nyc.json --coverage-check-statements 67",
31
- "test:sample:js:legacy-remote": "node . --port 8081 --cwd ./test/sample.js --url http://localhost:8081/test/testsuite.qunit.html",
32
- "test:sample:js:coverage:legacy-remote": "node . --port 8081 --cwd ./test/sample.js --url http://localhost:8081/test/testsuite.qunit.html --coverage --coverage-settings nyc.json --coverage-check-statements 67",
33
- "test:sample:js:remote": "start-server-and-test 'npm run serve:sample:js' http://localhost:8080 'node . --url http://localhost:8080/test/testsuite.qunit.html'",
34
- "test:sample:js:coverage:remote": "start-server-and-test 'npm run serve:sample:js' http://localhost:8080 'node . --url http://localhost:8080/test/testsuite.qunit.html --coverage --coverage-check-statements 67'",
35
- "serve:sample:js": "ui5 serve --config ./test/sample.js/ui5.yaml",
36
- "test:sample:ts:remote": "start-server-and-test 'npm run serve:sample:ts' http://localhost:8080 'node . --url http://localhost:8080/test/testsuite.qunit.html'",
37
- "serve:sample:ts": "cd ./test/sample.ts && node ui5.cjs serve",
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'",
39
- "serve:sample:ts:coverage": "cd ./test/sample.ts && node ui5.cjs serve --config ui5-coverage.yaml",
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
23
  "build:doc": "node build/doc",
43
24
  "clean": "npm uninstall -g ui5-test-runner puppeteer nyc selenium-webdriver playwright webdriverio"
44
25
  },
@@ -66,18 +47,20 @@
66
47
  "mime": "^3.0.0",
67
48
  "punybind": "^1.2.1",
68
49
  "punyexpr": "^1.0.4",
69
- "reserve": "^1.15.8"
50
+ "reserve": "^1.15.9"
70
51
  },
71
52
  "devDependencies": {
72
- "@openui5/types": "^1.121.1",
73
- "@ui5/cli": "^3.9.1",
53
+ "@openui5/types": "^1.122.1",
54
+ "@ui5/cli": "^3.9.2",
74
55
  "@ui5/middleware-code-coverage": "^1.1.1",
56
+ "dotenv": "^16.4.5",
75
57
  "jest": "^29.7.0",
76
58
  "nock": "^13.5.4",
77
59
  "nyc": "^15.1.0",
60
+ "rimraf": "^5.0.5",
78
61
  "standard": "^17.1.0",
79
62
  "start-server-and-test": "^2.0.3",
80
- "typescript": "^5.4.2",
63
+ "typescript": "^5.4.5",
81
64
  "ui5-tooling-transpile": "^3.3.7"
82
65
  },
83
66
  "optionalDependencies": {
@@ -5,34 +5,60 @@ const { URL } = require('url')
5
5
  const { getOutput } = require('./output')
6
6
  const { stripUrlHash } = require('./tools')
7
7
 
8
+ const addUrlParam = (url, param) => {
9
+ if (url.includes(param)) {
10
+ return url
11
+ }
12
+ if (url.includes('?')) {
13
+ return url + '&' + param
14
+ }
15
+ return url + '?' + param
16
+ }
17
+
8
18
  module.exports = {
9
- async addTestPages (job, url, pages) {
10
- getOutput(job).debug('probe', `addTestPages from ${url}`, pages)
19
+ async addTestPages (job, url, data) {
20
+ const { type, opa, modules, pages, page } = data
21
+ getOutput(job).debug('probe', `addTestPages from ${url}`, data)
11
22
  let testPageUrls
12
- pages = pages.map(relativeUrl => {
13
- const absoluteUrl = new URL(relativeUrl, url)
14
- return stripUrlHash(absoluteUrl.toString())
15
- })
16
- if (job.pageFilter) {
17
- const filter = new RegExp(job.pageFilter)
18
- testPageUrls = pages.filter(name => name.match(filter))
23
+ if (type === 'none') {
24
+ testPageUrls = []
19
25
  } else {
20
- testPageUrls = pages
21
- }
22
- if (job.pageParams) {
23
- testPageUrls = testPageUrls.map(url => {
24
- if (url.includes('?')) {
25
- return url + '&' + job.pageParams
26
+ let receivedPages
27
+ if (type === 'qunit') {
28
+ if (job.splitOpa && opa && modules && modules.length > 1) {
29
+ receivedPages = modules.map(moduleId => addUrlParam(stripUrlHash(page), `moduleId=${moduleId}`))
30
+ } else {
31
+ receivedPages = [page]
26
32
  }
27
- return url + '?' + job.pageParams
33
+ } else {
34
+ receivedPages = pages
35
+ }
36
+ receivedPages = receivedPages.map(relativeUrl => {
37
+ const absoluteUrl = new URL(relativeUrl, url)
38
+ return stripUrlHash(absoluteUrl.toString())
28
39
  })
40
+ if (job.pageFilter) {
41
+ const filter = new RegExp(job.pageFilter)
42
+ testPageUrls = receivedPages.filter(name => name.match(filter))
43
+ } else {
44
+ testPageUrls = receivedPages
45
+ }
46
+ if (job.pageParams) {
47
+ testPageUrls = testPageUrls.map(url => addUrlParam(url, job.pageParams))
48
+ }
49
+ }
50
+ let member
51
+ if (type === 'suite' && job.splitOpa) {
52
+ member = 'url'
53
+ } else {
54
+ member = 'testPageUrls'
29
55
  }
30
- job.testPageUrls = testPageUrls.reduce((uniques, url) => {
56
+ job[member] = testPageUrls.reduce((uniques, url) => {
31
57
  if (!uniques.includes(url)) {
32
58
  uniques.push(url)
33
59
  }
34
60
  return uniques
35
- }, job.testPageUrls || [])
61
+ }, job[member] || [])
36
62
  stop(job, url)
37
63
  }
38
64
  }
package/src/browsers.js CHANGED
@@ -119,8 +119,7 @@ async function start (job, url, scripts = []) {
119
119
  resolvedScripts.unshift(`window['ui5-test-runner/base-host'] = 'http://localhost:${job.port}'
120
120
  `)
121
121
  }
122
- const progress = newProgress(job)
123
- progress.label = url
122
+ const progress = newProgress(job, url)
124
123
  const pageBrowser = {
125
124
  url,
126
125
  reportDir,
@@ -6,8 +6,9 @@ const { join } = require('path')
6
6
  const { getOutput } = require('../output')
7
7
  const { performance } = require('perf_hooks')
8
8
  const { cleanDir, allocPromise, filename } = require('../tools')
9
- const { $browsers } = require('../symbols')
9
+ const { $statusProgressTotal, $statusProgressCount } = require('../symbols')
10
10
  const tests = require('./tests')
11
+ const parallelize = require('../parallelize')
11
12
 
12
13
  async function capabilities (job) {
13
14
  const output = getOutput(job)
@@ -17,9 +18,9 @@ async function capabilities (job) {
17
18
  job.status = 'Serving'
18
19
  } else {
19
20
  if (job.debugKeepReport) {
20
- output.wrap(() => console.log('Report folder', job.reportDir, 'not cleaned because of --debug-keep-report.'))
21
+ output.log('Report folder', job.reportDir, 'not cleaned because of --debug-keep-report.')
21
22
  } else if (code !== 0) {
22
- output.wrap(() => console.error('Report folder', job.reportDir, 'not cleaned because of errors.'))
23
+ output.error('Report folder', job.reportDir, 'not cleaned because of errors.')
23
24
  } else {
24
25
  await cleanDir(job.reportDir)
25
26
  }
@@ -33,7 +34,7 @@ async function capabilities (job) {
33
34
  try {
34
35
  await probe(job)
35
36
  } catch (e) {
36
- output.wrap(() => console.error('Unable to probe'))
37
+ output.error('Unable to probe')
37
38
  exit(-1)
38
39
  }
39
40
 
@@ -90,21 +91,13 @@ async function capabilities (job) {
90
91
  const filteredTests = tests
91
92
  .filter((test) => !test.for || test.for(job.browserCapabilities))
92
93
  .filter(({ name }) => !job.debugCapabilitiesTest || name.startsWith(job.debugCapabilitiesTest))
93
- output.wrap(() => console.log('Number of tests :', filteredTests.length))
94
+ output.log('Number of tests :', filteredTests.length)
94
95
 
95
96
  let errors = 0
96
97
 
97
- const { promise: forEver } = allocPromise()
98
-
99
- const next = async () => {
100
- if (filteredTests.length === 0) {
101
- if (!job[$browsers] || Object.keys(job[$browsers]).length === 0) {
102
- output.wrap(() => console.log('Done.'))
103
- exit(errors)
104
- }
105
- return
106
- }
107
- const { label, url, scripts, endpoint = () => { } } = filteredTests.shift()
98
+ const task = async (test) => {
99
+ const { promise, resolve } = allocPromise()
100
+ const { label, url, scripts, endpoint = () => { } } = test
108
101
 
109
102
  const listenerIndex = listeners.length
110
103
  let pageUrl
@@ -130,6 +123,7 @@ async function capabilities (job) {
130
123
  if (done.called) {
131
124
  return
132
125
  }
126
+ ++job[$statusProgressCount]
133
127
  done.called = true
134
128
  if (timeoutId) {
135
129
  clearTimeout(timeoutId)
@@ -137,15 +131,15 @@ async function capabilities (job) {
137
131
  await stop(job, pageUrl)
138
132
  const timeSpent = Math.floor(performance.now() - now)
139
133
  if (error) {
140
- output.wrap(() => console.log('❌', label, `[${filename(pageUrl)}]`, error))
134
+ output.log('❌', label, `[${filename(pageUrl)}]`, error)
141
135
  if (job.failFast) {
142
136
  exit(1)
143
137
  }
144
138
  ++errors
145
139
  } else {
146
- output.wrap(() => console.log('✔️', label, timeSpent, 'ms'))
140
+ output.log('✔️ ', label, timeSpent, 'ms')
147
141
  }
148
- await next()
142
+ resolve()
149
143
  }
150
144
 
151
145
  const context = {
@@ -168,6 +162,8 @@ async function capabilities (job) {
168
162
  done('Failed')
169
163
  }
170
164
  })
165
+
166
+ return promise
171
167
  }
172
168
 
173
169
  let parallel
@@ -177,14 +173,14 @@ async function capabilities (job) {
177
173
  parallel = job.parallel
178
174
  }
179
175
 
180
- const testsCount = filteredTests.length
181
- for (let i = 0; i < Math.max(Math.min(parallel, testsCount), 1); ++i) {
182
- next()
183
- }
176
+ job[$statusProgressTotal] = filteredTests.length
177
+ job[$statusProgressCount] = 0
178
+ await parallelize(task, filteredTests, parallel)
184
179
 
185
- await forEver
180
+ output.log('Done.')
181
+ exit(errors)
186
182
  } catch (error) {
187
- output.wrap(() => console.error(error))
183
+ output.error(error)
188
184
  exit(-1)
189
185
  }
190
186
  }
@@ -34,12 +34,13 @@ module.exports = [{
34
34
  scripts: ['post.js', 'qunit-redirect.js'],
35
35
  endpoint: function (data) {
36
36
  assert(data.endpoint === 'addTestPages', 'addTestPages was triggered')
37
- assert(data.body.length === 2, 'Two pages received')
37
+ assert(data.body.type === 'suite', 'type = suite')
38
+ assert(data.body.pages.length === 2, 'Two pages received')
38
39
  const pages = [
39
40
  '/unit/unitTests.qunit.html',
40
41
  '/integration/opaTests.iframe.qunit.html'
41
42
  ]
42
- pages.forEach((page, index) => assert(data.body[index].endsWith(page), page))
43
+ pages.forEach((page, index) => assert(data.body.pages[index].endsWith(page), page))
43
44
  }
44
45
  }, {
45
46
  label: 'Scripts (External QUnit)',
@@ -34,8 +34,17 @@ async function main () {
34
34
  tests="${module.tests.length}"
35
35
  >`)
36
36
  for (const test of module.tests) {
37
+ let time
38
+ if (test.start && test.end) {
39
+ time = (new Date(test.end) - new Date(test.start)) / 1000
40
+ }
37
41
  o(` <testcase
38
- name="${xmlEscape(test.name)}"
42
+ name="${xmlEscape(test.name)}" ${
43
+ time === undefined
44
+ ? ''
45
+ : `
46
+ time="${time}"`
47
+ }
39
48
  >`)
40
49
  if (test.skip) {
41
50
  o(' <skipped></skipped>')
@@ -10,16 +10,17 @@ module.exports = async ({
10
10
  const chrome = require(join(settings.modules['selenium-webdriver'], 'chrome'))
11
11
 
12
12
  const chromeOptions = new chrome.Options()
13
- chromeOptions.excludeSwitches('enable-logging')
14
13
  if (!options.visible) {
15
- chromeOptions.addArguments('headless')
14
+ chromeOptions.addArguments('--headless=new')
15
+ chromeOptions.addArguments('--log-level=3')
16
16
  }
17
- chromeOptions.addArguments('start-maximized')
18
- chromeOptions.addArguments('disable-extensions')
17
+ chromeOptions.addArguments('--start-maximized')
18
+ chromeOptions.addArguments('--disable-extensions')
19
19
  chromeOptions.setLoggingPrefs(loggingPreferences)
20
20
  if (options.binary) {
21
- chromeOptions.setBinary(options.binary)
21
+ chromeOptions.setChromeBinaryPath(options.binary)
22
22
  }
23
+ chromeOptions.excludeSwitches('--enable-logging')
23
24
 
24
25
  const builder = new Builder()
25
26
  .forBrowser(Browser.CHROME)
@@ -82,7 +82,7 @@
82
82
  reject(xhr.statusText)
83
83
  })
84
84
  xhr.open('POST', base + '/_/' + url)
85
- xhr.setRequestHeader('x-page-url', location)
85
+ xhr.setRequestHeader('x-page-url', top.location)
86
86
  xhr.send(stringify(data))
87
87
  })
88
88
  }
@@ -20,8 +20,9 @@
20
20
 
21
21
  function getModules () {
22
22
  if (QUnit.config && QUnit.config.modules) {
23
- return QUnit.config.modules.map(({ name, tests }) => ({
23
+ return QUnit.config.modules.map(({ name, moduleId, tests }) => ({
24
24
  name,
25
+ moduleId,
25
26
  tests: tests.map(({ name, testId, skip }) => ({ name, testId, skip }))
26
27
  }))
27
28
  }
@@ -6,6 +6,7 @@
6
6
  return // already installed
7
7
  }
8
8
  window[MODULE] = true
9
+ window['ui5-test-runner/qunit-hooks'] = true // prevents qunit-hooks
9
10
 
10
11
  /* global suite */
11
12
 
@@ -21,14 +22,41 @@
21
22
 
22
23
  window.jsUnitTestSuite = jsUnitTestSuite
23
24
 
25
+ let QUnit
26
+
27
+ Object.defineProperty(window, 'QUnit', {
28
+ get: function () {
29
+ return QUnit
30
+ },
31
+
32
+ set: function (value) {
33
+ QUnit = value
34
+
35
+ const { test } = QUnit
36
+ QUnit.test = (label) => test(label, (assert) => assert.ok(true, label))
37
+
38
+ let timeoutId
39
+ QUnit.moduleDone(function () {
40
+ if (timeoutId) {
41
+ clearTimeout(timeoutId)
42
+ }
43
+ timeoutId = setTimeout(notify, 10)
44
+ })
45
+
46
+ function notify () {
47
+ const modules = QUnit.config.modules.map(({ moduleId }) => moduleId)
48
+ const opa = !!window?.sap?.ui?.test?.Opa5
49
+ post('addTestPages', { type: 'qunit', opa, modules, page: location.toString() })
50
+ }
51
+ }
52
+ })
53
+
24
54
  window.addEventListener('load', function () {
25
55
  if (typeof suite === 'function') {
26
56
  suite()
27
- post('addTestPages', pages)
28
- } else if (typeof QUnit === 'object') {
29
- post('addTestPages', [location.toString()])
30
- } else {
31
- post('addTestPages', []) // No page
57
+ post('addTestPages', { type: 'suite', pages })
58
+ } else if (!QUnit) {
59
+ post('addTestPages', { type: 'none ' })
32
60
  }
33
61
  })
34
62
  }())
package/src/job-mode.js CHANGED
@@ -30,7 +30,8 @@ function buildAndCheckMode (job) {
30
30
  'browserCloseTimeout',
31
31
  'failFast',
32
32
  'keepAlive',
33
- 'alternateNpmPath'
33
+ 'alternateNpmPath',
34
+ 'outputInterval'
34
35
  ])
35
36
  return 'capabilities'
36
37
  }
package/src/job.js CHANGED
@@ -99,7 +99,7 @@ function getCommand (cwd) {
99
99
  .option('-fo, --fail-opa-fast [flag]', '[💻🔗] Stop the OPA page execution after the first failing test', boolean, false)
100
100
  .option('-k, --keep-alive [flag]', '[💻🔗🧪] Keep the server alive', boolean, false)
101
101
  .option('-l, --log-server [flag]', '[💻🔗🧪] Log inner server traces', boolean, false)
102
- .option('-p, --parallel <count>', '[💻🔗🧪] Number of parallel tests executions', 2)
102
+ .option('-p, --parallel <count>', '[💻🔗🧪] Number of parallel tests executions', integer, 2)
103
103
  .option('-b, --browser <command>', '[💻🔗🧪] Browser instantiation command (relative to cwd or use $/ for provided ones)', '$/puppeteer.js')
104
104
  .option('--browser-args <argument...>', '[💻🔗🧪] Browser instantiation command parameters (use -- instead)')
105
105
  .option('--alternate-npm-path <path>', '[💻🔗] Alternate NPM path to look for packages (priority: local, alternate, global)')
@@ -118,8 +118,9 @@ function getCommand (cwd) {
118
118
  .option('--screenshot [flag]', '[💻🔗] Take screenshots during the tests execution (if supported by the browser)', boolean, true)
119
119
  .option('--no-screenshot', '[💻🔗] Disable screenshots')
120
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)
121
122
  .option('-rg, --report-generator <path...>', '[💻🔗] Report generator paths (relative to cwd or use $/ for provided ones)', ['$/report.js'])
122
- .option('--progress-page <path>', '[💻🔗] progress page path (relative to cwd or use $/ for provided ones)', '$/report/default.html')
123
+ .option('--progress-page <path>', '[💻🔗] Progress page path (relative to cwd or use $/ for provided ones)', '$/report/default.html')
123
124
 
124
125
  .option('--coverage [flag]', '[💻🔗] Enable or disable code coverage', boolean)
125
126
  .option('--no-coverage', '[💻🔗] Disable code coverage')
@@ -321,6 +322,25 @@ function finalize (job) {
321
322
  enumerable: false,
322
323
  configurable: false
323
324
  })
325
+
326
+ /* istanbul ignore next */
327
+ if (process.env.DEBUG_ON_FAILED) {
328
+ let failed
329
+ Object.defineProperty(job, 'failed', {
330
+ get () {
331
+ return failed
332
+ },
333
+ set (value) {
334
+ if (value) {
335
+ // eslint-disable-next-line no-debugger
336
+ debugger
337
+ }
338
+ failed = value
339
+ },
340
+ enumerable: true,
341
+ configurable: false
342
+ })
343
+ }
324
344
  }
325
345
 
326
346
  function fromCmdLine (cwd, args) {
package/src/output.js CHANGED
@@ -79,7 +79,7 @@ function * bar (ratio, msg) {
79
79
  }
80
80
  yield '] '
81
81
  } else {
82
- const filled = Math.floor(BAR_WIDTH * ratio)
82
+ const filled = Math.floor(BAR_WIDTH * Math.min(ratio, 1))
83
83
  yield ''.padEnd(filled, '\u2588')
84
84
  yield ''.padEnd(BAR_WIDTH - filled, '\u2591')
85
85
  yield '] '
@@ -117,6 +117,13 @@ class Progress {
117
117
  }
118
118
 
119
119
  function progress (job, cleanFirst = true) {
120
+ if (process.send) {
121
+ process.send({
122
+ type: 'progress',
123
+ count: job[$statusProgressCount],
124
+ total: job[$statusProgressTotal]
125
+ })
126
+ }
120
127
  const sequence = []
121
128
  if (interactive) {
122
129
  if (cleanFirst) {
@@ -250,8 +257,6 @@ function build (job) {
250
257
  reportIntervalId: undefined,
251
258
  lines: 0,
252
259
 
253
- wrap: wrap(callback => callback()),
254
-
255
260
  version: () => {
256
261
  const { name, version } = require(join(__dirname, '../package.json'))
257
262
  log(job, p80()`${name}@${version}`)
@@ -261,6 +266,14 @@ function build (job) {
261
266
  log(job, p80()`Server running at ${pad.lt(url)}`)
262
267
  },
263
268
 
269
+ log: wrap((...texts) => {
270
+ log(job, ...texts)
271
+ }),
272
+
273
+ error: wrap((...texts) => {
274
+ err(job, ...texts)
275
+ }),
276
+
264
277
  debug: (moduleSpecifier, ...args) => {
265
278
  const [mainModule] = moduleSpecifier.split('/')
266
279
  if (job.debugVerbose && (job.debugVerbose.includes(moduleSpecifier) || job.debugVerbose.includes(mainModule))) {
@@ -310,7 +323,7 @@ function build (job) {
310
323
  reportOnJobProgress () {
311
324
  if (interactive) {
312
325
  this.reportIntervalId = setInterval(progress.bind(null, job), 250)
313
- } else if (job.outputInterval) {
326
+ } else if (job.outputInterval && !inJest) {
314
327
  this.reportIntervalId = setInterval(progress.bind(null, job), job.outputInterval)
315
328
  }
316
329
  },
@@ -563,6 +576,8 @@ function build (job) {
563
576
  }
564
577
 
565
578
  module.exports = {
579
+ interactive,
580
+
566
581
  getOutput (job) {
567
582
  if (!job[$output]) {
568
583
  job[$output] = build(job)
@@ -570,7 +585,9 @@ module.exports = {
570
585
  return job[$output]
571
586
  },
572
587
 
573
- newProgress (job) {
574
- return new Progress(job)
588
+ newProgress (job, label, total, count) {
589
+ const progress = new Progress(job)
590
+ Object.assign(progress, { label, total, count })
591
+ return progress
575
592
  }
576
593
  }
@@ -0,0 +1,61 @@
1
+ const { allocPromise } = require('./tools')
2
+
3
+ function complete (task) {
4
+ ++task.completed
5
+ if (--task.active === 0) {
6
+ task.resolve()
7
+ }
8
+ }
9
+
10
+ async function run (task) {
11
+ const {
12
+ method,
13
+ list,
14
+ parallel,
15
+ started,
16
+ completed,
17
+ stop,
18
+ reject
19
+ } = task
20
+ const { length } = list
21
+ if (stop || completed === length || started === length) {
22
+ complete(task)
23
+ return
24
+ }
25
+ if (task.active < parallel && length - started > task.active) {
26
+ ++task.active
27
+ run(task)
28
+ }
29
+ const index = task.started++
30
+ const parameter = list[index]
31
+ try {
32
+ await method(parameter, index, list)
33
+ } catch (error) {
34
+ task.stop = true
35
+ reject(error)
36
+ }
37
+ let remaining = list.length - index - 1
38
+ while (task.active < (parallel + 1) && remaining) {
39
+ --remaining
40
+ ++task.active
41
+ run(task)
42
+ }
43
+ complete(task)
44
+ }
45
+
46
+ module.exports = function parallelize (method, list, parallel) {
47
+ const { promise, resolve, reject } = allocPromise()
48
+ const task = {
49
+ method,
50
+ list,
51
+ parallel,
52
+ started: 0,
53
+ completed: 0,
54
+ active: 1,
55
+ stop: false,
56
+ resolve,
57
+ reject
58
+ }
59
+ run(task)
60
+ return promise
61
+ }
@@ -21,7 +21,6 @@ function invalidTestId (job, url, testId) {
21
21
  }
22
22
 
23
23
  function merge (targetModules, modules) {
24
- let count = 0
25
24
  modules.forEach(module => {
26
25
  const { name } = module
27
26
  const targetModule = targetModules.filter(({ name: targetName }) => name === targetName)[0]
@@ -35,9 +34,16 @@ function merge (targetModules, modules) {
35
34
  }
36
35
  })
37
36
  }
38
- count += module.tests.length
39
37
  })
40
- return count
38
+ }
39
+
40
+ function filterModules (modules, url) {
41
+ const moduleMatch = url.match(/\?.*\bmoduleId=([^&]+)/)
42
+ if (moduleMatch) {
43
+ const [, moduleId] = moduleMatch
44
+ return modules.filter(module => module.moduleId === moduleId)
45
+ }
46
+ return modules
41
47
  }
42
48
 
43
49
  function get (job, urlWithHash, { testId, modules, isOpa } = {}) {
@@ -47,9 +53,8 @@ function get (job, urlWithHash, { testId, modules, isOpa } = {}) {
47
53
  if (!page) {
48
54
  error(job, url, `No QUnit page found for ${urlWithHash}`)
49
55
  }
50
- if (modules && modules.length) {
51
- progress.total = page.count = merge(page.modules, modules)
52
- }
56
+ merge(page.modules, filterModules(modules || [], url))
57
+ progress.total = page.count = page.modules.reduce((total, { tests }) => total + tests.length, 0)
53
58
  if (!page.isOpa && isOpa) {
54
59
  page.isOpa = true
55
60
  }
@@ -104,7 +109,7 @@ module.exports = {
104
109
 
105
110
  async begin (job, urlWithHash, details) {
106
111
  getOutput(job).debug('qunit/begin', 'begin', urlWithHash, details)
107
- const { isOpa, totalTests, modules } = details
112
+ const { isOpa, modules } = details
108
113
  const url = stripUrlHash(urlWithHash)
109
114
  if (!job.qunitPages) {
110
115
  job.qunitPages = {}
@@ -115,13 +120,13 @@ module.exports = {
115
120
  isOpa: !!isOpa,
116
121
  failed: 0,
117
122
  passed: 0,
118
- count: totalTests,
119
- modules
123
+ count: 0,
124
+ modules: []
120
125
  }
121
126
  job.qunitPages[url] = qunitPage
122
- const { progress } = get(job, url)
127
+ const { page, progress } = get(job, url, { modules })
123
128
  progress.count = 0
124
- progress.total = totalTests
129
+ progress.total = page.count
125
130
  },
126
131
 
127
132
  async testStart (job, urlWithHash, details) {
package/src/symbols.js CHANGED
@@ -1,9 +1,5 @@
1
1
  module.exports = {
2
2
  $browsers: Symbol('browsers'),
3
- $probeUrlsStarted: Symbol('probeUrlsStarted'),
4
- $probeUrlsCompleted: Symbol('probeUrlsCompleted'),
5
- $testPagesStarted: Symbol('testPagesStarted'),
6
- $testPagesCompleted: Symbol('testPagesCompleted'),
7
3
  $valueSources: Symbol('valueSources'),
8
4
  $remoteOnLegacy: Symbol('remoteOnLegacy'),
9
5
  $proxifiedUrls: Symbol('proxifiedUrls'),
package/src/tests.js CHANGED
@@ -1,68 +1,49 @@
1
1
  'use strict'
2
2
 
3
- const { probe, start } = require('./browsers')
3
+ const { probe: probeBrowser, start } = require('./browsers')
4
4
  const { instrument } = require('./coverage')
5
5
  const { recreateDir } = require('./tools')
6
6
  const { globallyTimedOut } = require('./timeout')
7
7
  const { save, generate } = require('./report')
8
8
  const { getOutput } = require('./output')
9
9
  const {
10
- $probeUrlsStarted,
11
- $probeUrlsCompleted,
12
- $testPagesStarted,
13
- $testPagesCompleted,
14
10
  $statusProgressTotal,
15
- $statusProgressCount
11
+ $statusProgressCount,
12
+ $proxifiedUrls
16
13
  } = require('./symbols')
17
14
  const { UTRError } = require('./error')
18
- const { $proxifiedUrls } = require('./symbols')
19
15
  const { preload } = require('./ui5')
16
+ const parallelize = require('./parallelize')
20
17
 
21
- async function run (task, job) {
22
- const {
23
- urlsMember,
24
- startedMember,
25
- completedMember,
26
- method
27
- } = task
28
- const output = getOutput(job)
29
- const urls = job[urlsMember]
30
- const { length } = urls
31
- if (job[$statusProgressTotal] === undefined) {
18
+ function task (job, method) {
19
+ return async (url, index, { length }) => {
20
+ if (job[$statusProgressCount] === undefined) {
21
+ job[$statusProgressCount] = 0
22
+ }
32
23
  job[$statusProgressTotal] = length
33
- job[$statusProgressCount] = 0
34
- }
35
- if (job[completedMember] === length) {
36
- return
37
- }
38
- if (job[startedMember] === length) {
39
- return
40
- }
41
- const index = job[startedMember]++
42
- const url = urls[index]
43
- if (globallyTimedOut(job)) {
44
- output.globalTimeout(url)
45
- job.failed = true
46
- job.timedOut = true
47
- } else if (job.failFast && job.failed) {
48
- output.failFast(url)
49
- } else {
50
- try {
51
- await method(job, url)
52
- } catch (error) {
24
+ const output = getOutput(job)
25
+ if (globallyTimedOut(job)) {
26
+ output.globalTimeout(url)
53
27
  job.failed = true
28
+ job.timedOut = true
29
+ } else if (job.failFast && job.failed) {
30
+ output.failFast(url)
31
+ } else {
32
+ try {
33
+ await method(job, url)
34
+ } catch (error) {
35
+ job.failed = true
36
+ }
54
37
  }
38
+ ++job[$statusProgressCount]
55
39
  }
56
- ++job[completedMember]
57
- ++job[$statusProgressCount]
58
- return run(task, job)
59
40
  }
60
41
 
61
42
  async function probeUrl (job, url) {
62
43
  const output = getOutput(job)
63
44
  try {
64
45
  let scripts
65
- if (job.mode === 'url' && job.browserCapabilities.scripts) {
46
+ if (job.browserCapabilities.scripts) {
66
47
  scripts = [
67
48
  'post.js',
68
49
  'qunit-redirect.js'
@@ -111,48 +92,32 @@ async function runTestPage (job, url) {
111
92
  }
112
93
  }
113
94
 
114
- function parallelize (task, job) {
115
- const {
116
- urlsMember,
117
- completedMember,
118
- startedMember
119
- } = task
120
- job[startedMember] = 0
121
- job[completedMember] = 0
122
- const max = Math.min(job.parallel, job[urlsMember].length)
123
- const promises = []
124
- for (let i = 0; i < max; ++i) {
125
- promises.push(run(task, job))
126
- }
127
- return Promise.all(promises)
128
- }
129
-
130
95
  async function process (job) {
131
96
  const output = getOutput(job)
132
97
  job.start = new Date()
133
- delete job.failed
98
+ job.failed = false
134
99
  await instrument(job)
135
100
  await save(job)
136
101
  job.testPageUrls = []
137
102
 
138
103
  job.status = 'Probing urls'
139
- await parallelize({
140
- urlsMember: 'url',
141
- startedMember: $probeUrlsStarted,
142
- completedMember: $probeUrlsCompleted,
143
- method: probeUrl
144
- }, job)
104
+ try {
105
+ await parallelize(task(job, probeUrl), job.url, job.parallel)
106
+ } catch (e) {
107
+ output.genericError(e)
108
+ job.failed = true
109
+ }
145
110
 
146
111
  /* istanbul ignore else */
147
- if (!job.debugProbeOnly) {
112
+ if (!job.debugProbeOnly && !job.failed) {
148
113
  if (job.testPageUrls.length !== 0) {
149
114
  job.status = 'Executing test pages'
150
- await parallelize({
151
- urlsMember: 'testPageUrls',
152
- startedMember: $testPagesStarted,
153
- completedMember: $testPagesCompleted,
154
- method: runTestPage
155
- }, job)
115
+ try {
116
+ await parallelize(task(job, runTestPage), job.testPageUrls, job.parallel)
117
+ } catch (e) {
118
+ output.genericError(e)
119
+ job.failed = true
120
+ }
156
121
  } else if (Object.keys(job.qunitPages || []).length === 0) {
157
122
  output.noTestPageFound()
158
123
  job.failed = true
@@ -169,7 +134,7 @@ module.exports = {
169
134
  if (job.preload) {
170
135
  await preload(job)
171
136
  }
172
- await probe(job)
137
+ await probeBrowser(job)
173
138
  if (job.mode !== 'url') {
174
139
  job.url = [`http://localhost:${job.port}/${job.testsuite}`]
175
140
  } else if (!job.browserCapabilities.scripts) {
package/src/ui5.js CHANGED
@@ -5,8 +5,9 @@ const { createWriteStream } = require('fs')
5
5
  const { mkdir, unlink, stat } = require('fs').promises
6
6
  const { capture } = require('reserve')
7
7
  const { getOutput, newProgress } = require('./output')
8
- const { download, allocPromise } = require('./tools')
8
+ const { download } = require('./tools')
9
9
  const { $statusProgressCount, $statusProgressTotal } = require('./symbols')
10
+ const parallelize = require('./parallelize')
10
11
 
11
12
  const buildCacheBase = job => {
12
13
  const [, hostName] = /https?:\/\/([^/]*)/.exec(job.ui5)
@@ -34,31 +35,15 @@ module.exports = {
34
35
  const lib = async name => {
35
36
  progress.label = name
36
37
  progress.count = 0
37
- const { promise, resolve/*, reject */ } = allocPromise()
38
38
  const libPath = name.replace(/\./g, '/') + '/'
39
39
  const { resources } = require(await get(libPath + 'resources.json'))
40
40
  progress.total = resources.length
41
- let index = 0
42
- let active = 0
43
-
44
- const task = async () => {
45
- ++active
46
- const { name, size } = resources[index++]
41
+ progress.label = `${name} (${resources.length} files)`
42
+ await parallelize(async ({ name, size }) => {
47
43
  await get(libPath + name, size)
48
44
  ++progress.count
49
- if (index < resources.length) {
50
- task()
51
- }
52
- if (--active === 0) {
53
- resolve()
54
- }
55
- }
56
-
57
- for (let parallel = 0; parallel < 8; ++parallel) {
58
- task()
59
- }
60
-
61
- return promise
45
+ }, resources, 8)
46
+ ++job[$statusProgressCount]
62
47
  }
63
48
 
64
49
  job.status = 'Preloading UI5'
@@ -67,11 +52,7 @@ module.exports = {
67
52
  await get('sap-ui-version.json')
68
53
  await get('sap-ui-core.js')
69
54
  const progress = newProgress(job)
70
- await lib('sap.ui.core')
71
- for (const name of job.preload) {
72
- ++job[$statusProgressCount]
73
- await lib(name)
74
- }
55
+ await parallelize(lib, ['sap.ui.core', ...job.preload], 1)
75
56
  progress.done()
76
57
  },
77
58
 
@@ -1,38 +0,0 @@
1
- (function () {
2
- /* global punybind, punyexpr */
3
-
4
- const report = {}
5
-
6
- report.elapsed = function (start, end = Date.now()) {
7
- if (typeof end === 'string') {
8
- end = new Date(end).getTime()
9
- }
10
- if (typeof start === 'string') {
11
- start = new Date(start).getTime()
12
- }
13
- const ms = end - start
14
- if (isNaN(ms)) {
15
- return '-'
16
- }
17
- if (ms > 5000) {
18
- const mins = Math.floor(ms / 60000)
19
- const secs = Math.floor((ms % 60000) / 1000)
20
- return `${mins.toString().padStart(2, 0)}:${secs.toString().padStart(2, 0)}`
21
- }
22
- return `${ms} ms`
23
- }
24
-
25
- let setReady
26
- report.ready = new Promise(resolve => {
27
- setReady = resolve
28
- })
29
-
30
- window.addEventListener('load', () => {
31
- const safebind = punybind.use({
32
- compiler: punyexpr
33
- })
34
- safebind(document.body).then(setReady)
35
- })
36
-
37
- window.report = report
38
- }())
@@ -1,97 +0,0 @@
1
- <html>
2
- <head>
3
- <title>ui5-test-runner</title>
4
- <link rel="stylesheet" href="/_/report/styles.css">
5
- <script src="/_/punyexpr.js"></script>
6
- <script src="/_/punybind.js"></script>
7
- <script src="/_/report/common.js"></script>
8
- <script src="/_/report/main.js"></script>
9
- </head>
10
- <body>
11
- <div {{if}}="!(qunitPage || qunitTest)">
12
- <h1>{{ status || 'Test report' }}</h1>
13
- <div {{if}}="end === undefined" class="elapsed">In progress since {{ elapsed(start) }}</div>
14
- <div {{else}} class="elapsed">Duration : {{ elapsed(start, end) }} <a href="#" id="download">&#128230;</a></div>
15
- <div {{if}}="timedOut">
16
- &#9202;&#65039; Timed out
17
- </div>
18
- <table style="visibility: {{ testPageUrls.length > 0 ? 'visible' : 'hidden' }};">
19
- <tr>
20
- <th>&nbsp;</th>
21
- <th class="count">Type</th>
22
- <th class="status" style="width: 10rem;"><div><span>Status</span></div></th>
23
- <th class="count">Tests</th>
24
- <th class="count">Passed</th>
25
- <th class="count">Failed</th>
26
- <th class="elapsed">Elapsed</th>
27
- </tr>
28
- <tr {{for}}="url of testPageUrls">
29
- <td class="truncated">
30
- <span {{if}}="qunitPages === undefined || !qunitPages[url]">{{ url }}</span>
31
- <a {{else}} href="#{{ qunitPages[url].id }}">{{ url }}</a>
32
- </td>
33
- <td>
34
- <span {{if}}="!qunitPages[url]">-</span>
35
- <span {{elseif}}="qunitPages[url].isOpa" title="OPA test">&#129404;</span>
36
- <span {{elseif}}="qunitPages[url].start" title="Unit test">&#129514;</span>
37
- </td>
38
- <td>
39
- <span {{if}}="!qunitPages[url]">-</span>
40
- <span {{elseif}}="qunitPages[url].failed">&#10060;</span>
41
- <span {{elseif}}="qunitPages[url].end">&#10004;&#65039;</span>
42
- <span {{if}}="!qunitPages[url].end">
43
- <progress max="{{ qunitPages[url].count }}" value="{{ qunitPages[url].failed + qunitPages[url].passed }}"></progress>
44
- </span>
45
- </td>
46
- <td>{{ qunitPages[url] ? qunitPages[url].count : '-' }}</td>
47
- <td>{{ qunitPages[url] ? qunitPages[url].passed : '-' }}</td>
48
- <td>{{ qunitPages[url] ? qunitPages[url].failed : '-' }}</td>
49
- <td>
50
- <span {{if}}="!qunitPages[url]">-</span>
51
- <span {{elseif}}="qunitPages[url].end === undefined">{{ elapsed(qunitPages[url].start) }}</span>
52
- <span {{else}}>{{ elapsed(qunitPages[url].start, qunitPages[url].end) }}</span>
53
- </td>
54
- </tr>
55
- </table>
56
- </div>
57
- <div {{if}}="qunitPage">
58
- <h1 class="truncated">{{ qunitPage.url }}</h1>
59
- <a href="#">&#9204; back to report</a>
60
- <div {{if}}="qunitPage.end === undefined" class="elapsed">In progress since {{ elapsed(qunitPage.start) }}</div>
61
- <div {{else}} class="elapsed">Duration : {{ elapsed(qunitPage.start, qunitPage.end) }}</div>
62
- <div {{if}}="timedOut">
63
- &#9202;&#65039; Timed out
64
- </div>
65
- <div {{for}}="module of qunitPage.modules">
66
- <h2>{{ module.name }}</h2>
67
- <div {{for}}="test of module.tests">
68
- <a {{if}}="!test.skip" href="#{{ qunitPage.id }}-{{ test.testId }}">{{ test.name }}</a>
69
- <span {{else}}>{{ test.name }}</span>
70
- <span {{if}}="test.skip">&#9208;&#65039;</span>
71
- <span {{elseif}}="!test.end" class="elapsed">{{ elapsed(test.start) }}</span>
72
- <span {{elseif}}="test.report && !test.report.failed">&#10004;&#65039; {{ elapsed(test.start, test.end) }}</span>
73
- <span {{elseif}}="test.report">&#10060; {{ elapsed(test.start, test.end) }}</span>
74
- </div>
75
- </div>
76
- </div>
77
- <div {{elseif}}="qunitTest">
78
- <h1 class="truncated">{{ qunitTest.url }}</h1>
79
- <a href="#">&#9204; back to report</a>
80
- <h2>{{ qunitTest.module }}</h2>
81
- <a href="#{{ qunitTest.pageId }}">&#9204; back to page</a>
82
- <h3>{{ qunitTest.name }}
83
- <span {{if}}="qunitTest.report && qunitTest.report.passed">&#10004;&#65039;</span>
84
- <span {{elseif}}="qunitTest.report">&#10060;</span>
85
- </h3>
86
- <div {{if}}="qunitTest.end === undefined" class="elapsed">In progress since {{ elapsed(qunitTest.start) }}</div>
87
- <div {{else}} class="elapsed">Duration : {{ elapsed(qunitTest.start, qunitTest.end) }}</div>
88
- <div {{for}}="log of qunitTest.logs">
89
- <pre {{if}}="log.result">&#10004;&#65039; {{ log.message }}</pre>
90
- <pre {{else}}>&#10060; {{ log.message }}</pre>
91
- <img {{if}}="log.screenshot" loading="lazy" class="log" src="{{ qunitTest.pageId }}/{{ log.screenshot }}" alt="Copy folder '{{ qunitTest.pageId }}' from the job report">
92
- </div>
93
- <img {{if}}="qunitTest.screenshot" loading="lazy" class="log" src="{{ qunitTest.pageId }}/{{ qunitTest.screenshot }}" alt="Copy folder '{{ qunitTest.pageId }}' from the job report">
94
- </div>
95
- <div style="display: {{ disconnected ? 'block' : 'none' }};">&#10060; Disconnected</div>
96
- </body>
97
- </html>
@@ -1,69 +0,0 @@
1
- /* global report, job */
2
- report.ready.then(update => {
3
- const hashChange = hash => {
4
- const [, pageId, testId] = (hash || '').match(/#?([^-]*)(?:-(.*))?/)
5
- let [qunitPage, qunitTest] = [null, null]
6
- if (pageId) {
7
- const url = Object.keys(job.qunitPages).find(pageUrl => job.qunitPages[pageUrl].id === pageId)
8
- if (!url) {
9
- return
10
- }
11
- qunitPage = { url, ...job.qunitPages[url] }
12
- if (testId) {
13
- let test
14
- let moduleName
15
- qunitPage.modules.every(module => module.tests.every(candidate => {
16
- if (candidate.testId === testId) {
17
- moduleName = module.name
18
- test = candidate
19
- return false
20
- }
21
- return true
22
- }))
23
- qunitPage = null
24
- if (test) {
25
- qunitTest = {
26
- url,
27
- pageId,
28
- module: moduleName,
29
- ...test
30
- }
31
- }
32
- }
33
- }
34
- update({
35
- ...job,
36
- qunitPage,
37
- qunitTest,
38
- elapsed: report.elapsed
39
- })
40
- }
41
-
42
- window.addEventListener('hashchange', () => {
43
- hashChange(location.hash)
44
- })
45
- if (window.location.href === 'about:srcdoc') {
46
- window.addEventListener('click', (event) => {
47
- const { href } = event.target
48
- if (href) {
49
- const lastHash = href.lastIndexOf('#')
50
- hashChange(href.substring(lastHash))
51
- }
52
- event.preventDefault()
53
- return false
54
- })
55
- }
56
- hashChange(location.hash)
57
-
58
- window.addEventListener('click', (event) => {
59
- if (event.target.id === 'download') {
60
- const link = this.document.createElement('a')
61
- const blob = new Blob([JSON.stringify(job)], {
62
- type: 'application/json'
63
- })
64
- link.setAttribute('href', URL.createObjectURL(blob))
65
- link.setAttribute('download', 'ui5-test-runner-job.json')
66
- link.click()
67
- }
68
- })
69
- })
@@ -1,60 +0,0 @@
1
- (function () {
2
- /* global report */
3
-
4
- report.ready.then(update => {
5
- let lastState = {}
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
-
17
- async function refresh () {
18
- const [, page, test] = location.hash.match(/#?([^-]*)(?:-(.*))?/)
19
- let url = '/_/progress'
20
- if (page) {
21
- url += `?page=${page}`
22
- if (test) {
23
- url += `&test=${test}`
24
- }
25
- }
26
- let json
27
- try {
28
- const response = await fetch(url)
29
- json = await response.json()
30
- } catch (e) {
31
- update({
32
- ...lastState,
33
- disconnected: true
34
- })
35
- retry()
36
- return
37
- }
38
- if (test) {
39
- lastState = {
40
- qunitTest: json
41
- }
42
- } else if (page) {
43
- lastState = {
44
- qunitPage: json
45
- }
46
- } else {
47
- lastState = {
48
- ...json
49
- }
50
- }
51
- update({
52
- ...lastState,
53
- disconnected: false,
54
- elapsed: report.elapsed
55
- })
56
- setTimeout(refresh, 250)
57
- }
58
- refresh()
59
- })
60
- }())
@@ -1,66 +0,0 @@
1
- body {
2
- font-family: "Segoe UI", Arial, sans-serif;
3
- }
4
-
5
- h1 {
6
- margin-bottom: 0;
7
- }
8
-
9
- h2 {
10
- margin-bottom: 0;
11
- }
12
-
13
- h3 {
14
- margin-bottom: 0;
15
- }
16
-
17
- progress {
18
- width: 5rem;
19
- }
20
-
21
- th {
22
- text-align: left;
23
- }
24
-
25
- th.count {
26
- width: 5rem;
27
- }
28
-
29
- th.elapsed {
30
- width: 6rem;
31
- }
32
-
33
- th.status {
34
- width: 10rem;
35
- }
36
-
37
- a, a:hover {
38
- color: black;
39
- text-decoration: none;
40
- }
41
-
42
- a:hover {
43
- text-decoration: underline;
44
- text-decoration-style: dotted;
45
- }
46
-
47
- table {
48
- table-layout: fixed;
49
- width: 100%;
50
- }
51
-
52
- .truncated {
53
- overflow: hidden;
54
- text-overflow: ellipsis;
55
- white-space: nowrap;
56
- direction: rtl;
57
- text-align: left;
58
- }
59
-
60
- img.log {
61
- width: 50%;
62
- }
63
-
64
- img.log:hover {
65
- width: unset;
66
- }