ui5-test-runner 4.0.0 ā 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.
- package/README.md +0 -15
- package/package.json +3 -8
- package/src/coverage.js +51 -42
- package/src/defaults/report/progress.js +11 -0
- package/src/endpoints.js +8 -0
- package/src/get-job-progress.js +9 -0
- package/src/inject/opa-iframe-coverage.js +0 -1
- package/src/inject/post.js +4 -4
- package/src/inject/qunit-hooks.js +18 -2
- package/src/inject/qunit-intercept.js +6 -0
- package/src/inject/ui5-coverage.js +6 -0
- package/src/job-mode.js +0 -1
- package/src/job.js +3 -6
- package/src/output.js +50 -24
- package/src/qunit-hooks.js +43 -29
- package/src/symbols.js +2 -1
- package/src/tests.js +6 -1
- package/src/add-test-pages.spec.js +0 -95
- package/src/browser.spec.js +0 -724
- package/src/cors.spec.js +0 -41
- package/src/coverage.spec.js +0 -120
- package/src/csv-reader.spec.js +0 -42
- package/src/csv-writer.spec.js +0 -77
- package/src/error.spec.js +0 -17
- package/src/get-job-progress.spec.js +0 -175
- package/src/inject/post.spec.js +0 -147
- package/src/job.spec.js +0 -424
- package/src/npm.spec.js +0 -98
- package/src/options.spec.js +0 -141
- package/src/qunit-hooks.spec.js +0 -747
- package/src/simulate.spec.js +0 -547
- package/src/timeout.spec.js +0 -39
- package/src/tools.spec.js +0 -90
- package/src/unhandled.spec.js +0 -63
package/README.md
CHANGED
|
@@ -17,7 +17,6 @@ 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
22
|
* Works with [Node.js](https://nodejs.org/en/download/) >= 18
|
|
@@ -30,20 +29,6 @@ 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
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner?ref=badge_large)
|
|
49
34
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui5-test-runner",
|
|
3
|
-
"version": "4.
|
|
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
|
},
|
|
@@ -74,8 +69,8 @@
|
|
|
74
69
|
"reserve": "^1.15.6"
|
|
75
70
|
},
|
|
76
71
|
"devDependencies": {
|
|
77
|
-
"@openui5/types": "^1.120.
|
|
78
|
-
"@ui5/cli": "^3.9.
|
|
72
|
+
"@openui5/types": "^1.120.7",
|
|
73
|
+
"@ui5/cli": "^3.9.1",
|
|
79
74
|
"@ui5/middleware-code-coverage": "^1.1.1",
|
|
80
75
|
"jest": "^29.7.0",
|
|
81
76
|
"nock": "^13.5.1",
|
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
|
-
|
|
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
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
|
208
|
+
const instrumentedSourcePath = join(instrumentedBasePath, url)
|
|
198
209
|
try {
|
|
199
|
-
await access(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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,
|
|
@@ -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)
|
package/src/get-job-progress.js
CHANGED
|
@@ -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
|
}
|
package/src/inject/post.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
if (window[
|
|
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[
|
|
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
package/src/job.js
CHANGED
|
@@ -105,9 +105,10 @@ function getCommand (cwd) {
|
|
|
105
105
|
.option('--no-npm-install', '[š»šš§Ŗ] Prevent any NPM install (execution may fail if a dependency is missing)')
|
|
106
106
|
.option('-bt, --browser-close-timeout <timeout>', '[š»šš§Ŗ] Maximum waiting time for browser close', timeout, 2000)
|
|
107
107
|
.option('-br, --browser-retry <count>', '[š»šš§Ŗ] Browser instantiation retries : if the command fails unexpectedly, it is re-executed (0 means no retry)', 1)
|
|
108
|
+
.option('-oi, --output-interval <interval>', '[š»šš§Ŗ] Interval for reporting progress on non interactive output (CI/CD) (0 means no output)', timeout, 30000)
|
|
108
109
|
|
|
109
110
|
// Common to legacy and url
|
|
110
|
-
.option('
|
|
111
|
+
.option('--webapp <path>', '[š»š] Base folder of the web application (relative to cwd)', 'webapp')
|
|
111
112
|
.option('-pf, --page-filter <regexp>', '[š»š] Filter out pages not matching the regexp')
|
|
112
113
|
.option('-pp, --page-params <params>', '[š»š] Add parameters to page URL')
|
|
113
114
|
.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)
|
|
@@ -135,7 +136,6 @@ function getCommand (cwd) {
|
|
|
135
136
|
.option('--libs <lib...>', '[š»] Library mapping (<relative>=<path> or <path>)', arrayOf(lib))
|
|
136
137
|
.option('--mappings <mapping...>', '[š»] Custom mapping (<match>=<file|url>(<config>))', arrayOf(mapping))
|
|
137
138
|
.option('--cache <path>', '[š»] Cache UI5 resources locally in the given folder (empty to disable)')
|
|
138
|
-
.option('--webapp <path>', '[š»] Base folder of the web application (relative to cwd)', 'webapp')
|
|
139
139
|
.option('--testsuite <path>', '[š»] Path of the testsuite file (relative to webapp, URL parameters are supported)', 'test/testsuite.qunit.html')
|
|
140
140
|
.option('-w, --watch [flag]', '[š»] Monitor the webapp folder and re-execute tests on change', boolean, false)
|
|
141
141
|
|
|
@@ -150,7 +150,6 @@ function getCommand (cwd) {
|
|
|
150
150
|
.addOption(new Option('--debug-keep-report', DEBUG_OPTION, boolean).hideHelp())
|
|
151
151
|
.addOption(new Option('--debug-capabilities-test <name>', DEBUG_OPTION).hideHelp())
|
|
152
152
|
.addOption(new Option('--debug-capabilities-no-timeout', DEBUG_OPTION, boolean).hideHelp())
|
|
153
|
-
.addOption(new Option('--debug-coverage', DEBUG_OPTION, boolean).hideHelp())
|
|
154
153
|
.addOption(new Option('--debug-coverage-no-custom-fs', DEBUG_OPTION, boolean).hideHelp())
|
|
155
154
|
.addOption(new Option('--debug-verbose <module...>', DEBUG_OPTION, arrayOf(string), []).hideHelp())
|
|
156
155
|
|
|
@@ -264,9 +263,7 @@ function finalize (job) {
|
|
|
264
263
|
if (job.coverage) {
|
|
265
264
|
function overrideIfNotSet (option, valueFromSettings) {
|
|
266
265
|
if (valueFromSettings && job[$valueSources][option] !== 'cli') {
|
|
267
|
-
|
|
268
|
-
output.wrap(() => console.log(`${option} extracted from nyc settings : ${valueFromSettings}`))
|
|
269
|
-
}
|
|
266
|
+
output.debug('coverage', `${option} extracted from nyc settings : ${valueFromSettings}`)
|
|
270
267
|
job[option] = valueFromSettings
|
|
271
268
|
}
|
|
272
269
|
}
|
package/src/output.js
CHANGED
|
@@ -14,6 +14,7 @@ const { filename, noop, pad } = require('./tools')
|
|
|
14
14
|
const inJest = typeof jest !== 'undefined'
|
|
15
15
|
const interactive = process.stdout.columns !== undefined && !inJest
|
|
16
16
|
const $output = Symbol('output')
|
|
17
|
+
const $outputStart = Symbol('output-start')
|
|
17
18
|
|
|
18
19
|
if (!interactive) {
|
|
19
20
|
const UTF8_BOM_CODE = '\ufeff'
|
|
@@ -31,6 +32,15 @@ if (inJest) {
|
|
|
31
32
|
cons = console
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
const formatTime = duration => {
|
|
36
|
+
duration = Math.ceil(duration / 1000)
|
|
37
|
+
const seconds = duration % 60
|
|
38
|
+
const minutes = (duration - seconds) / 60
|
|
39
|
+
return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const getElapsed = job => formatTime(Date.now() - job[$outputStart])
|
|
43
|
+
|
|
34
44
|
const write = (...parts) => parts.forEach(part => process.stdout.write(part))
|
|
35
45
|
|
|
36
46
|
function clean (job) {
|
|
@@ -84,8 +94,16 @@ function bar (ratio, msg) {
|
|
|
84
94
|
const TICKS = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
|
|
85
95
|
|
|
86
96
|
function progress (job, cleanFirst = true) {
|
|
87
|
-
if (
|
|
88
|
-
|
|
97
|
+
if (interactive) {
|
|
98
|
+
if (cleanFirst) {
|
|
99
|
+
clean(job)
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
if (job[$browsers]) {
|
|
103
|
+
write(`${getElapsed(job)} ā Progress\nāāāāāāā“āāāāāāāāāā\n`)
|
|
104
|
+
} else {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
89
107
|
}
|
|
90
108
|
const output = job[$output]
|
|
91
109
|
output.lines = 1
|
|
@@ -131,11 +149,26 @@ function progress (job, cleanFirst = true) {
|
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
|
|
134
|
-
function output (job, ...
|
|
135
|
-
writeFileSync(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
function output (job, ...args) {
|
|
153
|
+
writeFileSync(
|
|
154
|
+
join(job.reportDir, 'output.txt'),
|
|
155
|
+
args.map(arg => {
|
|
156
|
+
if (typeof arg === 'object') {
|
|
157
|
+
return JSON.stringify(arg, undefined, 2)
|
|
158
|
+
}
|
|
159
|
+
if (arg === undefined) {
|
|
160
|
+
return 'undefined'
|
|
161
|
+
}
|
|
162
|
+
if (arg === null) {
|
|
163
|
+
return 'null'
|
|
164
|
+
}
|
|
165
|
+
return arg.toString()
|
|
166
|
+
}).join(' ') + '\n',
|
|
167
|
+
{
|
|
168
|
+
encoding: 'utf-8',
|
|
169
|
+
flag: 'a'
|
|
170
|
+
}
|
|
171
|
+
)
|
|
139
172
|
}
|
|
140
173
|
|
|
141
174
|
function log (job, ...texts) {
|
|
@@ -185,13 +218,6 @@ function browserIssue (job, { type, url, code, dir }) {
|
|
|
185
218
|
log(job, p`āāāāāāāāāāā${pad.x('ā')}ā`)
|
|
186
219
|
}
|
|
187
220
|
|
|
188
|
-
const formatTime = duration => {
|
|
189
|
-
duration = Math.ceil(duration / 1000)
|
|
190
|
-
const seconds = duration % 60
|
|
191
|
-
const minutes = (duration - seconds) / 60
|
|
192
|
-
return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0')
|
|
193
|
-
}
|
|
194
|
-
|
|
195
221
|
function build (job) {
|
|
196
222
|
let wrap
|
|
197
223
|
if (interactive) {
|
|
@@ -206,8 +232,7 @@ function build (job) {
|
|
|
206
232
|
} else {
|
|
207
233
|
wrap = method => method
|
|
208
234
|
}
|
|
209
|
-
|
|
210
|
-
const getElapsed = () => formatTime(Date.now() - outputStart)
|
|
235
|
+
job[$outputStart] = Date.now()
|
|
211
236
|
|
|
212
237
|
return {
|
|
213
238
|
lastTick: 0,
|
|
@@ -228,6 +253,7 @@ function build (job) {
|
|
|
228
253
|
debug: wrap((module, ...args) => {
|
|
229
254
|
if (job.debugVerbose && job.debugVerbose.includes(module)) {
|
|
230
255
|
console.log(`š${module}`, ...args)
|
|
256
|
+
output(job, `š${module}`, ...args)
|
|
231
257
|
}
|
|
232
258
|
}),
|
|
233
259
|
|
|
@@ -251,7 +277,7 @@ function build (job) {
|
|
|
251
277
|
} else {
|
|
252
278
|
method = log
|
|
253
279
|
}
|
|
254
|
-
const text = `${getElapsed()} ā ${status}`
|
|
280
|
+
const text = `${getElapsed(job)} ā ${status}`
|
|
255
281
|
method(job, '')
|
|
256
282
|
method(job, text)
|
|
257
283
|
method(job, 'āāāāāāā“'.padEnd(text.length, 'ā'))
|
|
@@ -268,6 +294,8 @@ function build (job) {
|
|
|
268
294
|
reportOnJobProgress () {
|
|
269
295
|
if (interactive) {
|
|
270
296
|
this.reportIntervalId = setInterval(progress.bind(null, job), 250)
|
|
297
|
+
} else if (job.outputInterval) {
|
|
298
|
+
this.reportIntervalId = setInterval(progress.bind(null, job), job.outputInterval)
|
|
271
299
|
}
|
|
272
300
|
},
|
|
273
301
|
|
|
@@ -311,7 +339,7 @@ function build (job) {
|
|
|
311
339
|
},
|
|
312
340
|
|
|
313
341
|
browserStart (url) {
|
|
314
|
-
const text = p80()`${getElapsed()} >> ${pad.lt(url)} [${filename(url)}]`
|
|
342
|
+
const text = p80()`${getElapsed(job)} >> ${pad.lt(url)} [${filename(url)}]`
|
|
315
343
|
if (interactive) {
|
|
316
344
|
output(job, text)
|
|
317
345
|
} else {
|
|
@@ -325,7 +353,7 @@ function build (job) {
|
|
|
325
353
|
if (page) {
|
|
326
354
|
duration = ' (' + formatTime(page.end - page.start) + ')'
|
|
327
355
|
}
|
|
328
|
-
const text = p80()`${getElapsed()} << ${pad.lt(url)} ${duration} [${filename(url)}]`
|
|
356
|
+
const text = p80()`${getElapsed(job)} << ${pad.lt(url)} ${duration} [${filename(url)}]`
|
|
329
357
|
if (interactive) {
|
|
330
358
|
output(job, text)
|
|
331
359
|
} else {
|
|
@@ -418,10 +446,6 @@ function build (job) {
|
|
|
418
446
|
log(job, p80()`Skipping nyc instrumentation (--url)`)
|
|
419
447
|
}),
|
|
420
448
|
|
|
421
|
-
qunitEarlyStart: wrap(url => {
|
|
422
|
-
log(job, p80()`QUnit start without tests in ${pad.lt(url)}`)
|
|
423
|
-
}),
|
|
424
|
-
|
|
425
449
|
endpointError: wrap(({ api, url, data, error }) => {
|
|
426
450
|
const p = p80()
|
|
427
451
|
log(job, p`āāāāāāāāāāā${pad.x('ā')}ā`)
|
|
@@ -514,7 +538,9 @@ function build (job) {
|
|
|
514
538
|
stop () {
|
|
515
539
|
if (this.reportIntervalId) {
|
|
516
540
|
clearInterval(this.reportIntervalId)
|
|
517
|
-
|
|
541
|
+
if (interactive) {
|
|
542
|
+
clean(job)
|
|
543
|
+
}
|
|
518
544
|
}
|
|
519
545
|
}
|
|
520
546
|
}
|