@testim/testim-cli 3.212.0 → 3.216.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 +46 -85
- package/agent/routers/codim/service.js +0 -2
- package/cliAgentMode.js +0 -1
- package/codim/template.js/package.json +2 -2
- package/codim/template.ts/package.json +2 -2
- package/commons/featureFlags.js +1 -1
- package/commons/httpRequest.js +21 -9
- package/commons/httpRequestCounters.js +41 -10
- package/commons/httpRequestCounters.test.js +9 -9
- package/commons/npmWrapper.js +14 -7
- package/commons/npmWrapper.test.js +1 -1
- package/commons/testimDesiredCapabilitiesBuilder.js +21 -0
- package/npm-shrinkwrap.json +21848 -6389
- package/package.json +7 -8
- package/player/seleniumTestPlayer.js +4 -2
- package/player/services/frameLocator.js +106 -102
- package/player/services/tabService.js +28 -28
- package/player/stepActions/inputFileStepAction.js +4 -1
- package/player/stepActions/pixelValidationStepAction.js +8 -1
- package/player/utils/imageCaptureUtils.js +10 -1
- package/player/utils/screenshotUtils.js +1 -1
- package/runOptions.js +22 -4
- package/runner.js +4 -3
- package/services/lambdatestService.js +1 -0
- package/testRunHandler.js +16 -5
- package/workers/BaseWorker.js +10 -10
- package/workers/WorkerExtension.js +6 -6
- package/workers/WorkerSelenium.js +5 -10
- package/workers/workerUtils.js +0 -36
- package/agent/routers/cliJsCode/runNpmWorker.js +0 -39
package/runner.js
CHANGED
|
@@ -25,6 +25,7 @@ const FREE_PLAN_MINIMUM_BROWSER_TIMEOUT = 30 * 60 * 1000;
|
|
|
25
25
|
const TestPlanRunner = require('./runners/TestPlanRunner');
|
|
26
26
|
const labFeaturesService = require('./services/labFeaturesService');
|
|
27
27
|
const featureAvailabilityService = require('./commons/featureAvailabilityService');
|
|
28
|
+
const featureFlagService = require('./commons/featureFlags');
|
|
28
29
|
|
|
29
30
|
const logger = require('./commons/logger').getLogger('runner');
|
|
30
31
|
|
|
@@ -268,12 +269,12 @@ async function init(options) {
|
|
|
268
269
|
await labFeaturesService.loadLabFeatures(projectById.id, companyByProjectId.activePlan);
|
|
269
270
|
}
|
|
270
271
|
|
|
271
|
-
if (options.lightweightMode && options.lightweightMode.type === '
|
|
272
|
+
if (options.lightweightMode && options.lightweightMode.type === 'turboMode' && (featureFlagService.flags.highSpeedMode.getValue() === 'disabled' || options.company.planType === 'free')) {
|
|
272
273
|
delete options.lightweightMode;
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
if (options.lightweightMode && options.lightweightMode.type === '
|
|
276
|
-
console.log('
|
|
276
|
+
if (options.lightweightMode && options.lightweightMode.type === 'turboMode') {
|
|
277
|
+
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
278
|
}
|
|
278
279
|
|
|
279
280
|
gridService.keepAlive.start(project);
|
package/testRunHandler.js
CHANGED
|
@@ -18,6 +18,8 @@ const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
|
|
|
18
18
|
const { preloadTests } = require('./commons/preloadTests');
|
|
19
19
|
|
|
20
20
|
const RETRIES_ON_TIMEOUT = 3;
|
|
21
|
+
const MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE = 20 * 1000; // max size, in characters, of stringified run data sent over URL params. Chosen arbitrarily, this value should be changed according to data.
|
|
22
|
+
const canSendRunDataOverUrl = (runData) => JSON.stringify(runData).length < MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE;
|
|
21
23
|
|
|
22
24
|
const TestRun = function (executionId, executionName, test, options, branchToUse, testRunStatus) {
|
|
23
25
|
this._executionId = executionId;
|
|
@@ -175,7 +177,14 @@ TestRun.prototype.getRunRequestParams = async function () {
|
|
|
175
177
|
|
|
176
178
|
if (this._options.lightweightMode && this._options.lightweightMode.general) {
|
|
177
179
|
runRequestParams.company = this._options.company;
|
|
178
|
-
|
|
180
|
+
const runData = this.getRunData();
|
|
181
|
+
runRequestParams.lightweightMode.isRunDataSentInUrl = canSendRunDataOverUrl(runData);
|
|
182
|
+
if (runRequestParams.lightweightMode.isRunDataSentInUrl) {
|
|
183
|
+
runRequestParams.runData = runData;
|
|
184
|
+
logger.info(`Run data sent as URL param, test id: ${this.getTestId()} run data length: ${JSON.stringify(runData).length}`);
|
|
185
|
+
} else {
|
|
186
|
+
logger.warn(`Run data is too big to be sent as a URL param. Test id: ${this.getTestId()}, run data size: ${JSON.stringify(runData).length} (limit: ${MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE} characters)`);
|
|
187
|
+
}
|
|
179
188
|
runRequestParams.isLocalRun = Boolean(this._options.useLocalChromeDriver || this._options.useChromeLauncher);
|
|
180
189
|
}
|
|
181
190
|
|
|
@@ -195,9 +204,11 @@ TestRun.prototype.getRunRequestParams = async function () {
|
|
|
195
204
|
return runRequestParams;
|
|
196
205
|
};
|
|
197
206
|
|
|
198
|
-
TestRun.prototype.getRunTestUrl = function () {
|
|
199
|
-
|
|
200
|
-
|
|
207
|
+
TestRun.prototype.getRunTestUrl = async function () {
|
|
208
|
+
const runRequestParams = await this.getRunRequestParams();
|
|
209
|
+
const url = `https://run.testim.io/?params=${encodeURIComponent(JSON.stringify(runRequestParams))}`;
|
|
210
|
+
logger.info(`Test (${this.getTestId()}) run URL length: ${url.length}`);
|
|
211
|
+
return url;
|
|
201
212
|
};
|
|
202
213
|
|
|
203
214
|
TestRun.prototype.setSessionId = function (sessionId) {
|
|
@@ -240,7 +251,7 @@ TestRun.prototype.clearTestResult = function () {
|
|
|
240
251
|
const mustClearPreviousStepResults = !this.isAllowReportTestResultRetries() && (this._timeoutRetryCount > 1 || this._retryCount > 1);
|
|
241
252
|
|
|
242
253
|
if (this._options.lightweightMode && this._options.lightweightMode.disableResults &&
|
|
243
|
-
|
|
254
|
+
!mustClearPreviousStepResults && canSendRunDataOverUrl(runData)) {
|
|
244
255
|
return Promise.resolve();
|
|
245
256
|
}
|
|
246
257
|
|
package/workers/BaseWorker.js
CHANGED
|
@@ -8,7 +8,7 @@ const { timeoutMessages, testRunStatus, stepResult, runnerTestStatus } = require
|
|
|
8
8
|
const logger = require('../commons/logger').getLogger('base-worker');
|
|
9
9
|
const testResultService = require('../commons/socket/testResultService');
|
|
10
10
|
const remoteStepService = require('../commons/socket/remoteStepService');
|
|
11
|
-
const { isNetworkHealthy } = require('../commons/httpRequest');
|
|
11
|
+
const { isNetworkHealthy, didNetworkConnectivityTestFail } = require('../commons/httpRequest');
|
|
12
12
|
const testimServicesApi = require('../commons/testimServicesApi');
|
|
13
13
|
const gridService = require('../services/gridService');
|
|
14
14
|
const LambdatestService = require('../services/lambdatestService');
|
|
@@ -264,13 +264,14 @@ class BaseWorker {
|
|
|
264
264
|
return undefined;
|
|
265
265
|
}
|
|
266
266
|
};
|
|
267
|
+
const getNetworkErrorMessage = () => 'Due to network connectivity issues, Testim CLI has been unable to connect to the grid.\n' +
|
|
268
|
+
`Please make sure the CLI has stable access to the internet. ${didNetworkConnectivityTestFail() ? '(Internal: network connectivity test failed)' : ''}`;
|
|
267
269
|
|
|
268
|
-
const buildError = (err) => {
|
|
269
|
-
if (!
|
|
270
|
+
const buildError = (err, wasNetworkHealthy) => {
|
|
271
|
+
if (!wasNetworkHealthy && featureFlags.flags.errorMessageOnBadNetwork.isEnabled()) {
|
|
270
272
|
return {
|
|
271
273
|
errorType: NETWORK_ERROR,
|
|
272
|
-
reason:
|
|
273
|
-
'Please make sure the CLI has stable access to the internet.',
|
|
274
|
+
reason: getNetworkErrorMessage(),
|
|
274
275
|
};
|
|
275
276
|
}
|
|
276
277
|
|
|
@@ -324,17 +325,16 @@ class BaseWorker {
|
|
|
324
325
|
};
|
|
325
326
|
|
|
326
327
|
const onRunError = async (err, testRunHandler) => {
|
|
327
|
-
|
|
328
|
+
const wasNetworkHealthy = await isNetworkHealthy();
|
|
329
|
+
if (!wasNetworkHealthy && featureFlags.flags.warnOnBadNetwork.isEnabled()) {
|
|
328
330
|
// intentional, we want to log to stderr:
|
|
329
331
|
// eslint-disable-next-line no-console
|
|
330
|
-
console.warn(
|
|
331
|
-
// eslint-disable-next-line no-console
|
|
332
|
-
console.warn('Please make sure the CLI has stable access to the internet.');
|
|
332
|
+
console.warn(getNetworkErrorMessage());
|
|
333
333
|
}
|
|
334
334
|
logger.warn('error on run', { err });
|
|
335
335
|
|
|
336
336
|
const projectId = this.userData && this.userData.projectId;
|
|
337
|
-
const { errorType, reason } = buildError(err);
|
|
337
|
+
const { errorType, reason } = buildError(err, wasNetworkHealthy);
|
|
338
338
|
testimServicesApi.updateTestResult(projectId, this.testResultId, this.testId, {
|
|
339
339
|
status: testRunStatus.COMPLETED,
|
|
340
340
|
success: false,
|
|
@@ -87,7 +87,10 @@ class WorkerExtension extends BaseWorker {
|
|
|
87
87
|
.then(url => {
|
|
88
88
|
reporter.onWaitToTestStart(this.id);
|
|
89
89
|
return Promise.all([
|
|
90
|
-
driver.url(url).tap(() => { startStausDetails.driverUrlFinished = true; }).
|
|
90
|
+
driver.url(url).tap(() => { startStausDetails.driverUrlFinished = true; }).catch(err => {
|
|
91
|
+
logger.error('error from driver.url', { err, testResultId, executionId, testId, url, urlLength: url.length });
|
|
92
|
+
throw err;
|
|
93
|
+
}),
|
|
91
94
|
testRunHandler.onStarted(TEST_START_TIMEOUT_MS).tap(() => { startStausDetails.testRunHandlerStartedFinished = true; }),
|
|
92
95
|
])
|
|
93
96
|
.timeout(TEST_START_TIMEOUT_MS, timeoutMessages.TEST_START_TIMEOUT_MSG)
|
|
@@ -107,14 +110,11 @@ class WorkerExtension extends BaseWorker {
|
|
|
107
110
|
};
|
|
108
111
|
driver.registerToClosedBrowser(onBrowserClosed);
|
|
109
112
|
return testRunHandler.onCompleted().timeout(this.testRunTimeout, timeoutMessages.TEST_COMPLETE_TIMEOUT_MSG)
|
|
110
|
-
.
|
|
113
|
+
.then(async testResult => {
|
|
111
114
|
driver.unregisterToClosedBrowser(onBrowserClosed);
|
|
112
115
|
if (this.lambdatestService.isLambdatestRun()) {
|
|
113
|
-
|
|
116
|
+
await driver.executeJS(`lambda-status=${!testResult.success ? 'failed' : 'passed'}`).catch(() => { });
|
|
114
117
|
}
|
|
115
|
-
return undefined;
|
|
116
|
-
})
|
|
117
|
-
.then(testResult => {
|
|
118
118
|
if (!driver.isAlive()) {
|
|
119
119
|
logger.warn(`possible grid unresponsive for test ${this.testId}, result ${this.testResultId} (execution: ${this.executionId})`);
|
|
120
120
|
testResult.gridIssues = 'could not validate grid is alive';
|
|
@@ -168,13 +168,10 @@ class WorkerSelenium extends BaseWorker {
|
|
|
168
168
|
}
|
|
169
169
|
throw err;
|
|
170
170
|
})
|
|
171
|
-
.
|
|
171
|
+
.then(async testResult => {
|
|
172
172
|
if (sessionPlayerInit.localAssetService) {
|
|
173
|
-
|
|
173
|
+
await sessionPlayerInit.localAssetService.drain();
|
|
174
174
|
}
|
|
175
|
-
return undefined;
|
|
176
|
-
})
|
|
177
|
-
.then(testResult => {
|
|
178
175
|
testResult.stepsResults = null;
|
|
179
176
|
testResult.resultId = this.testResultId;
|
|
180
177
|
if (!driver.isAlive()) {
|
|
@@ -187,13 +184,11 @@ class WorkerSelenium extends BaseWorker {
|
|
|
187
184
|
logger.warn(`possible browser keep alive issue ${this.testId}, result ${this.testResultId} (execution: ${this.executionId})`);
|
|
188
185
|
testResult.keepAliveIssue = maxKeepAliveGap;
|
|
189
186
|
}
|
|
190
|
-
|
|
191
|
-
})
|
|
192
|
-
.tap(testResult => {
|
|
187
|
+
const resultWithStats = { ...testResult, ...testRunHandler.seleniumPerfStats.getStats() };
|
|
193
188
|
if (this.lambdatestService.isLambdatestRun()) {
|
|
194
|
-
|
|
189
|
+
await driver.executeJS(`lambda-status=${!resultWithStats.success ? 'failed' : 'passed'}`).catch(() => { });
|
|
195
190
|
}
|
|
196
|
-
return
|
|
191
|
+
return resultWithStats;
|
|
197
192
|
});
|
|
198
193
|
}
|
|
199
194
|
|
package/workers/workerUtils.js
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
const Promise = require('bluebird');
|
|
2
|
-
const utils = require('../utils');
|
|
3
2
|
const gridService = require('../services/gridService');
|
|
4
3
|
const logger = require('../commons/logger').getLogger('worker-utils');
|
|
5
|
-
const { timeoutMessages } = require('../commons/constants');
|
|
6
|
-
const { GetBrowserError, PageNotAvailableError } = require('../errors');
|
|
7
|
-
|
|
8
|
-
const waitUntilBrowserTimeout = (err, startTime, interval, projectId, workerId, player, releaseSlotOnTestFinished) => {
|
|
9
|
-
logger.warn('failed getting browser from grid', { err });
|
|
10
|
-
return releasePlayer(workerId, releaseSlotOnTestFinished, projectId, player)
|
|
11
|
-
.then(() => {
|
|
12
|
-
const requestTime = Date.now() - startTime;
|
|
13
|
-
const timeDiff = interval - requestTime;
|
|
14
|
-
return Promise.delay(timeDiff).then(() => Promise.reject(err));
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
4
|
|
|
18
5
|
const releaseGridSlot = (workerId, releaseSlotOnTestFinished, projectId) => {
|
|
19
6
|
if (!releaseSlotOnTestFinished) {
|
|
@@ -29,26 +16,3 @@ const releasePlayer = (workerId, releaseSlotOnTestFinished, projectId, player) =
|
|
|
29
16
|
};
|
|
30
17
|
|
|
31
18
|
module.exports.releasePlayer = releasePlayer;
|
|
32
|
-
|
|
33
|
-
module.exports.getBrowserWithRetries = ({ getBrowserOnce, testPlayerFactory, releaseSlotOnTestFinished }, { totalTimeoutDuration, singleGetBrowserDuration, projectId, workerId, reporter }) => {
|
|
34
|
-
const maxGetBrowserAttempts = totalTimeoutDuration / singleGetBrowserDuration;
|
|
35
|
-
let failedAttempts = 0;
|
|
36
|
-
|
|
37
|
-
return utils.runWithRetries(() => {
|
|
38
|
-
const startTime = Date.now();
|
|
39
|
-
const player = testPlayerFactory();
|
|
40
|
-
return getBrowserOnce(player)
|
|
41
|
-
.then((getBrowserRes) => player || getBrowserRes)
|
|
42
|
-
.timeout(singleGetBrowserDuration, timeoutMessages.GET_BROWSER_TIMEOUT_MSG)
|
|
43
|
-
.tapCatch(() => reporter.onGetBrowserFailure(workerId, projectId, ++failedAttempts))
|
|
44
|
-
.tap(() => reporter.onGetBrowserSuccess(workerId, projectId))
|
|
45
|
-
.catch(err => waitUntilBrowserTimeout(err, startTime, singleGetBrowserDuration, projectId, workerId, player, releaseSlotOnTestFinished));
|
|
46
|
-
}, maxGetBrowserAttempts).catch(err => {
|
|
47
|
-
if (err instanceof PageNotAvailableError) {
|
|
48
|
-
throw err;
|
|
49
|
-
}
|
|
50
|
-
throw new GetBrowserError(err);
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
module.exports.isGetBrowserError = err => err && err.constructor && err.constructor.name === 'GetBrowserError';
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
module.exports = function (input) {
|
|
2
|
-
const {transactionId, packages, command, localPackageInstallFolder, proxyUri} = input;
|
|
3
|
-
return installLocalPackages(transactionId, packages, command, localPackageInstallFolder, proxyUri);
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const npm = require("npm");
|
|
8
|
-
const {NpmPackageError} = require('../../../errors');
|
|
9
|
-
|
|
10
|
-
function installLocalPackages(transactionId, packages, command, localPackageInstallFolder, proxyUri) {
|
|
11
|
-
if (!packages || packages.length === 0) {
|
|
12
|
-
return Promise.resolve();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const installFolder = path.join(localPackageInstallFolder, `/${transactionId}`);
|
|
16
|
-
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
const npmLoadConfig = {
|
|
19
|
-
prefix: installFolder,
|
|
20
|
-
loglevel: 'silent',
|
|
21
|
-
};
|
|
22
|
-
if(proxyUri) {
|
|
23
|
-
npmLoadConfig.proxy = proxyUri;
|
|
24
|
-
npmLoadConfig["https-proxy"] = proxyUri;
|
|
25
|
-
}
|
|
26
|
-
npm.load(npmLoadConfig, (err) => {
|
|
27
|
-
if (err) {
|
|
28
|
-
return reject(err);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
npm.commands[command](packages, (installErr, data) => {
|
|
32
|
-
if (installErr) {
|
|
33
|
-
return reject(new NpmPackageError(installErr.message));
|
|
34
|
-
}
|
|
35
|
-
resolve(Object.assign({data, installFolder}));
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
}
|