@testim/testim-cli 3.242.0 → 3.245.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/commons/chromedriverWrapper.js +4 -3
- package/commons/featureFlags.js +1 -0
- package/commons/httpRequestCounters.test.js +9 -9
- package/commons/npmWrapper.js +3 -3
- package/commons/npmWrapper.test.js +1 -1
- package/commons/socket/baseSocketServiceSocketIO.js +2 -2
- package/commons/testimAnalytics.js +8 -3
- package/commons/testimServicesApi.js +54 -55
- package/errors.js +2 -1
- package/npm-shrinkwrap.json +556 -492
- package/package.json +3 -3
- package/player/stepActions/RefreshStepAction.js +2 -4
- package/player/stepActions/evaluateExpressionStepAction.js +7 -7
- package/player/stepActions/inputFileStepAction.js +6 -2
- package/player/stepActions/scripts/doClick.js +3 -1
- package/player/stepActions/scripts/html5dragAction.js +3 -1
- package/player/stepActions/scripts/html5dragActionV2.js +3 -1
- package/player/stepActions/scripts/runCode.js +23 -6
- package/player/stepActions/scripts/scroll.js +1 -6
- package/player/stepActions/scripts/setText.js +4 -1
- package/player/utils/screenshotUtils.js +2 -2
- package/player/utils/windowUtils.js +3 -3
- package/services/lambdatestService.js +2 -2
- package/services/lambdatestService.test.js +2 -1
- package/testRunHandler.js +3 -2
- package/testRunStatus.js +2 -9
- package/utils.js +15 -20
- package/utils.test.js +1 -1
- package/workers/BaseWorker.js +9 -7
- package/workers/BaseWorker.test.js +11 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testim/testim-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.245.0",
|
|
4
4
|
"description": "Command line interface for running Testing on your CI",
|
|
5
5
|
"author": "Oren Rubin",
|
|
6
6
|
"contributors": [{
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"appium-dom-utils": "1.0.6",
|
|
58
58
|
"archiver": "5.3.0",
|
|
59
59
|
"bluebird": "3.7.2",
|
|
60
|
-
"bluebird-retry": "0.11.0",
|
|
61
60
|
"body-parser": "1.19.1",
|
|
62
61
|
"chalk": "4.1.2",
|
|
63
62
|
"chrome-launcher": "0.15.0",
|
|
@@ -87,6 +86,7 @@
|
|
|
87
86
|
"npm": "8.3.0",
|
|
88
87
|
"object-hash": "3.0.0",
|
|
89
88
|
"ora": "5.4.1",
|
|
89
|
+
"p-retry": "4.6.2",
|
|
90
90
|
"pako": "1.0.11",
|
|
91
91
|
"portfinder": "1.0.28",
|
|
92
92
|
"promise-queue": "2.2.5",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"test": "IS_UNIT_TEST=1 ../../node_modules/mocha/bin/_mocha --timeout 2000 --reporter spec --exit --recursive \"./src/**/*.test.js\" --exclude ./src/codim/template.js/tests/examples/**/*.test.js",
|
|
117
117
|
"test:watch": "IS_UNIT_TEST=1 ../../node_modules/mocha/bin/_mocha --timeout 2000 --exit --recursive \"./src/**/*.test.js\" --exclude ./src/codim/template.js/tests/examples/**/*.test.js --watch",
|
|
118
118
|
"test:cov": "nyc --reporter=lcov --reporter=text yarn test",
|
|
119
|
-
"upload-bundle-s3": "scripts/upload.js",
|
|
119
|
+
"upload-bundle-s3": "ts-node-transpile-only scripts/upload.js",
|
|
120
120
|
"prepare-version": "rm -rf ./deploy && mkdir -p deploy && gulp prepare-version-on-prem",
|
|
121
121
|
"make-onprem-deps": "cp ../../yarn.lock deploy && cd deploy && yarn install --production && bundle-deps",
|
|
122
122
|
"pack-onprem": "yarn prepare-version && yarn make-onprem-deps && cd deploy && npm pack && zip -r testim-cli.zip testim-cli-*.tgz",
|
|
@@ -6,10 +6,8 @@ require('bluebird');
|
|
|
6
6
|
class RefreshStepAction extends StepAction {
|
|
7
7
|
execute() {
|
|
8
8
|
return this.driver.reloadTab()
|
|
9
|
-
.
|
|
10
|
-
.catch(error => {
|
|
11
|
-
return { success: false, reason: error.message };
|
|
12
|
-
});
|
|
9
|
+
.then(() => ({ success: true }))
|
|
10
|
+
.catch(error => ({ success: false, reason: error.message }));
|
|
13
11
|
}
|
|
14
12
|
}
|
|
15
13
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
2
3
|
const Promise = require('bluebird');
|
|
3
4
|
const sessionPlayer = require('../../commons/getSessionPlayerRequire');
|
|
4
5
|
const StepAction = require('./stepAction');
|
|
6
|
+
|
|
5
7
|
const constants = sessionPlayer.commonConstants.stepResult;
|
|
6
8
|
const { stepParamBuilder } = sessionPlayer;
|
|
7
9
|
const logger = require('../../commons/logger').getLogger('evaluate-expression-step-action');
|
|
8
10
|
const _ = require('lodash');
|
|
9
11
|
|
|
10
12
|
class EvaluateExpressionStepAction extends StepAction {
|
|
11
|
-
|
|
12
13
|
execute() {
|
|
13
14
|
const step = this.step;
|
|
14
15
|
const context = this.context;
|
|
@@ -25,8 +26,8 @@ class EvaluateExpressionStepAction extends StepAction {
|
|
|
25
26
|
|
|
26
27
|
const params = ['context', ...incomingParams.as.functionParameters];
|
|
27
28
|
const args = [context, ...incomingParams.as.functionArguments];
|
|
28
|
-
const expressionToEvaluate = step.subType ===
|
|
29
|
-
const code = (
|
|
29
|
+
const expressionToEvaluate = step.subType === 'text' ? `'${step.expression.replace(/'/g, "\\\'")}'` : step.expression;
|
|
30
|
+
const code = (`return ${expressionToEvaluate}`).replace(/\n/g, '\\n');
|
|
30
31
|
const textEvaluateFunction = Function.apply(Function, params.concat([code]));
|
|
31
32
|
const evaluatedText = textEvaluateFunction.apply(null, args);
|
|
32
33
|
|
|
@@ -38,8 +39,8 @@ class EvaluateExpressionStepAction extends StepAction {
|
|
|
38
39
|
|
|
39
40
|
const result = {
|
|
40
41
|
success: true,
|
|
41
|
-
evaluatedText
|
|
42
|
-
data: context.data
|
|
42
|
+
evaluatedText,
|
|
43
|
+
data: context.data,
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
resolve(result);
|
|
@@ -48,7 +49,6 @@ class EvaluateExpressionStepAction extends StepAction {
|
|
|
48
49
|
}
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
|
-
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
module.exports = EvaluateExpressionStepAction;
|
|
@@ -86,18 +86,22 @@ class InputFileStepAction extends StepAction {
|
|
|
86
86
|
const target = this.context.data[this.step.targetId || 'targetId'];
|
|
87
87
|
const overrideAzureStorageUrl = featureFlagService.flags.overrideAzureStorageUrl.isEnabled();
|
|
88
88
|
const useJsInputCodeInSafari = featureFlagService.flags.useJsInputCodeInSafari.isEnabled();
|
|
89
|
+
const useJsInputCodeInFirefox = featureFlagService.flags.useJsInputCodeInFirefox.isEnabled();
|
|
89
90
|
const downloadToBase64 = featureFlagService.flags.downloadToBase64.isEnabled();
|
|
90
91
|
|
|
91
92
|
let fileUrls = await utils.addTokenToFileUrl(this.context.project.id, this.step.fileUrls, this.stepActionUtils.testimServicesApi, overrideAzureStorageUrl, logger);
|
|
92
93
|
|
|
94
|
+
|
|
95
|
+
const isSafariJsInputCode = this.driver.isSafari() && (useJsInputCodeInSafari || fileUrls.length > 1);
|
|
96
|
+
const isFirefoxJsInputCode = this.driver.isFirefox() && (useJsInputCodeInFirefox || fileUrls.length > 1);
|
|
97
|
+
|
|
93
98
|
if (downloadToBase64) {
|
|
94
99
|
fileUrls = await Promise.all(fileUrls.map(async ({ name, url }) => {
|
|
95
100
|
const res = await download(url);
|
|
96
101
|
return { name, url: `data:${res.type};base64,${Buffer.from(res.body).toString('base64')}` };
|
|
97
102
|
}));
|
|
98
103
|
}
|
|
99
|
-
|
|
100
|
-
if (this.driver.isSafari() && (useJsInputCodeInSafari || fileUrls.length > 1)) {
|
|
104
|
+
if (isSafariJsInputCode || isFirefoxJsInputCode) {
|
|
101
105
|
await this.driver.executeJSWithArray(`
|
|
102
106
|
const getLocatedElement = ${codeSnippets.getLocatedElementCode};
|
|
103
107
|
const downloadAndUploadFile = ${downloadAndUpload()};
|
|
@@ -3,23 +3,40 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const runCode = function (eventData, preCompiledCode) {
|
|
6
|
+
typeof Object.tstassign !== 'function' && (Object.tstassign = function (n, t) {
|
|
7
|
+
'use strict';
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
if (n == null) throw new TypeError('Cannot convert undefined or null to object'); for (var r = Object(n), e = 1; e < arguments.length; e++) { var o = arguments[e]; if (o != null) for (var a in o)Object.prototype.hasOwnProperty.call(o, a) && (r[a] = o[a]); } return r;
|
|
10
|
+
});
|
|
11
|
+
Object.assign = typeof Object.assign !== 'function' ? Object.tstassign : Object.assign;
|
|
9
12
|
|
|
10
13
|
function appendToStorage(name, data) {
|
|
11
14
|
const sessionItem = 'data-testim-' + name;
|
|
15
|
+
|
|
16
|
+
const nativeFuncErrMsg = 'Native sessionStorage is not available';
|
|
17
|
+
function isNativeFunction(fn) {
|
|
18
|
+
if (!fn || !fn.toString) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return fn.toString().indexOf('[native code]') > -1;
|
|
22
|
+
}
|
|
12
23
|
try {
|
|
24
|
+
if (![window.sessionStorage.setItem, window.sessionStorage.getItem].every(isNativeFunction)) {
|
|
25
|
+
throw new Error(nativeFuncErrMsg);
|
|
26
|
+
}
|
|
13
27
|
const oldData = JSON.parse(window.sessionStorage.getItem(sessionItem) || '{}');
|
|
14
28
|
const newData = Object.tstassign({}, oldData, data);
|
|
15
29
|
window.sessionStorage.setItem(sessionItem, JSON.stringify(newData));
|
|
16
30
|
} catch (err) {
|
|
17
|
-
// any
|
|
31
|
+
// any variation QuotaExceededError from browsers
|
|
18
32
|
const isQuotaExceededError = err.message.toLowerCase().indexOf('quota') > -1;
|
|
33
|
+
const isNativeFunctionError = err.message === nativeFuncErrMsg;
|
|
34
|
+
|
|
19
35
|
if (err.message.indexOf('sessionStorage') > -1 || // Chrome + Firefox
|
|
20
36
|
err.message.indexOf('The operation is insecure') > -1 || // Safari
|
|
21
37
|
err.message.indexOf('SecurityError') > -1 || // Edge
|
|
22
|
-
isQuotaExceededError
|
|
38
|
+
isQuotaExceededError ||
|
|
39
|
+
isNativeFunctionError
|
|
23
40
|
) {
|
|
24
41
|
var storage = document.head.querySelector('#testim-storage-backup');
|
|
25
42
|
if (!storage) {
|
|
@@ -30,13 +47,13 @@ const runCode = function (eventData, preCompiledCode) {
|
|
|
30
47
|
const oldData = JSON.parse(storage.getAttribute(sessionItem) || '{}');
|
|
31
48
|
const newData = Object.tstassign({}, oldData, data);
|
|
32
49
|
storage.setAttribute(sessionItem, JSON.stringify(newData));
|
|
33
|
-
if (isQuotaExceededError) {
|
|
50
|
+
if (isQuotaExceededError || isNativeFunctionError) {
|
|
34
51
|
try {
|
|
35
52
|
window.sessionStorage.removeItem(sessionItem);
|
|
36
53
|
} catch (e) {
|
|
37
54
|
// Prevents future retries from looking in sessionStorage with old data
|
|
38
55
|
}
|
|
39
|
-
window.TSTA.useFallbackStorage = true;
|
|
56
|
+
(window.TSTA = window.TSTA || {}).useFallbackStorage = true;
|
|
40
57
|
}
|
|
41
58
|
return;
|
|
42
59
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
/* eslint-disable no-var */
|
|
3
|
-
/* eslint-disable no-redeclare */
|
|
4
|
-
/* eslint-disable object-shorthand */
|
|
5
|
-
/* eslint-disable one-var */
|
|
6
|
-
/* eslint-disable operator-linebreak */
|
|
1
|
+
/* globals getLocatedElement, elementScrollTo */
|
|
7
2
|
|
|
8
3
|
'use strict';
|
|
9
4
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Promise = require('bluebird');
|
|
4
|
-
const
|
|
4
|
+
const pRetry = require('p-retry');
|
|
5
5
|
|
|
6
6
|
class ScreenshotUtils {
|
|
7
7
|
constructor(tabId, driver, options = { takeScreenshots: true }) {
|
|
@@ -29,7 +29,7 @@ class ScreenshotUtils {
|
|
|
29
29
|
const SCREENSHOT_RETRY_DELAY = 2000;
|
|
30
30
|
const devicePixelRatioPromise = this.currentDevicePixelRatio ? Promise.resolve(this.currentDevicePixelRatio) : this.getDevicePixelRatio();
|
|
31
31
|
const getScreenshot = () => Promise.all([devicePixelRatioPromise, this.driver.takeScreenshot()]);
|
|
32
|
-
return
|
|
32
|
+
return pRetry(getScreenshot, { retries: MAX_RETRY_COUNT, minTimeout: SCREENSHOT_RETRY_DELAY })
|
|
33
33
|
.then(([devicePixelRatio, image]) => {
|
|
34
34
|
const base64 = image ? image.value : '';
|
|
35
35
|
const dataUrl = `data:image/png;base64,${this.base64AddPadding(base64.replace(/[\r\n]/g, ''))}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Promise = require('bluebird');
|
|
4
|
-
const
|
|
4
|
+
const pRetry = require('p-retry');
|
|
5
5
|
const { PageNotAvailableError } = require('../../errors');
|
|
6
6
|
const logger = require('../../commons/logger').getLogger('window-utils');
|
|
7
7
|
|
|
@@ -152,12 +152,12 @@ WindowUtils.prototype.quit = function () {
|
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
WindowUtils.prototype.getOsAndBrowser = function () {
|
|
155
|
-
return
|
|
155
|
+
return pRetry(() => this.driver.getBrowserAndOS(), { retries: 3 })
|
|
156
156
|
.then(osAndBrowser => ({ uaBrowserName: osAndBrowser.browser, uaOs: osAndBrowser.os, userAgent: osAndBrowser.userAgent, browserVersion: osAndBrowser.browserVersion }));
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
WindowUtils.prototype.getUserAgentInfo = function () {
|
|
160
|
-
return
|
|
160
|
+
return pRetry(() => this.driver.getUserAgentInfo(), { retries: 3 });
|
|
161
161
|
};
|
|
162
162
|
|
|
163
163
|
module.exports = WindowUtils;
|
|
@@ -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(
|
package/testRunStatus.js
CHANGED
|
@@ -17,7 +17,6 @@ const { calculateCoverage } = require('./coverage/jsCoverage');
|
|
|
17
17
|
const featureAvailabilityService = require('./commons/featureAvailabilityService');
|
|
18
18
|
const featureFlags = require('./commons/featureFlags');
|
|
19
19
|
const { mapFilesToLocalDrive } = require('./services/localRCASaver');
|
|
20
|
-
const { removeHiddenParamsInTestData } = require('./commons/testimServicesApi');
|
|
21
20
|
|
|
22
21
|
const gitBranch = utils.getEnvironmentGitBranch();
|
|
23
22
|
const gitCommit = process.env.GIT_COMMIT || process.env.CIRCLE_SHA1 || process.env.TRAVIS_COMMIT;
|
|
@@ -125,13 +124,6 @@ RunStatus.prototype.addRetryTestResult = async function ({
|
|
|
125
124
|
|
|
126
125
|
this.testRunStatus[newResultId] = newTestResult;
|
|
127
126
|
|
|
128
|
-
const { projectData } = this.options;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (newTestResult.config.testData) {
|
|
132
|
-
newTestResult.config.testData = removeHiddenParamsInTestData(newTestResult.config.testData, projectData.defaults);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
127
|
return servicesApi.addTestRetry({
|
|
136
128
|
projectId,
|
|
137
129
|
runId: executionId,
|
|
@@ -450,7 +442,8 @@ RunStatus.prototype.executionStart = function (executionId, projectId, startTime
|
|
|
450
442
|
logger.error('Failed to start suite', { err });
|
|
451
443
|
// eslint-disable-next-line no-console
|
|
452
444
|
console.error('Failed to start test run. Please contact support@testim.io');
|
|
453
|
-
})
|
|
445
|
+
})
|
|
446
|
+
.then(() => ({ beforeTests, tests, afterTests }));
|
|
454
447
|
});
|
|
455
448
|
};
|
|
456
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'));
|
|
@@ -71,20 +71,16 @@ function getRunConfigByBrowserName(browser, saucelabs, browserstack) {
|
|
|
71
71
|
return _.merge(selectedBrowser, selectedOS);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function handleAngularURIComponent(part) {
|
|
75
|
-
return part.replace(/(~|\/)/g, m => ({ '~': '~~', '/': '~2F' }[m]));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
74
|
function getTestUrl(editorUrl, projectId, testId, resultId, branch) {
|
|
79
75
|
let testUrl = '';
|
|
80
|
-
branch = branch ?
|
|
76
|
+
branch = branch ? encodeURIComponent(branch) : 'master';
|
|
81
77
|
if (projectId && testId) {
|
|
82
78
|
testUrl = `${editorUrl}/#/project/${projectId}/branch/${branch}/test/${testId}`;
|
|
83
79
|
if (resultId) {
|
|
84
80
|
testUrl += `?result-id=${resultId}`;
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
|
-
return
|
|
83
|
+
return testUrl;
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
function isPromise(obj) {
|
|
@@ -100,17 +96,6 @@ function getDurationSec(ms) {
|
|
|
100
96
|
return moment.duration(ms).asSeconds();
|
|
101
97
|
}
|
|
102
98
|
|
|
103
|
-
function runWithRetries(task, times, interval, predicate) {
|
|
104
|
-
times = times || 3;
|
|
105
|
-
interval = interval || 3000;
|
|
106
|
-
|
|
107
|
-
const opt = { max_tries: times, interval, throw_original: true };
|
|
108
|
-
if (predicate) {
|
|
109
|
-
opt.predicate = predicate;
|
|
110
|
-
}
|
|
111
|
-
return retry(task, opt);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
99
|
function getRunnerVersion() {
|
|
115
100
|
try {
|
|
116
101
|
const pack = require(`${__dirname}/package.json`);
|
|
@@ -247,7 +232,7 @@ function isURL(path) {
|
|
|
247
232
|
}
|
|
248
233
|
|
|
249
234
|
const DOWNLOAD_RETRY = 3;
|
|
250
|
-
const download = async (url) =>
|
|
235
|
+
const download = async (url) => pRetry(() => httpRequest.download(url), { retries: DOWNLOAD_RETRY });
|
|
251
236
|
|
|
252
237
|
const downloadAndSave = async (url, saveToLocation) => {
|
|
253
238
|
const res = await download(url);
|
|
@@ -315,6 +300,16 @@ function getPlanType(plan) {
|
|
|
315
300
|
return 'free';
|
|
316
301
|
}
|
|
317
302
|
|
|
303
|
+
/**
|
|
304
|
+
* @param time {number} in ms
|
|
305
|
+
* @returns {Promise}
|
|
306
|
+
*/
|
|
307
|
+
function delay(time) {
|
|
308
|
+
return new Promise(((resolve) => {
|
|
309
|
+
setTimeout(resolve, time);
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
|
|
318
313
|
const calcPercentile = (arr, percentile) => {
|
|
319
314
|
if (arr.length === 0) return 0;
|
|
320
315
|
if (typeof percentile !== 'number') throw new TypeError('p must be a number');
|
|
@@ -366,7 +361,6 @@ module.exports = {
|
|
|
366
361
|
getTestUrl,
|
|
367
362
|
getDuration,
|
|
368
363
|
getDurationSec,
|
|
369
|
-
runWithRetries,
|
|
370
364
|
getRunnerVersion,
|
|
371
365
|
getEnginesVersion,
|
|
372
366
|
getEnginesVersionAsync,
|
|
@@ -395,4 +389,5 @@ module.exports = {
|
|
|
395
389
|
isQuarantineAndNotRemoteRun,
|
|
396
390
|
groupTestsByRetries,
|
|
397
391
|
getPlanType,
|
|
392
|
+
delay,
|
|
398
393
|
};
|
package/utils.test.js
CHANGED
|
@@ -32,7 +32,7 @@ describe('utils', () => {
|
|
|
32
32
|
expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'normal-branch-name'))
|
|
33
33
|
.to.equal('http://localhost:8080/#/project/project/branch/normal-branch-name/test/test?result-id=result');
|
|
34
34
|
expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'branch/with/slashes'))
|
|
35
|
-
.to.equal('http://localhost:8080/#/project/project/branch/branch
|
|
35
|
+
.to.equal('http://localhost:8080/#/project/project/branch/branch%2Fwith%2Fslashes/test/test?result-id=result');
|
|
36
36
|
expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'branch with spaces'))
|
|
37
37
|
.to.equal('http://localhost:8080/#/project/project/branch/branch%20with%20spaces/test/test?result-id=result');
|
|
38
38
|
expect(utils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'encoded%20branch'))
|
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);
|