@testim/testim-cli 3.244.0 → 3.247.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/chromiumInstaller.js +92 -0
- package/cli.js +89 -87
- package/cliAgentMode.js +39 -114
- package/commons/chromedriverWrapper.js +4 -3
- package/commons/featureFlags.js +1 -0
- package/commons/getSessionPlayerRequire.js +2 -1
- package/commons/httpRequestCounters.test.js +9 -9
- package/commons/npmWrapper.js +3 -3
- package/commons/npmWrapper.test.js +1 -1
- package/commons/prepareRunner.js +2 -0
- package/commons/socket/baseSocketServiceSocketIO.js +2 -2
- package/commons/testimDesiredCapabilitiesBuilder.js +1 -3
- package/commons/testimServicesApi.js +45 -39
- package/errors.js +2 -1
- package/npm-shrinkwrap.json +467 -366
- package/package.json +2 -2
- package/player/WebDriverHttpRequest.js +45 -37
- package/player/chromeLauncherTestPlayer.js +2 -2
- package/player/services/playbackTimeoutCalculator.js +5 -1
- package/player/stepActions/RefreshStepAction.js +2 -4
- package/player/stepActions/inputFileStepAction.js +6 -2
- package/player/stepActions/sfdcStepAction.js +27 -0
- package/player/stepActions/stepActionRegistrar.js +12 -0
- package/player/utils/screenshotUtils.js +2 -2
- package/player/utils/windowUtils.js +3 -3
- package/player/webdriver.js +51 -52
- package/runOptions.js +3 -1
- package/runner.js +44 -41
- package/services/lambdatestService.js +2 -2
- package/services/lambdatestService.test.js +2 -1
- package/testRunHandler.js +7 -3
- package/testRunStatus.js +2 -1
- package/utils.js +54 -15
- package/utils.test.js +26 -0
- package/workers/BaseWorker.js +9 -7
- package/workers/BaseWorker.test.js +11 -12
package/runOptions.js
CHANGED
|
@@ -474,7 +474,7 @@ module.exports = {
|
|
|
474
474
|
const originalRequire = Module.prototype.require;
|
|
475
475
|
Module.prototype.require = function requireThatOverridesSessionPlayer(id) {
|
|
476
476
|
if (id.endsWith('getSessionPlayerRequire')) {
|
|
477
|
-
const sessionPlayerPath = path.resolve(fullPlayerPath, 'src/background/sessionPlayerInit.
|
|
477
|
+
const sessionPlayerPath = path.resolve(fullPlayerPath, 'src/background/sessionPlayerInit.ts');
|
|
478
478
|
return originalRequire.call(this, sessionPlayerPath);
|
|
479
479
|
}
|
|
480
480
|
if (id === 'rox-alias') {
|
|
@@ -1230,6 +1230,8 @@ module.exports = {
|
|
|
1230
1230
|
suiteNames: program.intersectWithSuite.length ? [program.intersectWithSuite].flat() : undefined,
|
|
1231
1231
|
suiteIds: program.intersectWithSuiteId.length ? [program.intersectWithSuiteId].flat() : undefined,
|
|
1232
1232
|
},
|
|
1233
|
+
|
|
1234
|
+
downloadBrowser: program.downloadBrowser,
|
|
1233
1235
|
});
|
|
1234
1236
|
},
|
|
1235
1237
|
};
|
package/runner.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint-disable no-console */
|
|
4
4
|
const { CLI_MODE } = require('./commons/constants');
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
5
|
const _ = require('lodash');
|
|
7
6
|
const { EDITOR_URL } = require('./commons/config');
|
|
8
7
|
const tunnel = require('./commons/testimTunnel');
|
|
@@ -29,7 +28,7 @@ const featureAvailabilityService = require('./commons/featureAvailabilityService
|
|
|
29
28
|
const logger = require('./commons/logger').getLogger('runner');
|
|
30
29
|
|
|
31
30
|
function validateCLIRunsAreAllowed(options) {
|
|
32
|
-
const hasCliAccess = _
|
|
31
|
+
const hasCliAccess = _.get(options, 'company.activePlan.premiumFeatures.allowCLI');
|
|
33
32
|
|
|
34
33
|
if (!hasCliAccess) {
|
|
35
34
|
const projectId = options.project;
|
|
@@ -38,20 +37,18 @@ function validateCLIRunsAreAllowed(options) {
|
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
function validateProjectQuotaNotDepleted(options) {
|
|
40
|
+
async function validateProjectQuotaNotDepleted(options) {
|
|
42
41
|
const projectId = options.project;
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
throw new QuotaDepletedError();
|
|
54
|
-
});
|
|
43
|
+
const usage = await servicesApi.getUsageForCurrentBillingPeriod(projectId);
|
|
44
|
+
const isExecutionBlocked = usage && usage.isExecutionBlocked;
|
|
45
|
+
if (!isExecutionBlocked) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.error('You have reached the limit of runs for the billing month, please upgrade your plan at https://www.testim.io/upgrade-contact-us?source=cli');
|
|
50
|
+
analytics.track(options.authData.uid, 'execution-quota-surpassed', { projectId });
|
|
51
|
+
throw new QuotaDepletedError();
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
function validateOptionsForCompany(options, company) {
|
|
@@ -60,26 +57,27 @@ function validateOptionsForCompany(options, company) {
|
|
|
60
57
|
return;
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
const companyRetention = _
|
|
60
|
+
const companyRetention = _.get(company, 'activePlan.premiumFeatures.resultRetention');
|
|
64
61
|
if (optionsRetention > companyRetention) {
|
|
65
62
|
throw new ArgError(`Retention days (${optionsRetention}) cannot be greater than the company's retention days (${companyRetention}). Run aborted`);
|
|
66
63
|
}
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
function validateCliAccount(options) {
|
|
66
|
+
async function validateCliAccount(options) {
|
|
70
67
|
if (options.lightweightMode && options.lightweightMode.disableQuotaBlocking) {
|
|
71
|
-
return
|
|
68
|
+
return;
|
|
72
69
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
try {
|
|
71
|
+
await Promise.all([
|
|
72
|
+
validateProjectQuotaNotDepleted(options),
|
|
73
|
+
validateCLIRunsAreAllowed(options),
|
|
74
|
+
]);
|
|
75
|
+
} catch (err) {
|
|
77
76
|
if (err instanceof ArgError || err instanceof QuotaDepletedError) {
|
|
78
|
-
|
|
77
|
+
throw err;
|
|
79
78
|
}
|
|
80
79
|
logger.error('could not validate cli account', { err });
|
|
81
|
-
|
|
82
|
-
});
|
|
80
|
+
}
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
function analyticsIdentify(projectId) {
|
|
@@ -125,10 +123,10 @@ function setCompany(options, company) {
|
|
|
125
123
|
if (onprem) {
|
|
126
124
|
const { mode, extensionPath, ext, playerPath } = options;
|
|
127
125
|
if ([CLI_MODE.SELENIUM].includes(mode) && !playerPath) {
|
|
128
|
-
|
|
126
|
+
throw new ArgError('in selenium on prem mode --player-path must be provided');
|
|
129
127
|
}
|
|
130
128
|
if (mode === 'extension' && !extensionPath && !ext) {
|
|
131
|
-
|
|
129
|
+
throw new ArgError('In extension on prem mode --ext or --extension-path must be provided');
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
132
|
const isPOC = Boolean(activePlan.isPoc);
|
|
@@ -153,16 +151,14 @@ function setCompany(options, company) {
|
|
|
153
151
|
isStartUp,
|
|
154
152
|
activePlan,
|
|
155
153
|
};
|
|
156
|
-
return undefined;
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
function setSystemInfo(options, editorConfig) {
|
|
160
157
|
if (EDITOR_URL) {
|
|
161
158
|
options.editorUrl = EDITOR_URL;
|
|
162
|
-
return
|
|
159
|
+
return;
|
|
163
160
|
}
|
|
164
161
|
options.editorUrl = editorConfig.editorUrl;
|
|
165
|
-
return undefined;
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
function setAllGrids(options, allGrids) {
|
|
@@ -198,7 +194,7 @@ async function setMockNetworkRules(options) {
|
|
|
198
194
|
}
|
|
199
195
|
}
|
|
200
196
|
|
|
201
|
-
function runRunner(options, customExtensionLocalLocation) {
|
|
197
|
+
async function runRunner(options, customExtensionLocalLocation) {
|
|
202
198
|
perf.log('in runner.js runRunner');
|
|
203
199
|
|
|
204
200
|
const { project, remoteRunId, useLocalChromeDriver, useChromeLauncher } = options;
|
|
@@ -210,15 +206,21 @@ function runRunner(options, customExtensionLocalLocation) {
|
|
|
210
206
|
npmDriver.checkNpmVersion();
|
|
211
207
|
perf.log('in runner.js after checkNpmVersion');
|
|
212
208
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
209
|
+
await validateCliAccount(options);
|
|
210
|
+
|
|
211
|
+
perf.log('in runRunner before tunnel.connect');
|
|
212
|
+
await tunnel.connect(options);
|
|
213
|
+
perf.log('in runRunner after tunnel.connect');
|
|
214
|
+
|
|
215
|
+
const testPlanRunner = new TestPlanRunner(customExtensionLocalLocation);
|
|
216
|
+
const results = await testPlanRunner.run(options);
|
|
217
|
+
|
|
218
|
+
perf.log('before tunnel.disconnect');
|
|
219
|
+
await tunnel.disconnect(options);
|
|
220
|
+
await gridService.keepAlive.end(project);
|
|
221
|
+
perf.log('after tunnel.disconnect and gridService.keepAlive.end');
|
|
222
|
+
|
|
223
|
+
return results;
|
|
222
224
|
}
|
|
223
225
|
|
|
224
226
|
function showFreeGridRunWarningIfNeeded(options) {
|
|
@@ -237,7 +239,7 @@ function showFreeGridRunWarningIfNeeded(options) {
|
|
|
237
239
|
* - Reporting the user to analytics
|
|
238
240
|
* - Authenticating the user and exchanging their token for a jwt
|
|
239
241
|
* - Sets the grids for the company and validates the user has permission to run the CLI
|
|
240
|
-
* @param {Object} options - the run options
|
|
242
|
+
* @param {Object} options - the run options passed to the CLI, namely the project and token
|
|
241
243
|
*/
|
|
242
244
|
async function init(options) {
|
|
243
245
|
perf.log('start runner init');
|
|
@@ -273,6 +275,7 @@ async function init(options) {
|
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
if (options.lightweightMode && options.lightweightMode.type === 'turboMode') {
|
|
278
|
+
// eslint-disable-next-line max-len
|
|
276
279
|
console.log('\nTurbo mode will ignore step delays. Test artifacts like screenshots and logs will only be saved for failed runs. For more information see our docs: https://help.testim.io/docs/turbo-mode');
|
|
277
280
|
}
|
|
278
281
|
|
|
@@ -288,5 +291,5 @@ async function init(options) {
|
|
|
288
291
|
|
|
289
292
|
module.exports = {
|
|
290
293
|
run: runRunner,
|
|
291
|
-
init
|
|
294
|
+
init,
|
|
292
295
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
2
|
const childProcess = require('child_process');
|
|
3
|
-
const
|
|
3
|
+
const pRetry = require('p-retry');
|
|
4
4
|
const fse = require('fs-extra');
|
|
5
5
|
const portfinder = require('portfinder');
|
|
6
6
|
const ms = require('ms');
|
|
@@ -154,7 +154,7 @@ class LambdatestService {
|
|
|
154
154
|
|
|
155
155
|
// verify that LT tunnel strated successfully
|
|
156
156
|
try {
|
|
157
|
-
const ltInfo = await
|
|
157
|
+
const ltInfo = await pRetry(() => httpRequest.get(`http://127.0.0.1:${infoAPIPort}/api/v1.0/info`, {}, {}, undefined, { skipProxy: true }), { retries: 30, minTimeout: 2000 });
|
|
158
158
|
logger.info('LT tunnel info', ltInfo);
|
|
159
159
|
} catch (err) {
|
|
160
160
|
logger.error('Failed to start LT tunnel', { err, stdoutResult, stderrResult });
|
|
@@ -4,6 +4,7 @@ const httpRequest = require('../commons/httpRequest');
|
|
|
4
4
|
const LambdatestService = require('./lambdatestService');
|
|
5
5
|
const utils = require('../utils');
|
|
6
6
|
const fse = require('fs-extra');
|
|
7
|
+
const { AbortError } = require('p-retry');
|
|
7
8
|
const os = require('os');
|
|
8
9
|
const childProcess = require('child_process');
|
|
9
10
|
const EventEmitter = require('events');
|
|
@@ -312,7 +313,7 @@ describe('LambdatestService', () => {
|
|
|
312
313
|
sinon.assert.calledOnce(httpGetStub);
|
|
313
314
|
});
|
|
314
315
|
it('should throw when tunnel did not start', async () => {
|
|
315
|
-
|
|
316
|
+
httpGetStub.rejects(new AbortError('tunnel did not start'));
|
|
316
317
|
await expect(LambdatestService.connectTunnel({ ...credentials })).to.be.rejectedWith(Error, 'tunnel did not start');
|
|
317
318
|
processMock.stdout.emit('data', '');
|
|
318
319
|
processMock.stderr.emit('data', '');
|
package/testRunHandler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const pRetry = require('p-retry');
|
|
3
4
|
const _ = require('lodash');
|
|
4
5
|
const testimCustomToken = require('./commons/testimCustomToken');
|
|
5
6
|
const remoteStepService = require('./commons/socket/remoteStepService');
|
|
@@ -312,12 +313,12 @@ class TestRun {
|
|
|
312
313
|
const { sessionId: extensionSessionId } = extensionSession || {};
|
|
313
314
|
perf.log('before Runtime.evaluate');
|
|
314
315
|
|
|
315
|
-
await
|
|
316
|
+
await pRetry(async () => {
|
|
316
317
|
const { result } = await cdpTestRunner.cdpCommand('Runtime.evaluate', { expression: 'typeof runTestimTest !== \'undefined\'', returnByValue: true }, extensionSessionId);
|
|
317
318
|
if (!result.value) {
|
|
318
319
|
throw new Error('runTestimTest not available on global scope');
|
|
319
320
|
}
|
|
320
|
-
}, 100, 30);
|
|
321
|
+
}, { retries: 100, minTimeout: 30 });
|
|
321
322
|
|
|
322
323
|
perf.log('after wait for runTestimTest function');
|
|
323
324
|
const { result } = await cdpTestRunner.cdpCommand(
|
|
@@ -519,7 +520,10 @@ class TestRun {
|
|
|
519
520
|
waitForTestEnd();
|
|
520
521
|
}
|
|
521
522
|
})
|
|
522
|
-
.
|
|
523
|
+
.then(async res => {
|
|
524
|
+
await this.onCompletedCleanup();
|
|
525
|
+
return res;
|
|
526
|
+
})
|
|
523
527
|
.finally(() => onConnected && !this._options.disableSockets && testResultService.off('socket-connected', onConnected));
|
|
524
528
|
}
|
|
525
529
|
|
package/testRunStatus.js
CHANGED
|
@@ -442,7 +442,8 @@ RunStatus.prototype.executionStart = function (executionId, projectId, startTime
|
|
|
442
442
|
logger.error('Failed to start suite', { err });
|
|
443
443
|
// eslint-disable-next-line no-console
|
|
444
444
|
console.error('Failed to start test run. Please contact support@testim.io');
|
|
445
|
-
})
|
|
445
|
+
})
|
|
446
|
+
.then(() => ({ beforeTests, tests, afterTests }));
|
|
446
447
|
});
|
|
447
448
|
};
|
|
448
449
|
|
package/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const moment = require('moment');
|
|
4
|
-
const
|
|
4
|
+
const pRetry = require('p-retry');
|
|
5
5
|
const _ = require('lodash');
|
|
6
6
|
const Promise = require('bluebird');
|
|
7
7
|
const fs = Promise.promisifyAll(require('fs'));
|
|
@@ -10,6 +10,10 @@ const { sessionType, testStatus: testStatusConst } = require('./commons/constant
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const httpRequest = require('./commons/httpRequest');
|
|
12
12
|
const decompress = require('decompress');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const HOMEDIR = os.homedir();
|
|
16
|
+
const TESTIM_BROWSER_DIR = path.join(HOMEDIR, '.testim-browser-profile');
|
|
13
17
|
|
|
14
18
|
const OSS = [
|
|
15
19
|
{ osName: 'Linux', bs: { os: 'LINUX' }, sl: { platform: 'Linux' } },
|
|
@@ -96,17 +100,6 @@ function getDurationSec(ms) {
|
|
|
96
100
|
return moment.duration(ms).asSeconds();
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
function runWithRetries(task, times, interval, predicate) {
|
|
100
|
-
times = times || 3;
|
|
101
|
-
interval = interval || 3000;
|
|
102
|
-
|
|
103
|
-
const opt = { max_tries: times, interval, throw_original: true };
|
|
104
|
-
if (predicate) {
|
|
105
|
-
opt.predicate = predicate;
|
|
106
|
-
}
|
|
107
|
-
return retry(task, opt);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
103
|
function getRunnerVersion() {
|
|
111
104
|
try {
|
|
112
105
|
const pack = require(`${__dirname}/package.json`);
|
|
@@ -194,7 +187,7 @@ function extractElementId(element) {
|
|
|
194
187
|
}
|
|
195
188
|
|
|
196
189
|
function isURL(path) {
|
|
197
|
-
const legacyPattern =
|
|
190
|
+
const legacyPattern = /^(https?:\/\/)/i;
|
|
198
191
|
|
|
199
192
|
// https://gist.github.com/dperini/729294 (validator.js based on).
|
|
200
193
|
const pattern = new RegExp(
|
|
@@ -243,7 +236,7 @@ function isURL(path) {
|
|
|
243
236
|
}
|
|
244
237
|
|
|
245
238
|
const DOWNLOAD_RETRY = 3;
|
|
246
|
-
const download = async (url) =>
|
|
239
|
+
const download = async (url) => pRetry(() => httpRequest.download(url), { retries: DOWNLOAD_RETRY });
|
|
247
240
|
|
|
248
241
|
const downloadAndSave = async (url, saveToLocation) => {
|
|
249
242
|
const res = await download(url);
|
|
@@ -311,6 +304,16 @@ function getPlanType(plan) {
|
|
|
311
304
|
return 'free';
|
|
312
305
|
}
|
|
313
306
|
|
|
307
|
+
/**
|
|
308
|
+
* @param time {number} in ms
|
|
309
|
+
* @returns {Promise}
|
|
310
|
+
*/
|
|
311
|
+
function delay(time) {
|
|
312
|
+
return new Promise(((resolve) => {
|
|
313
|
+
setTimeout(resolve, time);
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
|
|
314
317
|
const calcPercentile = (arr, percentile) => {
|
|
315
318
|
if (arr.length === 0) return 0;
|
|
316
319
|
if (typeof percentile !== 'number') throw new TypeError('p must be a number');
|
|
@@ -357,12 +360,45 @@ function groupTestsByRetries(testResults = []) { // NOTE: This duplicates a func
|
|
|
357
360
|
.value();
|
|
358
361
|
}
|
|
359
362
|
|
|
363
|
+
async function getCdpAddressForHost(browserInstanceHost, timeout) {
|
|
364
|
+
try {
|
|
365
|
+
/**
|
|
366
|
+
Example response:
|
|
367
|
+
{
|
|
368
|
+
"Browser": "Chrome/81.0.4044.138",
|
|
369
|
+
"Protocol-Version": "1.3",
|
|
370
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
|
|
371
|
+
"V8-Version": "8.1.307.32",
|
|
372
|
+
"WebKit-Version": "537.36 (@8c6c7ba89cc9453625af54f11fd83179e23450fa)",
|
|
373
|
+
"webSocketDebuggerUrl": "ws://localhost:58938/devtools/browser/d4290379-ec08-4d03-a41a-ab9d9d4c36ac"
|
|
374
|
+
}
|
|
375
|
+
*/
|
|
376
|
+
const debuggerAddress = await httpRequest.get(`http://${browserInstanceHost}/json/version`, undefined, undefined, timeout);
|
|
377
|
+
return debuggerAddress.webSocketDebuggerUrl;
|
|
378
|
+
} catch (e) {
|
|
379
|
+
throw new Error('unable to connect to devtools server');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function getArgsOnRemoteRunFailure() {
|
|
384
|
+
const { argv: args } = process;
|
|
385
|
+
if (!args.includes('--remoteRunId')) {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
remoteRunId: args[args.indexOf('--remoteRunId') + 1],
|
|
390
|
+
projectId: args[args.indexOf('--project') + 1],
|
|
391
|
+
token: args[args.indexOf('--token') + 1],
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
360
396
|
module.exports = {
|
|
397
|
+
TESTIM_BROWSER_DIR,
|
|
361
398
|
removePropertyFromObject,
|
|
362
399
|
getTestUrl,
|
|
363
400
|
getDuration,
|
|
364
401
|
getDurationSec,
|
|
365
|
-
runWithRetries,
|
|
366
402
|
getRunnerVersion,
|
|
367
403
|
getEnginesVersion,
|
|
368
404
|
getEnginesVersionAsync,
|
|
@@ -391,4 +427,7 @@ module.exports = {
|
|
|
391
427
|
isQuarantineAndNotRemoteRun,
|
|
392
428
|
groupTestsByRetries,
|
|
393
429
|
getPlanType,
|
|
430
|
+
delay,
|
|
431
|
+
getCdpAddressForHost,
|
|
432
|
+
getArgsOnRemoteRunFailure,
|
|
394
433
|
};
|
package/utils.test.js
CHANGED
|
@@ -39,4 +39,30 @@ describe('utils', () => {
|
|
|
39
39
|
.to.equal('http://localhost:8080/#/project/project/branch/encoded%2520branch/test/test?result-id=result');
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
describe('getArgsOnRemoteRunFailure', () => {
|
|
44
|
+
let originalArgv;
|
|
45
|
+
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
originalArgv = process.argv;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
process.argv = originalArgv;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should return undefined if no remote run is current', () => {
|
|
55
|
+
process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id'];
|
|
56
|
+
expect(utils.getArgsOnRemoteRunFailure()).to.be.undefined;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return details if remote run is current', () => {
|
|
60
|
+
process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id', '--remoteRunId', 'remote-run-id'];
|
|
61
|
+
expect(utils.getArgsOnRemoteRunFailure()).to.eql({
|
|
62
|
+
remoteRunId: 'remote-run-id',
|
|
63
|
+
projectId: 'project-id',
|
|
64
|
+
token: 'token',
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
42
68
|
});
|
package/workers/BaseWorker.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Bluebird = require('bluebird');
|
|
4
4
|
const moment = require('moment');
|
|
5
|
+
const pRetry = require('p-retry');
|
|
5
6
|
const ms = require('ms');
|
|
6
7
|
|
|
7
8
|
const { timeoutMessages, testRunStatus, stepResult, runnerTestStatus } = require('../commons/constants');
|
|
@@ -116,7 +117,8 @@ class BaseWorker {
|
|
|
116
117
|
try {
|
|
117
118
|
perf.log('before getSlotOnce retries');
|
|
118
119
|
let failedGetSlotAttempts = 0;
|
|
119
|
-
|
|
120
|
+
|
|
121
|
+
let gridInfo = await pRetry(async () => {
|
|
120
122
|
const startTime = Date.now();
|
|
121
123
|
try {
|
|
122
124
|
return await Bluebird.resolve(this.getSlotOnce(testRunHandler))
|
|
@@ -124,10 +126,10 @@ class BaseWorker {
|
|
|
124
126
|
} catch (error) {
|
|
125
127
|
logger.error('error getting grid slot', { error, testId: this.testId, testResultId: this.testResultId, executionId: this.executionId });
|
|
126
128
|
failedGetSlotAttempts++;
|
|
127
|
-
await
|
|
129
|
+
await utils.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
|
|
128
130
|
throw error;
|
|
129
131
|
}
|
|
130
|
-
}, this.options.getBrowserRetries);
|
|
132
|
+
}, { retries: this.options.getBrowserRetries - 1, minTimeout: 0 });
|
|
131
133
|
perf.log('after getSlotOnce retries');
|
|
132
134
|
|
|
133
135
|
perf.log('before getBrowserOnce retries');
|
|
@@ -136,7 +138,7 @@ class BaseWorker {
|
|
|
136
138
|
throw new Error('No free browser slots in desired grid');
|
|
137
139
|
}
|
|
138
140
|
let failedGetBrowserAttempts = 0;
|
|
139
|
-
testPlayer = await
|
|
141
|
+
testPlayer = await pRetry(async () => {
|
|
140
142
|
const startTime = Date.now();
|
|
141
143
|
const player = this.initPlayer(testRunHandler);
|
|
142
144
|
try {
|
|
@@ -162,11 +164,11 @@ class BaseWorker {
|
|
|
162
164
|
player.onDone();
|
|
163
165
|
|
|
164
166
|
if (!(error instanceof PageNotAvailableError)) {
|
|
165
|
-
await
|
|
167
|
+
await utils.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
|
|
166
168
|
}
|
|
167
169
|
throw error;
|
|
168
170
|
}
|
|
169
|
-
}, getBrowserRetriesNumber
|
|
171
|
+
}, { retries: getBrowserRetriesNumber - 1, minTimeout: 0 });
|
|
170
172
|
perf.log('after getBrowserOnce retries');
|
|
171
173
|
} catch (err) {
|
|
172
174
|
await releasePlayer(this.id, this.releaseSlotOnTestFinished, projectId, testPlayer);
|
|
@@ -235,7 +237,7 @@ class BaseWorker {
|
|
|
235
237
|
testResult.testRetryKey = testRetryKey;
|
|
236
238
|
await this.onTestCompleted(this.id, this.testId, testResult, sessionId, shouldRerun);
|
|
237
239
|
if (this.executionQueue.hasMoreTests() && !(this.options.lightweightMode && this.options.lightweightMode.general)) {
|
|
238
|
-
await
|
|
240
|
+
await utils.delay(DELAY_BETWEEN_TESTS);
|
|
239
241
|
}
|
|
240
242
|
await this.runTestCleanup();
|
|
241
243
|
if (shouldRerun) {
|
|
@@ -3,10 +3,9 @@ const proxyquire = require('proxyquire');
|
|
|
3
3
|
const { expect, sinon } = require('../../test/utils/testUtils');
|
|
4
4
|
const gridService = require('../services/gridService');
|
|
5
5
|
const reporter = require('../reports/reporter');
|
|
6
|
-
const Bluebird = require('bluebird');
|
|
7
6
|
const { PageNotAvailableError, GridError, GetBrowserError } = require('../errors');
|
|
8
7
|
const servicesApi = require('../commons/testimServicesApi');
|
|
9
|
-
const
|
|
8
|
+
const utils = require('../utils');
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
describe('BaseWorker', () => {
|
|
@@ -41,7 +40,7 @@ describe('BaseWorker', () => {
|
|
|
41
40
|
getGridSlotStub = sinon.stub(gridService, 'getGridSlot').resolves({});
|
|
42
41
|
|
|
43
42
|
|
|
44
|
-
sandbox.stub(
|
|
43
|
+
sandbox.stub(utils, 'delay').returns(Promise.resolve());
|
|
45
44
|
sandbox.stub(reporter);
|
|
46
45
|
});
|
|
47
46
|
|
|
@@ -57,7 +56,7 @@ describe('BaseWorker', () => {
|
|
|
57
56
|
it('should get grid slot from server', async () => {
|
|
58
57
|
await worker.getTestPlayer(testRunHandlerMock);
|
|
59
58
|
sinon.assert.calledOnce(getGridSlotStub);
|
|
60
|
-
sinon.assert.notCalled(
|
|
59
|
+
sinon.assert.notCalled(utils.delay);
|
|
61
60
|
});
|
|
62
61
|
it('should retry getting slot until it succeeds', async () => {
|
|
63
62
|
getGridSlotStub.onFirstCall().rejects();
|
|
@@ -65,7 +64,7 @@ describe('BaseWorker', () => {
|
|
|
65
64
|
|
|
66
65
|
await worker.getTestPlayer(testRunHandlerMock);
|
|
67
66
|
sinon.assert.calledTwice(getGridSlotStub);
|
|
68
|
-
sinon.assert.
|
|
67
|
+
sinon.assert.calledOnce(utils.delay);
|
|
69
68
|
});
|
|
70
69
|
it('should not proceed to getting browser if all retries used for getting a slot', async () => {
|
|
71
70
|
getGridSlotStub.resolves({});
|
|
@@ -74,7 +73,7 @@ describe('BaseWorker', () => {
|
|
|
74
73
|
sinon.assert.calledOnce(getGridSlotStub);
|
|
75
74
|
sinon.assert.notCalled(worker.getBrowserOnce);
|
|
76
75
|
sinon.assert.notCalled(worker.initPlayer);
|
|
77
|
-
sinon.assert.notCalled(
|
|
76
|
+
sinon.assert.notCalled(utils.delay);
|
|
78
77
|
});
|
|
79
78
|
});
|
|
80
79
|
|
|
@@ -84,7 +83,7 @@ describe('BaseWorker', () => {
|
|
|
84
83
|
sinon.assert.calledOnce(getGridSlotStub);
|
|
85
84
|
sinon.assert.calledOnce(worker.getBrowserOnce);
|
|
86
85
|
sinon.assert.calledOnce(worker.initPlayer);
|
|
87
|
-
sinon.assert.notCalled(
|
|
86
|
+
sinon.assert.notCalled(utils.delay);
|
|
88
87
|
});
|
|
89
88
|
it('should retry getting browser until it succeeds', async () => {
|
|
90
89
|
worker.getBrowserOnce.onFirstCall().rejects();
|
|
@@ -95,16 +94,16 @@ describe('BaseWorker', () => {
|
|
|
95
94
|
sinon.assert.calledTwice(worker.getBrowserOnce);
|
|
96
95
|
sinon.assert.calledTwice(worker.initPlayer);
|
|
97
96
|
sinon.assert.calledOnce(testPlayerMock.onDone);
|
|
98
|
-
sinon.assert.
|
|
97
|
+
sinon.assert.calledOnce(utils.delay);
|
|
99
98
|
});
|
|
100
99
|
it('should not retry if page is not available', async () => {
|
|
101
100
|
worker.getBrowserOnce.throws(() => new PageNotAvailableError());
|
|
102
101
|
|
|
103
|
-
await expect(worker.getTestPlayer(testRunHandlerMock)).to.eventually.be.rejectedWith(
|
|
102
|
+
await expect(worker.getTestPlayer(testRunHandlerMock)).to.eventually.be.rejectedWith(Error);
|
|
104
103
|
sinon.assert.calledOnce(getGridSlotStub);
|
|
105
104
|
sinon.assert.calledOnce(worker.getBrowserOnce);
|
|
106
105
|
sinon.assert.calledOnce(testPlayerMock.onDone);
|
|
107
|
-
sinon.assert.notCalled(
|
|
106
|
+
sinon.assert.notCalled(utils.delay);
|
|
108
107
|
});
|
|
109
108
|
it('should handle get grid error', async () => {
|
|
110
109
|
worker.getBrowserOnce.throws(() => new GridError());
|
|
@@ -153,8 +152,8 @@ describe('BaseWorker', () => {
|
|
|
153
152
|
getHybridGridProviderStub.onFirstCall().resolves({ provider: 'loacker', connectionDetails: { host: 'localhost', external: { user: 'user', key: 'password' } } });
|
|
154
153
|
getHybridGridProviderStub.onSecondCall().resolves({ provider: 'nitzan', connectionDetails: { host: 'google.com', port: 443 } });
|
|
155
154
|
getHybridGridProviderStub.onThirdCall().resolves({ provider: 'a', connectionDetails: { host: 'google.com', port: 4444 } });
|
|
156
|
-
worker.getBrowserOnce.onFirstCall().rejects();
|
|
157
|
-
worker.getBrowserOnce.onSecondCall().rejects();
|
|
155
|
+
worker.getBrowserOnce.onFirstCall().rejects(new Error());
|
|
156
|
+
worker.getBrowserOnce.onSecondCall().rejects(new Error());
|
|
158
157
|
worker.getBrowserOnce.onThirdCall().resolves({});
|
|
159
158
|
|
|
160
159
|
await worker.getTestPlayer(testRunHandlerMock);
|