@testim/testim-cli 3.267.0 → 3.269.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/agent/routers/cliJsCode/service.js +53 -20
- package/bluebirdConfig.js +5 -4
- package/commons/httpRequest.js +8 -8
- package/commons/runnerFileCache.js +19 -6
- package/commons/socket/baseSocketServiceSocketIO.js +25 -2
- package/commons/socket/testResultService.js +0 -1
- package/commons/testimDesiredCapabilitiesBuilder.js +22 -8
- package/commons/testimServicesApi.js +31 -5
- package/credentialsManager.js +4 -3
- package/npm-shrinkwrap.json +38 -38
- package/package.json +1 -1
- package/player/extensionTestPlayer.js +8 -6
- package/player/seleniumTestPlayer.js +16 -11
- package/player/services/tabService.js +70 -75
- package/player/stepActions/baseCliJsStepAction.js +3 -3
- package/player/stepActions/nodePackageStepAction.js +2 -2
- package/player/utils/windowUtils.js +19 -12
- package/reports/consoleReporter.js +6 -2
- package/runOptions.d.ts +4 -3
- package/runOptions.js +3 -2
- package/runner.js +1 -1
- package/stepPlayers/nodePackageStepPlayback.js +18 -14
- package/testRunHandler.js +232 -234
- package/testimNpmDriver.js +17 -18
- package/utils/promiseUtils.js +1 -0
- package/workers/BaseWorker.js +22 -22
- package/workers/BaseWorker.test.js +1 -6
- package/workers/WorkerAppium.js +74 -19
- package/workers/WorkerExtension.js +11 -5
- package/workers/WorkerExtensionSingleBrowser.js +7 -2
- package/workers/WorkerSelenium.js +23 -23
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const path = require('path');
|
|
@@ -5,7 +7,8 @@ const os = require('os');
|
|
|
5
7
|
const dataUriToBuffer = require('data-uri-to-buffer');
|
|
6
8
|
const { spawn: threadSpawn, config } = require('threads');
|
|
7
9
|
const Promise = require('bluebird');
|
|
8
|
-
const
|
|
10
|
+
const fse = require('fs-extra');
|
|
11
|
+
const fs = require('fs');
|
|
9
12
|
const utils = require('../../../utils');
|
|
10
13
|
const logger = require('../../../commons/logger').getLogger('cli-service');
|
|
11
14
|
const { getS3Artifact } = require('../../../commons/testimServicesApi');
|
|
@@ -13,6 +16,7 @@ const npmWrapper = require('../../../commons/npmWrapper');
|
|
|
13
16
|
const featureFlags = require('../../../commons/featureFlags');
|
|
14
17
|
const { TimeoutError } = require('../../../errors');
|
|
15
18
|
|
|
19
|
+
/** @type {import('worker_threads') | false} */
|
|
16
20
|
let workerThreads;
|
|
17
21
|
|
|
18
22
|
config.set({
|
|
@@ -32,6 +36,15 @@ function convertWindowsBackslash(input) {
|
|
|
32
36
|
return input.replace(/\\/g, '/');
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} transactionId
|
|
41
|
+
* @param {any} incomingParams
|
|
42
|
+
* @param {any} context
|
|
43
|
+
* @param {any} code
|
|
44
|
+
* @param {Record<string, any>} packageLocalLocations
|
|
45
|
+
* @param {number=} timeout
|
|
46
|
+
* @param {string=} fileDataUrl
|
|
47
|
+
*/
|
|
35
48
|
function runCode(transactionId, incomingParams, context, code, packageLocalLocations = {}, timeout = undefined, fileDataUrl = undefined) {
|
|
36
49
|
const requireCode = Object.keys(packageLocalLocations).reduce((all, pMember) => {
|
|
37
50
|
all += `
|
|
@@ -204,7 +217,7 @@ function runCode(transactionId, incomingParams, context, code, packageLocalLocat
|
|
|
204
217
|
|
|
205
218
|
const testimConsoleLogDataAggregates = [];
|
|
206
219
|
const thread = threadSpawn(constructWithArguments(Function, ['input', 'done', 'progress', runFn]));
|
|
207
|
-
return new Promise((resolve) => {
|
|
220
|
+
return utils.promiseTimeout(new Promise((resolve) => {
|
|
208
221
|
thread
|
|
209
222
|
.send({ incomingParams, context, code })
|
|
210
223
|
.on('message', message => {
|
|
@@ -226,7 +239,7 @@ function runCode(transactionId, incomingParams, context, code, packageLocalLocat
|
|
|
226
239
|
tstConsoleLogs: testimConsoleLogDataAggregates,
|
|
227
240
|
status: 'failed',
|
|
228
241
|
result: {
|
|
229
|
-
resultValue: err
|
|
242
|
+
resultValue: err?.toString(),
|
|
230
243
|
exports: {},
|
|
231
244
|
exportsTest: {},
|
|
232
245
|
exportsGlobal: {},
|
|
@@ -237,22 +250,25 @@ function runCode(transactionId, incomingParams, context, code, packageLocalLocat
|
|
|
237
250
|
.on('exit', () => {
|
|
238
251
|
logger.debug('Run code worker has been terminated', { transactionId });
|
|
239
252
|
});
|
|
240
|
-
})
|
|
241
|
-
.catch(
|
|
253
|
+
}), timeout)
|
|
254
|
+
.catch(err => {
|
|
255
|
+
if (!(err instanceof utils.TimeoutError)) {
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
242
258
|
logger.warn('timeout to run code', { transactionId, err });
|
|
243
|
-
return
|
|
259
|
+
return {
|
|
244
260
|
tstConsoleLogs: testimConsoleLogDataAggregates,
|
|
245
261
|
status: 'failed',
|
|
246
262
|
result: {
|
|
247
|
-
resultValue: err
|
|
263
|
+
resultValue: err?.toString(),
|
|
248
264
|
exports: {},
|
|
249
265
|
exportsTest: {},
|
|
250
266
|
exportsGlobal: {},
|
|
251
267
|
},
|
|
252
268
|
success: false,
|
|
253
|
-
}
|
|
269
|
+
};
|
|
254
270
|
})
|
|
255
|
-
.finally(() => thread
|
|
271
|
+
.finally(() => thread?.kill());
|
|
256
272
|
}
|
|
257
273
|
|
|
258
274
|
function requireOrImportMethod(path) {
|
|
@@ -260,7 +276,6 @@ function requireOrImportMethod(path) {
|
|
|
260
276
|
return { sync: true, lib: require(path) };
|
|
261
277
|
} catch (err) {
|
|
262
278
|
if (err.code === 'ERR_REQUIRE_ESM') {
|
|
263
|
-
const fs = require('fs');
|
|
264
279
|
const pathModule = require('path');
|
|
265
280
|
|
|
266
281
|
const lib = fs.promises.readFile(`${path}${pathModule.sep}package.json`).then(file => {
|
|
@@ -275,7 +290,22 @@ function requireOrImportMethod(path) {
|
|
|
275
290
|
}
|
|
276
291
|
}
|
|
277
292
|
|
|
278
|
-
|
|
293
|
+
/**
|
|
294
|
+
* @param {string} transactionId
|
|
295
|
+
* @param {any} incomingParams
|
|
296
|
+
* @param {any} context
|
|
297
|
+
* @param {any} code
|
|
298
|
+
* @param {number=} timeout
|
|
299
|
+
*/
|
|
300
|
+
function runCodeWithWorkerThread(
|
|
301
|
+
transactionId,
|
|
302
|
+
incomingParams,
|
|
303
|
+
context,
|
|
304
|
+
code,
|
|
305
|
+
packageLocalLocations = {},
|
|
306
|
+
timeout = undefined,
|
|
307
|
+
fileDataUrl = undefined,
|
|
308
|
+
) {
|
|
279
309
|
// technically shouldn't happen, but better safe than sorry.
|
|
280
310
|
if (!workerThreads) {
|
|
281
311
|
workerThreads = require('worker_threads');
|
|
@@ -477,7 +507,7 @@ function runCodeWithWorkerThread(transactionId, incomingParams, context, code, p
|
|
|
477
507
|
const thread = new Worker(runFn, {
|
|
478
508
|
eval: true,
|
|
479
509
|
});
|
|
480
|
-
return new Promise((resolve) => {
|
|
510
|
+
return utils.promiseTimeout(new Promise((resolve) => {
|
|
481
511
|
thread
|
|
482
512
|
.on('message', message => {
|
|
483
513
|
if (message.action === 'finish') {
|
|
@@ -500,7 +530,7 @@ function runCodeWithWorkerThread(transactionId, incomingParams, context, code, p
|
|
|
500
530
|
tstConsoleLogs: testimConsoleLogDataAggregates,
|
|
501
531
|
status: 'failed',
|
|
502
532
|
result: {
|
|
503
|
-
resultValue: err
|
|
533
|
+
resultValue: err?.toString(),
|
|
504
534
|
exports: {},
|
|
505
535
|
exportsTest: {},
|
|
506
536
|
exportsGlobal: {},
|
|
@@ -513,26 +543,29 @@ function runCodeWithWorkerThread(transactionId, incomingParams, context, code, p
|
|
|
513
543
|
});
|
|
514
544
|
// context can contain methods and proxies which cannot pass.
|
|
515
545
|
thread.postMessage({ incomingParams, context: JSON.parse(JSON.stringify(context)), code });
|
|
516
|
-
})
|
|
517
|
-
.catch(
|
|
546
|
+
}), timeout)
|
|
547
|
+
.catch(err => {
|
|
548
|
+
if (!(err instanceof utils.TimeoutError)) {
|
|
549
|
+
throw err;
|
|
550
|
+
}
|
|
518
551
|
logger.warn('timeout to run code', { transactionId, err });
|
|
519
|
-
return
|
|
552
|
+
return {
|
|
520
553
|
tstConsoleLogs: testimConsoleLogDataAggregates,
|
|
521
554
|
status: 'failed',
|
|
522
555
|
result: {
|
|
523
|
-
resultValue: err
|
|
556
|
+
resultValue: err?.toString(),
|
|
524
557
|
exports: {},
|
|
525
558
|
exportsTest: {},
|
|
526
559
|
exportsGlobal: {},
|
|
527
560
|
},
|
|
528
561
|
success: false,
|
|
529
|
-
}
|
|
562
|
+
};
|
|
530
563
|
})
|
|
531
|
-
.finally(() => thread
|
|
564
|
+
.finally(() => thread?.terminate());
|
|
532
565
|
}
|
|
533
566
|
|
|
534
567
|
function removeFolder(installFolder) {
|
|
535
|
-
return new Promise(resolve =>
|
|
568
|
+
return new Promise(resolve => fse.remove(installFolder)
|
|
536
569
|
.then(resolve)
|
|
537
570
|
.catch(err => {
|
|
538
571
|
logger.warn('failed to remove install npm packages folder', { err });
|
package/bluebirdConfig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Configure bluebird promises
|
|
@@ -6,18 +6,19 @@
|
|
|
6
6
|
const Promise = require('bluebird');
|
|
7
7
|
const { isDebuggerConnected } = require('./commons/detectDebugger');
|
|
8
8
|
const { OVERRIDE_TIMEOUTS } = require('./commons/config');
|
|
9
|
+
|
|
9
10
|
Promise.config({
|
|
10
11
|
// Disable warnings.
|
|
11
12
|
warnings: false,
|
|
12
13
|
// Enable long stack traces.
|
|
13
14
|
longStackTraces: Boolean(isDebuggerConnected()),
|
|
14
15
|
// Disable cancellation.
|
|
15
|
-
cancellation: false
|
|
16
|
+
cancellation: false,
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
let warnedAboutDebugger = false;
|
|
19
20
|
if (OVERRIDE_TIMEOUTS) {
|
|
20
|
-
|
|
21
|
+
const old = Promise.prototype.timeout;
|
|
21
22
|
const timeoutOverride = Number(OVERRIDE_TIMEOUTS || 6e5);
|
|
22
23
|
if (!OVERRIDE_TIMEOUTS && !warnedAboutDebugger) {
|
|
23
24
|
warnedAboutDebugger = true;
|
|
@@ -31,7 +32,7 @@ if (OVERRIDE_TIMEOUTS) {
|
|
|
31
32
|
if (process.env.IS_BLUEBIRD_NATIVE_PROMISE_SCHEDULER) {
|
|
32
33
|
// If the debugger is connected we skip the trampoline in order to schedule with native promise scheduling
|
|
33
34
|
// which makes the V8 debugger aware of promise scheduling and makes async stack traces work without a lot of unnecessary bluebird-specific frames.
|
|
34
|
-
const NativePromise = (async function () {}
|
|
35
|
+
const NativePromise = (async function () {}()).constructor;
|
|
35
36
|
const ResolvedNativePromise = NativePromise.resolve();
|
|
36
37
|
Promise.setScheduler(fn => ResolvedNativePromise.then(fn));
|
|
37
38
|
}
|
package/commons/httpRequest.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const logger = require('./logger').getLogger('http-request');
|
|
4
4
|
const superagent = require('superagent');
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
5
|
const { makeCounters } = require('./httpRequestCounters');
|
|
6
|
+
const { promiseFromCallback } = require('../utils/promiseUtils');
|
|
7
7
|
|
|
8
8
|
const wrapWithMonitoring = makeCounters();
|
|
9
9
|
|
|
@@ -63,7 +63,7 @@ function deleteFullRes(url, body = {}, headers = {}, timeout = DEFAULT_REQUEST_T
|
|
|
63
63
|
request.proxy(getProxy());
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
return
|
|
66
|
+
return promiseFromCallback((callback) => request.end(callback));
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function post({
|
|
@@ -98,7 +98,7 @@ function postFullRes(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT,
|
|
|
98
98
|
request.retry(retry);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
return
|
|
101
|
+
return promiseFromCallback((callback) => request.end(callback)).catch(e => e, e => {
|
|
102
102
|
e.url = url;
|
|
103
103
|
e.originalRequestTimeout = timeout;
|
|
104
104
|
e.additionalSetHeaders = headers;
|
|
@@ -127,7 +127,7 @@ function postForm(url, fields, files, headers = {}, timeout = DEFAULT_REQUEST_TI
|
|
|
127
127
|
request.proxy(getProxy());
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
return
|
|
130
|
+
return promiseFromCallback((callback) => request.end(callback))
|
|
131
131
|
.then((res) => {
|
|
132
132
|
if (res.type === 'text/plain') {
|
|
133
133
|
return res.text;
|
|
@@ -156,7 +156,7 @@ function _get(url, query, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT, { isB
|
|
|
156
156
|
request.proxy(getProxy());
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
return
|
|
159
|
+
return promiseFromCallback((callback) => request.end(callback));
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
function getText(url, query, headers) {
|
|
@@ -191,7 +191,7 @@ function head(url) {
|
|
|
191
191
|
request.proxy(getProxy());
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
return
|
|
194
|
+
return promiseFromCallback((callback) => request.end(callback))
|
|
195
195
|
.catch(logErrorAndRethrow('failed to head request', { url }));
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -210,7 +210,7 @@ function put(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT) {
|
|
|
210
210
|
request.proxy(getProxy());
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
return
|
|
213
|
+
return promiseFromCallback((callback) => request.end(callback))
|
|
214
214
|
.then((res) => res.body)
|
|
215
215
|
.catch(logErrorAndRethrow('failed to put request', { url }));
|
|
216
216
|
}
|
|
@@ -236,7 +236,7 @@ function download(url) {
|
|
|
236
236
|
request.proxy(getProxy());
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
return
|
|
239
|
+
return promiseFromCallback((callback) => request.end(callback))
|
|
240
240
|
.then(response => {
|
|
241
241
|
logger.info('finished to download', { url });
|
|
242
242
|
return response;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const path = require('path');
|
|
4
6
|
const Promise = require('bluebird');
|
|
5
7
|
const debounce = require('lodash/debounce');
|
|
6
|
-
const fs =
|
|
8
|
+
const fs = require('fs');
|
|
7
9
|
const { getCliLocation } = require('../utils');
|
|
8
10
|
|
|
9
11
|
const logger = require('./logger').getLogger('local cache');
|
|
10
12
|
const crypto = require('crypto');
|
|
13
|
+
const utils = require('../utils');
|
|
11
14
|
|
|
12
15
|
let cacheLocation = path.resolve(getCliLocation(), 'testim-cache');
|
|
13
16
|
|
|
@@ -25,10 +28,19 @@ const THREE_HOURS = 1000 * 60 * 60 * 3;
|
|
|
25
28
|
|
|
26
29
|
const getCacheFileLocation = () => path.resolve(path.resolve(cacheLocation, 'testim.cache'));
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
async function getLocalRunnerCache() {
|
|
32
|
+
try {
|
|
33
|
+
return await utils.promiseTimeout(
|
|
34
|
+
fs.promises.readFile(getCacheFileLocation()).then(async buffer => {
|
|
35
|
+
const key = await _encryptKeyPromise;
|
|
36
|
+
return decrypt(key, buffer);
|
|
37
|
+
}),
|
|
38
|
+
30_000,
|
|
39
|
+
);
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
32
44
|
|
|
33
45
|
let localRunnerCache = getLocalRunnerCache();
|
|
34
46
|
|
|
@@ -59,7 +71,7 @@ const encryptAndSave = debounce(async (object) => {
|
|
|
59
71
|
if (!pathExists) {
|
|
60
72
|
await fs.promises.mkdir(cacheLocation, { recursive: true });
|
|
61
73
|
}
|
|
62
|
-
await fs.
|
|
74
|
+
await fs.promises.writeFile(getCacheFileLocation(), result);
|
|
63
75
|
} catch (err) {
|
|
64
76
|
logger.error('failed saving cache', { err });
|
|
65
77
|
error = err;
|
|
@@ -77,6 +89,7 @@ function decrypt(key, buffer) {
|
|
|
77
89
|
const keyBuffer = Buffer.from(key);
|
|
78
90
|
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.concat([keyBuffer, Buffer.alloc(32)], 32), iv);
|
|
79
91
|
const decrypted = decipher.update(encryptedText);
|
|
92
|
+
// @ts-ignore
|
|
80
93
|
return JSON.parse(Buffer.concat([decrypted, decipher.final()]));
|
|
81
94
|
}
|
|
82
95
|
/**
|
|
@@ -4,6 +4,7 @@ const Promise = require('bluebird');
|
|
|
4
4
|
const pRetry = require('p-retry');
|
|
5
5
|
const io = require('socket.io-client');
|
|
6
6
|
const config = require('../config');
|
|
7
|
+
const utils = require('../../utils');
|
|
7
8
|
|
|
8
9
|
const MAX_SOCKET_RECONNECT_ATTEMPT = 50;
|
|
9
10
|
const MAX_RECONNECT_ATTEMPT_BEFORE_SWITCH = 10;
|
|
@@ -15,7 +16,9 @@ const logger = require('../logger').getLogger('base socket service');
|
|
|
15
16
|
class BaseSocketServiceSocketIO {
|
|
16
17
|
constructor() {
|
|
17
18
|
this.attempts = 0;
|
|
19
|
+
/** @type {{ [testResultId: string]: testId }} */
|
|
18
20
|
this.rooms = {};
|
|
21
|
+
/** @type {undefined | Promise<void>} */
|
|
19
22
|
this.emitPromiseQueue = undefined;
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -28,10 +31,18 @@ class BaseSocketServiceSocketIO {
|
|
|
28
31
|
});
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} resultId
|
|
36
|
+
* @param {string} testId
|
|
37
|
+
*/
|
|
31
38
|
joinRoom(resultId, testId) {
|
|
32
39
|
this.rooms[resultId] = testId;
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} resultId
|
|
44
|
+
* @param {string} testId
|
|
45
|
+
*/
|
|
35
46
|
leaveRoom(resultId) {
|
|
36
47
|
delete this.rooms[resultId];
|
|
37
48
|
}
|
|
@@ -97,6 +108,10 @@ class BaseSocketServiceSocketIO {
|
|
|
97
108
|
});
|
|
98
109
|
}
|
|
99
110
|
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} projectId
|
|
113
|
+
* @param {string} ns
|
|
114
|
+
*/
|
|
100
115
|
initNewSocket(projectId, ns) {
|
|
101
116
|
const opts = {
|
|
102
117
|
query: { projectId },
|
|
@@ -124,6 +139,10 @@ class BaseSocketServiceSocketIO {
|
|
|
124
139
|
});
|
|
125
140
|
}
|
|
126
141
|
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} projectId
|
|
144
|
+
* @param {string} ns
|
|
145
|
+
*/
|
|
127
146
|
init(projectId, ns) {
|
|
128
147
|
const opts = {
|
|
129
148
|
query: { projectId },
|
|
@@ -146,6 +165,10 @@ class BaseSocketServiceSocketIO {
|
|
|
146
165
|
this.addSocketHandlers();
|
|
147
166
|
}
|
|
148
167
|
|
|
168
|
+
/**
|
|
169
|
+
* @param {string} eventName
|
|
170
|
+
* @param {*} eventData
|
|
171
|
+
*/
|
|
149
172
|
emitPromise(eventName, eventData) {
|
|
150
173
|
const errorneousEvents = {};
|
|
151
174
|
|
|
@@ -158,10 +181,10 @@ class BaseSocketServiceSocketIO {
|
|
|
158
181
|
|
|
159
182
|
return reject(new Error('bad ack'));
|
|
160
183
|
});
|
|
161
|
-
})
|
|
184
|
+
});
|
|
162
185
|
|
|
163
186
|
this.emitPromiseQueue = (this.emitPromiseQueue || Promise.resolve())
|
|
164
|
-
.then(() => pRetry(emitAndWait, { retries: 200, minTimeout: 3000 }))
|
|
187
|
+
.then(() => pRetry(() => utils.promiseTimeout(emitAndWait(), EMIT_PROMISE_TIMEOUT), { retries: 200, minTimeout: 3000, factor: 1 }))
|
|
165
188
|
.finally(() => {
|
|
166
189
|
if (Object.keys(errorneousEvents).length > 0) {
|
|
167
190
|
logger.error('Bad acknowledge from socket emit', { errorneousEvents });
|
|
@@ -561,11 +561,11 @@ function buildSeleniumOptions(browserOptions, testName, testRunConfig, gridInfo,
|
|
|
561
561
|
}
|
|
562
562
|
|
|
563
563
|
//testRunConfig not in used for now
|
|
564
|
-
function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp, options }) {
|
|
564
|
+
function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp, options, appPath, androidActivityWait }) {
|
|
565
565
|
const { deviceModel, osVersion, deviceUdid } = options;
|
|
566
566
|
const headspinSelector = {};
|
|
567
567
|
|
|
568
|
-
if (!nativeApp) {
|
|
568
|
+
if (!nativeApp && !appPath) {
|
|
569
569
|
throw Error('missing mobile app!');
|
|
570
570
|
}
|
|
571
571
|
if (gridInfo.type !== gridTypes.TESTIM_HEADSPIN) {
|
|
@@ -576,23 +576,38 @@ function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp, o
|
|
|
576
576
|
hostname: gridInfo.host,
|
|
577
577
|
port: gridInfo.port,
|
|
578
578
|
path: `/v0/${gridInfo.accessToken}/wd/hub`,
|
|
579
|
+
// connectionRetryTimeout: 900000, -- not used for now
|
|
580
|
+
};
|
|
581
|
+
//TODO: check if more caps should be defined as default
|
|
582
|
+
let appCaps = {
|
|
583
|
+
'headspin:capture': true,
|
|
584
|
+
'appium:autoAcceptAlerts': true,
|
|
579
585
|
};
|
|
580
|
-
|
|
581
|
-
let appCaps = {};
|
|
582
586
|
switch (projectType) {
|
|
583
587
|
case 'ios':
|
|
584
588
|
appCaps = {
|
|
585
|
-
|
|
589
|
+
...appCaps,
|
|
586
590
|
platformName: 'iOS',
|
|
587
591
|
'appium:automationName': 'XCUITest',
|
|
592
|
+
...(nativeApp && { 'appium:bundleId': nativeApp.id }),
|
|
593
|
+
...(appPath && {
|
|
594
|
+
'appium:app': appPath,
|
|
595
|
+
}),
|
|
588
596
|
};
|
|
589
597
|
break;
|
|
590
598
|
case 'android':
|
|
591
599
|
appCaps = {
|
|
600
|
+
...appCaps,
|
|
592
601
|
platformName: 'Android',
|
|
593
602
|
'appium:automationName': 'UiAutomator2',
|
|
594
|
-
'appium:
|
|
595
|
-
|
|
603
|
+
'appium:appWaitActivity': androidActivityWait,
|
|
604
|
+
...(nativeApp && {
|
|
605
|
+
'appium:appPackage': nativeApp.id || nativeApp.packageName,
|
|
606
|
+
'appium:appActivity': nativeApp.activity,
|
|
607
|
+
}),
|
|
608
|
+
...(appPath && {
|
|
609
|
+
'appium:app': appPath,
|
|
610
|
+
}),
|
|
596
611
|
};
|
|
597
612
|
break;
|
|
598
613
|
default:
|
|
@@ -609,7 +624,6 @@ function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp, o
|
|
|
609
624
|
delete headspinSelector.model;
|
|
610
625
|
delete headspinSelector.os_version;
|
|
611
626
|
}
|
|
612
|
-
|
|
613
627
|
if (!_.isEmpty(headspinSelector)) {
|
|
614
628
|
appCaps['headspin:selector'] = headspinSelector;
|
|
615
629
|
}
|
|
@@ -237,6 +237,15 @@ function getSuiteTestList({
|
|
|
237
237
|
}), { retries: DEFAULT_REQUEST_RETRY });
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
async function getAppDetails({ appId, projectId }) {
|
|
241
|
+
try {
|
|
242
|
+
return await pRetry(() => getWithAuth(`/mobile-app/app/${appId}?projectId=${projectId}`), { retries: DEFAULT_REQUEST_RETRY, factor: 1 });
|
|
243
|
+
} catch (error) {
|
|
244
|
+
logger.error('failed getting application details', { appId, error });
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
240
249
|
function getUsageForCurrentBillingPeriod(projectId) {
|
|
241
250
|
return pRetry(() => getWithAuth(`/plan/project/${projectId}/usage-current-billing-period`), { retries: DEFAULT_REQUEST_RETRY })
|
|
242
251
|
.catch((error) => {
|
|
@@ -395,10 +404,16 @@ function saveRemoteStep(projectId, resultId, stepId, remoteStep) {
|
|
|
395
404
|
}), { retries: DEFAULT_REQUEST_RETRY });
|
|
396
405
|
}
|
|
397
406
|
|
|
407
|
+
/** @param {string} uri */
|
|
398
408
|
function relativize(uri) {
|
|
399
409
|
return uri.startsWith('/') ? uri : `/${uri}`;
|
|
400
410
|
}
|
|
401
411
|
|
|
412
|
+
/**
|
|
413
|
+
* @param {string} filePath
|
|
414
|
+
* @param {string} bucket
|
|
415
|
+
* @param {string} projectId
|
|
416
|
+
*/
|
|
402
417
|
function getStorageRelativePath(filePath, bucket, projectId) {
|
|
403
418
|
let fullPath = relativize(filePath);
|
|
404
419
|
if (projectId) {
|
|
@@ -411,7 +426,15 @@ function getStorageRelativePath(filePath, bucket, projectId) {
|
|
|
411
426
|
return fullPath;
|
|
412
427
|
}
|
|
413
428
|
|
|
414
|
-
|
|
429
|
+
/**
|
|
430
|
+
* @param {string} projectId
|
|
431
|
+
* @param {string} testId
|
|
432
|
+
* @param {string} testResultId
|
|
433
|
+
* @param {*} content
|
|
434
|
+
* @param {string} subType
|
|
435
|
+
* @param {string} mimeType
|
|
436
|
+
*/
|
|
437
|
+
async function uploadArtifact(projectId, testId, testResultId, content, subType, mimeType = 'application/octet-stream') {
|
|
415
438
|
let fileSuffix = null;
|
|
416
439
|
if (mimeType === 'application/json') {
|
|
417
440
|
fileSuffix = '.json';
|
|
@@ -431,9 +454,11 @@ function uploadArtifact(projectId, testId, testResultId, content, subType, mimeT
|
|
|
431
454
|
},
|
|
432
455
|
};
|
|
433
456
|
|
|
434
|
-
|
|
435
|
-
'X-Asset-Encoding': 'gzip',
|
|
436
|
-
|
|
457
|
+
await pRetry(
|
|
458
|
+
() => postAuthFormData(`/storage${storagePath}`, {}, files, { 'X-Asset-Encoding': 'gzip' }),
|
|
459
|
+
{ retries: DEFAULT_REQUEST_RETRY, factor: 1 }
|
|
460
|
+
);
|
|
461
|
+
return storagePath;
|
|
437
462
|
}
|
|
438
463
|
|
|
439
464
|
const uploadRunDataArtifact = _.memoize(async (projectId, testId, testResultId, runData) => {
|
|
@@ -530,6 +555,7 @@ function updateRemoteRunFailure(body) {
|
|
|
530
555
|
}
|
|
531
556
|
|
|
532
557
|
module.exports = {
|
|
558
|
+
getAppDetails,
|
|
533
559
|
getS3Artifact,
|
|
534
560
|
getTestPlan,
|
|
535
561
|
saveTestPlanResult,
|
|
@@ -553,7 +579,7 @@ module.exports = {
|
|
|
553
579
|
saveRemoteStep,
|
|
554
580
|
getEditorUrl,
|
|
555
581
|
getLabFeaturesByProjectId,
|
|
556
|
-
uploadRunDataArtifact
|
|
582
|
+
uploadRunDataArtifact,
|
|
557
583
|
updateTestDataArtifact: Promise.method(updateTestDataArtifact),
|
|
558
584
|
initializeUserWithAuth,
|
|
559
585
|
addTestRetry,
|
package/credentialsManager.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const Promise = require('bluebird');
|
|
6
6
|
const YAML = require('yaml');
|
|
7
7
|
const os = require('os');
|
|
8
|
+
const utils = require('./utils');
|
|
8
9
|
const { launchChrome } = require('./commons/chrome-launcher');
|
|
9
10
|
|
|
10
11
|
async function getProjectId() {
|
|
@@ -17,7 +18,7 @@ async function getToken() {
|
|
|
17
18
|
|
|
18
19
|
function timeout(promise, ms) {
|
|
19
20
|
// we need this to time out even if we disabled timeouts system wide
|
|
20
|
-
return Promise.race([promise,
|
|
21
|
+
return Promise.race([promise, utils.delay(ms).then(() => { throw new utils.TimeoutError('timeout'); })]);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
async function getCredentialsFromChrome() {
|
|
@@ -73,14 +74,14 @@ async function doLogin({ overwriteExisting = true, projects = null } = {}) {
|
|
|
73
74
|
projects = await timeout(Promise.resolve(getCredentialsFromChrome()), 62000).catch(e => null);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
if (projects
|
|
77
|
+
if (projects?.token) { // V1(legacy) of the login extension API
|
|
77
78
|
credentials.token = projects.token;
|
|
78
79
|
credentials.projectId = projects.projectId;
|
|
79
80
|
spinner.succeed();
|
|
80
81
|
|
|
81
82
|
await writeCredentials(testimCredentialsFile, credentials);
|
|
82
83
|
return;
|
|
83
|
-
} if (projects
|
|
84
|
+
} if (projects?.length) { // V2(current) of the login extension API
|
|
84
85
|
spinner.succeed();
|
|
85
86
|
|
|
86
87
|
const response = projects.length === 1 ?
|