ui5-test-runner 1.1.4 → 2.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 +32 -188
- package/index.js +47 -16
- package/package.json +30 -12
- package/src/add-test-pages.js +35 -0
- package/src/add-test-pages.spec.js +95 -0
- package/src/browser.spec.js +724 -0
- package/src/browsers.js +220 -59
- package/src/capabilities/index.js +194 -0
- package/src/capabilities/tests/basic/iframe.html +8 -0
- package/src/capabilities/tests/basic/index.html +12 -0
- package/src/capabilities/tests/basic/index.js +20 -0
- package/src/capabilities/tests/basic/ui5.html +24 -0
- package/src/capabilities/tests/dynamic-include/index.js +21 -0
- package/src/capabilities/tests/dynamic-include/mix.html +11 -0
- package/src/capabilities/tests/dynamic-include/one.html +11 -0
- package/src/capabilities/tests/dynamic-include/post.js +3 -0
- package/src/capabilities/tests/dynamic-include/test.js +1 -0
- package/src/capabilities/tests/dynamic-include/two.html +11 -0
- package/src/capabilities/tests/index.js +16 -0
- package/src/capabilities/tests/local-storage/index.html +16 -0
- package/src/capabilities/tests/local-storage/index.js +21 -0
- package/src/capabilities/tests/screenshot/index.html +13 -0
- package/src/capabilities/tests/screenshot/index.js +18 -0
- package/src/capabilities/tests/scripts/index.js +50 -0
- package/src/capabilities/tests/scripts/qunit.html +22 -0
- package/src/capabilities/tests/scripts/testsuite.html +10 -0
- package/src/capabilities/tests/scripts/testsuite.js +8 -0
- package/src/capabilities/tests/timeout/index.html +21 -0
- package/src/capabilities/tests/timeout/index.js +19 -0
- package/src/capabilities/tests/traces/index.html +18 -0
- package/src/capabilities/tests/traces/index.js +81 -0
- package/src/cors.js +1 -1
- package/src/cors.spec.js +41 -0
- package/src/coverage.js +30 -18
- package/src/coverage.spec.js +79 -0
- package/src/csv-reader.js +36 -0
- package/src/csv-reader.spec.js +42 -0
- package/src/csv-writer.js +52 -0
- package/src/csv-writer.spec.js +77 -0
- package/src/defaults/browser.js +144 -0
- package/src/defaults/jsdom/compatibility.js +95 -0
- package/src/defaults/jsdom/debug.js +23 -0
- package/src/defaults/jsdom/resource-loader.js +43 -0
- package/src/defaults/jsdom/sap.ui.test.matchers.visible.js +39 -0
- package/src/defaults/jsdom.js +64 -0
- package/src/defaults/junit-xml-report.js +64 -0
- package/src/defaults/puppeteer.js +111 -0
- package/src/defaults/report/common.js +38 -0
- package/src/defaults/report/default.html +84 -0
- package/src/defaults/report/main.js +44 -0
- package/src/defaults/report/progress.js +49 -0
- package/src/defaults/report/styles.css +66 -0
- package/src/defaults/report.js +69 -0
- package/src/defaults/selenium-webdriver/chrome.js +38 -0
- package/src/defaults/selenium-webdriver/edge.js +25 -0
- package/src/defaults/selenium-webdriver/firefox.js +31 -0
- package/src/defaults/selenium-webdriver.js +138 -0
- package/src/endpoints.js +70 -124
- package/src/error.js +52 -0
- package/src/error.spec.js +17 -0
- package/src/get-job-progress.js +69 -0
- package/src/get-job-progress.spec.js +175 -0
- package/src/inject/post.js +96 -0
- package/src/inject/post.spec.js +147 -0
- package/src/inject/qunit-hooks.js +6 -21
- package/src/inject/qunit-intercept.js +30 -0
- package/src/inject/qunit-redirect.js +15 -7
- package/src/job-mode.js +45 -0
- package/src/job.js +254 -108
- package/src/job.spec.js +413 -0
- package/src/npm.js +73 -0
- package/src/npm.spec.js +98 -0
- package/src/options.js +73 -0
- package/src/options.spec.js +125 -0
- package/src/output.js +450 -131
- package/src/qunit-hooks.js +116 -0
- package/src/qunit-hooks.spec.js +687 -0
- package/src/report.js +42 -0
- package/src/reserve.js +3 -2
- package/src/simulate.spec.js +437 -0
- package/src/symbols.js +8 -0
- package/src/tests.js +127 -84
- package/src/timeout.spec.js +39 -0
- package/src/tools.js +111 -4
- package/src/tools.spec.js +90 -0
- package/src/ui5.js +3 -3
- package/src/unhandled.js +6 -6
- package/src/unhandled.spec.js +63 -0
- package/defaults/chromium.js +0 -62
- package/src/progress.html +0 -71
- package/src/report.html +0 -202
- /package/{defaults → src/defaults}/nyc.json +0 -0
package/src/job.spec.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
const { dirname, join } = require('path')
|
|
2
|
+
const { fromObject, fromCmdLine } = require('./job')
|
|
3
|
+
const normalizePath = path => path.replace(/\\/g, '/') // win -> unix
|
|
4
|
+
const { $valueSources } = require('./symbols')
|
|
5
|
+
const { UTRError } = require('./error')
|
|
6
|
+
|
|
7
|
+
const cwd = join(__dirname, '../test/project')
|
|
8
|
+
|
|
9
|
+
function buildJob (parameters) {
|
|
10
|
+
return fromObject(cwd, parameters)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('job', () => {
|
|
14
|
+
describe('parameter parsing', () => {
|
|
15
|
+
it('provides default values', () => {
|
|
16
|
+
const job = buildJob({})
|
|
17
|
+
expect(job.cwd).toStrictEqual(cwd)
|
|
18
|
+
expect(job.port).toStrictEqual(0)
|
|
19
|
+
expect(job.ui5).toStrictEqual('https://ui5.sap.com')
|
|
20
|
+
expect(job.browser.startsWith(dirname(dirname(__dirname)))).toStrictEqual(true)
|
|
21
|
+
expect(normalizePath(job.browser).endsWith('defaults/puppeteer.js')).toStrictEqual(true)
|
|
22
|
+
expect(normalizePath(job.webapp).endsWith('/test/project/webapp')).toStrictEqual(true)
|
|
23
|
+
expect(job.keepAlive).toStrictEqual(false)
|
|
24
|
+
expect(job.screenshot).toStrictEqual(true)
|
|
25
|
+
expect(job[$valueSources]).toMatchObject({
|
|
26
|
+
cwd: 'default',
|
|
27
|
+
port: 'default',
|
|
28
|
+
ui5: 'default',
|
|
29
|
+
browser: 'default'
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('parses parameters', () => {
|
|
34
|
+
const job = buildJob({
|
|
35
|
+
cwd: '../project2',
|
|
36
|
+
port: 8080,
|
|
37
|
+
keepAlive: null,
|
|
38
|
+
ui5: 'http://localhost:8088/ui5'
|
|
39
|
+
})
|
|
40
|
+
expect(normalizePath(job.cwd).endsWith('/test/project2')).toStrictEqual(true)
|
|
41
|
+
expect(job.port).toStrictEqual(8080)
|
|
42
|
+
expect(job.keepAlive).toStrictEqual(true)
|
|
43
|
+
expect(job.ui5).toStrictEqual('http://localhost:8088/ui5')
|
|
44
|
+
expect(normalizePath(job.webapp).endsWith('/test/project2/webapp')).toStrictEqual(true)
|
|
45
|
+
expect(job[$valueSources]).toMatchObject({
|
|
46
|
+
cwd: 'cli',
|
|
47
|
+
port: 'cli',
|
|
48
|
+
keepAlive: 'cli',
|
|
49
|
+
ui5: 'cli',
|
|
50
|
+
browser: 'default'
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('complex parameter parsing', () => {
|
|
55
|
+
it('implements boolean flag', () => {
|
|
56
|
+
const job = buildJob({
|
|
57
|
+
keepAlive: false,
|
|
58
|
+
coverage: null,
|
|
59
|
+
logServer: null
|
|
60
|
+
})
|
|
61
|
+
expect(job.keepAlive).toStrictEqual(false)
|
|
62
|
+
expect(job.coverage).toStrictEqual(true)
|
|
63
|
+
expect(job.logServer).toStrictEqual(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('implements boolean switch off', () => {
|
|
67
|
+
const job = buildJob({
|
|
68
|
+
noCoverage: null,
|
|
69
|
+
noScreenshot: null
|
|
70
|
+
})
|
|
71
|
+
expect(job.coverage).toStrictEqual(false)
|
|
72
|
+
expect(job.screenshot).toStrictEqual(false)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('url disables webapp checking and coverage', () => {
|
|
76
|
+
const job = buildJob({
|
|
77
|
+
webapp: 'not_a_folder',
|
|
78
|
+
url: 'http://localhost:8080'
|
|
79
|
+
})
|
|
80
|
+
expect(job.url).toStrictEqual(['http://localhost:8080'])
|
|
81
|
+
expect(job.coverage).toStrictEqual(false)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('url still allows coverage', () => {
|
|
85
|
+
const job = buildJob({
|
|
86
|
+
webapp: 'not_a_folder',
|
|
87
|
+
url: 'http://localhost:8080',
|
|
88
|
+
coverage: true
|
|
89
|
+
})
|
|
90
|
+
expect(job.url).toStrictEqual(['http://localhost:8080'])
|
|
91
|
+
expect(job.coverage).toStrictEqual(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('multi values', () => {
|
|
95
|
+
const absoluteLibPath = join(__dirname, '../test/project/webapp/lib')
|
|
96
|
+
|
|
97
|
+
describe('url', () => {
|
|
98
|
+
it('accepts multiple urls', () => {
|
|
99
|
+
const job = buildJob({
|
|
100
|
+
url: [
|
|
101
|
+
'http://localhost:8080/page1.html',
|
|
102
|
+
'http://localhost:8080/page2.html'
|
|
103
|
+
]
|
|
104
|
+
})
|
|
105
|
+
expect(job.url).toMatchObject([
|
|
106
|
+
'http://localhost:8080/page1.html',
|
|
107
|
+
'http://localhost:8080/page2.html'
|
|
108
|
+
])
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('libs', () => {
|
|
113
|
+
it('accepts one library', () => {
|
|
114
|
+
const job = buildJob({
|
|
115
|
+
libs: [absoluteLibPath]
|
|
116
|
+
})
|
|
117
|
+
expect(job.libs).toMatchObject([{
|
|
118
|
+
relative: '',
|
|
119
|
+
source: absoluteLibPath
|
|
120
|
+
}])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('accepts two libraries', () => {
|
|
124
|
+
const project2Path = join(__dirname, '../test/project2')
|
|
125
|
+
const job = buildJob({
|
|
126
|
+
libs: [absoluteLibPath, 'project2/=../project2']
|
|
127
|
+
})
|
|
128
|
+
expect(job.libs).toMatchObject([{
|
|
129
|
+
relative: '',
|
|
130
|
+
source: absoluteLibPath
|
|
131
|
+
}, {
|
|
132
|
+
relative: 'project2/',
|
|
133
|
+
source: project2Path
|
|
134
|
+
}])
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('browser parameters', () => {
|
|
139
|
+
it('allows passing extra parameter', () => {
|
|
140
|
+
const job = buildJob({
|
|
141
|
+
'--': ['--visible']
|
|
142
|
+
})
|
|
143
|
+
expect(job.browserArgs).toEqual(['--visible'])
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('allows passing extra parameters', () => {
|
|
147
|
+
const job = buildJob({
|
|
148
|
+
'--': ['--visible', '--verbose']
|
|
149
|
+
})
|
|
150
|
+
expect(job.browserArgs).toEqual(['--visible', '--verbose'])
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe('parameters using $/', () => {
|
|
156
|
+
const job = buildJob({
|
|
157
|
+
cwd,
|
|
158
|
+
browser: '$/selenium-webdriver.js'
|
|
159
|
+
})
|
|
160
|
+
expect(job.browser).toStrictEqual(join(__dirname, './defaults/selenium-webdriver.js'))
|
|
161
|
+
expect(job.coverageSettings).toStrictEqual(join(__dirname, './defaults/nyc.json'))
|
|
162
|
+
expect(job.reportGenerator).toEqual([join(__dirname, './defaults/report.js')])
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('custom mappings', () => {
|
|
166
|
+
it('offers custom mappings', () => {
|
|
167
|
+
const job = buildJob({
|
|
168
|
+
cwd,
|
|
169
|
+
mappings: [
|
|
170
|
+
'^/otherlib/(.+)=file(./otherfolder/otherlib/$1)',
|
|
171
|
+
'^/ui/oDataService/v1/odata/v4/ServiceName/(.+)=url(http://localhost:18082/odata/v4/ServiceName/$1)'
|
|
172
|
+
]
|
|
173
|
+
})
|
|
174
|
+
expect(job.mappings).toEqual([
|
|
175
|
+
{
|
|
176
|
+
match: '^/otherlib/(.+)',
|
|
177
|
+
file: './otherfolder/otherlib/$1'
|
|
178
|
+
}, {
|
|
179
|
+
match: '^/ui/oDataService/v1/odata/v4/ServiceName/(.+)',
|
|
180
|
+
url: 'http://localhost:18082/odata/v4/ServiceName/$1'
|
|
181
|
+
}
|
|
182
|
+
])
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('rejects invalid mapping', () => {
|
|
186
|
+
expect(() => buildJob({
|
|
187
|
+
cwd,
|
|
188
|
+
mappings: [
|
|
189
|
+
'^/otherlib/(.+)=custom(./otherfolder/otherlib/$1)'
|
|
190
|
+
]
|
|
191
|
+
})).toThrowError()
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('validation', () => {
|
|
198
|
+
it('fails on negative integers', () => {
|
|
199
|
+
expect(() => buildJob({
|
|
200
|
+
port: -1
|
|
201
|
+
})).toThrow()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('fails on invalid URL', () => {
|
|
205
|
+
expect(() => buildJob({
|
|
206
|
+
ui5: 'not_an_url'
|
|
207
|
+
})).toThrow()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('fails on a missing file (does not exist)', () => {
|
|
211
|
+
expect(() => buildJob({
|
|
212
|
+
testsuite: 'not_a_file'
|
|
213
|
+
})).toThrow()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('fails on a missing file (points to a folder)', () => {
|
|
217
|
+
expect(() => buildJob({
|
|
218
|
+
testsuite: 'lib'
|
|
219
|
+
})).toThrow()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('fails on a missing folder (does not exist)', () => {
|
|
223
|
+
expect(() => buildJob({
|
|
224
|
+
webapp: 'not_a_folder'
|
|
225
|
+
})).toThrow()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('fails on a missing folder (points to a file)', () => {
|
|
229
|
+
expect(() => buildJob({
|
|
230
|
+
webapp: 'webapp/lib/README.md'
|
|
231
|
+
})).toThrow()
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
describe('Path parameters validation', () => {
|
|
235
|
+
const parameters = ['webapp', 'browser', 'testsuite']
|
|
236
|
+
|
|
237
|
+
parameters.forEach(parameter => {
|
|
238
|
+
it(`fails on invalid path for ${parameter}`, () => {
|
|
239
|
+
expect(() => buildJob({ [parameter]: 'nope' })).toThrow()
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
describe('libs', () => {
|
|
245
|
+
it('fails on invalid lib path (absolute)', () => {
|
|
246
|
+
const absoluteLibPath = join(__dirname, '../test/project/webapp/lib2')
|
|
247
|
+
expect(() => buildJob({
|
|
248
|
+
libs: absoluteLibPath
|
|
249
|
+
})).toThrow()
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('fails on invalid lib path (relative)', () => {
|
|
253
|
+
expect(() => buildJob({
|
|
254
|
+
libs: '../project3'
|
|
255
|
+
})).toThrow()
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
describe('Using ui5-test-runner.json', () => {
|
|
261
|
+
const project2 = join(__dirname, '../test/project2')
|
|
262
|
+
|
|
263
|
+
it('enables option overriding at the command level', () => {
|
|
264
|
+
const job = fromCmdLine(cwd, [
|
|
265
|
+
'--port', '1',
|
|
266
|
+
'--port', '2',
|
|
267
|
+
'-k', 'true',
|
|
268
|
+
'-k', 'false'
|
|
269
|
+
])
|
|
270
|
+
expect(job.port).toStrictEqual(2)
|
|
271
|
+
expect(job.keepAlive).toStrictEqual(false)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('preload settings', () => {
|
|
275
|
+
const job = buildJob({
|
|
276
|
+
cwd: project2
|
|
277
|
+
})
|
|
278
|
+
expect(job.pageTimeout).toStrictEqual(900000)
|
|
279
|
+
expect(job.globalTimeout).toStrictEqual(3600000)
|
|
280
|
+
expect(job.failFast).toStrictEqual(true)
|
|
281
|
+
expect(job.libs).toEqual([{
|
|
282
|
+
relative: 'lib/',
|
|
283
|
+
source: join(project2, 'webapp')
|
|
284
|
+
}])
|
|
285
|
+
expect(job.browserArgs).toEqual(['-1'])
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('allows command line override', () => {
|
|
289
|
+
const job = buildJob({
|
|
290
|
+
cwd: project2,
|
|
291
|
+
globalTimeout: 900000
|
|
292
|
+
})
|
|
293
|
+
expect(job.pageTimeout).toStrictEqual(900000)
|
|
294
|
+
expect(job.globalTimeout).toStrictEqual(900000)
|
|
295
|
+
expect(job.failFast).toStrictEqual(true)
|
|
296
|
+
expect(job.libs).toEqual([{
|
|
297
|
+
relative: 'lib/',
|
|
298
|
+
source: join(project2, 'webapp')
|
|
299
|
+
}])
|
|
300
|
+
expect(job.ui5).toStrictEqual('https://ui5.sap.com')
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('preloads and overrides command line settings', () => {
|
|
304
|
+
const job = buildJob({
|
|
305
|
+
cwd: project2,
|
|
306
|
+
pageTimeout: 60000,
|
|
307
|
+
globalTimeout: 900000,
|
|
308
|
+
libs: 'project2/=../project2'
|
|
309
|
+
})
|
|
310
|
+
expect(job.pageTimeout).toStrictEqual(900000)
|
|
311
|
+
expect(job.globalTimeout).toStrictEqual(900000)
|
|
312
|
+
expect(job.failFast).toStrictEqual(true)
|
|
313
|
+
expect(job.libs).toEqual([{
|
|
314
|
+
relative: 'lib/',
|
|
315
|
+
source: join(project2, 'webapp')
|
|
316
|
+
}, {
|
|
317
|
+
relative: 'project2/',
|
|
318
|
+
source: join(cwd, '../project2')
|
|
319
|
+
}])
|
|
320
|
+
expect(job.ui5).toStrictEqual('https://ui5.sap.com')
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('preloads and concatenates browser settings', () => {
|
|
324
|
+
const job = buildJob({
|
|
325
|
+
cwd: project2,
|
|
326
|
+
'--': [-2]
|
|
327
|
+
})
|
|
328
|
+
expect(job.browserArgs).toEqual(['-1', '-2'])
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
describe('mode', () => {
|
|
333
|
+
it('returns legacy by default', () => {
|
|
334
|
+
expect(fromObject(cwd, {}).mode).toStrictEqual('legacy')
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
describe('url', () => {
|
|
338
|
+
it('enables testing external projects', () => {
|
|
339
|
+
expect(fromObject(cwd, {
|
|
340
|
+
url: ['http://myserver.remote.url/ui5-app.html']
|
|
341
|
+
}).mode).toStrictEqual('url')
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// Assuming url could be used to access 'local' server, most options are supported
|
|
345
|
+
|
|
346
|
+
describe('incompatible options', () => {
|
|
347
|
+
const incompatible = {
|
|
348
|
+
testsuite: '../project2'
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
Object.keys(incompatible).forEach(option => {
|
|
352
|
+
it(`is incompatible with ${option}`, () => {
|
|
353
|
+
expect(() => fromObject(cwd, {
|
|
354
|
+
url: ['http://myserver.remote.url/ui5-app.html'],
|
|
355
|
+
[option]: incompatible[option]
|
|
356
|
+
})).toThrow(UTRError.MODE_INCOMPATIBLE_OPTION(option))
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
describe('capabilities', () => {
|
|
363
|
+
it('triggers the capabilities tester', () => {
|
|
364
|
+
expect(fromObject(cwd, {
|
|
365
|
+
capabilities: true
|
|
366
|
+
}).mode).toStrictEqual('capabilities')
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it('supports cwd, port, logServer, browser, parallel, reportDir, pageTimeout, browserCloseTimeout, failFast and keepAlive', () => {
|
|
370
|
+
expect(fromObject('.', {
|
|
371
|
+
capabilities: true,
|
|
372
|
+
cwd,
|
|
373
|
+
port: 8080,
|
|
374
|
+
logServer: true,
|
|
375
|
+
browser: '$/selenium-webdriver.js',
|
|
376
|
+
parallel: 2,
|
|
377
|
+
reportDir: join(cwd, '.report'),
|
|
378
|
+
pageTimeout: 1000,
|
|
379
|
+
browserCloseTimeout: 1000,
|
|
380
|
+
failFast: true,
|
|
381
|
+
keepAlive: true
|
|
382
|
+
}).mode).toStrictEqual('capabilities')
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
describe('incompatible options', () => {
|
|
386
|
+
const incompatible = {
|
|
387
|
+
libs: '../project2',
|
|
388
|
+
ui5: 'http://localhost:8088/ui5',
|
|
389
|
+
cache: join(cwd, '.cache'),
|
|
390
|
+
webapp: 'webapp',
|
|
391
|
+
testsuite: 'test/testsuite.qunit.html',
|
|
392
|
+
pageFilter: '.*',
|
|
393
|
+
pageParams: 'sap-ui-debug=true',
|
|
394
|
+
coverage: true,
|
|
395
|
+
coverageSettings: '$/nyc.json',
|
|
396
|
+
coverageTempDir: '.nyc_output',
|
|
397
|
+
coverageReportDir: 'coverage',
|
|
398
|
+
coverageReporters: 'lcov',
|
|
399
|
+
globalTimeout: 1000
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
Object.keys(incompatible).forEach(option => {
|
|
403
|
+
it(`is incompatible with ${option}`, () => {
|
|
404
|
+
expect(() => fromObject(cwd, {
|
|
405
|
+
capabilities: true,
|
|
406
|
+
[option]: incompatible[option]
|
|
407
|
+
})).toThrow(UTRError.MODE_INCOMPATIBLE_OPTION(option))
|
|
408
|
+
})
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
})
|
package/src/npm.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { exec } = require('child_process')
|
|
2
|
+
const { join } = require('path')
|
|
3
|
+
const { stat, readFile } = require('fs/promises')
|
|
4
|
+
const { UTRError } = require('./error')
|
|
5
|
+
const { getOutput } = require('./output')
|
|
6
|
+
|
|
7
|
+
function npm (job, ...args) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const childProcess = exec(`npm ${args.join(' ')}`, (err, stdout, stderr) => {
|
|
10
|
+
if (err) {
|
|
11
|
+
reject(UTRError.NPM_FAILED(stderr))
|
|
12
|
+
} else {
|
|
13
|
+
resolve(stdout.trim())
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
if (args[0] === 'install') {
|
|
17
|
+
getOutput(job).monitor(childProcess)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function folderExists (path) {
|
|
23
|
+
try {
|
|
24
|
+
const result = await stat(path)
|
|
25
|
+
return result.isDirectory()
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let localRoot
|
|
32
|
+
let globalRoot
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
async resolvePackage (job, name) {
|
|
36
|
+
if (!localRoot) {
|
|
37
|
+
[localRoot, globalRoot] = await Promise.all([
|
|
38
|
+
npm(job, 'root'),
|
|
39
|
+
npm(job, 'root', '--global')
|
|
40
|
+
])
|
|
41
|
+
}
|
|
42
|
+
let modulePath
|
|
43
|
+
let justInstalled = false
|
|
44
|
+
const localPath = join(localRoot, name)
|
|
45
|
+
if (await folderExists(localPath)) {
|
|
46
|
+
modulePath = localPath
|
|
47
|
+
} else {
|
|
48
|
+
const globalPath = join(globalRoot, name)
|
|
49
|
+
if (!await folderExists(globalPath)) {
|
|
50
|
+
if (!job.npmInstall) {
|
|
51
|
+
throw UTRError.NPM_DEPENDENCY_NOT_FOUND(name)
|
|
52
|
+
}
|
|
53
|
+
const previousStatus = job.status
|
|
54
|
+
job.status = `Installing ${name}...`
|
|
55
|
+
await npm(job, 'install', name, '-g')
|
|
56
|
+
justInstalled = true
|
|
57
|
+
job.status = previousStatus
|
|
58
|
+
}
|
|
59
|
+
modulePath = globalPath
|
|
60
|
+
}
|
|
61
|
+
const output = getOutput(job)
|
|
62
|
+
const installedPackage = JSON.parse((await readFile(join(modulePath, 'package.json'))).toString())
|
|
63
|
+
const { version: installedVersion } = installedPackage
|
|
64
|
+
output.resolvedPackage(name, modulePath, installedVersion)
|
|
65
|
+
if (!justInstalled) {
|
|
66
|
+
const latestVersion = await npm(job, 'view', name, 'version')
|
|
67
|
+
if (latestVersion !== installedVersion) {
|
|
68
|
+
output.packageNotLatest(name, latestVersion)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return modulePath
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/npm.spec.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { join } = require('path')
|
|
2
|
+
const { resolvePackage } = require('./npm')
|
|
3
|
+
const { mock } = require('child_process')
|
|
4
|
+
const { cleanDir, createDir, recreateDir } = require('./tools')
|
|
5
|
+
const { writeFile } = require('fs/promises')
|
|
6
|
+
const { fromObject } = require('./job')
|
|
7
|
+
const { getOutput } = require('./output')
|
|
8
|
+
const { UTRError } = require('./error')
|
|
9
|
+
|
|
10
|
+
const tmp = join(__dirname, '../tmp')
|
|
11
|
+
const npmGlobal = join(tmp, 'npm/global')
|
|
12
|
+
|
|
13
|
+
describe('src/npm', () => {
|
|
14
|
+
const cwd = join(__dirname, '../test/project')
|
|
15
|
+
const reportDir = join(__dirname, '../tmp/npm/report')
|
|
16
|
+
|
|
17
|
+
let job
|
|
18
|
+
let output
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
await createDir(join(npmGlobal, 'existing_global'))
|
|
22
|
+
await writeFile(join(npmGlobal, 'existing_global', 'package.json'), `{
|
|
23
|
+
"version": "1.0.0"
|
|
24
|
+
}`)
|
|
25
|
+
await recreateDir(reportDir)
|
|
26
|
+
await cleanDir(join(npmGlobal, 'not_existing'))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
job = fromObject(cwd, {
|
|
31
|
+
reportDir,
|
|
32
|
+
coverage: false
|
|
33
|
+
})
|
|
34
|
+
job.status = 'Testing'
|
|
35
|
+
output = getOutput(job)
|
|
36
|
+
jest.spyOn(output, 'status')
|
|
37
|
+
jest.spyOn(output, 'resolvedPackage')
|
|
38
|
+
jest.spyOn(output, 'packageNotLatest')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('detects already installed local package', async () => {
|
|
42
|
+
const path = await resolvePackage(job, 'reserve')
|
|
43
|
+
expect(path).toStrictEqual(join(__dirname, '../node_modules/reserve'))
|
|
44
|
+
expect(output.resolvedPackage).toHaveBeenCalledTimes(1)
|
|
45
|
+
expect(output.packageNotLatest).not.toHaveBeenCalled()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('detects already installed global package (but warn as not the latest)', async () => {
|
|
49
|
+
mock({
|
|
50
|
+
api: 'exec',
|
|
51
|
+
scriptPath: 'npm',
|
|
52
|
+
args: ['view', 'existing_global', 'version'],
|
|
53
|
+
exec: async childProcess => childProcess.stdout.write('1.0.1\n'),
|
|
54
|
+
persist: true
|
|
55
|
+
})
|
|
56
|
+
const path = await resolvePackage(job, 'existing_global')
|
|
57
|
+
expect(path).toStrictEqual(join(npmGlobal, 'existing_global'))
|
|
58
|
+
expect(output.resolvedPackage).toHaveBeenCalledTimes(1)
|
|
59
|
+
expect(output.packageNotLatest).toHaveBeenCalled()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('fails if --no-npm-install is set', async () => {
|
|
63
|
+
await expect(resolvePackage({
|
|
64
|
+
...job,
|
|
65
|
+
npmInstall: false
|
|
66
|
+
}, 'not_existing')).rejects.toThrowError(UTRError.NPM_DEPENDENCY_NOT_FOUND('not_existing'))
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('installs missing package globally', async () => {
|
|
70
|
+
mock({
|
|
71
|
+
api: 'exec',
|
|
72
|
+
scriptPath: 'npm',
|
|
73
|
+
args: ['install', 'not_existing', '-g'],
|
|
74
|
+
exec: async childProcess => {
|
|
75
|
+
await createDir(join(npmGlobal, 'not_existing'))
|
|
76
|
+
await writeFile(join(npmGlobal, 'not_existing', 'package.json'), `{
|
|
77
|
+
"version": "1.0.0"
|
|
78
|
+
}`)
|
|
79
|
+
childProcess.stdout.write('OK installed')
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
const path = await resolvePackage(job, 'not_existing')
|
|
83
|
+
expect(path).toStrictEqual(join(npmGlobal, 'not_existing'))
|
|
84
|
+
expect(output.resolvedPackage).toHaveBeenCalledTimes(1)
|
|
85
|
+
expect(output.packageNotLatest).not.toHaveBeenCalled()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('fails if the package cannot be installed', async () => {
|
|
89
|
+
mock({
|
|
90
|
+
api: 'exec',
|
|
91
|
+
scriptPath: 'npm',
|
|
92
|
+
args: ['install', 'fail_to_install', '-g'],
|
|
93
|
+
exec: childProcess => { throw new Error('KO failed') }
|
|
94
|
+
})
|
|
95
|
+
await expect(resolvePackage(job, 'fail_to_install')).rejects.toThrowError(UTRError.NPM_FAILED('Error: KO failed'))
|
|
96
|
+
expect(output.status).toHaveBeenCalledTimes(1) // Won't restore previous status
|
|
97
|
+
})
|
|
98
|
+
})
|
package/src/options.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { InvalidArgumentError } = require('commander')
|
|
4
|
+
|
|
5
|
+
function integer (value) {
|
|
6
|
+
const result = parseInt(value, 10)
|
|
7
|
+
if (isNaN(result)) {
|
|
8
|
+
throw new InvalidArgumentError('Invalid integer')
|
|
9
|
+
}
|
|
10
|
+
if (result < 0) {
|
|
11
|
+
throw new InvalidArgumentError('Only >= 0')
|
|
12
|
+
}
|
|
13
|
+
return result
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
any (value) {
|
|
18
|
+
return value
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
boolean (value, defaultValue) {
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
return !defaultValue
|
|
24
|
+
}
|
|
25
|
+
if (['true', 'yes', 'on'].includes(value)) {
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
if (['false', 'no', 'off'].includes(value)) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
throw new InvalidArgumentError('Invalid boolean')
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
integer,
|
|
35
|
+
|
|
36
|
+
timeout (value) {
|
|
37
|
+
const int = integer(value)
|
|
38
|
+
if (value.endsWith('ms')) {
|
|
39
|
+
return int
|
|
40
|
+
}
|
|
41
|
+
const specifier = value.substring(int.toString().length)
|
|
42
|
+
if (['s', 'sec'].includes(specifier)) {
|
|
43
|
+
return int * 1000
|
|
44
|
+
}
|
|
45
|
+
if (['m', 'min'].includes(specifier)) {
|
|
46
|
+
return int * 60 * 1000
|
|
47
|
+
}
|
|
48
|
+
if (specifier) {
|
|
49
|
+
throw new InvalidArgumentError('Invalid timeout')
|
|
50
|
+
}
|
|
51
|
+
return int
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
url (value) {
|
|
55
|
+
if (!value.match(/^https?:\/\/[^ "]+$/)) {
|
|
56
|
+
throw new InvalidArgumentError('Invalid URL')
|
|
57
|
+
}
|
|
58
|
+
return value
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
arrayOf (typeValidator) {
|
|
62
|
+
return function (value, previousValue) {
|
|
63
|
+
let result
|
|
64
|
+
if (previousValue === undefined) {
|
|
65
|
+
result = []
|
|
66
|
+
} else {
|
|
67
|
+
result = [...previousValue]
|
|
68
|
+
}
|
|
69
|
+
result.push(typeValidator(value))
|
|
70
|
+
return result
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|