@testim/testim-cli 3.268.0 → 3.270.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/cliAgentMode.js +0 -10
- package/commons/httpRequest.js +8 -8
- package/commons/prepareRunner.js +0 -5
- package/commons/runnerFileCache.js +19 -6
- package/commons/testimDesiredCapabilitiesBuilder.js +13 -5
- package/commons/testimServicesApi.js +2 -2
- package/credentialsManager.js +4 -3
- package/npm-shrinkwrap.json +26 -26
- 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/polyfills/index.js +4 -0
- package/runners/TestPlanRunner.js +1 -1
- package/runners/runnerUtils.js +0 -1
- package/stepPlayers/nodePackageStepPlayback.js +18 -14
- package/testRunHandler.js +17 -0
- package/testimNpmDriver.js +3 -1
- package/utils/promiseUtils.js +1 -0
- package/workers/WorkerAppium.js +14 -4
- package/workers/WorkerSelenium.js +9 -9
|
@@ -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/cliAgentMode.js
CHANGED
|
@@ -90,15 +90,6 @@ async function runAgentMode(options) {
|
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
async function hackForCoralogixAndXhr() {
|
|
94
|
-
Promise.resolve().then(() => {
|
|
95
|
-
// @ts-ignore
|
|
96
|
-
global.xhr2 = require('./commons/xhr2'); // this is inside a `then` to not block and let network requests start
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// this gets picked up later in sessionPlayerInit
|
|
100
|
-
}
|
|
101
|
-
|
|
102
93
|
let startedWithStart = false;
|
|
103
94
|
|
|
104
95
|
function getStartedWithStart() {
|
|
@@ -179,7 +170,6 @@ async function startTestimStandaloneBrowser(options) {
|
|
|
179
170
|
|
|
180
171
|
let shouldDownloadExtension = !(options.ext || options.extensionPath);
|
|
181
172
|
|
|
182
|
-
await hackForCoralogixAndXhr();
|
|
183
173
|
if (shouldDownloadExtension && await fs.pathExists(downloadedExtensionPath)) {
|
|
184
174
|
const stat = await fs.stat(downloadedExtensionPath);
|
|
185
175
|
shouldDownloadExtension = (Date.now() - EXTENSION_CACHE_TIME > stat.mtimeMs);
|
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;
|
package/commons/prepareRunner.js
CHANGED
|
@@ -17,11 +17,6 @@ const PREPARE_MOCK_NETWORK_ERROR_PREFIX = 'JSON file supplied to --mock-network-
|
|
|
17
17
|
|
|
18
18
|
const logger = require('./logger').getLogger('prepare runner');
|
|
19
19
|
|
|
20
|
-
Promise.resolve().then(() => {
|
|
21
|
-
// @ts-ignore
|
|
22
|
-
global.xhr2 = require('./xhr2'); // this is inside a `then` to not block and let network requests start
|
|
23
|
-
});
|
|
24
|
-
|
|
25
20
|
/** @param {import('../runOptions').RunnerOptions} options */
|
|
26
21
|
async function prepare(options) {
|
|
27
22
|
/**
|
|
@@ -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
|
/**
|
|
@@ -561,7 +561,7 @@ 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, appPath }) {
|
|
564
|
+
function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp, options, appPath, androidActivityWait }) {
|
|
565
565
|
const { deviceModel, osVersion, deviceUdid } = options;
|
|
566
566
|
const headspinSelector = {};
|
|
567
567
|
|
|
@@ -576,10 +576,13 @@ 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
|
|
579
580
|
};
|
|
580
|
-
|
|
581
|
+
//TODO: check if more caps should be defined as default
|
|
581
582
|
let appCaps = {
|
|
582
583
|
'headspin:capture': true,
|
|
584
|
+
'appium:autoAcceptAlerts': true,
|
|
585
|
+
'appium:noReset': true,
|
|
583
586
|
};
|
|
584
587
|
switch (projectType) {
|
|
585
588
|
case 'ios':
|
|
@@ -588,7 +591,9 @@ function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp, o
|
|
|
588
591
|
platformName: 'iOS',
|
|
589
592
|
'appium:automationName': 'XCUITest',
|
|
590
593
|
...(nativeApp && { 'appium:bundleId': nativeApp.id }),
|
|
591
|
-
...(appPath && {
|
|
594
|
+
...(appPath && {
|
|
595
|
+
'appium:app': appPath,
|
|
596
|
+
}),
|
|
592
597
|
};
|
|
593
598
|
break;
|
|
594
599
|
case 'android':
|
|
@@ -596,11 +601,14 @@ function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp, o
|
|
|
596
601
|
...appCaps,
|
|
597
602
|
platformName: 'Android',
|
|
598
603
|
'appium:automationName': 'UiAutomator2',
|
|
604
|
+
'appium:appWaitActivity': androidActivityWait,
|
|
599
605
|
...(nativeApp && {
|
|
600
|
-
'appium:appPackage': nativeApp.packageName,
|
|
606
|
+
'appium:appPackage': nativeApp.id || nativeApp.packageName,
|
|
601
607
|
'appium:appActivity': nativeApp.activity,
|
|
602
608
|
}),
|
|
603
|
-
...(appPath && {
|
|
609
|
+
...(appPath && {
|
|
610
|
+
'appium:app': appPath,
|
|
611
|
+
}),
|
|
604
612
|
};
|
|
605
613
|
break;
|
|
606
614
|
default:
|
|
@@ -214,7 +214,7 @@ async function getTestPlanTestList(projectId, names, planIds, branch, intersecti
|
|
|
214
214
|
* testConfigIds?: string[];
|
|
215
215
|
* intersections: import('../runOptions').RunnerOptions['intersections'];
|
|
216
216
|
* }} param0
|
|
217
|
-
* @returns {import('services/src/suite/service').RunListResult}
|
|
217
|
+
* @returns {Promise<import('services/src/suite/service').RunListResult>}
|
|
218
218
|
*/
|
|
219
219
|
function getSuiteTestList({
|
|
220
220
|
projectId, labels, testIds, testNames, testConfigNames, suiteNames, suiteIds, branch, rerunFailedByRunId, testConfigIds, intersections,
|
|
@@ -234,7 +234,7 @@ function getSuiteTestList({
|
|
|
234
234
|
testConfigIds,
|
|
235
235
|
intersections,
|
|
236
236
|
},
|
|
237
|
-
}), { retries: DEFAULT_REQUEST_RETRY });
|
|
237
|
+
}), { retries: DEFAULT_REQUEST_RETRY, factor: 1 });
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
async function getAppDetails({ appId, projectId }) {
|
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 ?
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testim/testim-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.270.0",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@testim/testim-cli",
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.270.0",
|
|
10
10
|
"license": "Proprietary",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@applitools/eyes-sdk-core": "13.11.21",
|
|
@@ -1460,9 +1460,9 @@
|
|
|
1460
1460
|
}
|
|
1461
1461
|
},
|
|
1462
1462
|
"node_modules/@wdio/types/node_modules/@types/node": {
|
|
1463
|
-
"version": "18.11.
|
|
1464
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
1465
|
-
"integrity": "sha512-
|
|
1463
|
+
"version": "18.11.17",
|
|
1464
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
1465
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
1466
1466
|
},
|
|
1467
1467
|
"node_modules/@wdio/utils": {
|
|
1468
1468
|
"version": "7.24.0",
|
|
@@ -4416,9 +4416,9 @@
|
|
|
4416
4416
|
"integrity": "sha512-+iipnm2hvmlWs4MVNx7HwSTxhDxsXnQyK5F1OalZVXeUhdPgP/23T42NCyg0TK3wL/Yg92SVrSuGKqdg12o54w=="
|
|
4417
4417
|
},
|
|
4418
4418
|
"node_modules/devtools/node_modules/@types/node": {
|
|
4419
|
-
"version": "18.11.
|
|
4420
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
4421
|
-
"integrity": "sha512-
|
|
4419
|
+
"version": "18.11.17",
|
|
4420
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
4421
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
4422
4422
|
},
|
|
4423
4423
|
"node_modules/devtools/node_modules/ua-parser-js": {
|
|
4424
4424
|
"version": "1.0.32",
|
|
@@ -15468,9 +15468,9 @@
|
|
|
15468
15468
|
}
|
|
15469
15469
|
},
|
|
15470
15470
|
"node_modules/webdriver/node_modules/@types/node": {
|
|
15471
|
-
"version": "18.11.
|
|
15472
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
15473
|
-
"integrity": "sha512-
|
|
15471
|
+
"version": "18.11.17",
|
|
15472
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
15473
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
15474
15474
|
},
|
|
15475
15475
|
"node_modules/webdriverio": {
|
|
15476
15476
|
"version": "7.24.0",
|
|
@@ -15510,9 +15510,9 @@
|
|
|
15510
15510
|
}
|
|
15511
15511
|
},
|
|
15512
15512
|
"node_modules/webdriverio/node_modules/@types/node": {
|
|
15513
|
-
"version": "18.11.
|
|
15514
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
15515
|
-
"integrity": "sha512-
|
|
15513
|
+
"version": "18.11.17",
|
|
15514
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
15515
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
15516
15516
|
},
|
|
15517
15517
|
"node_modules/webdriverio/node_modules/brace-expansion": {
|
|
15518
15518
|
"version": "2.0.1",
|
|
@@ -17129,9 +17129,9 @@
|
|
|
17129
17129
|
},
|
|
17130
17130
|
"dependencies": {
|
|
17131
17131
|
"@types/node": {
|
|
17132
|
-
"version": "18.11.
|
|
17133
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
17134
|
-
"integrity": "sha512-
|
|
17132
|
+
"version": "18.11.17",
|
|
17133
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
17134
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
17135
17135
|
}
|
|
17136
17136
|
}
|
|
17137
17137
|
},
|
|
@@ -19496,9 +19496,9 @@
|
|
|
19496
19496
|
},
|
|
19497
19497
|
"dependencies": {
|
|
19498
19498
|
"@types/node": {
|
|
19499
|
-
"version": "18.11.
|
|
19500
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
19501
|
-
"integrity": "sha512-
|
|
19499
|
+
"version": "18.11.17",
|
|
19500
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
19501
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
19502
19502
|
},
|
|
19503
19503
|
"ua-parser-js": {
|
|
19504
19504
|
"version": "1.0.32",
|
|
@@ -27994,9 +27994,9 @@
|
|
|
27994
27994
|
},
|
|
27995
27995
|
"dependencies": {
|
|
27996
27996
|
"@types/node": {
|
|
27997
|
-
"version": "18.11.
|
|
27998
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
27999
|
-
"integrity": "sha512-
|
|
27997
|
+
"version": "18.11.17",
|
|
27998
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
27999
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
28000
28000
|
}
|
|
28001
28001
|
}
|
|
28002
28002
|
},
|
|
@@ -28035,9 +28035,9 @@
|
|
|
28035
28035
|
},
|
|
28036
28036
|
"dependencies": {
|
|
28037
28037
|
"@types/node": {
|
|
28038
|
-
"version": "18.11.
|
|
28039
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.
|
|
28040
|
-
"integrity": "sha512-
|
|
28038
|
+
"version": "18.11.17",
|
|
28039
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz",
|
|
28040
|
+
"integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng=="
|
|
28041
28041
|
},
|
|
28042
28042
|
"brace-expansion": {
|
|
28043
28043
|
"version": "2.0.1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const utils = require('../utils');
|
|
4
4
|
const WebDriver = require('./webdriver');
|
|
5
5
|
|
|
6
6
|
class ExtensionTestPlayer {
|
|
@@ -11,10 +11,12 @@ class ExtensionTestPlayer {
|
|
|
11
11
|
|
|
12
12
|
onDone() {
|
|
13
13
|
const END_DRIVER_TIMEOUT = 1000 * 60 * 2;
|
|
14
|
-
return this.driver.end()
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
return utils.promiseTimeout(this.driver.end(), END_DRIVER_TIMEOUT)
|
|
15
|
+
.catch((error) => {
|
|
16
|
+
if (error instanceof utils.TimeoutError) {
|
|
17
|
+
return this.driver.forceEnd();
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
18
20
|
})
|
|
19
21
|
.catch(() => {})
|
|
20
22
|
.then(() => {
|
|
@@ -22,6 +22,7 @@ const WebDriver = require('./webdriver');
|
|
|
22
22
|
// delete after https://github.com/testimio/clickim/pull/3430 release to the store
|
|
23
23
|
const CryptoJS = require('crypto-js');
|
|
24
24
|
const StepActionUtils = require('./utils/stepActionUtils');
|
|
25
|
+
const utils = require('../utils');
|
|
25
26
|
|
|
26
27
|
class SeleniumTestPlayer {
|
|
27
28
|
//eslint-disable-next-line default-param-last
|
|
@@ -83,7 +84,7 @@ class SeleniumTestPlayer {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
onStepCompleted(result, testId, resultId, step) {
|
|
86
|
-
if (step
|
|
87
|
+
if (step?.isTabOpener) {
|
|
87
88
|
this.tabService.addNewPopup(this.id, step.id)
|
|
88
89
|
.catch(() => { });
|
|
89
90
|
}
|
|
@@ -91,10 +92,13 @@ class SeleniumTestPlayer {
|
|
|
91
92
|
|
|
92
93
|
onDone() {
|
|
93
94
|
const END_DRIVER_TIMEOUT = 1000 * 60 * 2;
|
|
94
|
-
return this.driver.end()
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
return utils.promiseTimeout(this.driver.end(), END_DRIVER_TIMEOUT)
|
|
96
|
+
.catch(error => {
|
|
97
|
+
if (error instanceof utils.TimeoutError) {
|
|
98
|
+
return this.driver.forceEnd().catch(() => null);
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
})
|
|
98
102
|
.then(() => {
|
|
99
103
|
this.sessionPlayer.playbackManager.off(commonConstants.playback.RESULT, this.onStepCompleted);
|
|
100
104
|
this.sessionPlayer = null;
|
|
@@ -108,10 +112,11 @@ class SeleniumTestPlayer {
|
|
|
108
112
|
this.tabService.clearAllTabs(this.id);
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
addTab(openerStepId, options = { loadInfo: true }) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
async addTab(openerStepId, options = { loadInfo: true }) {
|
|
116
|
+
const ids = await this.driver.getTabIds();
|
|
117
|
+
const lastId = ids.at(-1);
|
|
118
|
+
await this.tabService.addNewTab(this.id, lastId, openerStepId, options);
|
|
119
|
+
return this.sessionPlayer.addPlaybackFrameHandler(lastId, undefined, { emptyPage: true });
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
async addAllTabs(openerStepId, options = { loadInfo: true, checkForMainTab: true, takeScreenshots: true }, blackList = []) {
|
|
@@ -132,8 +137,8 @@ class SeleniumTestPlayer {
|
|
|
132
137
|
// if we only have one tab because we removed the editor tab - we have to switchTab to one of the other tabs, otherwise
|
|
133
138
|
// tabService will assume it's on a good context but it's not.
|
|
134
139
|
const tabInfo = this.tabService.getMainTabInfo(this.id);
|
|
135
|
-
const
|
|
136
|
-
await this.tabService.switchTab(
|
|
140
|
+
const tabUtils = this.tabService.getTabUtils(this.id, tabInfo);
|
|
141
|
+
await this.tabService.switchTab(tabUtils.tabId, this.id, { forceSwitch: true });
|
|
137
142
|
}
|
|
138
143
|
// deal with checkForMainTab failing due to the page refreshing or JavaScript not responding or a similar issue
|
|
139
144
|
this.tabService.fixMissingMainTab(this.id);
|
|
@@ -12,6 +12,7 @@ const guid = require('../../utils').guid;
|
|
|
12
12
|
|
|
13
13
|
const UrlUtils = sessionPlayer.urlUtils;
|
|
14
14
|
const semver = require('semver');
|
|
15
|
+
const utils = require('../../utils');
|
|
15
16
|
|
|
16
17
|
const constants = sessionPlayer.commonConstants.stepResult;
|
|
17
18
|
const tabMatcher = sessionPlayer.tabMatcher;
|
|
@@ -156,55 +157,49 @@ class TabService {
|
|
|
156
157
|
Object.values(this.getAllTabInfos(sessionId))[0].isMain = true;
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
buildTabInfo(sessionId, tabId, order, openerStepId, options = {}) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return tab.isMainTab;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (!tab.isMainTab || tab.isMainTab === 'unknown') {
|
|
170
|
-
const missingMainTab = !tabService.getMainTabInfo(sessionId);
|
|
171
|
-
return missingMainTab;
|
|
172
|
-
}
|
|
173
|
-
return tab.isMainTab;
|
|
174
|
-
}
|
|
175
|
-
this.sessionTabs[sessionId].tabInfos[tabId] = {
|
|
176
|
-
infoId,
|
|
177
|
-
url: tab.url,
|
|
178
|
-
title: tab.title,
|
|
179
|
-
favIconUrl: tab.favIconUrl,
|
|
180
|
-
order,
|
|
181
|
-
from: this.getTabInfo(sessionId, tab.openerTabId),
|
|
182
|
-
isMain: isMainTab(this),
|
|
183
|
-
openerStepId,
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
return infoId;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
160
|
+
async buildTabInfo(sessionId, tabId, order, openerStepId, options = {}) {
|
|
161
|
+
const tab = await this.getTabDetails(tabId, sessionId, options);
|
|
162
|
+
const infoId = guid();
|
|
163
|
+
function isMainTab(tabService) {
|
|
164
|
+
if (options.checkForMainTab) {
|
|
165
|
+
return tab.isMainTab;
|
|
166
|
+
}
|
|
189
167
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
168
|
+
if (!tab.isMainTab || tab.isMainTab === 'unknown') {
|
|
169
|
+
const missingMainTab = !tabService.getMainTabInfo(sessionId);
|
|
170
|
+
return missingMainTab;
|
|
171
|
+
}
|
|
172
|
+
return tab.isMainTab;
|
|
173
|
+
}
|
|
174
|
+
this.sessionTabs[sessionId].tabInfos[tabId] = {
|
|
175
|
+
infoId,
|
|
176
|
+
url: tab.url,
|
|
177
|
+
title: tab.title,
|
|
178
|
+
favIconUrl: tab.favIconUrl,
|
|
179
|
+
order,
|
|
180
|
+
from: this.getTabInfo(sessionId, tab.openerTabId),
|
|
181
|
+
isMain: isMainTab(this),
|
|
182
|
+
openerStepId,
|
|
183
|
+
};
|
|
184
|
+
return infoId;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async addTab(sessionId, id, order, openerStepId, options = {}) {
|
|
188
|
+
const infoId = await this.buildTabInfo(sessionId, id, order, openerStepId, options);
|
|
189
|
+
const _windowUtils = new WindowUtils(id, this.driver);
|
|
190
|
+
this._utils[infoId] = {
|
|
191
|
+
attachDebugger: () => Promise.resolve(),
|
|
192
|
+
detachDebugger: () => Promise.resolve(),
|
|
193
|
+
onDebuggerDetached: () => {},
|
|
194
|
+
tabId: id,
|
|
195
|
+
domUtils: { getDOM: () => Promise.resolve() },
|
|
196
|
+
windowUtils: _windowUtils,
|
|
197
|
+
imageCaptureUtils: new ImageCaptureUtils(
|
|
198
|
+
id,
|
|
199
|
+
_windowUtils,
|
|
200
|
+
new ScreenshotUtils(id, this.driver, { takeScreenshots: options.takeScreenshots })
|
|
201
|
+
),
|
|
202
|
+
};
|
|
208
203
|
}
|
|
209
204
|
|
|
210
205
|
getTabUtilsByTabIdAndSessionId(sessionId, tabId) {
|
|
@@ -295,20 +290,20 @@ class TabService {
|
|
|
295
290
|
}
|
|
296
291
|
|
|
297
292
|
const infos = this.getAllTabInfos(sessionId);
|
|
298
|
-
const
|
|
293
|
+
const allTopFrameUrls = Object.keys(infos)
|
|
299
294
|
.map(tabId => infos[tabId].url);
|
|
300
295
|
|
|
301
|
-
if (this.exactUrlMatch(first, second,
|
|
296
|
+
if (this.exactUrlMatch(first, second, allTopFrameUrls)) {
|
|
302
297
|
return true;
|
|
303
298
|
}
|
|
304
299
|
|
|
305
300
|
const combineDomainAndPath = urlParts => (`${urlParts.domain}/${urlParts.path.join('/')}`);
|
|
306
|
-
if (this.singleExactMatchForParts(first, second,
|
|
301
|
+
if (this.singleExactMatchForParts(first, second, allTopFrameUrls, combineDomainAndPath)) {
|
|
307
302
|
return true;
|
|
308
303
|
}
|
|
309
304
|
|
|
310
305
|
const combineDomainPathAndHash = urlParts => (`${urlParts.domain}/${urlParts.path.join('/')}#${urlParts.hash}`);
|
|
311
|
-
if (this.singleExactMatchForParts(first, second,
|
|
306
|
+
if (this.singleExactMatchForParts(first, second, allTopFrameUrls, combineDomainPathAndHash)) {
|
|
312
307
|
return true;
|
|
313
308
|
}
|
|
314
309
|
|
|
@@ -409,32 +404,32 @@ class TabService {
|
|
|
409
404
|
});
|
|
410
405
|
}
|
|
411
406
|
|
|
412
|
-
getUnregisteredTabId(sessionId) {
|
|
413
|
-
|
|
414
|
-
|
|
407
|
+
async getUnregisteredTabId(sessionId) {
|
|
408
|
+
const ids = await this.driver.getTabIds();
|
|
409
|
+
return ids.find(tabId => !this.getAllTabIds(sessionId).includes(tabId));
|
|
415
410
|
}
|
|
416
411
|
|
|
417
|
-
waitForTabToOpen(sessionId) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
412
|
+
async waitForTabToOpen(sessionId) {
|
|
413
|
+
const newId = await this.getUnregisteredTabId(sessionId);
|
|
414
|
+
if (newId) {
|
|
415
|
+
return newId;
|
|
416
|
+
}
|
|
417
|
+
await utils.delay(500);
|
|
418
|
+
return await this.waitForTabToOpen(sessionId);
|
|
422
419
|
}
|
|
423
420
|
|
|
424
|
-
tryToAddTab(sessionId, openerStepId) {
|
|
421
|
+
async tryToAddTab(sessionId, openerStepId) {
|
|
425
422
|
if (this.pendingTabs[sessionId]) {
|
|
426
423
|
// don't mess with the main flow
|
|
427
|
-
return
|
|
424
|
+
return;
|
|
428
425
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
.then(() => (this.sessionTabs[sessionId].currentTab = null));
|
|
437
|
-
});
|
|
426
|
+
const newId = await this.getUnregisteredTabId(sessionId);
|
|
427
|
+
if (!newId) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
await this.addNewTab(sessionId, newId);
|
|
431
|
+
await this.addFrameHandler(newId);
|
|
432
|
+
this.sessionTabs[sessionId].currentTab = null;
|
|
438
433
|
}
|
|
439
434
|
|
|
440
435
|
addNewPopup(id, openerStepId) {
|
|
@@ -507,7 +502,7 @@ class TabService {
|
|
|
507
502
|
errorType: constants.INVALID_TEST_VERSION,
|
|
508
503
|
});
|
|
509
504
|
}
|
|
510
|
-
const openerStepId =
|
|
505
|
+
const openerStepId = step.tabInfo?.openerStepId;
|
|
511
506
|
return this.waitToPendingTabs(sessionId, openerStepId)
|
|
512
507
|
.then(() => {
|
|
513
508
|
let tabId;
|
|
@@ -529,10 +524,10 @@ class TabService {
|
|
|
529
524
|
|
|
530
525
|
if (!tabId) {
|
|
531
526
|
return this.tryToAddTab(sessionId, openerStepId)
|
|
532
|
-
.then(() =>
|
|
527
|
+
.then(() => { throw new Error('No tab ID found'); });
|
|
533
528
|
}
|
|
534
529
|
if (this.sessionTabs[sessionId].currentTab === tabId) {
|
|
535
|
-
return
|
|
530
|
+
return tabId;
|
|
536
531
|
}
|
|
537
532
|
return this.switchTab(tabId, sessionId)
|
|
538
533
|
.then(() => {
|
|
@@ -541,7 +536,7 @@ class TabService {
|
|
|
541
536
|
})
|
|
542
537
|
.catch(err => {
|
|
543
538
|
const windowClosedErrors = ['no such window', 'no window found', 'the window could not be found'];
|
|
544
|
-
if (err.message && windowClosedErrors.
|
|
539
|
+
if (err.message && windowClosedErrors.some(errorString => err.message.toLowerCase().includes(errorString))) {
|
|
545
540
|
this.sessionTabs[sessionId].tabCount--;
|
|
546
541
|
this.sessionTabs[sessionId].tabInfos[tabId].isClosed = true;
|
|
547
542
|
return this.getTabIdByTabInfo(sessionId, step);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const _ = require('lodash');
|
|
3
4
|
const BaseJsStepAction = require('./baseJsStepAction');
|
|
4
|
-
const Bluebird = require('bluebird');
|
|
5
5
|
const service = require('../../agent/routers/cliJsCode/service');
|
|
6
6
|
const sessionPlayer = require('../../commons/getSessionPlayerRequire');
|
|
7
|
-
const
|
|
7
|
+
const utils = require('../../utils');
|
|
8
8
|
|
|
9
9
|
const constants = sessionPlayer.commonConstants.stepResult;
|
|
10
10
|
|
|
@@ -36,7 +36,7 @@ class BaseCliJsStepAction extends BaseJsStepAction {
|
|
|
36
36
|
try {
|
|
37
37
|
return await this.executeCliCode();
|
|
38
38
|
} catch (err) {
|
|
39
|
-
if (err instanceof
|
|
39
|
+
if (err instanceof utils.TimeoutError) {
|
|
40
40
|
return {
|
|
41
41
|
success: false,
|
|
42
42
|
errorType: constants.ACTION_TIMEOUT,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const StepAction = require('./stepAction');
|
|
4
|
-
const Bluebird = require('bluebird');
|
|
5
4
|
const { NpmPackageError } = require('../../errors');
|
|
6
5
|
|
|
7
6
|
const service = require('../../agent/routers/cliJsCode/service');
|
|
7
|
+
const utils = require('../../utils');
|
|
8
8
|
|
|
9
9
|
class NodePackageStepAction extends StepAction {
|
|
10
10
|
async performAction() {
|
|
@@ -29,7 +29,7 @@ class NodePackageStepAction extends StepAction {
|
|
|
29
29
|
message: err.message,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
if (err instanceof
|
|
32
|
+
if (err instanceof utils.TimeoutError) {
|
|
33
33
|
return {
|
|
34
34
|
success: false,
|
|
35
35
|
code: 'timeout',
|
|
@@ -6,6 +6,7 @@ const Promise = require('bluebird');
|
|
|
6
6
|
const pRetry = require('p-retry');
|
|
7
7
|
const { delay } = require('../../utils');
|
|
8
8
|
const { PageNotAvailableError } = require('../../errors');
|
|
9
|
+
const utils = require('../../utils');
|
|
9
10
|
const logger = require('../../commons/logger').getLogger('window-utils');
|
|
10
11
|
|
|
11
12
|
class WindowUtils {
|
|
@@ -18,6 +19,7 @@ class WindowUtils {
|
|
|
18
19
|
this.driver = driver;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
// Seems unused and not working since bad args to executeJS
|
|
21
23
|
getElementFromPoint(x, y) {
|
|
22
24
|
/* eslint-disable */
|
|
23
25
|
function elementFromPoint(x, y) {
|
|
@@ -47,7 +49,7 @@ class WindowUtils {
|
|
|
47
49
|
|
|
48
50
|
scrollToPositionWithoutAnimation(pos) {
|
|
49
51
|
/* eslint-disable */
|
|
50
|
-
// if scroll
|
|
52
|
+
// if scroll behavior is not supported, then the scrolling is not animated anyway
|
|
51
53
|
function scrollWithoutAnimation(position) {
|
|
52
54
|
var scrollBehaviorSupported = 'scrollBehavior' in document.documentElement.style;
|
|
53
55
|
if (scrollBehaviorSupported) {
|
|
@@ -126,15 +128,13 @@ class WindowUtils {
|
|
|
126
128
|
return Promise.resolve();
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
checkSize(size) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return { actualSize, expectedSize: size };
|
|
137
|
-
});
|
|
131
|
+
async checkSize(size) {
|
|
132
|
+
await utils.delay(1000);
|
|
133
|
+
const actualSize = await this.getViewportSize();
|
|
134
|
+
if (actualSize.width !== size.width || actualSize.height !== size.height) {
|
|
135
|
+
return Promise.reject({ actualSize, expectedSize: size });
|
|
136
|
+
}
|
|
137
|
+
return { actualSize, expectedSize: size };
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
async setViewportSize(size) {
|
|
@@ -155,12 +155,19 @@ class WindowUtils {
|
|
|
155
155
|
return false;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
return
|
|
158
|
+
return (
|
|
159
|
+
locationObj.href !== 'chrome-error://chromewebdata/' &&
|
|
160
|
+
locationObj.href !== 'safari-resource:/ErrorPage.html' &&
|
|
161
|
+
locationObj.href.indexOf('res://ieframe.dll/http_404.htm') !== 0 &&
|
|
162
|
+
locationObj.href.indexOf('ms-appx-web://microsoft.microsoftedge/assets/errorpages/') !== 0
|
|
163
|
+
);
|
|
159
164
|
}
|
|
160
165
|
/* eslint-enable */
|
|
161
166
|
|
|
162
167
|
const result = await this.driver.executeJS(pageIsAvailable);
|
|
163
|
-
|
|
168
|
+
if (!result.value) {
|
|
169
|
+
throw new PageNotAvailableError('validatePageIsAvailable:PageNotAvailableError');
|
|
170
|
+
}
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
focusTab() {
|
package/polyfills/index.js
CHANGED
|
@@ -358,7 +358,7 @@ class TestPlanRunner {
|
|
|
358
358
|
const suiteResult = await getSuite(options, branchToUse);
|
|
359
359
|
perf.log('after getSuite');
|
|
360
360
|
|
|
361
|
-
if (!suiteResult
|
|
361
|
+
if (!suiteResult?.tests[0]?.length) {
|
|
362
362
|
if (options.rerunFailedByRunId) {
|
|
363
363
|
throw new ArgError('No failed tests found in the provided run');
|
|
364
364
|
}
|
package/runners/runnerUtils.js
CHANGED
|
@@ -10,7 +10,6 @@ const { ArgError } = require('../errors');
|
|
|
10
10
|
/**
|
|
11
11
|
* @param {import('../runOptions').RunnerOptions} options
|
|
12
12
|
* @param {string} branchToUse
|
|
13
|
-
* @returns
|
|
14
13
|
*/
|
|
15
14
|
async function getSuite(options, branchToUse) {
|
|
16
15
|
if (options.lightweightMode?.onlyTestIdsNoSuite && options.testId) {
|
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
const service = require('../agent/routers/cliJsCode/service');
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const { NpmPackageError } = require('../errors');
|
|
5
|
+
const utils = require('../utils');
|
|
6
6
|
|
|
7
|
-
module.exports.run = (browser, step) => {
|
|
8
|
-
const {stepId, testResultId, retryIndex, stepResultId, packageData, timeout} = step.data;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
module.exports.run = async (browser, step) => {
|
|
8
|
+
const { stepId, testResultId, retryIndex, stepResultId, packageData, timeout } = step.data;
|
|
9
|
+
try {
|
|
10
|
+
const data = await service.installPackage(stepId, testResultId, retryIndex, packageData, stepResultId, timeout);
|
|
11
|
+
return { data, success: true };
|
|
12
|
+
} catch (err) {
|
|
13
|
+
if (err instanceof NpmPackageError) {
|
|
12
14
|
return {
|
|
13
15
|
success: false,
|
|
14
|
-
code:
|
|
15
|
-
message: err.message
|
|
16
|
+
code: 'invalid-node-package',
|
|
17
|
+
message: err.message,
|
|
16
18
|
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
+
}
|
|
20
|
+
if (err instanceof utils.TimeoutError) {
|
|
19
21
|
return {
|
|
20
22
|
success: false,
|
|
21
|
-
code:
|
|
23
|
+
code: 'timeout',
|
|
22
24
|
};
|
|
23
|
-
}
|
|
25
|
+
}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
24
28
|
};
|
package/testRunHandler.js
CHANGED
|
@@ -98,6 +98,20 @@ class TestRun {
|
|
|
98
98
|
return this._executionName;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
get androidActivityWait() {
|
|
102
|
+
const activity = 'appMetadata' in this._nativeApp ? this._nativeApp.appMetadata.activity : this._nativeApp.activity;
|
|
103
|
+
const lastValue = activity.split('.').pop();
|
|
104
|
+
return activity.replace(lastValue, '*');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get nativeAppLink() {
|
|
108
|
+
let link = null;
|
|
109
|
+
if ('filePath' in this._nativeApp) {
|
|
110
|
+
link = `${config.SERVICES_HOST}/storage${this._nativeApp.filePath}?access_token=${this._options.authData.token}`;
|
|
111
|
+
}
|
|
112
|
+
return link;
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
async getNativeAppData() {
|
|
102
116
|
const { appId, baseUrl } = this._options;
|
|
103
117
|
if (baseUrl && !this._nativeApp && !appId) {
|
|
@@ -112,6 +126,9 @@ class TestRun {
|
|
|
112
126
|
};
|
|
113
127
|
}
|
|
114
128
|
if (this._nativeApp && !appId) {
|
|
129
|
+
if ('appMetadata' in this._nativeApp) {
|
|
130
|
+
return this._nativeApp.appMetadata;
|
|
131
|
+
}
|
|
115
132
|
return this._nativeApp;
|
|
116
133
|
}
|
|
117
134
|
return null;
|
package/testimNpmDriver.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
/* eslint-disable no-console */
|
|
2
4
|
const semver = require('semver');
|
|
3
5
|
const config = require('./commons/config');
|
|
@@ -32,7 +34,7 @@ async function checkNpmVersion() {
|
|
|
32
34
|
return;
|
|
33
35
|
}
|
|
34
36
|
try {
|
|
35
|
-
const latestVersion = await utils.promiseTimeout(getNpmVersion(
|
|
37
|
+
const latestVersion = await utils.promiseTimeout(getNpmVersion(), 5000, 'The API call to NPM timed out');
|
|
36
38
|
const packVersion = getPackageVersion();
|
|
37
39
|
if (packVersion && semver.lt(packVersion, latestVersion)) {
|
|
38
40
|
console.log(chalk.yellow(
|
package/utils/promiseUtils.js
CHANGED
package/workers/WorkerAppium.js
CHANGED
|
@@ -31,7 +31,10 @@ class WorkerAppium extends BaseWorker {
|
|
|
31
31
|
async getBrowserOnce(testRunHandler, customExtensionLocalLocation, appiumTestPlayer, gridInfo) {
|
|
32
32
|
reporter.onGetSession(this.id, this.testName, testRunHandler.runMode);
|
|
33
33
|
const { driver } = appiumTestPlayer;
|
|
34
|
-
|
|
34
|
+
const projectType = this.options.projectData.type;
|
|
35
|
+
const nativeApp = await testRunHandler.getNativeAppData();
|
|
36
|
+
const androidActivityWait = projectType === 'android' ? testRunHandler.androidActivityWait : null;
|
|
37
|
+
let appPath = testRunHandler.nativeAppLink;
|
|
35
38
|
if (this.options.appId) {
|
|
36
39
|
const { project: projectId, appId } = this.options;
|
|
37
40
|
const mobileApp = await testimServicesApi.getAppDetails({ appId, projectId });
|
|
@@ -41,10 +44,17 @@ class WorkerAppium extends BaseWorker {
|
|
|
41
44
|
}
|
|
42
45
|
appPath = `${config.SERVICES_HOST}/storage${mobileApp.filePath}?access_token=${this.options.authData.token}`;
|
|
43
46
|
}
|
|
44
|
-
const nativeApp = await testRunHandler.getNativeAppData();
|
|
45
|
-
const projectType = this.options.projectData.type;
|
|
46
47
|
try {
|
|
47
|
-
const capabilities = desiredCapabilitiesBuilder.buildAppiumOptions({
|
|
48
|
+
const capabilities = desiredCapabilitiesBuilder.buildAppiumOptions({
|
|
49
|
+
projectType,
|
|
50
|
+
gridInfo,
|
|
51
|
+
testRunConfig: this.testRunConfig,
|
|
52
|
+
nativeApp,
|
|
53
|
+
options: this.options,
|
|
54
|
+
appPath,
|
|
55
|
+
androidActivityWait,
|
|
56
|
+
});
|
|
57
|
+
|
|
48
58
|
const activeSession = await driver.remote(capabilities);
|
|
49
59
|
driver.activeSession = activeSession;
|
|
50
60
|
await this.updateDeviceInfo(testRunHandler, activeSession);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Promise = require('bluebird');
|
|
4
|
-
|
|
5
|
-
const { timeoutMessages } = require('../commons/constants');
|
|
6
|
-
const { PageNotAvailableError } = require('../errors');
|
|
7
4
|
const BaseWorker = require('./BaseWorker');
|
|
8
5
|
const logger = require('../commons/logger').getLogger('worker-selenium');
|
|
9
6
|
const reporter = require('../reports/reporter');
|
|
@@ -11,6 +8,9 @@ const SeleniumTestPlayer = require('../player/seleniumTestPlayer');
|
|
|
11
8
|
const WindowUtils = require('../player/utils/windowUtils');
|
|
12
9
|
const sessionPlayerInit = require('../commons/getSessionPlayerRequire');
|
|
13
10
|
const perf = require('../commons/performance-logger');
|
|
11
|
+
const utils = require('../utils');
|
|
12
|
+
const { timeoutMessages } = require('../commons/constants');
|
|
13
|
+
const { PageNotAvailableError } = require('../errors');
|
|
14
14
|
const { preloadTests } = require('../commons/preloadTests');
|
|
15
15
|
|
|
16
16
|
// this navigation timeout is handled from outside the worker, so don't pass a small timeout to navigate
|
|
@@ -147,9 +147,9 @@ class WorkerSelenium extends BaseWorker {
|
|
|
147
147
|
).catch(reject))
|
|
148
148
|
.log('right after playTestByCode')
|
|
149
149
|
.timeout(this.testRunTimeout, timeoutMessages.TEST_COMPLETE_TIMEOUT_MSG)
|
|
150
|
-
.catch(
|
|
151
|
-
if (
|
|
152
|
-
sessionPlayer.stopPlayingOnTestTimeout();
|
|
150
|
+
.catch(err => {
|
|
151
|
+
if (err instanceof utils.TimeoutError) {
|
|
152
|
+
sessionPlayer.stopPlayingOnTestTimeout?.();
|
|
153
153
|
}
|
|
154
154
|
throw err;
|
|
155
155
|
})
|
|
@@ -179,9 +179,9 @@ class WorkerSelenium extends BaseWorker {
|
|
|
179
179
|
preloadedTest
|
|
180
180
|
).catch(reject))
|
|
181
181
|
.timeout(this.testRunTimeout, timeoutMessages.TEST_COMPLETE_TIMEOUT_MSG)
|
|
182
|
-
.catch(
|
|
183
|
-
if (
|
|
184
|
-
sessionPlayer.stopPlayingOnTestTimeout();
|
|
182
|
+
.catch(err => {
|
|
183
|
+
if (err instanceof utils.TimeoutError) {
|
|
184
|
+
sessionPlayer.stopPlayingOnTestTimeout?.();
|
|
185
185
|
}
|
|
186
186
|
throw err;
|
|
187
187
|
})
|