ui5-test-runner 2.0.4 → 3.0.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 +5 -2
- package/index.js +0 -0
- package/package.json +5 -5
- package/src/browser.spec.js +1 -1
- package/src/defaults/report/default.html +1 -0
- package/src/defaults/report/main.js +17 -4
- package/src/defaults/report.js +2 -1
- package/src/endpoints.js +2 -2
- package/src/job.js +1 -0
- package/src/npm.js +13 -7
- package/src/npm.spec.js +1 -1
- package/src/output.js +23 -10
- package/src/qunit-hooks.js +37 -18
- package/src/qunit-hooks.spec.js +41 -0
- package/src/report.js +0 -1
- package/src/tools.js +1 -10
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
|
|
|
20
20
|
|
|
21
21
|
## 💿 How to install
|
|
22
22
|
|
|
23
|
-
* Works with [Node.js](https://nodejs.org/en/download/) >=
|
|
23
|
+
* Works with [Node.js](https://nodejs.org/en/download/) >= 16
|
|
24
24
|
* Local installation
|
|
25
25
|
* `npm install --save-dev ui5-test-runner`
|
|
26
26
|
* Trigger either with `npx ui5-test-runner` or through an npm script invoking `ui5-test-runner`
|
|
@@ -47,7 +47,10 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
|
|
|
47
47
|
## ⚖️ License
|
|
48
48
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner?ref=badge_large)
|
|
49
49
|
|
|
50
|
-
## ⚠️ Breaking
|
|
50
|
+
## ⚠️ Breaking changes
|
|
51
|
+
|
|
52
|
+
### v3
|
|
53
|
+
* Dropping support of Node.js 14
|
|
51
54
|
|
|
52
55
|
### v2
|
|
53
56
|
|
package/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui5-test-runner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Standalone test runner for UI5",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"ui5-test-runner": "./index.js"
|
|
13
13
|
},
|
|
14
14
|
"engines": {
|
|
15
|
-
"node": ">=
|
|
15
|
+
"node": ">=16"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"lint": "standard --fix",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test:integration:puppeteer": "node . --capabilities --browser $/puppeteer.js",
|
|
23
23
|
"test:integration:selenium-webdriver-chrome": "node . --capabilities --browser $/selenium-webdriver.js -- --browser chrome",
|
|
24
24
|
"test:integration:jsdom": "node . --capabilities --browser $/jsdom.js",
|
|
25
|
-
"test:report": "reserve --config ./test/report/reserve.json",
|
|
25
|
+
"test:report": "node ./src/defaults/report.js ./test/report && reserve --config ./test/report/reserve.json",
|
|
26
26
|
"build:doc": "node build/doc"
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://github.com/ArnaudBuchholz/ui5-test-runner#readme",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"commander": "^10.0.
|
|
48
|
+
"commander": "^10.0.1",
|
|
49
49
|
"mime": "^3.0.0",
|
|
50
50
|
"punybind": "^1.2.1",
|
|
51
51
|
"punyexpr": "^1.0.4",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"jest": "^29.5.0",
|
|
56
|
-
"nock": "^13.3.
|
|
56
|
+
"nock": "^13.3.1",
|
|
57
57
|
"nyc": "^15.1.0",
|
|
58
58
|
"standard": "^17.0.0"
|
|
59
59
|
},
|
package/src/browser.spec.js
CHANGED
|
@@ -142,7 +142,7 @@ describe('src/browser', () => {
|
|
|
142
142
|
})
|
|
143
143
|
await probe(job)
|
|
144
144
|
expect(job.browserCapabilities.modules).toStrictEqual(['reserve', 'dependentModule'])
|
|
145
|
-
expect(job.browserModules.reserve).toStrictEqual('reserve')
|
|
145
|
+
expect(job.browserModules.reserve).toStrictEqual(join(__dirname, '../node_modules/reserve'))
|
|
146
146
|
expect(job.browserModules.dependentModule).toStrictEqual(join(npmGlobal, 'dependentModule'))
|
|
147
147
|
})
|
|
148
148
|
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
<pre {{else}}>❌ {{ log.message }}</pre>
|
|
79
79
|
<img {{if}}="log.screenshot" loading="lazy" class="log" src="{{ qunitTest.pageId }}/{{ log.screenshot }}" alt="Copy folder '{{ qunitTest.pageId }}' from the job report">
|
|
80
80
|
</div>
|
|
81
|
+
<img {{if}}="qunitTest.screenshot" loading="lazy" class="log" src="{{ qunitTest.pageId }}/{{ qunitTest.screenshot }}" alt="Copy folder '{{ qunitTest.pageId }}' from the job report">
|
|
81
82
|
</div>
|
|
82
83
|
<div style="display: {{ disconnected ? 'block' : 'none' }};">❌ Disconnected</div>
|
|
83
84
|
</body>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* global report, job */
|
|
2
2
|
report.ready.then(update => {
|
|
3
|
-
const hashChange =
|
|
4
|
-
const [, pageId, testId] =
|
|
3
|
+
const hashChange = hash => {
|
|
4
|
+
const [, pageId, testId] = (hash || '').match(/#?([^-]*)(?:-(.*))?/)
|
|
5
5
|
let [qunitPage, qunitTest] = [null, null]
|
|
6
6
|
if (pageId) {
|
|
7
7
|
const url = Object.keys(job.qunitPages).find(pageUrl => job.qunitPages[pageUrl].id === pageId)
|
|
@@ -39,6 +39,19 @@ report.ready.then(update => {
|
|
|
39
39
|
})
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
window.addEventListener('hashchange',
|
|
43
|
-
|
|
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)
|
|
44
57
|
})
|
package/src/defaults/report.js
CHANGED
|
@@ -12,7 +12,8 @@ async function readDefault (name) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
async function readDependency (name) {
|
|
15
|
-
|
|
15
|
+
const path = join(resolveDependencyPath(name), `dist/${name}.js`)
|
|
16
|
+
return (await readFile(path)).toString()
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function minifyJs (src) {
|
package/src/endpoints.js
CHANGED
|
@@ -12,8 +12,8 @@ const { readFile } = require('fs/promises')
|
|
|
12
12
|
const { TextEncoder } = require('util')
|
|
13
13
|
const { resolveDependencyPath } = require('./npm.js')
|
|
14
14
|
|
|
15
|
-
const punyexprBinPath = resolveDependencyPath('punyexpr')
|
|
16
|
-
const punybindBinPath = resolveDependencyPath('punybind')
|
|
15
|
+
const punyexprBinPath = join(resolveDependencyPath('punyexpr'), 'dist/punyexpr.js')
|
|
16
|
+
const punybindBinPath = join(resolveDependencyPath('punybind'), 'dist/punybind.js')
|
|
17
17
|
|
|
18
18
|
module.exports = job => {
|
|
19
19
|
async function endpointImpl (api, implementation, request) {
|
package/src/job.js
CHANGED
|
@@ -95,6 +95,7 @@ function getCommand (cwd) {
|
|
|
95
95
|
.option('-r, --report-dir <path>', '[💻🔗🧪] Directory to output test reports (relative to cwd)', 'report')
|
|
96
96
|
.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)
|
|
97
97
|
.option('-f, --fail-fast [flag]', '[💻🔗🧪] Stop the execution after the first failing page', boolean, false)
|
|
98
|
+
.option('-fo, --fail-opa-fast [flag]', '[💻🔗] Stop the OPA page execution after the first failing test', boolean, false)
|
|
98
99
|
.option('-k, --keep-alive [flag]', '[💻🔗🧪] Keep the server alive', boolean, false)
|
|
99
100
|
.option('-l, --log-server [flag]', '[💻🔗🧪] Log inner server traces', boolean, false)
|
|
100
101
|
.option('-p, --parallel <count>', '[💻🔗🧪] Number of parallel tests executions', 2)
|
package/src/npm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { exec } = require('child_process')
|
|
2
|
-
const { join } = require('path')
|
|
2
|
+
const { sep, join } = require('path')
|
|
3
3
|
const { stat } = require('fs/promises')
|
|
4
4
|
const { UTRError } = require('./error')
|
|
5
5
|
const { getOutput } = require('./output')
|
|
@@ -31,18 +31,24 @@ async function folderExists (path) {
|
|
|
31
31
|
let localRoot
|
|
32
32
|
let globalRoot
|
|
33
33
|
|
|
34
|
+
function resolveDependencyPath (name) {
|
|
35
|
+
require(name)
|
|
36
|
+
const pattern = `${sep}node_modules${sep}${name}${sep}`
|
|
37
|
+
const path = Object.keys(require.cache).filter(path => path.includes(pattern))[0]
|
|
38
|
+
if (path) {
|
|
39
|
+
const pos = path.indexOf(pattern)
|
|
40
|
+
return path.substring(0, pos + pattern.length - 1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
module.exports = {
|
|
35
|
-
resolveDependencyPath
|
|
36
|
-
require(name)
|
|
37
|
-
return Object.keys(require.cache).filter(path => path.endsWith(`${name}.js`))[0]
|
|
38
|
-
},
|
|
45
|
+
resolveDependencyPath,
|
|
39
46
|
|
|
40
47
|
async resolvePackage (job, name) {
|
|
41
48
|
let modulePath
|
|
42
49
|
let justInstalled = false
|
|
43
50
|
try {
|
|
44
|
-
|
|
45
|
-
modulePath = name
|
|
51
|
+
modulePath = resolveDependencyPath(name)
|
|
46
52
|
} catch (e) {
|
|
47
53
|
}
|
|
48
54
|
if (!modulePath) {
|
package/src/npm.spec.js
CHANGED
|
@@ -40,7 +40,7 @@ describe('src/npm', () => {
|
|
|
40
40
|
|
|
41
41
|
it('detects already installed local package', async () => {
|
|
42
42
|
const path = await resolvePackage(job, 'reserve')
|
|
43
|
-
expect(path).toStrictEqual('reserve')
|
|
43
|
+
expect(path).toStrictEqual(join(__dirname, '../node_modules/reserve'))
|
|
44
44
|
expect(output.resolvedPackage).toHaveBeenCalledTimes(1)
|
|
45
45
|
expect(output.packageNotLatest).not.toHaveBeenCalled()
|
|
46
46
|
})
|
package/src/output.js
CHANGED
|
@@ -185,6 +185,13 @@ function browserIssue (job, { type, url, code, dir }) {
|
|
|
185
185
|
log(job, p`└──────────${pad.x('─')}┘`)
|
|
186
186
|
}
|
|
187
187
|
|
|
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
|
+
|
|
188
195
|
function build (job) {
|
|
189
196
|
let wrap
|
|
190
197
|
if (interactive) {
|
|
@@ -199,6 +206,8 @@ function build (job) {
|
|
|
199
206
|
} else {
|
|
200
207
|
wrap = method => method
|
|
201
208
|
}
|
|
209
|
+
const outputStart = Date.now()
|
|
210
|
+
const getElapsed = () => formatTime(Date.now() - outputStart)
|
|
202
211
|
|
|
203
212
|
return {
|
|
204
213
|
lastTick: 0,
|
|
@@ -236,9 +245,10 @@ function build (job) {
|
|
|
236
245
|
} else {
|
|
237
246
|
method = log
|
|
238
247
|
}
|
|
248
|
+
const text = `${getElapsed()} │ ${status}`
|
|
239
249
|
method(job, '')
|
|
240
|
-
method(job,
|
|
241
|
-
method(job, ''.
|
|
250
|
+
method(job, text)
|
|
251
|
+
method(job, '──────┴'.padEnd(text.length, '─'))
|
|
242
252
|
},
|
|
243
253
|
|
|
244
254
|
watching: wrap(path => {
|
|
@@ -295,18 +305,25 @@ function build (job) {
|
|
|
295
305
|
},
|
|
296
306
|
|
|
297
307
|
browserStart (url) {
|
|
308
|
+
const text = p80()`${getElapsed()} >> ${pad.lt(url)}`
|
|
298
309
|
if (interactive) {
|
|
299
|
-
output(job,
|
|
310
|
+
output(job, text)
|
|
300
311
|
} else {
|
|
301
|
-
wrap(() => log(job,
|
|
312
|
+
wrap(() => log(job, text))()
|
|
302
313
|
}
|
|
303
314
|
},
|
|
304
315
|
|
|
305
316
|
browserStopped (url) {
|
|
317
|
+
let duration = ''
|
|
318
|
+
const page = job.qunitPages && job.qunitPages[url]
|
|
319
|
+
if (page) {
|
|
320
|
+
duration = ' (' + formatTime(page.end - page.start) + ')'
|
|
321
|
+
}
|
|
322
|
+
const text = p80()`${getElapsed()} << ${pad.lt(url + duration)}`
|
|
306
323
|
if (interactive) {
|
|
307
|
-
output(job,
|
|
324
|
+
output(job, text)
|
|
308
325
|
} else {
|
|
309
|
-
wrap(() => log(job,
|
|
326
|
+
wrap(() => log(job, text))()
|
|
310
327
|
}
|
|
311
328
|
},
|
|
312
329
|
|
|
@@ -436,10 +453,6 @@ function build (job) {
|
|
|
436
453
|
log(job, p80()`!! FAILFAST ${pad.lt(url)}`)
|
|
437
454
|
}),
|
|
438
455
|
|
|
439
|
-
timeSpent: wrap((start, end = new Date()) => {
|
|
440
|
-
log(job, p80()`Time spent: ${end - start}ms`)
|
|
441
|
-
}),
|
|
442
|
-
|
|
443
456
|
noTestPageFound: wrap(() => {
|
|
444
457
|
err(job, p80()`No test page found (or all filtered out)`)
|
|
445
458
|
}),
|
package/src/qunit-hooks.js
CHANGED
|
@@ -32,6 +32,24 @@ function get (job, urlWithHash, testId) {
|
|
|
32
32
|
return { url, page, test }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
async function done (job, urlWithHash, report) {
|
|
36
|
+
const { url, page } = get(job, urlWithHash)
|
|
37
|
+
if (job.browserCapabilities.screenshot) {
|
|
38
|
+
try {
|
|
39
|
+
await screenshot(job, url, 'done')
|
|
40
|
+
} catch (error) {
|
|
41
|
+
getOutput(job).genericError(error, url)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (report.__coverage__) {
|
|
45
|
+
collect(job, url, report.__coverage__)
|
|
46
|
+
delete report.__coverage__
|
|
47
|
+
}
|
|
48
|
+
page.end = new Date()
|
|
49
|
+
page.report = report
|
|
50
|
+
stop(job, url)
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
module.exports = {
|
|
36
54
|
get,
|
|
37
55
|
|
|
@@ -82,7 +100,8 @@ module.exports = {
|
|
|
82
100
|
if (failed) {
|
|
83
101
|
if (job.browserCapabilities.screenshot) {
|
|
84
102
|
try {
|
|
85
|
-
await screenshot(job, url, testId)
|
|
103
|
+
const absoluteName = await screenshot(job, url, testId)
|
|
104
|
+
test.screenshot = basename(absoluteName)
|
|
86
105
|
} catch (error) {
|
|
87
106
|
getOutput(job).genericError(error, url)
|
|
88
107
|
}
|
|
@@ -94,23 +113,23 @@ module.exports = {
|
|
|
94
113
|
}
|
|
95
114
|
test.end = new Date()
|
|
96
115
|
test.report = report
|
|
116
|
+
if (job.failOpaFast && failed) {
|
|
117
|
+
// skip remaining tests
|
|
118
|
+
page.modules.forEach(module => {
|
|
119
|
+
module.tests.forEach(test => {
|
|
120
|
+
if (!test.report) {
|
|
121
|
+
test.skip = true
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
await done(job, urlWithHash, {
|
|
126
|
+
failed: page.failed,
|
|
127
|
+
passed: page.passed,
|
|
128
|
+
total: page.count,
|
|
129
|
+
runtime: 0
|
|
130
|
+
})
|
|
131
|
+
}
|
|
97
132
|
},
|
|
98
133
|
|
|
99
|
-
|
|
100
|
-
const { url, page } = get(job, urlWithHash)
|
|
101
|
-
if (job.browserCapabilities.screenshot) {
|
|
102
|
-
try {
|
|
103
|
-
await screenshot(job, url, 'done')
|
|
104
|
-
} catch (error) {
|
|
105
|
-
getOutput(job).genericError(error, url)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (report.__coverage__) {
|
|
109
|
-
collect(job, url, report.__coverage__)
|
|
110
|
-
delete report.__coverage__
|
|
111
|
-
}
|
|
112
|
-
page.end = new Date()
|
|
113
|
-
page.report = report
|
|
114
|
-
stop(job, url)
|
|
115
|
-
}
|
|
134
|
+
done
|
|
116
135
|
}
|
package/src/qunit-hooks.spec.js
CHANGED
|
@@ -33,6 +33,7 @@ describe('src/qunit-hooks', () => {
|
|
|
33
33
|
|
|
34
34
|
beforeEach(() => {
|
|
35
35
|
screenshot.mockReset()
|
|
36
|
+
screenshot.mockImplementation((job, url, testId) => Promise.resolve(`whatever/${testId}.png`))
|
|
36
37
|
stop.mockClear()
|
|
37
38
|
mockGenericError.mockClear()
|
|
38
39
|
job = {
|
|
@@ -549,6 +550,8 @@ describe('src/qunit-hooks', () => {
|
|
|
549
550
|
total: 1
|
|
550
551
|
})
|
|
551
552
|
expect(screenshot).toHaveBeenCalledWith(job, url, '1a')
|
|
553
|
+
const { test } = get(job, url, '1a')
|
|
554
|
+
expect(test.screenshot).toStrictEqual('1a.png')
|
|
552
555
|
})
|
|
553
556
|
|
|
554
557
|
it('takes a screenshot (hash changing)', async () => {
|
|
@@ -600,6 +603,44 @@ describe('src/qunit-hooks', () => {
|
|
|
600
603
|
expect(stop).toHaveBeenCalledWith(job, url)
|
|
601
604
|
expect(job.failed).toStrictEqual(true)
|
|
602
605
|
})
|
|
606
|
+
|
|
607
|
+
describe.only('fail OPA fast behavior', () => {
|
|
608
|
+
beforeEach(async () => {
|
|
609
|
+
job.failOpaFast = true
|
|
610
|
+
await testDone(job, url, {
|
|
611
|
+
...getTestDoneFor1a(),
|
|
612
|
+
passed: 0,
|
|
613
|
+
failed: 1,
|
|
614
|
+
total: 1
|
|
615
|
+
})
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
it('fails the test immediately', () => {
|
|
619
|
+
const { test } = get(job, url, '1a')
|
|
620
|
+
expect(test).toMatchObject({
|
|
621
|
+
report: {
|
|
622
|
+
passed: 0,
|
|
623
|
+
failed: 1,
|
|
624
|
+
total: 1
|
|
625
|
+
}
|
|
626
|
+
})
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it('flags the remaining tests as skipped', () => {
|
|
630
|
+
const { page } = get(job, url)
|
|
631
|
+
page.modules.forEach(module => {
|
|
632
|
+
module.tests.forEach(test => {
|
|
633
|
+
if (test.testId !== '1a') {
|
|
634
|
+
expect(test.skip).toStrictEqual(true)
|
|
635
|
+
}
|
|
636
|
+
})
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it('stops the page immediately', () => {
|
|
641
|
+
expect(stop).toHaveBeenCalledWith(job, url)
|
|
642
|
+
})
|
|
643
|
+
})
|
|
603
644
|
})
|
|
604
645
|
|
|
605
646
|
describe('done', () => {
|
package/src/report.js
CHANGED
package/src/tools.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { mkdir, stat } = fsPromises
|
|
3
|
+
const { mkdir, rm, stat } = require('fs').promises
|
|
5
4
|
const { createHash } = require('crypto')
|
|
6
5
|
|
|
7
|
-
let rm
|
|
8
|
-
/* istanbul ignore next */ // Hard to test both in the same run
|
|
9
|
-
if (process.version > 'v14.14') {
|
|
10
|
-
rm = fsPromises.rm
|
|
11
|
-
} else {
|
|
12
|
-
rm = fsPromises.rmdir
|
|
13
|
-
}
|
|
14
|
-
|
|
15
6
|
const recursive = { recursive: true }
|
|
16
7
|
|
|
17
8
|
const stripUrlHash = url => url.split('#')[0]
|