ui5-test-runner 5.4.3 → 5.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +33 -10
- package/package.json +9 -8
- package/src/batch.js +196 -0
- package/src/capabilities/index.js +12 -10
- package/src/capabilities/tests/scripts/index.js +2 -2
- package/src/capabilities/tests/ui5/index.js +39 -0
- package/src/capabilities/tests/ui5/language.html +50 -0
- package/src/defaults/browser.js +2 -2
- package/src/defaults/junit-xml-report.js +1 -1
- package/src/end.js +58 -0
- package/src/if.js +10 -0
- package/src/job-mode.js +10 -5
- package/src/job.js +109 -47
- package/src/options.js +4 -2
- package/src/output.js +16 -0
- package/src/parallelize.js +4 -2
- package/src/qunit-hooks.js +4 -2
- package/src/report.js +1 -1
- package/src/start.js +25 -21
- package/src/tests.js +8 -1
- package/src/ui5.js +1 -1
- package/src/capabilities/tests/ui5-focus/index.js +0 -12
- /package/src/capabilities/tests/{ui5-focus/index.html → ui5/focus.html} +0 -0
package/index.js
CHANGED
|
@@ -12,7 +12,10 @@ const { preload } = require('./src/ui5')
|
|
|
12
12
|
const { probe: probeBrowser } = require('./src/browsers')
|
|
13
13
|
const { recreateDir, allocPromise } = require('./src/tools')
|
|
14
14
|
const reserveConfigurationFactory = require('./src/reserve')
|
|
15
|
-
const start = require('./src/start')
|
|
15
|
+
const { start } = require('./src/start')
|
|
16
|
+
const { executeIf } = require('./src/if')
|
|
17
|
+
const { batch } = require('./src/batch')
|
|
18
|
+
const { end } = require('./src/end')
|
|
16
19
|
|
|
17
20
|
function send (message) {
|
|
18
21
|
if (process.send) {
|
|
@@ -48,9 +51,33 @@ async function main () {
|
|
|
48
51
|
output = getOutput(job)
|
|
49
52
|
await recreateDir(job.reportDir)
|
|
50
53
|
output.version()
|
|
54
|
+
if (job.batchMode) {
|
|
55
|
+
output.batchMode()
|
|
56
|
+
}
|
|
57
|
+
output.reportOnJobProgress()
|
|
51
58
|
if (job.mode === 'capabilities') {
|
|
52
59
|
return capabilities(job)
|
|
53
60
|
}
|
|
61
|
+
if (job.if && !executeIf(job)) {
|
|
62
|
+
output.skipIf()
|
|
63
|
+
output.stop()
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let startedCommand
|
|
68
|
+
if (job.startCommand) {
|
|
69
|
+
startedCommand = await start(job)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (job.mode === 'batch') {
|
|
73
|
+
return await batch(job)
|
|
74
|
+
.finally(() => {
|
|
75
|
+
if (startedCommand) {
|
|
76
|
+
return startedCommand.stop()
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
54
81
|
const configuration = await reserveConfigurationFactory(job)
|
|
55
82
|
output.debug('reserve', 'configuration', configuration)
|
|
56
83
|
const server = serve(configuration)
|
|
@@ -64,7 +91,6 @@ async function main () {
|
|
|
64
91
|
job.port = port
|
|
65
92
|
send({ msg: 'ready', port: job.port })
|
|
66
93
|
output.serving(url)
|
|
67
|
-
output.reportOnJobProgress()
|
|
68
94
|
serverReady()
|
|
69
95
|
})
|
|
70
96
|
.on('error', error => {
|
|
@@ -73,11 +99,6 @@ async function main () {
|
|
|
73
99
|
serverError()
|
|
74
100
|
})
|
|
75
101
|
await serverStarted
|
|
76
|
-
let startedCommand
|
|
77
|
-
if (job.start) {
|
|
78
|
-
output.reportOnJobProgress()
|
|
79
|
-
startedCommand = await start(job)
|
|
80
|
-
}
|
|
81
102
|
if (job.preload) {
|
|
82
103
|
await preload(job)
|
|
83
104
|
}
|
|
@@ -90,8 +111,8 @@ async function main () {
|
|
|
90
111
|
if (job.watch) {
|
|
91
112
|
delete job.start
|
|
92
113
|
if (!job.watching) {
|
|
93
|
-
output.watching(job.
|
|
94
|
-
watch(job.
|
|
114
|
+
output.watching(job.watchFolder)
|
|
115
|
+
watch(job.watchFolder, { recursive: true }, async (eventType, filename) => {
|
|
95
116
|
output.changeDetected(eventType, filename)
|
|
96
117
|
if (!job.start) {
|
|
97
118
|
await recreateDir(job.reportDir)
|
|
@@ -106,12 +127,14 @@ async function main () {
|
|
|
106
127
|
} else if (job.failed) {
|
|
107
128
|
process.exitCode = -1
|
|
108
129
|
}
|
|
130
|
+
if (job.endScript) {
|
|
131
|
+
await end(job)
|
|
132
|
+
}
|
|
109
133
|
output.stop()
|
|
110
134
|
await server.close()
|
|
111
135
|
if (startedCommand) {
|
|
112
136
|
await startedCommand.stop()
|
|
113
137
|
}
|
|
114
|
-
console.log('done ?')
|
|
115
138
|
}
|
|
116
139
|
|
|
117
140
|
main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui5-test-runner",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.1",
|
|
4
4
|
"description": "Standalone test runner for UI5",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
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",
|
|
18
18
|
"test:unit": "jest",
|
|
19
19
|
"test:unit:debug": "node --inspect node_modules/jest/bin/jest.js --runInBand --no-coverage",
|
|
20
|
-
"test:e2e": "node test/e2e",
|
|
20
|
+
"test:e2e": "node . --batch test/e2e/.*\\.json --report-dir e2e --start \"node test/e2e/serve.js\" --start-wait-url http://localhost:8081 --start-wait-method HEAD --start-timeout 30s",
|
|
21
21
|
"test:report": "node ./src/defaults/report.js ./test/report && reserve --config ./test/report/reserve.json",
|
|
22
22
|
"test:text-report": "node ./src/defaults/text-report.js ./test/report",
|
|
23
23
|
"build:doc": "node build/doc",
|
|
@@ -47,20 +47,20 @@
|
|
|
47
47
|
"ps-tree": "^1.2.0",
|
|
48
48
|
"punybind": "^1.2.1",
|
|
49
49
|
"punyexpr": "^1.0.4",
|
|
50
|
-
"reserve": "2.0
|
|
50
|
+
"reserve": "2.1.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@openui5/types": "^1.
|
|
53
|
+
"@openui5/types": "^1.133.0",
|
|
54
54
|
"@ui5/cli": "^4.0.13",
|
|
55
55
|
"@ui5/middleware-code-coverage": "^2.0.1",
|
|
56
56
|
"dotenv": "^16.4.7",
|
|
57
57
|
"jest": "^29.7.0",
|
|
58
|
-
"nock": "^14.0.
|
|
58
|
+
"nock": "^14.0.1",
|
|
59
59
|
"nyc": "^17.1.0",
|
|
60
60
|
"rimraf": "^6.0.1",
|
|
61
61
|
"standard": "^17.1.2",
|
|
62
|
-
"typescript": "^5.
|
|
63
|
-
"ui5-tooling-transpile": "^3.
|
|
62
|
+
"typescript": "^5.8.2",
|
|
63
|
+
"ui5-tooling-transpile": "^3.7.1"
|
|
64
64
|
},
|
|
65
65
|
"optionalDependencies": {
|
|
66
66
|
"fsevents": "^2.3.3"
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
],
|
|
78
78
|
"globals": [
|
|
79
79
|
"sap",
|
|
80
|
-
"opaTest"
|
|
80
|
+
"opaTest",
|
|
81
|
+
"normalizePath"
|
|
81
82
|
]
|
|
82
83
|
}
|
|
83
84
|
}
|
package/src/batch.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const { open, readdir, stat, unlink } = require('fs/promises')
|
|
2
|
+
const { extname, isAbsolute, join } = require('path')
|
|
3
|
+
const { allocPromise, filename } = require('./tools')
|
|
4
|
+
const { fork } = require('child_process')
|
|
5
|
+
const { interactive, getOutput, newProgress } = require('./output')
|
|
6
|
+
const { parallelize } = require('./parallelize')
|
|
7
|
+
const { $statusProgressCount } = require('./symbols')
|
|
8
|
+
const { $valueSources } = require('./symbols')
|
|
9
|
+
const { getCommand, toLongName } = require('./job')
|
|
10
|
+
|
|
11
|
+
const batchParameters = getCommand('.').options
|
|
12
|
+
.filter(option => option.description.includes('📡'))
|
|
13
|
+
.reduce((dictionary, option) => {
|
|
14
|
+
dictionary[option.long.substring(2)] = option
|
|
15
|
+
return dictionary
|
|
16
|
+
}, {})
|
|
17
|
+
|
|
18
|
+
const root = join(__dirname, '..')
|
|
19
|
+
|
|
20
|
+
const folder = (batchItems, job, folderPath) => {
|
|
21
|
+
getOutput(job).debug('batch', `adding folder: ${folderPath}`)
|
|
22
|
+
batchItems.push({
|
|
23
|
+
job,
|
|
24
|
+
path: folderPath,
|
|
25
|
+
id: filename(folderPath),
|
|
26
|
+
label: folderPath,
|
|
27
|
+
args: ['--cwd', folderPath]
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const configurationFile = (batchItems, job, configurationFilePath) => {
|
|
32
|
+
getOutput(job).debug('batch', `adding configuration file: ${configurationFilePath}`)
|
|
33
|
+
try {
|
|
34
|
+
const {
|
|
35
|
+
batchId: id = filename(configurationFilePath),
|
|
36
|
+
batchLabel: label = configurationFilePath
|
|
37
|
+
} = require(configurationFilePath)
|
|
38
|
+
batchItems.push({
|
|
39
|
+
job,
|
|
40
|
+
path: configurationFilePath,
|
|
41
|
+
id,
|
|
42
|
+
label,
|
|
43
|
+
args: ['--config', configurationFilePath]
|
|
44
|
+
})
|
|
45
|
+
} catch (e) {
|
|
46
|
+
getOutput(job).batchFailed(configurationFilePath, 'invalid JSON configuration file')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const task = async ({ job, id, label, args }) => {
|
|
51
|
+
const output = getOutput(job)
|
|
52
|
+
const progress = newProgress(job)
|
|
53
|
+
const reportDir = join(job.reportDir, id)
|
|
54
|
+
progress.label = `${label} (${id})`
|
|
55
|
+
progress.count = 1
|
|
56
|
+
if (!interactive) {
|
|
57
|
+
output.log(`${label}...`)
|
|
58
|
+
}
|
|
59
|
+
const { promise, resolve, reject } = allocPromise()
|
|
60
|
+
const parameters = [
|
|
61
|
+
...args,
|
|
62
|
+
'--batch-mode'
|
|
63
|
+
]
|
|
64
|
+
if (job[$valueSources]) {
|
|
65
|
+
if (job[$valueSources].reportDir === 'cli') {
|
|
66
|
+
parameters.push('--report-dir', reportDir)
|
|
67
|
+
}
|
|
68
|
+
Object.keys(job[$valueSources])
|
|
69
|
+
.filter(name => job[$valueSources][name] === 'cli')
|
|
70
|
+
.forEach(name => {
|
|
71
|
+
const longName = toLongName(name)
|
|
72
|
+
const option = batchParameters[longName]
|
|
73
|
+
if (option) {
|
|
74
|
+
parameters.push(`--${longName}`)
|
|
75
|
+
if (!option.optional && !option.negate) {
|
|
76
|
+
if (option.variadic) {
|
|
77
|
+
parameters.push(...job[name].map(value => value.toString()))
|
|
78
|
+
} else {
|
|
79
|
+
parameters.push(job[name].toString())
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const stdoutFilename = join(job.reportDir, `${id}.stdout.txt`)
|
|
87
|
+
const stdout = await open(stdoutFilename, 'w')
|
|
88
|
+
const stderrFilename = join(job.reportDir, `${id}.stderr.txt`)
|
|
89
|
+
const stderr = await open(stderrFilename, 'w')
|
|
90
|
+
const childProcess = fork(
|
|
91
|
+
join(root, 'index.js'),
|
|
92
|
+
parameters,
|
|
93
|
+
{
|
|
94
|
+
stdio: [0, stdout, stderr, 'ipc']
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
childProcess.on('message', data => {
|
|
98
|
+
if (data.type === 'progress') {
|
|
99
|
+
const { count, total } = data
|
|
100
|
+
progress.count = count
|
|
101
|
+
progress.total = total
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
childProcess.on('close', async code => {
|
|
105
|
+
await stdout.close()
|
|
106
|
+
await stderr.close()
|
|
107
|
+
if (code !== 0) {
|
|
108
|
+
reject(code)
|
|
109
|
+
} else {
|
|
110
|
+
await unlink(stdoutFilename)
|
|
111
|
+
await unlink(stderrFilename)
|
|
112
|
+
resolve()
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
return promise
|
|
116
|
+
.then(() => {
|
|
117
|
+
output.log('✔️ ', progress.label)
|
|
118
|
+
}, (reason) => {
|
|
119
|
+
++job.errors
|
|
120
|
+
output.log('❌', progress.label, reason)
|
|
121
|
+
})
|
|
122
|
+
.finally(() => {
|
|
123
|
+
++job[$statusProgressCount]
|
|
124
|
+
progress.done()
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function batch (job) {
|
|
129
|
+
/**
|
|
130
|
+
* job
|
|
131
|
+
* path: full path
|
|
132
|
+
* id: batchId | hash (using filename)
|
|
133
|
+
* label: batchLabel | configuration file path | path
|
|
134
|
+
* args: []
|
|
135
|
+
* --report-dir is always passed to aggregate reports under one root folder
|
|
136
|
+
*/
|
|
137
|
+
const output = getOutput(job)
|
|
138
|
+
const batchItems = []
|
|
139
|
+
for (const batch of job.batch) {
|
|
140
|
+
output.debug('batch', `processing: ${batch}`)
|
|
141
|
+
// check if path
|
|
142
|
+
try {
|
|
143
|
+
let path = batch
|
|
144
|
+
if (!isAbsolute(path)) {
|
|
145
|
+
path = join(job.cwd, path)
|
|
146
|
+
}
|
|
147
|
+
const pathStat = await stat(path)
|
|
148
|
+
if (pathStat.isDirectory()) {
|
|
149
|
+
folder(batchItems, job, path)
|
|
150
|
+
} else if (pathStat.isFile() && extname(path) === '.json') {
|
|
151
|
+
configurationFile(batchItems, job, path)
|
|
152
|
+
} else {
|
|
153
|
+
output.batchFailed(batch, 'only folders and JSON configuration files are supported')
|
|
154
|
+
}
|
|
155
|
+
continue
|
|
156
|
+
} catch (e) {
|
|
157
|
+
// ignore
|
|
158
|
+
}
|
|
159
|
+
// Try using regular expression match
|
|
160
|
+
let re
|
|
161
|
+
try {
|
|
162
|
+
re = new RegExp(batch)
|
|
163
|
+
} catch (e) {
|
|
164
|
+
getOutput(job).batchFailed(batch, 'invalid regular expression')
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
const scan = async (cwd) => {
|
|
168
|
+
const names = await readdir(cwd)
|
|
169
|
+
for (const name of names) {
|
|
170
|
+
const path = join(cwd, name)
|
|
171
|
+
const pathStat = await stat(path)
|
|
172
|
+
if (pathStat.isDirectory()) {
|
|
173
|
+
if (re.test(path) || re.test(path.replaceAll('\\', '/'))) {
|
|
174
|
+
folder(batchItems, job, path)
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
await scan(path)
|
|
178
|
+
} else if (pathStat.isFile() && (re.test(path) || re.test(path.replaceAll('\\', '/')))) {
|
|
179
|
+
configurationFile(batchItems, job, path)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
await scan(job.cwd)
|
|
184
|
+
}
|
|
185
|
+
if (batchItems.length) {
|
|
186
|
+
job.status = 'Running batch items...'
|
|
187
|
+
await parallelize(task, batchItems, job.parallel)
|
|
188
|
+
// TODO: end command ?
|
|
189
|
+
} else {
|
|
190
|
+
output.batchFailed(job.batch, 'no match')
|
|
191
|
+
}
|
|
192
|
+
output.stop()
|
|
193
|
+
return 0
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { batch, task }
|
|
@@ -8,7 +8,7 @@ const { performance } = require('perf_hooks')
|
|
|
8
8
|
const { cleanDir, allocPromise, filename } = require('../tools')
|
|
9
9
|
const { $statusProgressTotal, $statusProgressCount } = require('../symbols')
|
|
10
10
|
const tests = require('./tests')
|
|
11
|
-
const parallelize = require('../parallelize')
|
|
11
|
+
const { parallelize } = require('../parallelize')
|
|
12
12
|
|
|
13
13
|
async function capabilities (job) {
|
|
14
14
|
const output = getOutput(job)
|
|
@@ -30,16 +30,14 @@ async function capabilities (job) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
exit(-1)
|
|
39
|
-
}
|
|
33
|
+
await probe(job)
|
|
34
|
+
} catch (e) {
|
|
35
|
+
output.error('Unable to probe')
|
|
36
|
+
exit(-1)
|
|
37
|
+
}
|
|
40
38
|
|
|
39
|
+
try {
|
|
41
40
|
const listeners = []
|
|
42
|
-
|
|
43
41
|
const configuration = await check({
|
|
44
42
|
port: job.port,
|
|
45
43
|
mappings: [
|
|
@@ -103,7 +101,7 @@ async function capabilities (job) {
|
|
|
103
101
|
|
|
104
102
|
const task = async (test) => {
|
|
105
103
|
const { promise, resolve } = allocPromise()
|
|
106
|
-
const { label, url, scripts, endpoint = () => { } } = test
|
|
104
|
+
const { label, url, args, scripts, endpoint = () => { } } = test
|
|
107
105
|
|
|
108
106
|
const listenerIndex = listeners.length
|
|
109
107
|
let pageUrl
|
|
@@ -161,6 +159,10 @@ async function capabilities (job) {
|
|
|
161
159
|
}
|
|
162
160
|
}
|
|
163
161
|
|
|
162
|
+
if (args) {
|
|
163
|
+
// TODO replace or concat ?
|
|
164
|
+
}
|
|
165
|
+
|
|
164
166
|
start(job, pageUrl, scripts)
|
|
165
167
|
.catch(reason => done(reason))
|
|
166
168
|
.then(() => {
|
|
@@ -44,8 +44,8 @@ module.exports = [{
|
|
|
44
44
|
}
|
|
45
45
|
}, {
|
|
46
46
|
label: 'Scripts (External QUnit)',
|
|
47
|
-
for: capabilities => !!capabilities.scripts,
|
|
48
|
-
url: 'https://ui5.sap.com/test-resources/sap/m/demokit/orderbrowser/webapp/test/
|
|
47
|
+
for: capabilities => !!capabilities.scripts && !capabilities.modules.includes('jsdom'),
|
|
48
|
+
url: 'https://ui5.sap.com/test-resources/sap/m/demokit/orderbrowser/webapp/test/Test.qunit.html?testsuite=test-resources/sap/ui/demo/orderbrowser/testsuite.qunit&test=unit/unitTests',
|
|
49
49
|
scripts: ['post.js', 'qunit-hooks.js'],
|
|
50
50
|
endpoint: qUnitEndpoints
|
|
51
51
|
}, {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('assert')
|
|
4
|
+
|
|
5
|
+
module.exports = [{
|
|
6
|
+
label: 'UI5 focus handling',
|
|
7
|
+
for: capabilities => !capabilities.modules.includes('jsdom'), // does not work on JSDOM
|
|
8
|
+
url: 'ui5/focus.html',
|
|
9
|
+
endpoint: ({ body }) => {
|
|
10
|
+
assert.strictEqual(body['is-focus-set'], true)
|
|
11
|
+
}
|
|
12
|
+
}, {
|
|
13
|
+
label: 'UI5 language handling (EN)',
|
|
14
|
+
for: () => false, // capabilities => capabilities.modules.includes('puppeteer'),
|
|
15
|
+
url: 'ui5/language.html',
|
|
16
|
+
args: ['--language en'],
|
|
17
|
+
endpoint: ({ body }) => {
|
|
18
|
+
assert.strictEqual(body.language, 'en')
|
|
19
|
+
assert.strictEqual(body.message, 'No matching items found.')
|
|
20
|
+
}
|
|
21
|
+
}, {
|
|
22
|
+
label: 'UI5 language handling (DE)',
|
|
23
|
+
for: () => false, // capabilities => capabilities.modules.includes('puppeteer'),
|
|
24
|
+
url: 'ui5/language.html',
|
|
25
|
+
args: ['--language de'],
|
|
26
|
+
endpoint: ({ body }) => {
|
|
27
|
+
assert.strictEqual(body.language, 'de')
|
|
28
|
+
assert.strictEqual(body.message, 'Keine passenden Elemente gefunden.')
|
|
29
|
+
}
|
|
30
|
+
}, {
|
|
31
|
+
label: 'UI5 language handling (FR)',
|
|
32
|
+
for: () => false, // capabilities => capabilities.modules.includes('puppeteer'),
|
|
33
|
+
url: 'ui5/language.html',
|
|
34
|
+
args: ['--language fr'],
|
|
35
|
+
endpoint: ({ body }) => {
|
|
36
|
+
assert.strictEqual(body.language, 'fr')
|
|
37
|
+
assert.strictEqual(body.message, 'Aucun élément concordant trouvé.')
|
|
38
|
+
}
|
|
39
|
+
}]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<script id='sap-ui-bootstrap'
|
|
7
|
+
src='https://ui5.sap.com/resources/sap-ui-core.js'
|
|
8
|
+
data-sap-ui-libs='sap.m'
|
|
9
|
+
data-sap-ui-theme='sap_horizon'
|
|
10
|
+
data-sap-ui-compatVersion='edge'>
|
|
11
|
+
</script>
|
|
12
|
+
<style>
|
|
13
|
+
html, body { height: 100%; }
|
|
14
|
+
</style>
|
|
15
|
+
<script id="myXml" type="text/xmldata">
|
|
16
|
+
<mvc:View
|
|
17
|
+
xmlns:mvc="sap.ui.core.mvc"
|
|
18
|
+
xmlns="sap.m"
|
|
19
|
+
controllerName="myController"
|
|
20
|
+
displayBlock="true"
|
|
21
|
+
height="100%"
|
|
22
|
+
>
|
|
23
|
+
<MessagePage showHeader="false" icon="sap-icon://filter"/>
|
|
24
|
+
</mvc:View>
|
|
25
|
+
</script>
|
|
26
|
+
<script>
|
|
27
|
+
sap.ui.require([
|
|
28
|
+
"sap/ui/core/mvc/Controller",
|
|
29
|
+
"sap/ui/core/mvc/XMLView"
|
|
30
|
+
], function (Controller, XMLView, Dialog, Button) {
|
|
31
|
+
Controller.extend("myController", {
|
|
32
|
+
onAfterRendering: function () {
|
|
33
|
+
// Notify the test framework
|
|
34
|
+
const xhr = new XMLHttpRequest();
|
|
35
|
+
xhr.open('POST', '/_/log');
|
|
36
|
+
xhr.send(JSON.stringify({
|
|
37
|
+
language: navigator.language,
|
|
38
|
+
message: document.querySelector('.sapMMessagePageMainText').innerText
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
XMLView.create({definition: jQuery('#myXml').html()}).then(function (oView) {
|
|
44
|
+
oView.placeAt(document.querySelector('body'));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
48
|
+
</head>
|
|
49
|
+
<body class='sapUiBody'></body>
|
|
50
|
+
</html>
|
package/src/defaults/browser.js
CHANGED
|
@@ -2,7 +2,7 @@ const { readFile, writeFile } = require('fs/promises')
|
|
|
2
2
|
const { join } = require('path')
|
|
3
3
|
const { Command, InvalidArgumentError } = require('commander')
|
|
4
4
|
const { buildCsvWriter } = require('../csv-writer')
|
|
5
|
-
const { any, boolean, integer } = require('../options')
|
|
5
|
+
const { any, arrayOf, boolean, integer, string } = require('../options')
|
|
6
6
|
|
|
7
7
|
const noop = () => { }
|
|
8
8
|
|
|
@@ -51,7 +51,7 @@ module.exports = ({
|
|
|
51
51
|
]
|
|
52
52
|
}
|
|
53
53
|
if (option === 'language') {
|
|
54
|
-
return [['-l, --language <lang...>', 'Language(s)', ['en-US']]]
|
|
54
|
+
return [['-l, --language <lang...>', 'Language(s) (see rfc5646)', arrayOf(string, true), ['en-US']]]
|
|
55
55
|
}
|
|
56
56
|
if (option === 'unsecure') {
|
|
57
57
|
return [['-u, --unsecure', 'Disable security features', false]]
|
package/src/end.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const { fork } = require('node:child_process')
|
|
2
|
+
const { join } = require('node:path')
|
|
3
|
+
const { getOutput } = require('./output')
|
|
4
|
+
const { allocPromise } = require('./tools')
|
|
5
|
+
|
|
6
|
+
async function end (job) {
|
|
7
|
+
const { endScript: end } = job
|
|
8
|
+
const output = getOutput(job)
|
|
9
|
+
const [script, ...args] = end.split(' ')
|
|
10
|
+
|
|
11
|
+
job.status = 'Executing end script'
|
|
12
|
+
|
|
13
|
+
output.debug('end', 'Starting script :', end)
|
|
14
|
+
const childProcess = fork(
|
|
15
|
+
script,
|
|
16
|
+
[...args, join(job.reportDir, 'job.js')],
|
|
17
|
+
{
|
|
18
|
+
cwd: job.cwd,
|
|
19
|
+
stdio: [0, 'pipe', 'pipe', 'ipc'],
|
|
20
|
+
windowsHide: true
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const { promise: childProcessExit, resolve: childProcessExited } = allocPromise()
|
|
25
|
+
childProcess.on('close', childProcessExited)
|
|
26
|
+
output.monitor(childProcess)
|
|
27
|
+
|
|
28
|
+
job.status = 'Waiting for script to end'
|
|
29
|
+
|
|
30
|
+
if (job.endTimeout) {
|
|
31
|
+
let timedOut = false
|
|
32
|
+
const { promise: endTimeoutSignal, resolve: endTimeoutReached } = allocPromise()
|
|
33
|
+
const timeoutId = setTimeout(() => {
|
|
34
|
+
timedOut = true
|
|
35
|
+
endTimeoutReached()
|
|
36
|
+
}, job.endTimeout)
|
|
37
|
+
|
|
38
|
+
await Promise.race([
|
|
39
|
+
childProcessExit,
|
|
40
|
+
endTimeoutSignal
|
|
41
|
+
])
|
|
42
|
+
clearTimeout(timeoutId)
|
|
43
|
+
|
|
44
|
+
if (timedOut) {
|
|
45
|
+
childProcess.kill()
|
|
46
|
+
throw new Error('Timeout while waiting for end script')
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
await childProcessExit
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
output.debug('end', 'Ended with exit code :', childProcess.exitCode)
|
|
53
|
+
|
|
54
|
+
// IMPORTANT : the end command CHANGES the exit code
|
|
55
|
+
process.exitCode = childProcess.exitCode
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { end }
|
package/src/if.js
ADDED
package/src/job-mode.js
CHANGED
|
@@ -17,6 +17,9 @@ function check (job, allowed, forbidden) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function buildAndCheckMode (job) {
|
|
20
|
+
if (job.batch) {
|
|
21
|
+
return 'batch'
|
|
22
|
+
}
|
|
20
23
|
if (job.capabilities) {
|
|
21
24
|
check(job, [
|
|
22
25
|
'capabilities',
|
|
@@ -33,8 +36,12 @@ function buildAndCheckMode (job) {
|
|
|
33
36
|
'keepAlive',
|
|
34
37
|
'alternateNpmPath',
|
|
35
38
|
'outputInterval',
|
|
36
|
-
'screenshotTimeout'
|
|
37
|
-
|
|
39
|
+
'screenshotTimeout',
|
|
40
|
+
'config',
|
|
41
|
+
'batchMode',
|
|
42
|
+
'batchId',
|
|
43
|
+
'batchLabel'
|
|
44
|
+
])
|
|
38
45
|
return 'capabilities'
|
|
39
46
|
}
|
|
40
47
|
if (job.url && job.url.length) {
|
|
@@ -43,7 +50,6 @@ function buildAndCheckMode (job) {
|
|
|
43
50
|
'libs',
|
|
44
51
|
'mappings',
|
|
45
52
|
'cache',
|
|
46
|
-
'watch',
|
|
47
53
|
'testsuite'
|
|
48
54
|
])
|
|
49
55
|
return 'url'
|
|
@@ -51,8 +57,7 @@ function buildAndCheckMode (job) {
|
|
|
51
57
|
check(job, undefined, [
|
|
52
58
|
'coverageProxy',
|
|
53
59
|
'coverageProxyInclude',
|
|
54
|
-
'coverageProxyExclude'
|
|
55
|
-
'start'
|
|
60
|
+
'coverageProxyExclude'
|
|
56
61
|
])
|
|
57
62
|
return 'legacy'
|
|
58
63
|
}
|
package/src/job.js
CHANGED
|
@@ -93,68 +93,83 @@ function getCommand (cwd) {
|
|
|
93
93
|
new Option('-c, --cwd <path>', '[💻🔗🧪] Set working directory')
|
|
94
94
|
.default(cwd, 'current working directory')
|
|
95
95
|
)
|
|
96
|
+
.option('--config <json>', '[💻🔗🧪] Configuration file (relative to cwd)', string, 'ui5-test-runner.json')
|
|
96
97
|
.option('--port <port>', '[💻🔗🧪] Port to use (0 to use any free one)', integer, 0)
|
|
97
98
|
.option('-r, --report-dir <path>', '[💻🔗🧪] Directory to output test reports (relative to cwd)', 'report')
|
|
98
|
-
.option('-pt, --page-timeout <timeout>', '[
|
|
99
|
-
.option('-f, --fail-fast [flag]', '[
|
|
100
|
-
.option('-fo, --fail-opa-fast [flag]', '[
|
|
99
|
+
.option('-pt, --page-timeout <timeout>', '[💻🔗🧪📡] Limit the page execution time, fails the page if it takes longer than the timeout (0 means no timeout)', timeout, 0)
|
|
100
|
+
.option('-f, --fail-fast [flag]', '[💻🔗🧪📡] Stop the execution after the first failing page', boolean, false)
|
|
101
|
+
.option('-fo, --fail-opa-fast [flag]', '[💻🔗📡] Stop the OPA page execution after the first failing test', boolean, false)
|
|
101
102
|
.option('-k, --keep-alive [flag]', '[💻🔗🧪] Keep the server alive', boolean, false)
|
|
102
|
-
.option('-l, --log-server [flag]', '[
|
|
103
|
+
.option('-l, --log-server [flag]', '[💻🔗🧪📡] Log inner server traces', boolean, false)
|
|
103
104
|
.option('-p, --parallel <count>', '[💻🔗🧪] Number of parallel tests executions', integer, 2)
|
|
104
|
-
.option('-b, --browser <command>', '[
|
|
105
|
-
.option('--browser-args <argument...>', '[
|
|
106
|
-
.option('--alternate-npm-path <path>', '[
|
|
107
|
-
.option('--no-npm-install', '[
|
|
108
|
-
.option('-bt, --browser-close-timeout <timeout>', '[
|
|
109
|
-
.option('-br, --browser-retry <count>', '[
|
|
110
|
-
.option('-oi, --output-interval <interval>', '[
|
|
111
|
-
.option('--offline [flag]', '[
|
|
105
|
+
.option('-b, --browser <command>', '[💻🔗🧪📡] Browser instantiation command (relative to cwd or use $/ for provided ones)', '$/puppeteer.js')
|
|
106
|
+
.option('--browser-args <argument...>', '[💻🔗🧪📡] Browser instantiation command parameters (use -- instead)')
|
|
107
|
+
.option('--alternate-npm-path <path>', '[💻🔗📡] Alternate NPM path to look for packages (priority: local, alternate, global)')
|
|
108
|
+
.option('--no-npm-install', '[💻🔗🧪📡] Prevent any NPM install (execution may fail if a dependency is missing)')
|
|
109
|
+
.option('-bt, --browser-close-timeout <timeout>', '[💻🔗🧪📡] Maximum waiting time for browser close', timeout, 2000)
|
|
110
|
+
.option('-br, --browser-retry <count>', '[💻🔗🧪📡] Browser instantiation retries : if the command fails unexpectedly, it is re-executed (0 means no retry)', 1)
|
|
111
|
+
.option('-oi, --output-interval <interval>', '[💻🔗🧪📡] Interval for reporting progress on non interactive output (CI/CD) (0 means no output)', timeout, 30000)
|
|
112
|
+
.option('--offline [flag]', '[💻🔗🧪📡] Limit network usage (implies --no-npm-install)', boolean, false)
|
|
112
113
|
|
|
113
114
|
// Common to legacy and url
|
|
114
115
|
.option('--webapp <path>', '[💻🔗] Base folder of the web application (relative to cwd)', 'webapp')
|
|
115
|
-
.option('-pf, --page-filter <regexp>', '[
|
|
116
|
-
.option('-pp, --page-params <params>', '[
|
|
117
|
-
.option('--page-close-timeout <timeout>', '[
|
|
118
|
-
.option('-t, --global-timeout <timeout>', '[
|
|
119
|
-
.option('--screenshot [flag]', '[
|
|
120
|
-
.option('--no-screenshot', '[
|
|
121
|
-
.option('
|
|
122
|
-
.option('-
|
|
123
|
-
.option('-
|
|
124
|
-
.option('--
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.option('--
|
|
128
|
-
.option('-
|
|
116
|
+
.option('-pf, --page-filter <regexp>', '[💻🔗📡] Filter out pages not matching the regexp')
|
|
117
|
+
.option('-pp, --page-params <params>', '[💻🔗📡] Add parameters to page URL')
|
|
118
|
+
.option('--page-close-timeout <timeout>', '[💻🔗📡] Maximum waiting time for page close', timeout, 250)
|
|
119
|
+
.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)
|
|
120
|
+
.option('--screenshot [flag]', '[💻🔗📡] Take screenshots during the tests execution (if supported by the browser)', boolean, true)
|
|
121
|
+
.option('--no-screenshot', '[💻🔗📡] Disable screenshots during the tests execution (but not on failure, see --screenshot-on-failure)')
|
|
122
|
+
.option('--screenshot-on-failure <flag>', '[💻🔗📡] Take a screenshot when a test fails (even if --screenshot is false)', boolean, true)
|
|
123
|
+
.option('-st, --screenshot-timeout <timeout>', '[💻🔗📡] Maximum waiting time for browser screenshot', timeout, 5000)
|
|
124
|
+
.option('-so, --split-opa [flag]', '[💻🔗📡] Split OPA tests using QUnit modules', boolean, false)
|
|
125
|
+
.option('-rg, --report-generator <path...>', '[💻🔗📡] Report generator paths (relative to cwd or use $/ for provided ones)', ['$/report.js'])
|
|
126
|
+
.option('--progress-page <path>', '[💻🔗📡] Progress page path (relative to cwd or use $/ for provided ones)', '$/report/default.html')
|
|
127
|
+
|
|
128
|
+
.option('--coverage [flag]', '[💻🔗📡] Enable or disable code coverage', boolean)
|
|
129
|
+
.option('--no-coverage', '[💻🔗📡] Disable code coverage')
|
|
130
|
+
.option('-cs, --coverage-settings <path>', '[💻🔗📡] Path to a custom .nycrc.json file providing settings for instrumentation (relative to cwd or use $/ for provided ones)', '$/.nycrc.json')
|
|
129
131
|
.option('-ctd, --coverage-temp-dir <path>', '[💻🔗] Directory to output raw coverage information to (relative to cwd)', '.nyc_output')
|
|
130
132
|
.option('-crd, --coverage-report-dir <path>', '[💻🔗] Directory to store the coverage report files (relative to cwd)', 'coverage')
|
|
131
|
-
.option('-cr, --coverage-reporters <reporter...>', '[
|
|
132
|
-
.option('-ccb, --coverage-check-branches <percent>', '[
|
|
133
|
-
.option('-ccf, --coverage-check-functions <percent>', '[
|
|
134
|
-
.option('-ccl, --coverage-check-lines <percent>', '[
|
|
135
|
-
.option('-ccs, --coverage-check-statements <percent>', '[
|
|
136
|
-
.option('-crs, --coverage-remote-scanner <path>', '[
|
|
133
|
+
.option('-cr, --coverage-reporters <reporter...>', '[💻🔗📡] List of nyc reporters to use (text is always used)', ['lcov', 'cobertura'])
|
|
134
|
+
.option('-ccb, --coverage-check-branches <percent>', '[💻🔗📡] What % of branches must be covered', percent, 0)
|
|
135
|
+
.option('-ccf, --coverage-check-functions <percent>', '[💻🔗📡] What % of functions must be covered', percent, 0)
|
|
136
|
+
.option('-ccl, --coverage-check-lines <percent>', '[💻🔗📡] What % of lines must be covered', percent, 0)
|
|
137
|
+
.option('-ccs, --coverage-check-statements <percent>', '[💻🔗📡] What % of statements must be covered', percent, 0)
|
|
138
|
+
.option('-crs, --coverage-remote-scanner <path>', '[💻🔗📡] Scan for files when all coverage is requested', '$/scan-ui5.js')
|
|
137
139
|
.option('-s, --serve-only [flag]', '[💻🔗] Serve only', boolean, false)
|
|
138
140
|
|
|
141
|
+
.option('-w, --watch [flag]', '[💻🔗] Monitor the webapp folder (or the one specified with --watch-folder) and re-execute tests on change', boolean, false)
|
|
142
|
+
.option('--watch-folder <path>', '[💻🔗] Folder to monitor with watch (enables --watch if not specified)', string)
|
|
143
|
+
|
|
144
|
+
.option('--start <command>', '[💻🔗] Start command (might be an NPM script or a shell command)', string)
|
|
145
|
+
.option('--start-wait-url <command>', '[💻🔗] URL to wait for (🔗 defaulted to first url)', url)
|
|
146
|
+
.option('--start-wait-method <method>', '[💻🔗] HTTP method to check the waited URL', 'GET')
|
|
147
|
+
.option('--start-timeout <timeout>', '[💻🔗] Maximum waiting time for the start command (based on when the first URL becomes available)', timeout, 5000)
|
|
148
|
+
|
|
149
|
+
.option('--end <script>', '[💻🔗] End script (will receive path to `job.js`)', string)
|
|
150
|
+
.option('--end-timeout <timeout>', '[💻🔗] Maximum waiting time for the end script', timeout, 5000)
|
|
151
|
+
|
|
139
152
|
// Specific to legacy (and might be used with url if pointing to local project)
|
|
140
|
-
.option('--ui5 <url>', '[
|
|
141
|
-
.option('--disable-ui5 [flag]', '[
|
|
142
|
-
.option('--libs <lib...>', '[
|
|
143
|
-
.option('--mappings <mapping...>', '[
|
|
144
|
-
.option('--cache <path>', '[
|
|
145
|
-
.option('--preload <library...>', '[
|
|
153
|
+
.option('--ui5 <url>', '[💻📡] UI5 url', url, 'https://ui5.sap.com')
|
|
154
|
+
.option('--disable-ui5 [flag]', '[💻📡] Disable UI5 mapping (also disable libs)', boolean, false)
|
|
155
|
+
.option('--libs <lib...>', '[💻📡] Library mapping (<relative>=<path> or <path>)', arrayOf(lib))
|
|
156
|
+
.option('--mappings <mapping...>', '[💻📡] Custom mapping (<match>=<file|url>(<config>))', arrayOf(mapping))
|
|
157
|
+
.option('--cache <path>', '[💻📡] Cache UI5 resources locally in the given folder (empty to disable)')
|
|
158
|
+
.option('--preload <library...>', '[💻📡] Preload UI5 libraries in the cache folder (only if --cache is used)', arrayOf(string))
|
|
146
159
|
.option('--testsuite <path>', '[💻] Path of the testsuite file (relative to webapp, URL parameters are supported)', 'test/testsuite.qunit.html')
|
|
147
|
-
.option('-w, --watch [flag]', '[💻] Monitor the webapp folder and re-execute tests on change', boolean, false)
|
|
148
|
-
|
|
149
|
-
// Specific to url
|
|
150
|
-
.option('--start <command>', '[🔗] Start command (might be an NPM script or a shell command)', string)
|
|
151
|
-
.option('--start-timeout <timeout>', '[🔗] Maximum waiting time for the start command (based on when the first URL becomes available)', timeout, 5000)
|
|
152
160
|
|
|
153
161
|
// Specific to coverage in url mode (experimental)
|
|
154
162
|
.option('-cp, --coverage-proxy [flag]', `[🔗] ${EXPERIMENTAL_OPTION} use internal proxy to instrument remote files`, boolean, false)
|
|
155
163
|
.option('-cpi, --coverage-proxy-include <regexp>', `[🔗] ${EXPERIMENTAL_OPTION} urls to instrument for coverage`, regex, '.*')
|
|
156
164
|
.option('-cpe, --coverage-proxy-exclude <regexp>', `[🔗] ${EXPERIMENTAL_OPTION} urls to ignore for coverage`, regex, '/((test-)?resources|tests?)/')
|
|
157
165
|
|
|
166
|
+
// Batch mode related
|
|
167
|
+
.addOption(new Option('--batch-mode', 'Changes the way options are defaulted (in particular coverage temporary folders)', boolean).hideHelp())
|
|
168
|
+
.option('--batch <specification...>', 'Batch specification', arrayOf(string))
|
|
169
|
+
.option('--batch-id <id>', 'Batch id (used for naming report folder)', string)
|
|
170
|
+
.option('--batch-label <label>', 'Batch label (used while reporting on execution)', string)
|
|
171
|
+
.option('--if <condition>', 'Condition runner execution', string)
|
|
172
|
+
|
|
158
173
|
.addOption(new Option('--debug-dev-mode', DEBUG_OPTION, boolean).hideHelp())
|
|
159
174
|
.addOption(new Option('--debug-probe-only', DEBUG_OPTION, boolean).hideHelp())
|
|
160
175
|
.addOption(new Option('--debug-keep-browser-open', DEBUG_OPTION, boolean).hideHelp())
|
|
@@ -211,6 +226,7 @@ function checkAccess ({ path, label, file /*, write */ }) {
|
|
|
211
226
|
|
|
212
227
|
function finalize (job) {
|
|
213
228
|
function toAbsolute (path, from = job.cwd) {
|
|
229
|
+
path = path.replace(/📂report\b/, job.reportDir)
|
|
214
230
|
if (!isAbsolute(path)) {
|
|
215
231
|
path = join(from, path)
|
|
216
232
|
}
|
|
@@ -230,6 +246,7 @@ function finalize (job) {
|
|
|
230
246
|
function updateToAbsolute (member, from = job.cwd) {
|
|
231
247
|
job[member] = toAbsolute(job[member], from)
|
|
232
248
|
}
|
|
249
|
+
|
|
233
250
|
'browser,coverageSettings,coverageRemoteScanner,progressPage'
|
|
234
251
|
.split(',')
|
|
235
252
|
.forEach(setting => { job[setting] = checkDefault(job[setting]) })
|
|
@@ -282,6 +299,16 @@ function finalize (job) {
|
|
|
282
299
|
})
|
|
283
300
|
}
|
|
284
301
|
|
|
302
|
+
if (job.watchFolder) {
|
|
303
|
+
job.watch = true
|
|
304
|
+
job.watchFolder = updateToAbsolute(job.watchFolder)
|
|
305
|
+
} else if (job.watch) {
|
|
306
|
+
job.watchFolder = job.webapp
|
|
307
|
+
}
|
|
308
|
+
if (job.watchFolder) {
|
|
309
|
+
checkAccess({ path: job.watchFolder, label: 'Folder to watch' })
|
|
310
|
+
}
|
|
311
|
+
|
|
285
312
|
const output = getOutput(job)
|
|
286
313
|
|
|
287
314
|
if (job.coverage) {
|
|
@@ -336,6 +363,25 @@ function finalize (job) {
|
|
|
336
363
|
configurable: false
|
|
337
364
|
})
|
|
338
365
|
|
|
366
|
+
// Because start and end are already used
|
|
367
|
+
job.startCommand = job.start
|
|
368
|
+
delete job.start
|
|
369
|
+
job.endScript = job.end
|
|
370
|
+
delete job.end
|
|
371
|
+
|
|
372
|
+
if (job.startCommand) {
|
|
373
|
+
if (!job.startWaitUrl) {
|
|
374
|
+
job.startWaitUrl = job.url[0]
|
|
375
|
+
}
|
|
376
|
+
if (!job.startWaitUrl) {
|
|
377
|
+
throw new Error('Start command defined but no URL to wait for')
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (job.batchMode) {
|
|
382
|
+
job.outputInterval = 1000
|
|
383
|
+
}
|
|
384
|
+
|
|
339
385
|
/* istanbul ignore next */
|
|
340
386
|
if (process.env.DEBUG_ON_FAILED) {
|
|
341
387
|
let failed
|
|
@@ -359,19 +405,34 @@ function finalize (job) {
|
|
|
359
405
|
function fromCmdLine (cwd, args) {
|
|
360
406
|
let job = parse(cwd, args)
|
|
361
407
|
|
|
362
|
-
let defaultPath
|
|
363
|
-
|
|
364
|
-
|
|
408
|
+
let defaultPath
|
|
409
|
+
const isConfigSet = job[$valueSources].config === 'cli'
|
|
410
|
+
if (isAbsolute(job.config)) {
|
|
411
|
+
defaultPath = job.config
|
|
412
|
+
} else {
|
|
413
|
+
defaultPath = join(job.cwd, job.config)
|
|
414
|
+
if (!isAbsolute(defaultPath)) {
|
|
415
|
+
defaultPath = join(job.initialCwd, defaultPath)
|
|
416
|
+
}
|
|
365
417
|
}
|
|
366
418
|
let hasDefaultSettings = false
|
|
367
419
|
try {
|
|
368
420
|
checkAccess({ path: defaultPath, file: true })
|
|
369
421
|
hasDefaultSettings = true
|
|
370
422
|
} catch (e) {
|
|
423
|
+
if (isConfigSet) {
|
|
424
|
+
throw e
|
|
425
|
+
}
|
|
371
426
|
// ignore
|
|
372
427
|
}
|
|
373
428
|
if (hasDefaultSettings) {
|
|
374
429
|
const defaults = require(defaultPath)
|
|
430
|
+
if (defaults.cwd && !isAbsolute(defaults.cwd)) {
|
|
431
|
+
// make it relative to the configuration file
|
|
432
|
+
defaults.cwd = join(dirname(defaultPath), defaults.cwd)
|
|
433
|
+
} else if (isConfigSet) {
|
|
434
|
+
defaults.cwd = dirname(defaultPath)
|
|
435
|
+
}
|
|
375
436
|
const { before, after, browser } = buildArgs(defaults)
|
|
376
437
|
const sep = args.indexOf('--')
|
|
377
438
|
if (sep === -1) {
|
|
@@ -397,5 +458,6 @@ function fromObject (cwd, parameters) {
|
|
|
397
458
|
module.exports = {
|
|
398
459
|
getCommand,
|
|
399
460
|
fromCmdLine,
|
|
400
|
-
fromObject
|
|
461
|
+
fromObject,
|
|
462
|
+
toLongName
|
|
401
463
|
}
|
package/src/options.js
CHANGED
|
@@ -70,10 +70,12 @@ module.exports = {
|
|
|
70
70
|
return value
|
|
71
71
|
},
|
|
72
72
|
|
|
73
|
-
arrayOf (typeValidator) {
|
|
73
|
+
arrayOf (typeValidator, overrideDefault) {
|
|
74
|
+
let count = 0
|
|
74
75
|
return function (value, previousValue) {
|
|
76
|
+
++count
|
|
75
77
|
let result
|
|
76
|
-
if (previousValue === undefined) {
|
|
78
|
+
if (previousValue === undefined || (overrideDefault && count === 1)) {
|
|
77
79
|
result = []
|
|
78
80
|
} else {
|
|
79
81
|
result = [...previousValue]
|
package/src/output.js
CHANGED
|
@@ -427,6 +427,18 @@ function build (job) {
|
|
|
427
427
|
browserIssue(job, { type: 'failed', url, code, dir })
|
|
428
428
|
}),
|
|
429
429
|
|
|
430
|
+
skipIf: wrap(() => {
|
|
431
|
+
log(job, p80()`⚠️ [SKIPIF] Skipping execution (--if)`)
|
|
432
|
+
}),
|
|
433
|
+
|
|
434
|
+
batchFailed: wrap((batch, reason) => {
|
|
435
|
+
log(job, p80()`⚠️ [BATCHF] Failed to resolve batch ${batch}: ${reason}`)
|
|
436
|
+
}),
|
|
437
|
+
|
|
438
|
+
batchMode: wrap((batch, reason) => {
|
|
439
|
+
log(job, p80()`⚠️ [BATCHM] Batch mode item execution`)
|
|
440
|
+
}),
|
|
441
|
+
|
|
430
442
|
startFailed: wrap((url, error) => {
|
|
431
443
|
const p = p80()
|
|
432
444
|
log(job, p`┌──────────${pad.x('─')}┐`)
|
|
@@ -492,6 +504,10 @@ function build (job) {
|
|
|
492
504
|
log(job, p80()`⚠️ [SKPNYC] Skipping nyc instrumentation (--url)`)
|
|
493
505
|
}),
|
|
494
506
|
|
|
507
|
+
coverageNotFound: wrap(() => {
|
|
508
|
+
log(job, p80()`⚠️ [COVMIS] Coverage missing`)
|
|
509
|
+
}),
|
|
510
|
+
|
|
495
511
|
assumingOneOrigin: wrap(() => {
|
|
496
512
|
log(job, p80()`⚠️ [COVORG] Considering only one origin`)
|
|
497
513
|
}),
|
package/src/parallelize.js
CHANGED
|
@@ -34,7 +34,7 @@ async function run (task) {
|
|
|
34
34
|
task.stop = true
|
|
35
35
|
reject(error)
|
|
36
36
|
}
|
|
37
|
-
let remaining = list.length -
|
|
37
|
+
let remaining = list.length - task.started
|
|
38
38
|
while (task.active < (parallel + 1) && remaining) {
|
|
39
39
|
--remaining
|
|
40
40
|
++task.active
|
|
@@ -43,7 +43,7 @@ async function run (task) {
|
|
|
43
43
|
complete(task)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
function parallelize (method, list, parallel) {
|
|
47
47
|
const { promise, resolve, reject } = allocPromise()
|
|
48
48
|
const task = {
|
|
49
49
|
method,
|
|
@@ -59,3 +59,5 @@ module.exports = function parallelize (method, list, parallel) {
|
|
|
59
59
|
run(task)
|
|
60
60
|
return promise
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
module.exports = { parallelize }
|
package/src/qunit-hooks.js
CHANGED
|
@@ -91,7 +91,7 @@ async function done (job, urlWithHash, report) {
|
|
|
91
91
|
const { promise, resolve } = allocPromise()
|
|
92
92
|
page[$doneResolve] = resolve
|
|
93
93
|
page[$doneTimeout] = setTimeout(async () => {
|
|
94
|
-
if (job.browserCapabilities.screenshot) {
|
|
94
|
+
if (job.browserCapabilities.screenshot && job.screenshot) {
|
|
95
95
|
try {
|
|
96
96
|
await screenshot(job, url, 'done')
|
|
97
97
|
} catch (error) {
|
|
@@ -102,6 +102,8 @@ async function done (job, urlWithHash, report) {
|
|
|
102
102
|
if (report.__coverage__) {
|
|
103
103
|
await collect(job, url, report.__coverage__)
|
|
104
104
|
delete report.__coverage__
|
|
105
|
+
} else if (job.coverage) {
|
|
106
|
+
getOutput(job).coverageNotFound()
|
|
105
107
|
}
|
|
106
108
|
page.report = report
|
|
107
109
|
stop(job, url)
|
|
@@ -177,7 +179,7 @@ module.exports = {
|
|
|
177
179
|
}
|
|
178
180
|
++progress.count
|
|
179
181
|
if (failed) {
|
|
180
|
-
if (job.browserCapabilities.screenshot) {
|
|
182
|
+
if (job.browserCapabilities.screenshot && job.screenshotOnFailure) {
|
|
181
183
|
try {
|
|
182
184
|
const absoluteName = await screenshot(job, url, testId)
|
|
183
185
|
test.screenshot = basename(absoluteName)
|
package/src/report.js
CHANGED
package/src/start.js
CHANGED
|
@@ -6,29 +6,34 @@ const psTreeNodeCb = require('ps-tree')
|
|
|
6
6
|
const { promisify } = require('util')
|
|
7
7
|
const psTree = promisify(psTreeNodeCb)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let { start } = job
|
|
9
|
+
async function start (job) {
|
|
10
|
+
const { startWaitUrl: url, startWaitMethod: method } = job
|
|
11
|
+
let { startCommand: start } = job
|
|
13
12
|
const output = getOutput(job)
|
|
14
|
-
const [command] = start.split(' ')
|
|
13
|
+
const [command, ...parameters] = start.split(' ')
|
|
15
14
|
|
|
16
15
|
job.status = 'Executing start command'
|
|
17
16
|
|
|
18
|
-
// check if
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
// check if node
|
|
18
|
+
if (command === 'node') {
|
|
19
|
+
output.debug('start', `Replacing node with ${process.argv[0]}`)
|
|
20
|
+
start = [process.argv[0], ...parameters].join(' ')
|
|
21
|
+
} else {
|
|
22
|
+
// check if existing NPM script
|
|
23
|
+
const packagePath = join(job.cwd, 'package.json')
|
|
24
|
+
try {
|
|
25
|
+
const packageStat = await stat(packagePath)
|
|
26
|
+
if (packageStat.isFile()) {
|
|
27
|
+
output.debug('start', 'Found package.json in cwd')
|
|
28
|
+
const packageFile = JSON.parse(await readFile(packagePath, 'utf-8'))
|
|
29
|
+
if (packageFile.scripts[command]) {
|
|
30
|
+
output.debug('start', 'Found matching start script in package.json')
|
|
31
|
+
start = `npm run ${start}`
|
|
32
|
+
}
|
|
28
33
|
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
output.debug('start', 'Missing or invalid package.json in cwd', e)
|
|
29
36
|
}
|
|
30
|
-
} catch (e) {
|
|
31
|
-
output.debug('start', 'Missing or invalid package.json in cwd', e)
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
let childProcessExited = false
|
|
@@ -42,17 +47,14 @@ module.exports = async function start (job) {
|
|
|
42
47
|
childProcessExited = true
|
|
43
48
|
})
|
|
44
49
|
output.monitor(childProcess)
|
|
45
|
-
job[$startedProcess] = childProcess
|
|
46
50
|
|
|
47
51
|
job.status = 'Waiting for URL to be reachable'
|
|
48
52
|
|
|
49
|
-
const [url] = job.url
|
|
50
|
-
|
|
51
53
|
const begin = Date.now()
|
|
52
54
|
// eslint-disable-next-line no-unmodified-loop-condition
|
|
53
55
|
while (!childProcessExited && Date.now() - begin <= job.startTimeout) {
|
|
54
56
|
try {
|
|
55
|
-
const response = await fetch(url)
|
|
57
|
+
const response = await fetch(url, { method })
|
|
56
58
|
output.debug('start', url, response.status)
|
|
57
59
|
if (response.status === 200) {
|
|
58
60
|
break
|
|
@@ -89,3 +91,5 @@ module.exports = async function start (job) {
|
|
|
89
91
|
stop
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
|
|
95
|
+
module.exports = { start }
|
package/src/tests.js
CHANGED
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
$proxifiedUrls
|
|
12
12
|
} = require('./symbols')
|
|
13
13
|
const { UTRError } = require('./error')
|
|
14
|
-
const parallelize = require('./parallelize')
|
|
14
|
+
const { parallelize } = require('./parallelize')
|
|
15
15
|
|
|
16
16
|
function task (job, method) {
|
|
17
17
|
return async (url, index, { length }) => {
|
|
@@ -38,6 +38,11 @@ function task (job, method) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function probeUrl (job, url) {
|
|
41
|
+
const parsedUrl = new URL(url)
|
|
42
|
+
if (parsedUrl.port === '0') {
|
|
43
|
+
parsedUrl.port = job.port
|
|
44
|
+
url = parsedUrl.toString()
|
|
45
|
+
}
|
|
41
46
|
const output = getOutput(job)
|
|
42
47
|
try {
|
|
43
48
|
let scripts
|
|
@@ -122,6 +127,8 @@ async function process (job) {
|
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
await generate(job)
|
|
130
|
+
|
|
131
|
+
// TODO: #105 integrate end, find a way it can change job.failed
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
module.exports = {
|
package/src/ui5.js
CHANGED
|
@@ -7,7 +7,7 @@ const { capture } = require('reserve')
|
|
|
7
7
|
const { getOutput, newProgress } = require('./output')
|
|
8
8
|
const { download } = require('./tools')
|
|
9
9
|
const { $statusProgressCount, $statusProgressTotal } = require('./symbols')
|
|
10
|
-
const parallelize = require('./parallelize')
|
|
10
|
+
const { parallelize } = require('./parallelize')
|
|
11
11
|
|
|
12
12
|
const buildCacheBase = job => {
|
|
13
13
|
const [, hostName] = /https?:\/\/([^/]*)/.exec(job.ui5)
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const assert = require('assert')
|
|
4
|
-
|
|
5
|
-
module.exports = [{
|
|
6
|
-
label: 'UI5 focus handling',
|
|
7
|
-
for: capabilities => !capabilities.modules.includes('jsdom'), // does not work on JSDOM
|
|
8
|
-
url: 'ui5-focus/index.html',
|
|
9
|
-
endpoint: ({ body }) => {
|
|
10
|
-
assert.strictEqual(body['is-focus-set'], true)
|
|
11
|
-
}
|
|
12
|
-
}]
|
|
File without changes
|