ui5-test-runner 5.9.1 → 5.10.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 +3 -4
- package/jest.config.json +1 -0
- package/package.json +9 -9
- package/src/browsers.js +5 -1
- package/src/clean.js +5 -12
- package/src/cors.js +2 -2
- package/src/coverage.js +1 -1
- package/src/defaults/playwright.js +8 -1
- package/src/defaults/puppeteer.js +11 -0
- package/src/defaults/report/decompress.js +19 -0
- package/src/defaults/report.js +27 -7
- package/src/defaults/scan-ui5.js +7 -1
- package/src/endpoints.js +32 -0
- package/src/handle.js +43 -0
- package/src/inject/jest2qunit.js +289 -0
- package/src/inject/post.js +42 -7
- package/src/inject/qunit-hooks.js +22 -15
- package/src/job.js +15 -4
- package/src/npm.js +3 -1
- package/src/output.js +55 -3
- package/src/report.js +4 -6
- package/src/reserve.js +1 -1
- package/src/start.js +58 -20
- package/src/tests.js +50 -8
- package/src/ui5.js +105 -102
package/index.js
CHANGED
|
@@ -99,10 +99,9 @@ async function main () {
|
|
|
99
99
|
output.serving(url)
|
|
100
100
|
serverReady()
|
|
101
101
|
})
|
|
102
|
-
.on('error',
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
serverError()
|
|
102
|
+
.on('error', ({ reason }) => {
|
|
103
|
+
send({ msg: 'error', reason })
|
|
104
|
+
serverError(reason)
|
|
106
105
|
})
|
|
107
106
|
await serverStarted
|
|
108
107
|
if (job.preload) {
|
package/jest.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui5-test-runner",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.1",
|
|
4
4
|
"description": "Standalone test runner for UI5",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -46,24 +46,24 @@
|
|
|
46
46
|
"homepage": "https://github.com/ArnaudBuchholz/ui5-test-runner#readme",
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"commander": "^12.1.0",
|
|
49
|
-
"
|
|
49
|
+
"pidtree": "^0.6.0",
|
|
50
50
|
"punybind": "^1.2.1",
|
|
51
|
-
"punyexpr": "1.
|
|
51
|
+
"punyexpr": "1.2.0",
|
|
52
52
|
"reserve": "2.3.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@openui5/types": "^1.
|
|
56
|
-
"@ui5/cli": "^4.0.
|
|
55
|
+
"@openui5/types": "^1.140.0",
|
|
56
|
+
"@ui5/cli": "^4.0.26",
|
|
57
57
|
"@ui5/middleware-code-coverage": "^2.0.1",
|
|
58
58
|
"dotenv": "^16.5.0",
|
|
59
59
|
"jest": "^29.7.0",
|
|
60
|
-
"nock": "^14.0.
|
|
61
|
-
"npm-check-updates": "^18.0
|
|
60
|
+
"nock": "^14.0.10",
|
|
61
|
+
"npm-check-updates": "^18.3.0",
|
|
62
62
|
"nyc": "^17.1.0",
|
|
63
63
|
"rimraf": "^6.0.1",
|
|
64
64
|
"standard": "^17.1.2",
|
|
65
|
-
"typescript": "^5.
|
|
66
|
-
"ui5-tooling-transpile": "^3.
|
|
65
|
+
"typescript": "^5.9.2",
|
|
66
|
+
"ui5-tooling-transpile": "^3.9.2"
|
|
67
67
|
},
|
|
68
68
|
"optionalDependencies": {
|
|
69
69
|
"fsevents": "^2.3.3"
|
package/src/browsers.js
CHANGED
|
@@ -14,8 +14,9 @@ let lastScreenshotId = 0
|
|
|
14
14
|
const screenshots = {}
|
|
15
15
|
|
|
16
16
|
async function instantiate (job, config) {
|
|
17
|
+
const output = getOutput(job)
|
|
17
18
|
if (job.browserArgs.some((arg) => !arg.trim())) {
|
|
18
|
-
|
|
19
|
+
output.emptyBrowserArg()
|
|
19
20
|
job.browserArgs = job.browserArgs.filter((arg) => arg.trim())
|
|
20
21
|
}
|
|
21
22
|
const { dir, url } = config
|
|
@@ -48,6 +49,9 @@ async function instantiate (job, config) {
|
|
|
48
49
|
}
|
|
49
50
|
resolve(code)
|
|
50
51
|
})
|
|
52
|
+
childProcess.on('error', err => {
|
|
53
|
+
output.browserChildProcessError(url, err)
|
|
54
|
+
})
|
|
51
55
|
childProcess.closed = promise
|
|
52
56
|
childProcess.stdoutFilename = stdoutFilename
|
|
53
57
|
childProcess.stderrFilename = stderrFilename
|
package/src/clean.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { getOutput } = require('./output')
|
|
2
|
+
const { describeHandle } = require('./handle')
|
|
2
3
|
|
|
3
4
|
module.exports = {
|
|
4
5
|
cleanHandles (job) {
|
|
@@ -6,22 +7,14 @@ module.exports = {
|
|
|
6
7
|
const activeHandles = process._getActiveHandles ? process._getActiveHandles() : []
|
|
7
8
|
let displayWarning = true
|
|
8
9
|
for (const handle of activeHandles) {
|
|
9
|
-
const className
|
|
10
|
-
output.debug('handle', 'active handle',
|
|
11
|
-
if (className === 'TLSSocket') {
|
|
10
|
+
const { className, label } = describeHandle(handle)
|
|
11
|
+
output.debug('handle', 'active handle', label)
|
|
12
|
+
if (className === 'TLSSocket' || className === 'Socket') {
|
|
12
13
|
if (displayWarning) {
|
|
13
14
|
displayWarning = false
|
|
14
15
|
output.detectedLeakOfHandles()
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
if (handle._httpMessage) {
|
|
18
|
-
const { path, method, host, protocol } = handle._httpMessage
|
|
19
|
-
info = `${method} ${protocol}//${host}${path}`
|
|
20
|
-
} else {
|
|
21
|
-
const { localAddress, localPort, remoteAddress, remotePort } = handle
|
|
22
|
-
info = `from ${localAddress}:${localPort} to ${remoteAddress}:${remotePort}`
|
|
23
|
-
}
|
|
24
|
-
output.debug('handle', 'TLS socket', info)
|
|
17
|
+
output.debug('handle', className, label)
|
|
25
18
|
handle.destroy()
|
|
26
19
|
}
|
|
27
20
|
}
|
package/src/cors.js
CHANGED
|
@@ -7,8 +7,8 @@ module.exports = {
|
|
|
7
7
|
response.writeHead(200, {
|
|
8
8
|
'Access-Control-Allow-Origin': origin,
|
|
9
9
|
Vary: 'Origin',
|
|
10
|
-
'Access-Control-Allow-Headers': '
|
|
11
|
-
'Access-Control-Allow-Methods': '
|
|
10
|
+
'Access-Control-Allow-Headers': '*',
|
|
11
|
+
'Access-Control-Allow-Methods': '*',
|
|
12
12
|
'Access-Control-Allow-Credentials': 'true'
|
|
13
13
|
})
|
|
14
14
|
response.end()
|
package/src/coverage.js
CHANGED
|
@@ -169,7 +169,7 @@ async function buildAllIndex (job) {
|
|
|
169
169
|
await writeFile(join(job.coverageTempDir, 'all-index.json'), `{${index.join(',')}}`)
|
|
170
170
|
}
|
|
171
171
|
} catch (e) {
|
|
172
|
-
output.genericError(e)
|
|
172
|
+
output.genericError(e, e.url)
|
|
173
173
|
output.noInfoForAllCoverage()
|
|
174
174
|
} finally {
|
|
175
175
|
progress.done()
|
|
@@ -76,8 +76,15 @@ require('./browser')({
|
|
|
76
76
|
networkWriter
|
|
77
77
|
}) {
|
|
78
78
|
const browsers = require(modules.playwright)
|
|
79
|
+
const args = []
|
|
80
|
+
|
|
81
|
+
if (options.browser === 'chromium' && options.unsecure) {
|
|
82
|
+
args.push('--disable-web-security')
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
browser = await browsers[options.browser].launch({
|
|
80
|
-
headless: !options.visible
|
|
86
|
+
headless: !options.visible,
|
|
87
|
+
args
|
|
81
88
|
})
|
|
82
89
|
|
|
83
90
|
let recordVideo
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
let browser
|
|
4
4
|
let page
|
|
5
5
|
|
|
6
|
+
console.time('⏲ run ')
|
|
7
|
+
|
|
6
8
|
require('./browser')({
|
|
7
9
|
metadata: {
|
|
8
10
|
name: 'puppeteer',
|
|
@@ -52,7 +54,10 @@ require('./browser')({
|
|
|
52
54
|
consoleWriter,
|
|
53
55
|
networkWriter
|
|
54
56
|
}) {
|
|
57
|
+
console.timeEnd('⏲ run ')
|
|
58
|
+
console.time('⏲ require ')
|
|
55
59
|
const puppeteer = require(modules.puppeteer)
|
|
60
|
+
console.timeEnd('⏲ require ')
|
|
56
61
|
|
|
57
62
|
let args = []
|
|
58
63
|
let product
|
|
@@ -62,6 +67,7 @@ require('./browser')({
|
|
|
62
67
|
args = options.chromeArgs()
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
console.time('⏲ launch ')
|
|
65
71
|
browser = await puppeteer.launch({
|
|
66
72
|
product,
|
|
67
73
|
executablePath: options.binary,
|
|
@@ -69,6 +75,7 @@ require('./browser')({
|
|
|
69
75
|
defaultViewport: null,
|
|
70
76
|
args
|
|
71
77
|
})
|
|
78
|
+
console.timeEnd('⏲ launch ')
|
|
72
79
|
|
|
73
80
|
page = (await browser.pages())[0]
|
|
74
81
|
|
|
@@ -94,14 +101,18 @@ require('./browser')({
|
|
|
94
101
|
})
|
|
95
102
|
})
|
|
96
103
|
|
|
104
|
+
console.time('⏲ scripts ')
|
|
97
105
|
if (scripts && scripts.length) {
|
|
98
106
|
for (const script of scripts) {
|
|
99
107
|
await page.evaluateOnNewDocument(script)
|
|
100
108
|
}
|
|
101
109
|
}
|
|
110
|
+
console.timeEnd('⏲ scripts ')
|
|
102
111
|
|
|
103
112
|
await page.setDefaultNavigationTimeout(0)
|
|
113
|
+
console.time('⏲ navigate ')
|
|
104
114
|
await page.goto(url)
|
|
115
|
+
console.timeEnd('⏲ navigate ')
|
|
105
116
|
},
|
|
106
117
|
|
|
107
118
|
async error ({ error: e, exit }) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* global DecompressionStream */
|
|
2
|
+
/* eslint-disable-next-line no-unused-vars */
|
|
3
|
+
async function decompress (base64) {
|
|
4
|
+
const bin = atob(base64)
|
|
5
|
+
const uint8Array = new Uint8Array(bin.length)
|
|
6
|
+
for (let i = 0; i < bin.length; ++i) {
|
|
7
|
+
uint8Array[i] = bin.charCodeAt(i)
|
|
8
|
+
}
|
|
9
|
+
const readableStream = new ReadableStream({
|
|
10
|
+
start (ctl) {
|
|
11
|
+
ctl.enqueue(uint8Array)
|
|
12
|
+
ctl.close()
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
const decompressionStream = new DecompressionStream('gzip')
|
|
16
|
+
const decompressedStream = readableStream.pipeThrough(decompressionStream)
|
|
17
|
+
const jsonString = await new Response(decompressedStream).text()
|
|
18
|
+
return JSON.parse(jsonString)
|
|
19
|
+
}
|
package/src/defaults/report.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { join } = require('path')
|
|
3
|
+
const { join, isAbsolute } = require('path')
|
|
4
4
|
const { readFile, writeFile } = require('fs').promises
|
|
5
|
+
const zlib = require('zlib')
|
|
5
6
|
const [,, reportDir] = process.argv
|
|
7
|
+
const verbose = process.argv.includes('--verbose')
|
|
6
8
|
const { resolveDependencyPath } = require('../npm.js')
|
|
7
9
|
|
|
10
|
+
const log = verbose ? console.log : () => {}
|
|
11
|
+
|
|
12
|
+
log('🏗 Building report...')
|
|
13
|
+
|
|
8
14
|
const defaultDir = join(__dirname, 'report')
|
|
9
15
|
|
|
10
16
|
async function readDefault (name) {
|
|
@@ -35,30 +41,44 @@ function minifyJs (src) {
|
|
|
35
41
|
|
|
36
42
|
async function main () {
|
|
37
43
|
const html = await readDefault('default.html')
|
|
44
|
+
log('📦 default.html :', html.length)
|
|
38
45
|
const styles = (await readDefault('styles.css'))
|
|
39
46
|
.replace(/\{\r?\n\s+/g, '{')
|
|
40
47
|
.replace(/\}(\r?\n)+/g, '} ')
|
|
41
48
|
.replace(/;\r?\n\s*/g, ';')
|
|
42
49
|
.replace(/(:|,)\s*/g, (_, c) => c)
|
|
50
|
+
log('📦 styles.css :', styles.length)
|
|
43
51
|
|
|
44
52
|
const punyexpr = await readDependency('punyexpr')
|
|
53
|
+
log('📦 punyexpr :', punyexpr.length)
|
|
45
54
|
const punybind = await readDependency('punybind')
|
|
55
|
+
log('📦 punybind :', punybind.length)
|
|
46
56
|
const common = minifyJs(await readDefault('common.js'))
|
|
57
|
+
log('📦 common :', common.length)
|
|
47
58
|
const main = minifyJs(await readDefault('main.js'))
|
|
59
|
+
log('📦 main :', main.length)
|
|
48
60
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
const decompress = minifyJs(await readDefault('decompress.js'))
|
|
62
|
+
log('📦 decompress :', decompress.length)
|
|
63
|
+
const jobPath = isAbsolute(reportDir) ? reportDir : join(process.cwd(), reportDir)
|
|
64
|
+
log('📦 job path :', jobPath)
|
|
65
|
+
const rawJob = require(join(jobPath, 'job.js'))
|
|
66
|
+
const json = JSON.stringify(rawJob)
|
|
67
|
+
log('📦 json :', json.length)
|
|
68
|
+
const buffer = zlib.gzipSync(json)
|
|
69
|
+
const base64 = buffer.toString('base64')
|
|
70
|
+
log('📦 json (Gzip/b64) :', base64.length)
|
|
71
|
+
log('📦 compression :', (100 * base64.length / json.length).toFixed(2) + '%')
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
await writeFile(join(reportDir, 'report.html'), html
|
|
55
74
|
.replace(/(>|\}\})\r?\n\s*</g, (_, c) => `${c}<`)
|
|
56
75
|
.replace('<link rel="stylesheet" href="/_/report/styles.css">', `<style>${styles}</style>`)
|
|
57
76
|
.replace('<script src="/_/punyexpr.js"></script>', `<script>${punyexpr}</script>`)
|
|
58
77
|
.replace('<script src="/_/punybind.js"></script>', `<script>${punybind}</script>`)
|
|
59
78
|
.replace('<script src="/_/report/common.js"></script>', `<script>${common}</script>`)
|
|
60
|
-
.replace('<script src="/_/report/main.js"></script>', `<script
|
|
79
|
+
.replace('<script src="/_/report/main.js"></script>', `<script>${decompress};let job={};decompress("${base64}").then(json=>{job=json;report.ready.then(update=>update({...json,elapsed:report.elapsed}))});${main}</script>`)
|
|
61
80
|
)
|
|
81
|
+
log('✅ generated.')
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
main()
|
package/src/defaults/scan-ui5.js
CHANGED
|
@@ -2,7 +2,13 @@ module.exports = async function scanUI5 (url, onFolder, onFile) {
|
|
|
2
2
|
if (url.match(/\/((?:test-)?resources\/.*)/)) {
|
|
3
3
|
return // ignore UI5 resources
|
|
4
4
|
}
|
|
5
|
-
|
|
5
|
+
let html
|
|
6
|
+
try {
|
|
7
|
+
html = await (await fetch(url)).text()
|
|
8
|
+
} catch (e) {
|
|
9
|
+
e.url = url
|
|
10
|
+
throw e
|
|
11
|
+
}
|
|
6
12
|
const items = [...html.matchAll(/<a href="([^"]+)" class="icon/ig)]
|
|
7
13
|
.map(([_, item]) => item)
|
|
8
14
|
.filter(item => item.endsWith('/') || item.endsWith('.js') || item.endsWith('.ts'))
|
package/src/endpoints.js
CHANGED
|
@@ -123,6 +123,38 @@ module.exports = job => {
|
|
|
123
123
|
match: '^/_/QUnit/done',
|
|
124
124
|
custom: endpoint('QUnit/done', async (url, report) => done(job, url, report))
|
|
125
125
|
}, {
|
|
126
|
+
// Fast batch endpoint for QUnit (no screenshot)
|
|
127
|
+
match: '^/_/QUnit/batch',
|
|
128
|
+
custom: async (request, response) => {
|
|
129
|
+
const url = extractPageUrl(request.headers)
|
|
130
|
+
const data = await body(request)
|
|
131
|
+
response.writeHead(200)
|
|
132
|
+
response.end()
|
|
133
|
+
try {
|
|
134
|
+
for (const [hook, hookData] of data) {
|
|
135
|
+
switch (hook) {
|
|
136
|
+
case 'QUnit/begin':
|
|
137
|
+
await begin(job, url, hookData)
|
|
138
|
+
break
|
|
139
|
+
case 'QUnit/testStart':
|
|
140
|
+
await testStart(job, url, hookData)
|
|
141
|
+
break
|
|
142
|
+
case 'QUnit/log':
|
|
143
|
+
await log(job, url, hookData)
|
|
144
|
+
break
|
|
145
|
+
case 'QUnit/testDone':
|
|
146
|
+
await testDone(job, url, hookData)
|
|
147
|
+
break
|
|
148
|
+
case 'QUnit/done':
|
|
149
|
+
await done(job, url, hookData)
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
getOutput(job).endpointError({ api: 'QUnit/batch', url, data, error })
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}, {
|
|
126
158
|
// UI to follow progress
|
|
127
159
|
match: '^/_/progress.html',
|
|
128
160
|
cwd: dirname(job.progressPage),
|
package/src/handle.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { ServerResponse, ClientRequest } = require('node:http')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
describeHandle (handle) {
|
|
5
|
+
const className = handle && handle.constructor && handle.constructor.name
|
|
6
|
+
let label = className
|
|
7
|
+
if (['TLSSocket', 'Socket'].includes(className)) {
|
|
8
|
+
if (handle._httpMessage instanceof ServerResponse) {
|
|
9
|
+
const { method, url } = handle._httpMessage.req
|
|
10
|
+
label += ` IncomingRequest ${method} ${url}`
|
|
11
|
+
} else if (handle._httpMessage instanceof ClientRequest) {
|
|
12
|
+
const { path, method, host, protocol } = handle._httpMessage
|
|
13
|
+
label += ` ClientRequest ${method} ${protocol}//${host}${path}`
|
|
14
|
+
} else if (handle.localAddress) {
|
|
15
|
+
const { localAddress, localPort, remoteAddress, remotePort } = handle
|
|
16
|
+
if (remoteAddress === undefined) {
|
|
17
|
+
label += ` local ${localAddress}:${localPort}`
|
|
18
|
+
} else {
|
|
19
|
+
label += ` local ${localAddress}:${localPort} remote ${remoteAddress}:${remotePort}`
|
|
20
|
+
}
|
|
21
|
+
} else if (handle._handle) {
|
|
22
|
+
const underlyingHandle = handle._handle
|
|
23
|
+
const underlyingClassName = underlyingHandle && underlyingHandle.constructor && underlyingHandle.constructor.name
|
|
24
|
+
label += ` <-> ${underlyingClassName || 'unknown'}`
|
|
25
|
+
} else {
|
|
26
|
+
label += ' unknown'
|
|
27
|
+
}
|
|
28
|
+
} else if (className === 'WriteStream') {
|
|
29
|
+
const fd = ['stdin', 'stdout', 'stderr'][handle.fd] || `fd: ${handle.fd}`
|
|
30
|
+
label += ` ${fd} ${handle.columns}x${handle.rows} isTTY: ${handle.isTTY}`
|
|
31
|
+
} else if (className === 'Server') {
|
|
32
|
+
label += ` connections: ${handle._connections} events: ${handle._eventsCount}`
|
|
33
|
+
} else if (className === 'ChildProcess') {
|
|
34
|
+
label += ` pid: ${handle.pid}`
|
|
35
|
+
if (handle.spawnargs) {
|
|
36
|
+
label += ` ${handle.spawnargs.map(value => ('' + value).replaceAll(/ /g, '␣'))}`
|
|
37
|
+
} else {
|
|
38
|
+
label += ' unknown'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { className, label }
|
|
42
|
+
}
|
|
43
|
+
}
|