ui5-test-runner 4.5.1 → 5.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 +9 -12
- package/jest.config.json +27 -0
- package/package.json +8 -36
- package/src/browsers.js +3 -2
- package/src/capabilities/index.js +9 -3
- package/src/coverage.js +159 -40
- package/src/defaults/jsdom/compatibility.js +86 -18
- package/src/defaults/jsdom/resource-loader.js +10 -9
- package/src/defaults/jsdom.js +28 -14
- package/src/defaults/junit-xml-report.js +2 -1
- package/src/defaults/puppeteer.js +15 -3
- package/src/defaults/scan-ui5.js +20 -0
- package/src/defaults/selenium-webdriver/edge.js +1 -2
- package/src/defaults/selenium-webdriver/firefox.js +2 -3
- package/src/defaults/selenium-webdriver.js +1 -1
- package/src/defaults/webdriverio.js +89 -0
- package/src/endpoints.js +29 -11
- package/src/inject/post.js +4 -1
- package/src/job-mode.js +1 -0
- package/src/job.js +7 -3
- package/src/output.js +14 -3
- package/src/reserve.js +5 -5
- package/src/ui5.js +10 -5
package/README.md
CHANGED
|
@@ -34,18 +34,15 @@ A self-sufficient test runner for UI5 applications enabling parallel execution o
|
|
|
34
34
|
|
|
35
35
|
## ⚠️ Breaking changes
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* Dependencies are installed **on demand**
|
|
47
|
-
* Browser instantiation command evolved in an **incompatible way** (see [documentation](https://arnaudbuchholz.github.io/ui5-test-runner/browser.html)).
|
|
48
|
-
* Output is different (report, traces)
|
|
37
|
+
| Version | Reason |
|
|
38
|
+
|-|-|
|
|
39
|
+
| **5**.0.0 | • Some coverage reports now includes **all** files, leading to a potential decrease of coverage |
|
|
40
|
+
| **4**.0.0 | • Drop support of Node.js 16 |
|
|
41
|
+
| **3**.0.0 | • Drop support of Node.js 14 |
|
|
42
|
+
| **2**.0.0 | • Command line **parameters** as well as configuration file **syntax** have changed |
|
|
43
|
+
|| • Dependencies are installed **on demand** |
|
|
44
|
+
|| • Browser instantiation command evolved in an **incompatible way** (see [documentation](https://arnaudbuchholz.github.io/ui5-test-runner/browser.html)) |
|
|
45
|
+
|| • Output is different (report, traces) |
|
|
49
46
|
|
|
50
47
|
## ✒ Contributors
|
|
51
48
|
|
package/jest.config.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"testTimeout": 15000,
|
|
3
|
+
"setupFilesAfterEnv": [
|
|
4
|
+
"./test/setup.js"
|
|
5
|
+
],
|
|
6
|
+
"testPathIgnorePatterns": [
|
|
7
|
+
"/node_modules/",
|
|
8
|
+
"/capabilities/"
|
|
9
|
+
],
|
|
10
|
+
"collectCoverage": true,
|
|
11
|
+
"collectCoverageFrom": [
|
|
12
|
+
"src/*.js"
|
|
13
|
+
],
|
|
14
|
+
"coveragePathIgnorePatterns": [
|
|
15
|
+
"\\.spec\\.js",
|
|
16
|
+
"output\\.js",
|
|
17
|
+
"b\\capabilities\\b"
|
|
18
|
+
],
|
|
19
|
+
"coverageThreshold": {
|
|
20
|
+
"global": {
|
|
21
|
+
"branches": 80,
|
|
22
|
+
"functions": 80,
|
|
23
|
+
"lines": 80,
|
|
24
|
+
"statements": 80
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui5-test-runner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Standalone test runner for UI5",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
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",
|
|
24
|
-
"clean": "npm uninstall -g ui5-test-runner puppeteer nyc selenium-webdriver playwright webdriverio"
|
|
24
|
+
"clean": "npm uninstall -g ui5-test-runner puppeteer nyc selenium-webdriver playwright webdriverio jsdom"
|
|
25
25
|
},
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
@@ -43,25 +43,24 @@
|
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://github.com/ArnaudBuchholz/ui5-test-runner#readme",
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"commander": "^12.
|
|
47
|
-
"mime": "^3.0.0",
|
|
46
|
+
"commander": "^12.1.0",
|
|
48
47
|
"punybind": "^1.2.1",
|
|
49
48
|
"punyexpr": "^1.0.4",
|
|
50
|
-
"reserve": "
|
|
49
|
+
"reserve": "2.0.1"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
|
53
|
-
"@openui5/types": "^1.
|
|
54
|
-
"@ui5/cli": "^3.
|
|
52
|
+
"@openui5/types": "^1.124.0",
|
|
53
|
+
"@ui5/cli": "^3.10.3",
|
|
55
54
|
"@ui5/middleware-code-coverage": "^1.1.1",
|
|
56
55
|
"dotenv": "^16.4.5",
|
|
57
56
|
"jest": "^29.7.0",
|
|
58
57
|
"nock": "^13.5.4",
|
|
59
58
|
"nyc": "^15.1.0",
|
|
60
|
-
"rimraf": "^5.0.
|
|
59
|
+
"rimraf": "^5.0.7",
|
|
61
60
|
"standard": "^17.1.0",
|
|
62
61
|
"start-server-and-test": "^2.0.3",
|
|
63
62
|
"typescript": "^5.4.5",
|
|
64
|
-
"ui5-tooling-transpile": "^3.
|
|
63
|
+
"ui5-tooling-transpile": "^3.4.2"
|
|
65
64
|
},
|
|
66
65
|
"optionalDependencies": {
|
|
67
66
|
"fsevents": "^2.3.3"
|
|
@@ -77,32 +76,5 @@
|
|
|
77
76
|
"sap",
|
|
78
77
|
"opaTest"
|
|
79
78
|
]
|
|
80
|
-
},
|
|
81
|
-
"jest": {
|
|
82
|
-
"testTimeout": 15000,
|
|
83
|
-
"setupFilesAfterEnv": [
|
|
84
|
-
"./test/setup.js"
|
|
85
|
-
],
|
|
86
|
-
"testPathIgnorePatterns": [
|
|
87
|
-
"/node_modules/",
|
|
88
|
-
"/capabilities/"
|
|
89
|
-
],
|
|
90
|
-
"collectCoverage": true,
|
|
91
|
-
"collectCoverageFrom": [
|
|
92
|
-
"src/*.js"
|
|
93
|
-
],
|
|
94
|
-
"coveragePathIgnorePatterns": [
|
|
95
|
-
"\\.spec\\.js",
|
|
96
|
-
"output\\.js",
|
|
97
|
-
"b\\capabilities\\b"
|
|
98
|
-
],
|
|
99
|
-
"coverageThreshold": {
|
|
100
|
-
"global": {
|
|
101
|
-
"branches": 80,
|
|
102
|
-
"functions": 80,
|
|
103
|
-
"lines": 80,
|
|
104
|
-
"statements": 80
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
79
|
}
|
|
108
80
|
}
|
package/src/browsers.js
CHANGED
|
@@ -116,8 +116,9 @@ async function start (job, url, scripts = []) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
if (resolvedScripts.length) {
|
|
119
|
-
resolvedScripts.unshift(`
|
|
120
|
-
|
|
119
|
+
resolvedScripts.unshift(`(function () {
|
|
120
|
+
window['ui5-test-runner/base-host'] = 'http://localhost:${job.port}'
|
|
121
|
+
}())`)
|
|
121
122
|
}
|
|
122
123
|
const progress = newProgress(job, url)
|
|
123
124
|
const pageBrowser = {
|
|
@@ -62,17 +62,23 @@ async function capabilities (job) {
|
|
|
62
62
|
const listener = listeners[listenerIndex]
|
|
63
63
|
await listener({
|
|
64
64
|
endpoint,
|
|
65
|
-
body:
|
|
65
|
+
body: await body(request).json()
|
|
66
66
|
})
|
|
67
67
|
response.writeHead(200)
|
|
68
68
|
response.end()
|
|
69
69
|
}
|
|
70
70
|
}, {
|
|
71
71
|
match: '^/inject/(.*)',
|
|
72
|
-
|
|
72
|
+
cwd: join(__dirname, '../inject'),
|
|
73
|
+
file: '$1',
|
|
74
|
+
static: !job.debugDevMode
|
|
73
75
|
}, {
|
|
74
76
|
match: '^/(.*)',
|
|
75
|
-
|
|
77
|
+
cwd: __dirname,
|
|
78
|
+
file: '$1',
|
|
79
|
+
static: !job.debugDevMode
|
|
80
|
+
}, {
|
|
81
|
+
status: 404
|
|
76
82
|
}]
|
|
77
83
|
})
|
|
78
84
|
const server = serve(configuration)
|
package/src/coverage.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { join, dirname, isAbsolute } = require('path')
|
|
3
|
+
const { join, dirname, isAbsolute, relative, sep } = require('path')
|
|
4
4
|
const { fork } = require('child_process')
|
|
5
5
|
const { cleanDir, createDir, filename, download, allocPromise } = require('./tools')
|
|
6
6
|
const { readdir, readFile, stat, writeFile, access, constants } = require('fs').promises
|
|
7
7
|
const { Readable } = require('stream')
|
|
8
|
-
const { getOutput } = require('./output')
|
|
8
|
+
const { getOutput, newProgress } = require('./output')
|
|
9
9
|
const { resolvePackage } = require('./npm')
|
|
10
10
|
const { promisify } = require('util')
|
|
11
11
|
const { UTRError } = require('./error')
|
|
@@ -64,7 +64,7 @@ async function instrument (job) {
|
|
|
64
64
|
await cleanDir(job.coverageTempDir)
|
|
65
65
|
await createDir(join(job.coverageTempDir, 'settings'))
|
|
66
66
|
const settings = JSON.parse((await readFile(job.coverageSettings)).toString())
|
|
67
|
-
settings.cwd = job.
|
|
67
|
+
settings.cwd = job.webapp
|
|
68
68
|
if (!settings.exclude) {
|
|
69
69
|
settings.exclude = []
|
|
70
70
|
}
|
|
@@ -75,6 +75,7 @@ async function instrument (job) {
|
|
|
75
75
|
settings.exclude.push(join(job.reportDir, '**'))
|
|
76
76
|
settings.exclude.push(join(job.coverageReportDir, '**'))
|
|
77
77
|
await writeFile(job[$nycSettingsPath], JSON.stringify(settings))
|
|
78
|
+
job.nycSettings = settings
|
|
78
79
|
if (job.mode === 'url') {
|
|
79
80
|
if (!job[$remoteOnLegacy]) {
|
|
80
81
|
job[$coverageRemote] = true
|
|
@@ -86,6 +87,92 @@ async function instrument (job) {
|
|
|
86
87
|
await nyc(job, 'instrument', job.webapp, join(job.coverageTempDir, 'instrumented'), '--nycrc-path', job[$nycSettingsPath])
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
function getUrlOrigin (job) {
|
|
91
|
+
const { origin } = new URL(job.url[0])
|
|
92
|
+
if (job.url.some(url => new URL(url).origin !== origin)) {
|
|
93
|
+
getOutput(job).assumingOneOrigin()
|
|
94
|
+
}
|
|
95
|
+
return origin
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function buildAllIndex (job) {
|
|
99
|
+
async function scanFs (path, onFolder, onFile) {
|
|
100
|
+
const items = await readdir(path)
|
|
101
|
+
await onFolder(items.length)
|
|
102
|
+
for (const item of items) {
|
|
103
|
+
const itemPath = join(path, item)
|
|
104
|
+
const itemStat = await stat(itemPath)
|
|
105
|
+
if (itemStat.isDirectory()) {
|
|
106
|
+
await scanFs(itemPath, onFolder, onFile)
|
|
107
|
+
} else {
|
|
108
|
+
await onFile(itemPath, (await readFile(itemPath)).toString())
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const output = getOutput(job)
|
|
114
|
+
output.debug('coverage', 'Build index for all files...')
|
|
115
|
+
const progress = newProgress(job, 'Build index for all files', 1, 0)
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const index = []
|
|
119
|
+
let scan
|
|
120
|
+
let start
|
|
121
|
+
if (job.mode === 'legacy' || job[$remoteOnLegacy]) {
|
|
122
|
+
scan = scanFs
|
|
123
|
+
start = join(job.coverageTempDir, 'instrumented')
|
|
124
|
+
} else {
|
|
125
|
+
scan = require(job.coverageRemoteScanner)
|
|
126
|
+
start = getUrlOrigin(job)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await scan(
|
|
130
|
+
start,
|
|
131
|
+
count => {
|
|
132
|
+
progress.total += count
|
|
133
|
+
++progress.count
|
|
134
|
+
},
|
|
135
|
+
async (file, source) => {
|
|
136
|
+
if (file.endsWith('.js') || file.endsWith('.ts')) {
|
|
137
|
+
output.debug('coverage', file)
|
|
138
|
+
try {
|
|
139
|
+
const coverageData = source
|
|
140
|
+
.match(/coverageData\s*=\s*({[^;]*});/)[1]
|
|
141
|
+
.replace(/([^"])(\w+):/g, (_, before, name) => `${before}"${name}":`)
|
|
142
|
+
const [, coveragePath] = coverageData.match(/"path"\s*:\s*"([^"]+)"/)
|
|
143
|
+
const UNDEFINED = '__undefined__'
|
|
144
|
+
const validatedCoverageData = JSON.stringify(
|
|
145
|
+
JSON.parse(coverageData.replace(/\bundefined\b/g, `"${UNDEFINED}"`)),
|
|
146
|
+
(key, value) => {
|
|
147
|
+
if (value === UNDEFINED) {
|
|
148
|
+
return undefined
|
|
149
|
+
}
|
|
150
|
+
return value
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
index.push(`"${coveragePath}": ${validatedCoverageData}`)
|
|
154
|
+
} catch (e) {
|
|
155
|
+
output.debug('coverage', `Error when extracting all coverage for ${file}`, e)
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
output.debug('coverage', `Ignore all coverage for ${file}`)
|
|
159
|
+
}
|
|
160
|
+
++progress.count
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
if (index.length === 0) {
|
|
164
|
+
output.noInfoForAllCoverage()
|
|
165
|
+
} else {
|
|
166
|
+
await writeFile(join(job.coverageTempDir, 'all-index.json'), `{${index.join(',')}}`)
|
|
167
|
+
}
|
|
168
|
+
} catch (e) {
|
|
169
|
+
output.genericError(e)
|
|
170
|
+
output.noInfoForAllCoverage()
|
|
171
|
+
} finally {
|
|
172
|
+
progress.done()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
89
176
|
async function getReadableSource (job, pathOrUrl) {
|
|
90
177
|
if (isAbsolute(pathOrUrl)) {
|
|
91
178
|
try {
|
|
@@ -99,16 +186,64 @@ async function getReadableSource (job, pathOrUrl) {
|
|
|
99
186
|
return filePath
|
|
100
187
|
} catch (e) {}
|
|
101
188
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
189
|
+
const origin = getUrlOrigin(job)
|
|
190
|
+
if (!job.coverageSourceDir) {
|
|
191
|
+
job.coverageSourceDir = join(job.coverageTempDir, 'sources')
|
|
192
|
+
}
|
|
193
|
+
const filePath = join(job.coverageSourceDir, pathOrUrl)
|
|
105
194
|
await download(origin + pathOrUrl, filePath)
|
|
106
195
|
return filePath
|
|
107
196
|
} catch (e) {}
|
|
108
197
|
}
|
|
109
198
|
|
|
199
|
+
async function checkAllSourcesAreAvailable (job, coverageFilename) {
|
|
200
|
+
const output = getOutput(job)
|
|
201
|
+
job.status = 'Checking remote source files'
|
|
202
|
+
output.debug('coverage', 'Checking remote source files...')
|
|
203
|
+
const coverageData = require(coverageFilename)
|
|
204
|
+
const filenames = Object.keys(coverageData)
|
|
205
|
+
let changes = false
|
|
206
|
+
let basePath
|
|
207
|
+
for (const filename of filenames) {
|
|
208
|
+
const fileData = coverageData[filename]
|
|
209
|
+
const filePath = await getReadableSource(job, fileData.path)
|
|
210
|
+
if (!filePath) {
|
|
211
|
+
// TODO this will compromise coverage report generation
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
if (filePath && filePath !== fileData.path) {
|
|
215
|
+
fileData.path = filePath
|
|
216
|
+
changes = true
|
|
217
|
+
}
|
|
218
|
+
if (filename !== filePath) {
|
|
219
|
+
delete coverageData[filename]
|
|
220
|
+
coverageData[filePath] = fileData
|
|
221
|
+
changes = true
|
|
222
|
+
}
|
|
223
|
+
const fileFolder = dirname(filePath)
|
|
224
|
+
if (basePath === undefined) {
|
|
225
|
+
basePath = fileFolder
|
|
226
|
+
} else {
|
|
227
|
+
const diff = relative(basePath, fileFolder).split(sep)
|
|
228
|
+
while (diff.shift() === '..') {
|
|
229
|
+
basePath = dirname(basePath)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (basePath !== job.nycSettings.cwd) {
|
|
234
|
+
job.nycSettings.cwd = basePath
|
|
235
|
+
await writeFile(job[$nycSettingsPath], JSON.stringify(job.nycSettings))
|
|
236
|
+
}
|
|
237
|
+
if (changes) {
|
|
238
|
+
await writeFile(coverageFilename, JSON.stringify(coverageData))
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
110
242
|
async function generateCoverageReport (job) {
|
|
111
243
|
job.status = 'Generating coverage report'
|
|
244
|
+
if (job.nycSettings.all) {
|
|
245
|
+
await buildAllIndex(job)
|
|
246
|
+
}
|
|
112
247
|
const output = getOutput(job)
|
|
113
248
|
output.debug('coverage', 'Generating coverage report...')
|
|
114
249
|
await cleanDir(job.coverageReportDir)
|
|
@@ -116,23 +251,8 @@ async function generateCoverageReport (job) {
|
|
|
116
251
|
await createDir(coverageMergedDir)
|
|
117
252
|
const coverageFilename = join(coverageMergedDir, 'coverage.json')
|
|
118
253
|
await nyc(job, 'merge', job.coverageTempDir, coverageFilename)
|
|
119
|
-
if (job[$coverageRemote]
|
|
120
|
-
job
|
|
121
|
-
output.debug('coverage', 'Checking remote source files...')
|
|
122
|
-
const coverageData = require(coverageFilename)
|
|
123
|
-
const filenames = Object.keys(coverageData)
|
|
124
|
-
let changes = 0
|
|
125
|
-
for (const filename of filenames) {
|
|
126
|
-
const fileData = coverageData[filename]
|
|
127
|
-
const filePath = await getReadableSource(job, fileData.path)
|
|
128
|
-
if (filePath && filePath !== fileData.path) {
|
|
129
|
-
fileData.path = filePath
|
|
130
|
-
++changes
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (changes > 0) {
|
|
134
|
-
await writeFile(coverageFilename, JSON.stringify(coverageData))
|
|
135
|
-
}
|
|
254
|
+
if (job[$coverageRemote]) {
|
|
255
|
+
await checkAllSourcesAreAvailable(job, coverageFilename)
|
|
136
256
|
}
|
|
137
257
|
const reporters = job.coverageReporters.map(reporter => `--reporter=${reporter}`)
|
|
138
258
|
if (!job.coverageReporters.includes('text')) {
|
|
@@ -177,8 +297,8 @@ module.exports = {
|
|
|
177
297
|
const instrumentedBasePath = join(job.coverageTempDir, 'instrumented')
|
|
178
298
|
const instrumentedMapping = {
|
|
179
299
|
match: /(.*\.js)(\?.*)?$/,
|
|
180
|
-
|
|
181
|
-
'
|
|
300
|
+
cwd: instrumentedBasePath,
|
|
301
|
+
file: '$1'
|
|
182
302
|
}
|
|
183
303
|
if (job.mode === 'legacy' || job[$remoteOnLegacy]) {
|
|
184
304
|
return [{
|
|
@@ -201,7 +321,7 @@ module.exports = {
|
|
|
201
321
|
return [{
|
|
202
322
|
match: /(.*\.js)(\?.*)?$/,
|
|
203
323
|
custom: async (request, response, url) => {
|
|
204
|
-
if (!url.match(job.coverageProxyInclude) || url.match(
|
|
324
|
+
if (!url.match(job.coverageProxyInclude) || url.match(job.coverageProxyExclude)) {
|
|
205
325
|
getOutput(job).debug('coverage', 'coverage_proxy ignore', url)
|
|
206
326
|
return
|
|
207
327
|
}
|
|
@@ -210,21 +330,20 @@ module.exports = {
|
|
|
210
330
|
await access(instrumentedSourcePath, constants.R_OK)
|
|
211
331
|
return
|
|
212
332
|
} catch (e) {}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
333
|
+
if (!sources[url]) {
|
|
334
|
+
sources[url] = (async () => {
|
|
335
|
+
const sourcePath = await getReadableSource(job, url)
|
|
336
|
+
getOutput(job).debug('coverage', 'coverage_proxy instrument', url, sourcePath)
|
|
337
|
+
if (sourcePath) {
|
|
338
|
+
const source = (await readFile(sourcePath)).toString()
|
|
339
|
+
const instrumentedSource = await instrument(source, sourcePath)
|
|
340
|
+
await createDir(dirname(instrumentedSourcePath))
|
|
341
|
+
await writeFile(instrumentedSourcePath, instrumentedSource)
|
|
342
|
+
delete sources[url]
|
|
343
|
+
}
|
|
344
|
+
})()
|
|
217
345
|
}
|
|
218
|
-
sources[url]
|
|
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
|
-
})()
|
|
346
|
+
await sources[url]
|
|
228
347
|
}
|
|
229
348
|
},
|
|
230
349
|
instrumentedMapping,
|
|
@@ -8,32 +8,64 @@ function fakeMatchMedia () {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function wrapXHR (
|
|
12
|
-
const {
|
|
13
|
-
const
|
|
11
|
+
function wrapXHR ({ XMLHttpRequest }) {
|
|
12
|
+
const { open, send } = XMLHttpRequest.prototype
|
|
13
|
+
const $async = Symbol('async')
|
|
14
14
|
XMLHttpRequest.prototype.open = function (...args) {
|
|
15
|
-
const [method, url] = args
|
|
15
|
+
const [method, url, async] = args
|
|
16
16
|
const log = () => {
|
|
17
17
|
const { status } = this
|
|
18
|
-
|
|
18
|
+
console.log(JSON.stringify({
|
|
19
|
+
timestamp: new Date().toISOString(),
|
|
20
|
+
channel: 'network',
|
|
21
|
+
initiator: 'xhr',
|
|
19
22
|
method,
|
|
20
23
|
url,
|
|
24
|
+
async,
|
|
21
25
|
status
|
|
22
|
-
})
|
|
26
|
+
}))
|
|
23
27
|
}
|
|
24
28
|
this.addEventListener('load', log)
|
|
25
29
|
this.addEventListener('error', log)
|
|
30
|
+
if (async === false) {
|
|
31
|
+
this[$async] = { method, url }
|
|
32
|
+
}
|
|
26
33
|
return open.call(this, ...args)
|
|
27
34
|
}
|
|
35
|
+
XMLHttpRequest.prototype.send = function (...args) {
|
|
36
|
+
if (this[$async]) {
|
|
37
|
+
const { method, url } = this[$async]
|
|
38
|
+
console.log(JSON.stringify({
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
channel: 'debug',
|
|
41
|
+
message: '>> XMLHttpRequest.prototype.send',
|
|
42
|
+
method,
|
|
43
|
+
url,
|
|
44
|
+
async: false
|
|
45
|
+
}))
|
|
46
|
+
}
|
|
47
|
+
const result = send.call(this, ...args)
|
|
48
|
+
if (this[$async]) {
|
|
49
|
+
const { method, url } = this[$async]
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
channel: 'debug',
|
|
53
|
+
message: '<< XMLHttpRequest.prototype.send',
|
|
54
|
+
method,
|
|
55
|
+
url,
|
|
56
|
+
async: false
|
|
57
|
+
}))
|
|
58
|
+
}
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
28
61
|
}
|
|
29
62
|
|
|
30
|
-
function adjustXPathResult (
|
|
63
|
+
function adjustXPathResult ({ Document }) {
|
|
31
64
|
/* https://ui5.sap.com/resources/sap/ui/model/odata/AnnotationParser-dbg.js
|
|
32
65
|
getXPath: function() {
|
|
33
66
|
xmlNodes.length = xmlNodes.snapshotLength;
|
|
34
67
|
*/
|
|
35
|
-
const {
|
|
36
|
-
const evaluate = Document.prototype.evaluate
|
|
68
|
+
const { evaluate } = Document.prototype
|
|
37
69
|
Document.prototype.evaluate = function () {
|
|
38
70
|
const result = evaluate.apply(this, arguments)
|
|
39
71
|
let length = result.length
|
|
@@ -48,10 +80,10 @@ function adjustXPathResult (window) {
|
|
|
48
80
|
}
|
|
49
81
|
}
|
|
50
82
|
|
|
51
|
-
function fixMatchesDontThrow (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
function fixMatchesDontThrow ({ document }) {
|
|
84
|
+
/* https://github.com/jsdom/jsdom/issues/3057
|
|
85
|
+
Fix _nwsapiDontThrow which throws :-(
|
|
86
|
+
*/
|
|
55
87
|
const [impl] = Object.getOwnPropertySymbols(document)
|
|
56
88
|
const documentImpl = document[impl]
|
|
57
89
|
let _nwsapiDontThrow
|
|
@@ -74,10 +106,45 @@ function fixMatchesDontThrow (window) {
|
|
|
74
106
|
})
|
|
75
107
|
}
|
|
76
108
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
function fixCaseSensitiveSelectors ({ Document }) {
|
|
110
|
+
/* https://github.com/SAP/openui5/blob/f41ed5504db1dc576dae7e7d403aaa02b918fef5/src/sap.ui.core/src/ui5loader-autoconfig.js#L75
|
|
111
|
+
oResult = check(globalThis.document.querySelector('SCRIPT[src][id=sap-ui-bootstrap]'), rResources);
|
|
112
|
+
jsdom uses case sensitive implementation of querySelector
|
|
113
|
+
*/
|
|
114
|
+
const uppercaseTag = /\bSCRIPT\b/g
|
|
115
|
+
const { querySelector, querySelectorAll } = Document.prototype
|
|
116
|
+
Object.assign(Document.prototype, {
|
|
117
|
+
querySelector (selectors) {
|
|
118
|
+
const result = querySelector.call(this, selectors) || { length: 0 }
|
|
119
|
+
if (result.length === 0 && selectors.match(uppercaseTag)) {
|
|
120
|
+
console.log(JSON.stringify({
|
|
121
|
+
timestamp: new Date().toISOString(),
|
|
122
|
+
channel: 'debug',
|
|
123
|
+
message: 'overriding selectors upon empty result of document.querySelector',
|
|
124
|
+
selectors
|
|
125
|
+
}))
|
|
126
|
+
return querySelector.call(this, selectors.replace(uppercaseTag, tag => tag.toLowerCase()))
|
|
127
|
+
}
|
|
128
|
+
return result
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
querySelectorAll (selectors) {
|
|
132
|
+
const result = querySelectorAll.call(this, selectors) || { length: 0 }
|
|
133
|
+
if (result.length === 0 && selectors.match(uppercaseTag)) {
|
|
134
|
+
console.log(JSON.stringify({
|
|
135
|
+
timestamp: new Date().toISOString(),
|
|
136
|
+
channel: 'debug',
|
|
137
|
+
message: 'overriding selectors upon empty result of document.querySelectorAll',
|
|
138
|
+
selectors
|
|
139
|
+
}))
|
|
140
|
+
return querySelectorAll.call(this, selectors.replace(uppercaseTag, tag => tag.toLowerCase()))
|
|
141
|
+
}
|
|
142
|
+
return result
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = window => {
|
|
81
148
|
window.addEventListener('error', event => {
|
|
82
149
|
const { message, filename, lineno, colno } = event
|
|
83
150
|
window.console.error(`${filename}@${lineno}:${colno}: ${message}`)
|
|
@@ -89,7 +156,8 @@ module.exports = ({
|
|
|
89
156
|
}
|
|
90
157
|
window.matchMedia = window.matchMedia || fakeMatchMedia
|
|
91
158
|
|
|
92
|
-
wrapXHR(window
|
|
159
|
+
wrapXHR(window)
|
|
93
160
|
adjustXPathResult(window)
|
|
94
161
|
fixMatchesDontThrow(window)
|
|
162
|
+
fixCaseSensitiveSelectors(window)
|
|
95
163
|
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
module.exports =
|
|
2
|
-
jsdom,
|
|
3
|
-
networkWriter,
|
|
4
|
-
consoleWriter
|
|
5
|
-
}) => {
|
|
1
|
+
module.exports = jsdom => {
|
|
6
2
|
const { ResourceLoader: JSDOMResourceLoader } = jsdom
|
|
7
3
|
|
|
8
4
|
const { readFile } = require('fs/promises')
|
|
@@ -15,19 +11,24 @@ module.exports = ({
|
|
|
15
11
|
const { response } = request
|
|
16
12
|
let status
|
|
17
13
|
if (response === undefined) {
|
|
18
|
-
|
|
14
|
+
console.log(JSON.stringify({
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
channel: 'console',
|
|
19
17
|
type: 'error',
|
|
20
18
|
message: 'NETWORK ERROR : ' + (reason ? reason.toString() : 'unknown reason')
|
|
21
|
-
})
|
|
19
|
+
}))
|
|
22
20
|
status = 599
|
|
23
21
|
} else {
|
|
24
22
|
status = response.statusCode
|
|
25
23
|
}
|
|
26
|
-
|
|
24
|
+
console.log(JSON.stringify({
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
channel: 'network',
|
|
27
|
+
initiator: 'resource-loader',
|
|
27
28
|
method: 'GET',
|
|
28
29
|
url,
|
|
29
30
|
status
|
|
30
|
-
})
|
|
31
|
+
}))
|
|
31
32
|
}
|
|
32
33
|
request.then(log, log)
|
|
33
34
|
if (url.match(/sap\/ui\/test\/matchers\/Visible(-dbg)?.js/)) {
|
package/src/defaults/jsdom.js
CHANGED
|
@@ -11,24 +11,42 @@ require('./browser')({
|
|
|
11
11
|
capabilities: {
|
|
12
12
|
modules: ['jsdom'],
|
|
13
13
|
scripts: true,
|
|
14
|
-
traces: ['
|
|
14
|
+
traces: ['multiplex']
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
async run ({
|
|
19
19
|
settings: { url, scripts, modules },
|
|
20
|
-
options
|
|
21
|
-
consoleWriter,
|
|
22
|
-
networkWriter
|
|
20
|
+
options
|
|
23
21
|
}) {
|
|
24
22
|
const jsdom = require(modules.jsdom)
|
|
25
23
|
const { JSDOM, VirtualConsole } = jsdom
|
|
26
24
|
|
|
27
25
|
const virtualConsole = new VirtualConsole()
|
|
28
|
-
virtualConsole.on('error', (...args) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
virtualConsole.on('error', (...args) => console.log(JSON.stringify({
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
channel: 'console',
|
|
29
|
+
type: 'error',
|
|
30
|
+
message: args.join(' ')
|
|
31
|
+
})))
|
|
32
|
+
virtualConsole.on('warn', (...args) => console.log(JSON.stringify({
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
channel: 'console',
|
|
35
|
+
type: 'warning',
|
|
36
|
+
message: args.join(' ')
|
|
37
|
+
})))
|
|
38
|
+
virtualConsole.on('info', (...args) => console.log(JSON.stringify({
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
channel: 'console',
|
|
41
|
+
type: 'info',
|
|
42
|
+
message: args.join(' ')
|
|
43
|
+
})))
|
|
44
|
+
virtualConsole.on('log', (...args) => console.log(JSON.stringify({
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
channel: 'console',
|
|
47
|
+
type: 'log',
|
|
48
|
+
message: args.join(' ')
|
|
49
|
+
})))
|
|
32
50
|
|
|
33
51
|
let mainWindow
|
|
34
52
|
|
|
@@ -41,7 +59,7 @@ require('./browser')({
|
|
|
41
59
|
writable: false
|
|
42
60
|
})
|
|
43
61
|
}
|
|
44
|
-
require('./jsdom/compatibility')(
|
|
62
|
+
require('./jsdom/compatibility')(window)
|
|
45
63
|
if (options.debug) {
|
|
46
64
|
require('./jsdom/debug')(window)
|
|
47
65
|
}
|
|
@@ -64,11 +82,7 @@ require('./browser')({
|
|
|
64
82
|
runScripts: 'dangerously',
|
|
65
83
|
pretendToBeVisual: true,
|
|
66
84
|
virtualConsole,
|
|
67
|
-
resources: require('./jsdom/resource-loader')(
|
|
68
|
-
jsdom,
|
|
69
|
-
networkWriter,
|
|
70
|
-
consoleWriter
|
|
71
|
-
}),
|
|
85
|
+
resources: require('./jsdom/resource-loader')(jsdom),
|
|
72
86
|
beforeParse
|
|
73
87
|
})
|
|
74
88
|
}
|
|
@@ -39,7 +39,8 @@ async function main () {
|
|
|
39
39
|
time = (new Date(test.end) - new Date(test.start)) / 1000
|
|
40
40
|
}
|
|
41
41
|
o(` <testcase
|
|
42
|
-
name="${xmlEscape(test.name)}"
|
|
42
|
+
name="${xmlEscape(test.name)}"
|
|
43
|
+
classname="${xmlEscape(module.name)}" ${
|
|
43
44
|
time === undefined
|
|
44
45
|
? ''
|
|
45
46
|
: `
|
|
@@ -8,17 +8,22 @@ require('./browser')({
|
|
|
8
8
|
name: 'puppeteer',
|
|
9
9
|
options: [
|
|
10
10
|
['--visible [flag]', 'Show the browser', false],
|
|
11
|
+
['--firefox [flag]', 'Use firefox instead of chrome', false],
|
|
12
|
+
['--binary <binary>', 'Binary path'],
|
|
11
13
|
['-w, --viewport-width <width>', 'Viewport width', 1920],
|
|
12
14
|
['-h, --viewport-height <height>', 'Viewport height', 1080],
|
|
13
15
|
['-l, --language <lang...>', 'Language(s)', ['en-US']],
|
|
14
16
|
['-u, --unsecure', 'Disable security features', false],
|
|
15
17
|
['--basic-auth-username <username>', 'Username for basic authentication', ''],
|
|
16
18
|
['--basic-auth-password <password>', 'Password for basic authentication', '']
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async capabilities ({ settings, options }) {
|
|
23
|
+
return {
|
|
19
24
|
modules: ['puppeteer'],
|
|
20
25
|
screenshot: '.png',
|
|
21
|
-
scripts:
|
|
26
|
+
scripts: !options.firefox,
|
|
22
27
|
traces: ['console', 'network']
|
|
23
28
|
}
|
|
24
29
|
},
|
|
@@ -68,7 +73,14 @@ require('./browser')({
|
|
|
68
73
|
)
|
|
69
74
|
}
|
|
70
75
|
|
|
76
|
+
let product
|
|
77
|
+
if (options.firefox) {
|
|
78
|
+
product = 'firefox'
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
browser = await puppeteer.launch({
|
|
82
|
+
product,
|
|
83
|
+
executablePath: options.binary,
|
|
72
84
|
headless: options.visible ? false : 'new',
|
|
73
85
|
defaultViewport: null,
|
|
74
86
|
args
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = async function scanUI5 (url, onFolder, onFile) {
|
|
2
|
+
if (url.match(/\/((?:test-)?resources\/.*)/)) {
|
|
3
|
+
return // ignore UI5 resources
|
|
4
|
+
}
|
|
5
|
+
const html = await (await fetch(url)).text()
|
|
6
|
+
const items = [...html.matchAll(/<a href="([^"]+)" class="icon/ig)]
|
|
7
|
+
.map(([_, item]) => item)
|
|
8
|
+
.filter(item => item.endsWith('/') || item.endsWith('.js') || item.endsWith('.ts'))
|
|
9
|
+
await onFolder(items.length)
|
|
10
|
+
for (const item of items) {
|
|
11
|
+
const itemUrl = new URL(item, url).toString()
|
|
12
|
+
if (item.endsWith('/')) {
|
|
13
|
+
await scanUI5(itemUrl, onFolder, onFile)
|
|
14
|
+
} else if (item.endsWith('.ts')) {
|
|
15
|
+
await onFile(itemUrl, await (await fetch(itemUrl.replace(/\.ts$/, '.js'))).text())
|
|
16
|
+
} else {
|
|
17
|
+
await onFile(itemUrl, await (await fetch(itemUrl + '?instrument=true')).text())
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -4,8 +4,7 @@ module.exports = async ({
|
|
|
4
4
|
seleniumWebdriver,
|
|
5
5
|
settings,
|
|
6
6
|
options,
|
|
7
|
-
loggingPreferences
|
|
8
|
-
$capabilities
|
|
7
|
+
loggingPreferences
|
|
9
8
|
}) => {
|
|
10
9
|
const { Browser, Builder } = seleniumWebdriver
|
|
11
10
|
const edge = require(join(settings.modules['selenium-webdriver'], 'edge'))
|
|
@@ -4,15 +4,14 @@ module.exports = async ({
|
|
|
4
4
|
seleniumWebdriver,
|
|
5
5
|
settings,
|
|
6
6
|
options,
|
|
7
|
-
loggingPreferences
|
|
8
|
-
$capabilities
|
|
7
|
+
loggingPreferences
|
|
9
8
|
}) => {
|
|
10
9
|
const { Browser, Builder } = seleniumWebdriver
|
|
11
10
|
const firefox = require(join(settings.modules['selenium-webdriver'], 'firefox'))
|
|
12
11
|
|
|
13
12
|
const firefoxOptions = new firefox.Options()
|
|
14
13
|
if (!options.visible) {
|
|
15
|
-
firefoxOptions.headless
|
|
14
|
+
firefoxOptions.addArguments('-headless')
|
|
16
15
|
}
|
|
17
16
|
firefoxOptions.setLoggingPrefs(loggingPreferences)
|
|
18
17
|
if (options.binary) {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { InvalidArgumentError } = require('commander')
|
|
4
|
+
|
|
5
|
+
let browserio
|
|
6
|
+
|
|
7
|
+
function browser (value) {
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
return 'chrome'
|
|
10
|
+
}
|
|
11
|
+
if (!['chrome', 'firefox'].includes(value)) {
|
|
12
|
+
throw new InvalidArgumentError('Browser name')
|
|
13
|
+
}
|
|
14
|
+
return value
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
require('./browser')({
|
|
18
|
+
metadata: {
|
|
19
|
+
name: 'webdriverio',
|
|
20
|
+
options: [
|
|
21
|
+
['--visible [flag]', 'Show the browser', false],
|
|
22
|
+
['-b, --browser <name>', 'Browser driver', browser, 'chrome'],
|
|
23
|
+
['--binary <binary>', 'Binary path']
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async capabilities ({ settings, options }) {
|
|
28
|
+
return {
|
|
29
|
+
modules: ['webdriverio'],
|
|
30
|
+
screenshot: '.png',
|
|
31
|
+
scripts: true,
|
|
32
|
+
traces: []
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async screenshot ({ filename }) {
|
|
37
|
+
if (browserio) {
|
|
38
|
+
await browserio.saveScreenshot(filename)
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async beforeExit () {
|
|
44
|
+
if (browserio) {
|
|
45
|
+
await browserio.deleteSession()
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async run ({
|
|
50
|
+
settings: { url, scripts, modules },
|
|
51
|
+
options,
|
|
52
|
+
consoleWriter,
|
|
53
|
+
networkWriter
|
|
54
|
+
}) {
|
|
55
|
+
const { remote } = require(modules.webdriverio)
|
|
56
|
+
|
|
57
|
+
const [browserOptions, args] = {
|
|
58
|
+
chrome: [
|
|
59
|
+
'goog:chromeOptions',
|
|
60
|
+
options.visible ? [] : ['--headless=new', '--log-level=3', '--disable-gpu']
|
|
61
|
+
],
|
|
62
|
+
firefox: [
|
|
63
|
+
'moz:firefoxOptions',
|
|
64
|
+
options.visible ? [] : ['-headless']
|
|
65
|
+
]
|
|
66
|
+
}[options.browser]
|
|
67
|
+
|
|
68
|
+
browserio = await remote({
|
|
69
|
+
capabilities: {
|
|
70
|
+
browserName: options.browser,
|
|
71
|
+
webSocketUrl: true,
|
|
72
|
+
[browserOptions]: {
|
|
73
|
+
args,
|
|
74
|
+
binary: options.binary
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (scripts && scripts.length) {
|
|
80
|
+
for (const script of scripts) {
|
|
81
|
+
await browserio.scriptAddPreloadScript({
|
|
82
|
+
functionDeclaration: `() => ${script}`
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await browserio.url(url)
|
|
88
|
+
}
|
|
89
|
+
})
|
package/src/endpoints.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { join } = require('path')
|
|
3
|
+
const { join, dirname, basename } = require('path')
|
|
4
4
|
const { body } = require('reserve')
|
|
5
5
|
const { extractPageUrl } = require('./tools')
|
|
6
6
|
const { Request, Response } = require('reserve')
|
|
@@ -18,7 +18,7 @@ const punybindBinPath = join(resolveDependencyPath('punybind'), 'dist/punybind.j
|
|
|
18
18
|
module.exports = job => {
|
|
19
19
|
async function endpointImpl (api, implementation, request) {
|
|
20
20
|
const url = extractPageUrl(request.headers)
|
|
21
|
-
const data =
|
|
21
|
+
const data = await body(request)
|
|
22
22
|
try {
|
|
23
23
|
await implementation.call(this, url, data)
|
|
24
24
|
} catch (error) {
|
|
@@ -75,7 +75,9 @@ module.exports = job => {
|
|
|
75
75
|
}, {
|
|
76
76
|
// QUnit hooks
|
|
77
77
|
match: '^/_/qunit-hooks.js',
|
|
78
|
-
|
|
78
|
+
cwd: __dirname,
|
|
79
|
+
file: 'inject/qunit-hooks.js',
|
|
80
|
+
static: !job.debugDevMode
|
|
79
81
|
}, {
|
|
80
82
|
// Concatenate qunit.js source with hooks
|
|
81
83
|
match: /\/thirdparty\/(qunit(?:-2)?(?:-dbg)?\.js)/,
|
|
@@ -123,23 +125,33 @@ module.exports = job => {
|
|
|
123
125
|
}, {
|
|
124
126
|
// UI to follow progress
|
|
125
127
|
match: '^/_/progress.html',
|
|
126
|
-
|
|
128
|
+
cwd: dirname(job.progressPage),
|
|
129
|
+
file: basename(job.progressPage),
|
|
130
|
+
static: !job.debugDevMode
|
|
127
131
|
}, {
|
|
128
132
|
// Report 'main' substituted for progress
|
|
129
133
|
match: '^/_/report/main.js',
|
|
130
|
-
|
|
134
|
+
cwd: __dirname,
|
|
135
|
+
file: 'defaults/report/progress.js',
|
|
136
|
+
static: !job.debugDevMode
|
|
131
137
|
}, {
|
|
132
138
|
// Other report resources
|
|
133
139
|
match: '^/_/report/(.*)',
|
|
134
|
-
|
|
140
|
+
cwd: __dirname,
|
|
141
|
+
file: 'defaults/report/$1',
|
|
142
|
+
static: !job.debugDevMode
|
|
135
143
|
}, {
|
|
136
144
|
// punybind
|
|
137
145
|
match: '^/_/punybind.js',
|
|
138
|
-
|
|
146
|
+
cwd: dirname(punybindBinPath),
|
|
147
|
+
file: basename(punybindBinPath),
|
|
148
|
+
static: !job.debugDevMode
|
|
139
149
|
}, {
|
|
140
150
|
// punyexpr
|
|
141
151
|
match: '^/_/punyexpr.js',
|
|
142
|
-
|
|
152
|
+
cwd: dirname(punyexprBinPath),
|
|
153
|
+
file: basename(punyexprBinPath),
|
|
154
|
+
static: !job.debugDevMode
|
|
143
155
|
}, {
|
|
144
156
|
// Endpoint to retry on progress
|
|
145
157
|
method: 'INFO',
|
|
@@ -155,15 +167,21 @@ module.exports = job => {
|
|
|
155
167
|
}, {
|
|
156
168
|
// Endpoint to coverage files
|
|
157
169
|
match: '^/_/coverage/(.*)',
|
|
158
|
-
|
|
170
|
+
cwd: job.coverageReportDir,
|
|
171
|
+
file: '$1',
|
|
172
|
+
static: false
|
|
159
173
|
}, {
|
|
160
174
|
// Endpoint to report
|
|
161
175
|
match: '^/_/report.html',
|
|
162
|
-
|
|
176
|
+
cwd: __dirname,
|
|
177
|
+
file: 'report.html',
|
|
178
|
+
static: !job.debugDevMode
|
|
163
179
|
}, {
|
|
164
180
|
// Endpoint to report files
|
|
165
181
|
match: '^/_/(.*)',
|
|
166
|
-
|
|
182
|
+
cwd: job.reportDir,
|
|
183
|
+
file: '$1',
|
|
184
|
+
static: false
|
|
167
185
|
}]
|
|
168
186
|
: []
|
|
169
187
|
}
|
package/src/inject/post.js
CHANGED
|
@@ -83,7 +83,10 @@
|
|
|
83
83
|
})
|
|
84
84
|
xhr.open('POST', base + '/_/' + url)
|
|
85
85
|
xhr.setRequestHeader('x-page-url', top.location)
|
|
86
|
-
xhr.
|
|
86
|
+
xhr.setRequestHeader('content-type', 'application/json')
|
|
87
|
+
const json = stringify(data)
|
|
88
|
+
xhr.setRequestHeader('content-length', json.length)
|
|
89
|
+
xhr.send(json)
|
|
87
90
|
})
|
|
88
91
|
}
|
|
89
92
|
lastPost = lastPost
|
package/src/job-mode.js
CHANGED
package/src/job.js
CHANGED
|
@@ -132,6 +132,7 @@ function getCommand (cwd) {
|
|
|
132
132
|
.option('-ccf, --coverage-check-functions <percent>', '[💻🔗] What % of functions must be covered', percent, 0)
|
|
133
133
|
.option('-ccl, --coverage-check-lines <percent>', '[💻🔗] What % of lines must be covered', percent, 0)
|
|
134
134
|
.option('-ccs, --coverage-check-statements <percent>', '[💻🔗] What % of statements must be covered', percent, 0)
|
|
135
|
+
.option('-crs, --coverage-remote-scanner <path>', '[💻🔗] Scan for files when all coverage is requested', '$/scan-ui5.js')
|
|
135
136
|
.option('-s, --serve-only [flag]', '[💻🔗] Serve only', boolean, false)
|
|
136
137
|
|
|
137
138
|
// Specific to legacy (and might be used with url if pointing to local project)
|
|
@@ -146,9 +147,10 @@ function getCommand (cwd) {
|
|
|
146
147
|
|
|
147
148
|
// Specific to coverage in url mode (experimental)
|
|
148
149
|
.option('-cp, --coverage-proxy [flag]', `[🔗] ${EXPERIMENTAL_OPTION} use internal proxy to instrument remote files`, boolean, false)
|
|
149
|
-
.option('-cpi, --coverage-proxy-include <regexp>', `[🔗] ${EXPERIMENTAL_OPTION} urls to instrument for coverage`, regex,
|
|
150
|
-
.option('-cpe, --coverage-proxy-exclude <regexp>', `[🔗] ${EXPERIMENTAL_OPTION} urls to ignore for coverage`, regex,
|
|
150
|
+
.option('-cpi, --coverage-proxy-include <regexp>', `[🔗] ${EXPERIMENTAL_OPTION} urls to instrument for coverage`, regex, '.*')
|
|
151
|
+
.option('-cpe, --coverage-proxy-exclude <regexp>', `[🔗] ${EXPERIMENTAL_OPTION} urls to ignore for coverage`, regex, '/((test-)?resources|tests?)/')
|
|
151
152
|
|
|
153
|
+
.addOption(new Option('--debug-dev-mode', DEBUG_OPTION, boolean).hideHelp())
|
|
152
154
|
.addOption(new Option('--debug-probe-only', DEBUG_OPTION, boolean).hideHelp())
|
|
153
155
|
.addOption(new Option('--debug-keep-browser-open', DEBUG_OPTION, boolean).hideHelp())
|
|
154
156
|
.addOption(new Option('--debug-memory', DEBUG_OPTION, boolean).hideHelp())
|
|
@@ -219,7 +221,7 @@ function finalize (job) {
|
|
|
219
221
|
function updateToAbsolute (member, from = job.cwd) {
|
|
220
222
|
job[member] = toAbsolute(job[member], from)
|
|
221
223
|
}
|
|
222
|
-
'browser,coverageSettings,progressPage'
|
|
224
|
+
'browser,coverageSettings,coverageRemoteScanner,progressPage'
|
|
223
225
|
.split(',')
|
|
224
226
|
.forEach(setting => { job[setting] = checkDefault(job[setting]) })
|
|
225
227
|
updateToAbsolute('cwd', job.initialCwd)
|
|
@@ -299,6 +301,8 @@ function finalize (job) {
|
|
|
299
301
|
overrideDirIfNotSet('coverageReportDir', settings['report-dir'])
|
|
300
302
|
overrideDirIfNotSet('coverageTempDir', settings['temp-dir'])
|
|
301
303
|
overrideIfNotSet('coverageReporters', settings.reporter)
|
|
304
|
+
|
|
305
|
+
checkAccess({ path: job.coverageRemoteScanner, label: 'coverage remote scanner', file: true })
|
|
302
306
|
}
|
|
303
307
|
|
|
304
308
|
if (job.mode === 'url') {
|
package/src/output.js
CHANGED
|
@@ -260,6 +260,9 @@ function build (job) {
|
|
|
260
260
|
version: () => {
|
|
261
261
|
const { name, version } = require(join(__dirname, '../package.json'))
|
|
262
262
|
log(job, p80()`${name}@${version}`)
|
|
263
|
+
if (job.debugDevMode) {
|
|
264
|
+
log(job, p80()`⚠️ Development mode ⚠️`)
|
|
265
|
+
}
|
|
263
266
|
},
|
|
264
267
|
|
|
265
268
|
serving: url => {
|
|
@@ -364,7 +367,7 @@ function build (job) {
|
|
|
364
367
|
},
|
|
365
368
|
|
|
366
369
|
packageNotLatest (name, latestVersion) {
|
|
367
|
-
wrap(() => log(job,
|
|
370
|
+
wrap(() => log(job, `⚠️ [PKGVRS] latest version of ${name} is ${latestVersion}`))()
|
|
368
371
|
},
|
|
369
372
|
|
|
370
373
|
browserStart (url) {
|
|
@@ -472,7 +475,15 @@ function build (job) {
|
|
|
472
475
|
}),
|
|
473
476
|
|
|
474
477
|
instrumentationSkipped: wrap(() => {
|
|
475
|
-
log(job, p80()
|
|
478
|
+
log(job, p80()`⚠️ [SKPNYC] Skipping nyc instrumentation (--url)`)
|
|
479
|
+
}),
|
|
480
|
+
|
|
481
|
+
assumingOneOrigin: wrap(() => {
|
|
482
|
+
log(job, p80()`⚠️ [COVORG] Considering only one origin`)
|
|
483
|
+
}),
|
|
484
|
+
|
|
485
|
+
noInfoForAllCoverage: wrap(() => {
|
|
486
|
+
log(job, p80()`⚠️ [COVALL] Unable to process all coverage, report might be incomplete`)
|
|
476
487
|
}),
|
|
477
488
|
|
|
478
489
|
endpointError: wrap(({ api, url, data, error }) => {
|
|
@@ -548,7 +559,7 @@ function build (job) {
|
|
|
548
559
|
}),
|
|
549
560
|
|
|
550
561
|
unhandled: wrap(() => {
|
|
551
|
-
warn(job, p80()
|
|
562
|
+
warn(job, p80()`⚠️ [UNHAND] Some requests are not handled properly, check the unhandled.txt report for more info`)
|
|
552
563
|
}),
|
|
553
564
|
|
|
554
565
|
reportGeneratorFailed: wrap((generator, exitCode, buffers) => {
|
package/src/reserve.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const { join } = require('path')
|
|
2
1
|
const cors = require('./cors')
|
|
3
2
|
const endpoints = require('./endpoints')
|
|
4
3
|
const { mappings: coverage } = require('./coverage')
|
|
@@ -13,12 +12,13 @@ module.exports = async job => check({
|
|
|
13
12
|
...job.mappings ?? [],
|
|
14
13
|
...job.serveOnly ? [] : endpoints(job),
|
|
15
14
|
...ui5(job),
|
|
16
|
-
...await coverage(job),
|
|
15
|
+
...job.serveOnly ? [] : await coverage(job),
|
|
16
|
+
{
|
|
17
17
|
// Project mapping
|
|
18
18
|
match: /^\/(.*)/,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
cwd: job.webapp,
|
|
20
|
+
file: '$1',
|
|
21
|
+
static: !job.watch && !job.debugDevMode
|
|
22
22
|
},
|
|
23
23
|
...job.serveOnly ? [{ status: 404 }] : unhandled(job)
|
|
24
24
|
]
|
package/src/ui5.js
CHANGED
|
@@ -63,7 +63,7 @@ module.exports = {
|
|
|
63
63
|
|
|
64
64
|
const cacheBase = buildCacheBase(job)
|
|
65
65
|
const match = /\/((?:test-)?resources\/.*)/
|
|
66
|
-
const ifCacheEnabled = (request, url, match) => job.cache
|
|
66
|
+
const ifCacheEnabled = (request, url, match) => job.cache
|
|
67
67
|
const uncachable = {}
|
|
68
68
|
const cachingInProgress = {}
|
|
69
69
|
|
|
@@ -90,8 +90,9 @@ module.exports = {
|
|
|
90
90
|
// UI5 from cache
|
|
91
91
|
match,
|
|
92
92
|
'if-match': ifCacheEnabled,
|
|
93
|
-
|
|
94
|
-
'
|
|
93
|
+
cwd: cacheBase,
|
|
94
|
+
file: '$1',
|
|
95
|
+
static: !job.debugDevMode
|
|
95
96
|
}, {
|
|
96
97
|
// UI5 caching
|
|
97
98
|
method: 'GET',
|
|
@@ -130,8 +131,12 @@ module.exports = {
|
|
|
130
131
|
job.libs.forEach(({ relative, source }) => {
|
|
131
132
|
mappings.unshift({
|
|
132
133
|
match: new RegExp(`\\/resources\\/${relative.replace(/\//g, '\\/')}(.*)`),
|
|
133
|
-
|
|
134
|
-
'
|
|
134
|
+
cwd: source,
|
|
135
|
+
file: '$1',
|
|
136
|
+
static: !job.watch && !job.debugDevMode
|
|
137
|
+
}, {
|
|
138
|
+
match: new RegExp(`\\/resources\\/${relative.replace(/\//g, '\\/')}(.*)`),
|
|
139
|
+
status: 404
|
|
135
140
|
})
|
|
136
141
|
})
|
|
137
142
|
|