@testim/testim-cli 3.267.0 → 3.269.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/routers/cliJsCode/service.js +53 -20
- package/bluebirdConfig.js +5 -4
- package/commons/httpRequest.js +8 -8
- package/commons/runnerFileCache.js +19 -6
- package/commons/socket/baseSocketServiceSocketIO.js +25 -2
- package/commons/socket/testResultService.js +0 -1
- package/commons/testimDesiredCapabilitiesBuilder.js +22 -8
- package/commons/testimServicesApi.js +31 -5
- package/credentialsManager.js +4 -3
- package/npm-shrinkwrap.json +38 -38
- package/package.json +1 -1
- package/player/extensionTestPlayer.js +8 -6
- package/player/seleniumTestPlayer.js +16 -11
- package/player/services/tabService.js +70 -75
- package/player/stepActions/baseCliJsStepAction.js +3 -3
- package/player/stepActions/nodePackageStepAction.js +2 -2
- package/player/utils/windowUtils.js +19 -12
- package/reports/consoleReporter.js +6 -2
- package/runOptions.d.ts +4 -3
- package/runOptions.js +3 -2
- package/runner.js +1 -1
- package/stepPlayers/nodePackageStepPlayback.js +18 -14
- package/testRunHandler.js +232 -234
- package/testimNpmDriver.js +17 -18
- package/utils/promiseUtils.js +1 -0
- package/workers/BaseWorker.js +22 -22
- package/workers/BaseWorker.test.js +1 -6
- package/workers/WorkerAppium.js +74 -19
- package/workers/WorkerExtension.js +11 -5
- package/workers/WorkerExtensionSingleBrowser.js +7 -2
- package/workers/WorkerSelenium.js +23 -23
package/testRunHandler.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const pRetry = require('p-retry');
|
|
4
3
|
const _ = require('lodash');
|
|
4
|
+
const pRetry = require('p-retry');
|
|
5
|
+
const utils = require('./utils');
|
|
6
|
+
const config = require('./commons/config');
|
|
7
|
+
const perf = require('./commons/performance-logger');
|
|
8
|
+
const analytics = require('./commons/testimAnalytics');
|
|
5
9
|
const testimCustomToken = require('./commons/testimCustomToken');
|
|
10
|
+
const testimServicesApi = require('./commons/testimServicesApi');
|
|
11
|
+
const remoteStepPlayback = require('./stepPlayers/remoteStepPlayback');
|
|
6
12
|
const remoteStepService = require('./commons/socket/remoteStepService');
|
|
7
13
|
const testResultService = require('./commons/socket/testResultService');
|
|
8
|
-
const testimServicesApi = require('./commons/testimServicesApi');
|
|
9
|
-
const { timeoutMessages, CLI_MODE } = require('./commons/constants');
|
|
10
|
-
const logger = require('./commons/logger').getLogger('test-run-handler');
|
|
11
|
-
const perf = require('./commons/performance-logger');
|
|
12
14
|
const { URL } = require('url');
|
|
13
|
-
const
|
|
14
|
-
const remoteStepPlayback = require('./stepPlayers/remoteStepPlayback');
|
|
15
|
-
const utils = require('./utils');
|
|
16
|
-
const config = require('./commons/config');
|
|
17
|
-
const analytics = require('./commons/testimAnalytics');
|
|
18
|
-
const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
|
|
15
|
+
const { getLogger } = require('./commons/logger');
|
|
19
16
|
const { preloadTests } = require('./commons/preloadTests');
|
|
17
|
+
const { timeoutMessages, CLI_MODE } = require('./commons/constants');
|
|
18
|
+
const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
|
|
20
19
|
|
|
20
|
+
const logger = getLogger('test-run-handler');
|
|
21
21
|
const RETRIES_ON_TIMEOUT = 3;
|
|
22
22
|
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.
|
|
23
23
|
const canSendRunDataOverUrl = (runData) => JSON.stringify(runData).length < MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE;
|
|
@@ -26,7 +26,7 @@ class TestRun {
|
|
|
26
26
|
/**
|
|
27
27
|
* @param {string} executionId
|
|
28
28
|
* @param {string} executionName
|
|
29
|
-
* @param {
|
|
29
|
+
* @param {import('./runners/TestPlanRunner').ExecutionList[number]} test
|
|
30
30
|
* @param {import('./runOptions').RunnerOptions} options
|
|
31
31
|
* @param {string} branchToUse
|
|
32
32
|
* @param {import('./testRunStatus')} testRunStatus
|
|
@@ -58,76 +58,95 @@ class TestRun {
|
|
|
58
58
|
this.seleniumPerfStats = new SeleniumPerfStats();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
waitForExecutionStartedFinished() {
|
|
62
|
-
return this._testRunStatus.waitForExecutionStartedFinished() && this.clearTestResultFinished;
|
|
61
|
+
async waitForExecutionStartedFinished() {
|
|
62
|
+
return await this._testRunStatus.waitForExecutionStartedFinished() && await this.clearTestResultFinished;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
get testStatus() {
|
|
66
66
|
return this._testStatus;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
get runMode() {
|
|
70
70
|
return this._options.mode;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
get automationMode() {
|
|
74
74
|
return this._code ? 'codeful' : 'codeless';
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
get code() {
|
|
78
78
|
return this._code;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
get runConfig() {
|
|
82
82
|
return this._runConfig;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
get testResultId() {
|
|
86
86
|
return this._testResultId;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
get baseUrl() {
|
|
90
90
|
return this._baseUrl;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
get executionId() {
|
|
94
94
|
return this._executionId;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
get executionName() {
|
|
98
98
|
return this._executionName;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
+
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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}`;
|
|
109
111
|
}
|
|
112
|
+
return link;
|
|
113
|
+
}
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
async getNativeAppData() {
|
|
116
|
+
const { appId, baseUrl } = this._options;
|
|
117
|
+
if (baseUrl && !this._nativeApp && !appId) {
|
|
118
|
+
const url = baseUrl || this.baseUrl;
|
|
119
|
+
if (!url) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const [packageName, activity] = url.split(':');
|
|
123
|
+
return {
|
|
124
|
+
packageName,
|
|
125
|
+
activity,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (this._nativeApp && !appId) {
|
|
129
|
+
if ('appMetadata' in this._nativeApp) {
|
|
130
|
+
return this._nativeApp.appMetadata;
|
|
131
|
+
}
|
|
132
|
+
return this._nativeApp;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
116
135
|
}
|
|
117
136
|
|
|
118
|
-
|
|
137
|
+
get branch() {
|
|
119
138
|
return this._branch;
|
|
120
139
|
}
|
|
121
140
|
|
|
122
|
-
|
|
141
|
+
get sfdcCredential() {
|
|
123
142
|
return this._options.sfdcCredential;
|
|
124
143
|
}
|
|
125
144
|
|
|
126
|
-
|
|
145
|
+
get remoteRunId() {
|
|
127
146
|
return this._remoteRunId;
|
|
128
147
|
}
|
|
129
148
|
|
|
130
|
-
|
|
149
|
+
get overrideTestConfigId() {
|
|
131
150
|
return this._overrideTestConfigId;
|
|
132
151
|
}
|
|
133
152
|
|
|
@@ -148,53 +167,38 @@ class TestRun {
|
|
|
148
167
|
baseUrl: this._baseUrl,
|
|
149
168
|
branch: this._branch,
|
|
150
169
|
servicesUrl: config.EXTENSION_SERVICES_HOST,
|
|
151
|
-
remoteRunId: this.
|
|
152
|
-
previousTestResultId: this.
|
|
153
|
-
testRetryCount: this.
|
|
170
|
+
remoteRunId: this.remoteRunId,
|
|
171
|
+
previousTestResultId: this.previousTestResultId,
|
|
172
|
+
testRetryCount: this.retryCount,
|
|
173
|
+
...(this.code && { isCodeMode: true, testName: this.testName }),
|
|
174
|
+
...(this._options.shouldMonitorPerformance && { shouldMonitorPerformance: true }),
|
|
175
|
+
...(this._options.company && {
|
|
176
|
+
companyId: this._options.company.companyId,
|
|
177
|
+
onprem: this._options.company.onprem,
|
|
178
|
+
storageBaseUrl: this._options.company.storageBaseUrl,
|
|
179
|
+
storageType: this._options.company.storageType,
|
|
180
|
+
planType: this._options.company.planType,
|
|
181
|
+
isPOC: this._options.company.isPOC,
|
|
182
|
+
isStartUp: this._options.company.isStartUp,
|
|
183
|
+
}),
|
|
184
|
+
...(this._options.collectCodeCoverage && { codeCoverageUrlFilter: this._options.codeCoverageUrlFilter || `${this.baseUrl}*` }),
|
|
185
|
+
...(this._options.disableMockNetwork && { disableMockNetwork: this._options.disableMockNetwork }),
|
|
186
|
+
...(this._options.lightweightMode && { lightweightMode: this._options.lightweightMode }),
|
|
187
|
+
...(this.clearBrowser && { clearBrowser: true }),
|
|
188
|
+
...(this._options.localRCASaver && { localRCASaver: this._options.localRCASaver }),
|
|
189
|
+
...(this.sfdcCredential && { sfdcCredential: this.sfdcCredential }),
|
|
154
190
|
};
|
|
155
191
|
|
|
156
|
-
if (this._code) {
|
|
157
|
-
runRequestParams.isCodeMode = true;
|
|
158
|
-
runRequestParams.testName = this._testName;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (this._options.shouldMonitorPerformance) {
|
|
162
|
-
runRequestParams.shouldMonitorPerformance = true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (this._options.company) {
|
|
166
|
-
runRequestParams.companyId = this._options.company.companyId;
|
|
167
|
-
runRequestParams.onprem = this._options.company.onprem;
|
|
168
|
-
runRequestParams.storageBaseUrl = this._options.company.storageBaseUrl;
|
|
169
|
-
runRequestParams.storageType = this._options.company.storageType;
|
|
170
|
-
runRequestParams.planType = this._options.company.planType;
|
|
171
|
-
runRequestParams.isPOC = this._options.company.isPOC;
|
|
172
|
-
runRequestParams.isStartUp = this._options.company.isStartUp;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (this._options.collectCodeCoverage) {
|
|
176
|
-
if (this._options.codeCoverageUrlFilter) {
|
|
177
|
-
runRequestParams.codeCoverageUrlFilter = this._options.codeCoverageUrlFilter;
|
|
178
|
-
} else {
|
|
179
|
-
runRequestParams.codeCoverageUrlFilter = `${this._baseUrl}*`;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
192
|
if (this._options.disableMockNetwork) {
|
|
184
|
-
runRequestParams.disableMockNetwork = this._options.disableMockNetwork;
|
|
185
193
|
analytics.trackWithCIUser('user-disable-mock');
|
|
186
194
|
}
|
|
187
195
|
|
|
188
|
-
if (this._options.lightweightMode) {
|
|
189
|
-
runRequestParams.lightweightMode = this._options.lightweightMode;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
196
|
if (this._options.lightweightMode?.general) {
|
|
193
197
|
runRequestParams.company = this._options.company;
|
|
194
|
-
const runData = this.
|
|
198
|
+
const runData = this.runData;
|
|
195
199
|
runRequestParams.lightweightMode.isRunDataSentInUrl = canSendRunDataOverUrl(runData);
|
|
196
200
|
const stringifiedLength = JSON.stringify(runData).length;
|
|
197
|
-
const testId = this.
|
|
201
|
+
const testId = this.testId;
|
|
198
202
|
if (runRequestParams.lightweightMode.isRunDataSentInUrl) {
|
|
199
203
|
runRequestParams.runData = runData;
|
|
200
204
|
logger.info(`Run data sent as URL param, test id: ${testId} run data length: ${stringifiedLength}`);
|
|
@@ -209,59 +213,47 @@ class TestRun {
|
|
|
209
213
|
runRequestParams.preloadedTest = preloadedTests[runRequestParams.testId];
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
if (this.clearBrowser) {
|
|
213
|
-
runRequestParams.clearBrowser = true;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (this._options.localRCASaver) {
|
|
217
|
-
runRequestParams.localRCASaver = this._options.localRCASaver;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (this.getSfdcCredential()) {
|
|
221
|
-
runRequestParams.sfdcCredential = this.getSfdcCredential();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
216
|
return runRequestParams;
|
|
225
217
|
}
|
|
226
218
|
|
|
227
219
|
async getRunTestUrl() {
|
|
228
220
|
const runRequestParams = await this.getRunRequestParams();
|
|
229
221
|
const url = `https://run.testim.io/?params=${encodeURIComponent(JSON.stringify(runRequestParams))}`;
|
|
230
|
-
logger.info(`Test (${this.
|
|
222
|
+
logger.info(`Test (${this.testId}) run URL length: ${url.length}`);
|
|
231
223
|
return url;
|
|
232
224
|
}
|
|
233
225
|
|
|
234
|
-
|
|
226
|
+
set sessionId(sessionId) {
|
|
235
227
|
this._sessionId = sessionId;
|
|
236
228
|
}
|
|
237
229
|
|
|
238
|
-
|
|
230
|
+
get sessionId() {
|
|
239
231
|
return this._sessionId;
|
|
240
232
|
}
|
|
241
233
|
|
|
242
|
-
|
|
234
|
+
get testId() {
|
|
243
235
|
return this._testId;
|
|
244
236
|
}
|
|
245
237
|
|
|
246
|
-
|
|
238
|
+
get testName() {
|
|
247
239
|
return this._testName;
|
|
248
240
|
}
|
|
249
241
|
|
|
250
|
-
|
|
242
|
+
get runParams() {
|
|
251
243
|
return this._options.runParams[this._testResultId] || {};
|
|
252
244
|
}
|
|
253
245
|
|
|
254
|
-
|
|
246
|
+
get runData() {
|
|
255
247
|
return {
|
|
256
|
-
userParamsData: this.
|
|
248
|
+
userParamsData: this.runParams,
|
|
257
249
|
overrideTestConfigId: this._overrideTestConfigId || null,
|
|
258
250
|
};
|
|
259
251
|
}
|
|
260
252
|
|
|
261
|
-
clearTestResult() {
|
|
262
|
-
const runData = this.
|
|
263
|
-
if (this.
|
|
264
|
-
runData.code = this.
|
|
253
|
+
async clearTestResult() {
|
|
254
|
+
const runData = this.runData;
|
|
255
|
+
if (this.runMode === CLI_MODE.EXTENSION) {
|
|
256
|
+
runData.code = this.code;
|
|
265
257
|
}
|
|
266
258
|
|
|
267
259
|
if (this._options.mockNetworkRules) {
|
|
@@ -270,28 +262,33 @@ class TestRun {
|
|
|
270
262
|
const mustClearPreviousStepResults = (this._timeoutRetryCount > 1 || this._retryCount > 1);
|
|
271
263
|
|
|
272
264
|
if (this._options.lightweightMode?.disableResults && !mustClearPreviousStepResults && canSendRunDataOverUrl(runData)) {
|
|
273
|
-
return
|
|
265
|
+
return undefined;
|
|
274
266
|
}
|
|
275
267
|
|
|
276
|
-
|
|
277
|
-
|
|
268
|
+
const uploadRunDataArtifactAndFallbackToEmptyString = async () => {
|
|
269
|
+
try {
|
|
270
|
+
return await testimServicesApi.uploadRunDataArtifact(this._options.project, this._testId, this._testResultId, runData);
|
|
271
|
+
} catch (err) {
|
|
278
272
|
logger.error('failed to upload run data artifact (runner)', { err });
|
|
279
273
|
return '';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const clearTestResultFinished = async () => {
|
|
277
|
+
const runDataUrl = await uploadRunDataArtifactAndFallbackToEmptyString();
|
|
278
|
+
// make sure the execution is created by now
|
|
279
|
+
await this._testRunStatus.waitForExecutionStartedFinished();
|
|
280
|
+
// we probably can save this backend call by initializing the execution
|
|
281
|
+
return testimServicesApi.clearTestResult(this._options.project, this._testResultId, this._testId, {
|
|
282
|
+
name: this._testName,
|
|
283
|
+
resultId: this._testResultId,
|
|
284
|
+
status: 'pending',
|
|
285
|
+
retryCount: this._retryCount,
|
|
286
|
+
runDataUrl, // links the run data url to the test.
|
|
287
|
+
runData: runDataUrl ? undefined : runData, // put runData in mongo if we fail to upload to S3.
|
|
288
|
+
testRetryKey: this.retryKey,
|
|
294
289
|
});
|
|
290
|
+
};
|
|
291
|
+
this.clearTestResultFinished = clearTestResultFinished();
|
|
295
292
|
return this.clearTestResultFinished;
|
|
296
293
|
}
|
|
297
294
|
|
|
@@ -299,7 +296,7 @@ class TestRun {
|
|
|
299
296
|
return this._retryCount < this._maxRetryCount;
|
|
300
297
|
}
|
|
301
298
|
|
|
302
|
-
|
|
299
|
+
get retryKey() {
|
|
303
300
|
return `${this._retryCount}:${this._timeoutRetryCount}`;
|
|
304
301
|
}
|
|
305
302
|
|
|
@@ -335,7 +332,7 @@ class TestRun {
|
|
|
335
332
|
if (!result.value) {
|
|
336
333
|
throw new Error('runTestimTest not available on global scope');
|
|
337
334
|
}
|
|
338
|
-
}, { retries: 100, minTimeout: 30 });
|
|
335
|
+
}, { retries: 100, minTimeout: 30, factor: 1 });
|
|
339
336
|
|
|
340
337
|
perf.log('after wait for runTestimTest function');
|
|
341
338
|
const { result } = await cdpTestRunner.cdpCommand(
|
|
@@ -355,12 +352,11 @@ class TestRun {
|
|
|
355
352
|
}
|
|
356
353
|
|
|
357
354
|
isRetryKeyMismatch(testResult) {
|
|
358
|
-
return testResult.testRetryKey && (testResult.testRetryKey !== this.
|
|
355
|
+
return testResult.testRetryKey && (testResult.testRetryKey !== this.retryKey);
|
|
359
356
|
}
|
|
360
357
|
|
|
361
358
|
validateRunConfig() {
|
|
362
|
-
const baseUrl = this
|
|
363
|
-
const { browserValue } = this.getRunConfig();
|
|
359
|
+
const { baseUrl, runConfig: { browserValue } } = this;
|
|
364
360
|
|
|
365
361
|
if (baseUrl && browserValue === 'safari') {
|
|
366
362
|
let parsedUrl;
|
|
@@ -378,6 +374,7 @@ class TestRun {
|
|
|
378
374
|
}
|
|
379
375
|
}
|
|
380
376
|
|
|
377
|
+
/** @param {number} startTimeout */
|
|
381
378
|
onStarted(startTimeout) {
|
|
382
379
|
return new Promise(resolve => {
|
|
383
380
|
// We can't leave the test result as it may remove other listeners as well
|
|
@@ -388,20 +385,20 @@ class TestRun {
|
|
|
388
385
|
return;
|
|
389
386
|
}
|
|
390
387
|
if (this.isRetryKeyMismatch(testResult)) {
|
|
391
|
-
logger.warn(`ignoring result update for on started due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.
|
|
392
|
-
resultId: this.
|
|
393
|
-
testId: this.
|
|
388
|
+
logger.warn(`ignoring result update for on started due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.retryKey}`, {
|
|
389
|
+
resultId: this.testResultId,
|
|
390
|
+
testId: this.testId,
|
|
394
391
|
});
|
|
395
392
|
return;
|
|
396
393
|
}
|
|
397
394
|
if (['running', 'completed'].includes(testResult.status)) {
|
|
398
|
-
testResult.resultId = this.
|
|
395
|
+
testResult.resultId = this.testResultId;
|
|
399
396
|
if (testResult.status === 'completed') {
|
|
400
397
|
logger.info('setting _wasCompletedOnStartedCheck to true', {
|
|
401
398
|
testResult,
|
|
402
|
-
resultId: this.
|
|
403
|
-
testId: this.
|
|
404
|
-
testRetryKey: this.
|
|
399
|
+
resultId: this.testResultId,
|
|
400
|
+
testId: this.testId,
|
|
401
|
+
testRetryKey: this.retryKey,
|
|
405
402
|
});
|
|
406
403
|
this._wasCompletedOnStartedCheck = testResult;
|
|
407
404
|
}
|
|
@@ -411,24 +408,22 @@ class TestRun {
|
|
|
411
408
|
};
|
|
412
409
|
if (this._options.disableSockets) {
|
|
413
410
|
const timeLimit = Date.now() + startTimeout;
|
|
414
|
-
const checkIfDone = () => {
|
|
411
|
+
const checkIfDone = async () => {
|
|
415
412
|
if (Date.now() > timeLimit) {
|
|
416
413
|
return;
|
|
417
414
|
}
|
|
418
|
-
const testId = this
|
|
419
|
-
const resultId = this._testResultId;
|
|
420
|
-
const projectId = this._options.project;
|
|
421
|
-
const branch = this.getBranch();
|
|
415
|
+
const { testId, testResultId, branch, _options: { project: projectId } } = this;
|
|
422
416
|
|
|
423
|
-
|
|
417
|
+
try {
|
|
418
|
+
const restResult = await testimServicesApi.getTestResults(testId, testResultId, projectId, branch);
|
|
424
419
|
resolveResult(restResult);
|
|
425
420
|
if (!reportedStart) {
|
|
426
421
|
setTimeout(checkIfDone, 3000);
|
|
427
422
|
}
|
|
428
|
-
}
|
|
423
|
+
} catch (err) {
|
|
429
424
|
logger.error('failed to check if done', { err });
|
|
430
425
|
setTimeout(checkIfDone, 3000);
|
|
431
|
-
}
|
|
426
|
+
}
|
|
432
427
|
};
|
|
433
428
|
setTimeout(checkIfDone, 3000);
|
|
434
429
|
} else {
|
|
@@ -437,116 +432,119 @@ class TestRun {
|
|
|
437
432
|
});
|
|
438
433
|
}
|
|
439
434
|
|
|
440
|
-
checkViaRestAPIIfTestStarted() {
|
|
441
|
-
const testId = this
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
.catch(err => {
|
|
456
|
-
logger.error('failed to get test result after test start timeout', { err, testId, resultId, branch });
|
|
457
|
-
throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
|
|
458
|
-
});
|
|
435
|
+
async checkViaRestAPIIfTestStarted() {
|
|
436
|
+
const { testId, testResultId, _options: { project: projectId }, branch } = this;
|
|
437
|
+
try {
|
|
438
|
+
const testResult = testimServicesApi.getTestResults(testId, testResultId, projectId, branch);
|
|
439
|
+
const expectedStatuses = ['running', 'completed'];
|
|
440
|
+
if (expectedStatuses.includes(testResult.status)) {
|
|
441
|
+
logger.info(`get status: ${testResult.status} after not get test started status`, { testId, testResultId, branch });
|
|
442
|
+
return testResult;
|
|
443
|
+
}
|
|
444
|
+
logger.error(`test not start test status: ${testResult.status} (expected [${expectedStatuses.join(', ')}])`, { testId, testResultId, branch });
|
|
445
|
+
throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
logger.error('failed to get test result after test start timeout', { err, testId, testResultId, branch });
|
|
448
|
+
throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
|
|
449
|
+
}
|
|
459
450
|
}
|
|
460
451
|
|
|
461
|
-
onCompletedCleanup() {
|
|
462
|
-
if (
|
|
463
|
-
return
|
|
452
|
+
async onCompletedCleanup() {
|
|
453
|
+
if (this._options.disableSockets) {
|
|
454
|
+
return undefined;
|
|
464
455
|
}
|
|
465
|
-
return
|
|
456
|
+
return await testResultService.leaveTestResult(this._testResultId, this._testId);
|
|
466
457
|
}
|
|
467
458
|
|
|
468
|
-
onCompleted() {
|
|
459
|
+
async onCompleted() {
|
|
469
460
|
let onConnected;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
461
|
+
try {
|
|
462
|
+
const res = await new Promise(resolve => {
|
|
463
|
+
if (this._wasCompletedOnStartedCheck && !this.isRetryKeyMismatch(this._wasCompletedOnStartedCheck)) {
|
|
464
|
+
logger.info('test was already completed in on started check', {
|
|
465
|
+
resultId: this.testResultId,
|
|
466
|
+
testId: this.testId,
|
|
467
|
+
});
|
|
468
|
+
resolve(this._wasCompletedOnStartedCheck);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
479
471
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
472
|
+
if (!this._options.disableSockets) {
|
|
473
|
+
testResultService.listenToTestResult(this._testResultId, this._testId, testResult => {
|
|
474
|
+
if (this.isRetryKeyMismatch(testResult)) {
|
|
475
|
+
logger.warn(`ignoring result update for on completed due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.retryKey}`, {
|
|
476
|
+
resultId: this.testResultId,
|
|
477
|
+
testId: this.testId,
|
|
478
|
+
});
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (testResult.status === 'completed') {
|
|
482
|
+
testResult.resultId = this._testResultId;
|
|
483
|
+
resolve(testResult);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const debounceDelay = this._options.disableSockets ? 0 : Math.floor(10000 + (Math.random() * 5000));
|
|
488
|
+
const maxWait = this._options.disableSockets ? 0 : Math.floor(60000 + (Math.random() * 15000));
|
|
489
|
+
onConnected = _.debounce(async () => {
|
|
490
|
+
try {
|
|
491
|
+
const testResult = await testimServicesApi.getTestResults(this._testId, this._testResultId, this._options.project, this.branch);
|
|
492
|
+
if (this.isRetryKeyMismatch(testResult)) {
|
|
493
|
+
logger.warn(`ignoring result update for on completed (in reconnect) due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.retryKey}`, {
|
|
494
|
+
resultId: this.testResultId,
|
|
495
|
+
testId: this.testId,
|
|
496
|
+
});
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
if (testResult?.status === 'completed') {
|
|
500
|
+
logger.info('Socket reconnected - Test complete', { testId: this._testId, resultId: this._testResultId, projectId: this._options.project });
|
|
501
|
+
testResult.resultId = this._testResultId;
|
|
502
|
+
resolve(testResult);
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
} catch (err) {
|
|
507
|
+
logger.warn('Error while trying to check status on socket connect', err);
|
|
504
508
|
return false;
|
|
505
509
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const { isComplete } = await testimServicesApi.isTestResultCompleted(this._testResultId, this._options.project, this.getRetryKey());
|
|
522
|
-
if (isComplete) {
|
|
523
|
-
const isDone = await onConnected();
|
|
524
|
-
if (!isDone) {
|
|
525
|
-
logger.warn('onConnected returned false even though isComplete was true');
|
|
510
|
+
}, debounceDelay, { maxWait });
|
|
511
|
+
if (!this._options.disableSockets) {
|
|
512
|
+
testResultService.on('socket-connected', onConnected);
|
|
513
|
+
} else {
|
|
514
|
+
const waitForTestEnd = () => {
|
|
515
|
+
setTimeout(async () => {
|
|
516
|
+
try {
|
|
517
|
+
const { isComplete } = await testimServicesApi.isTestResultCompleted(this._testResultId, this._options.project, this.retryKey);
|
|
518
|
+
if (isComplete) {
|
|
519
|
+
const isDone = await onConnected();
|
|
520
|
+
if (!isDone) {
|
|
521
|
+
logger.warn('onConnected returned false even though isComplete was true');
|
|
522
|
+
waitForTestEnd();
|
|
523
|
+
}
|
|
524
|
+
} else {
|
|
526
525
|
waitForTestEnd();
|
|
527
526
|
}
|
|
528
|
-
}
|
|
527
|
+
} catch (err) {
|
|
528
|
+
logger.error('failed to check is complete', { err });
|
|
529
529
|
waitForTestEnd();
|
|
530
530
|
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
531
|
+
}, 3000);
|
|
532
|
+
};
|
|
533
|
+
waitForTestEnd();
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
await this.onCompletedCleanup();
|
|
537
|
+
return res;
|
|
538
|
+
} finally {
|
|
539
|
+
if (onConnected && !this._options.disableSockets) {
|
|
540
|
+
testResultService.off('socket-connected', onConnected);
|
|
538
541
|
}
|
|
539
|
-
}
|
|
540
|
-
.then(async res => {
|
|
541
|
-
await this.onCompletedCleanup();
|
|
542
|
-
return res;
|
|
543
|
-
})
|
|
544
|
-
.finally(() => onConnected && !this._options.disableSockets && testResultService.off('socket-connected', onConnected));
|
|
542
|
+
}
|
|
545
543
|
}
|
|
546
544
|
|
|
547
545
|
listenToRemoteStep(browser) {
|
|
548
|
-
remoteStepService.listenToRemoteStep(this.
|
|
549
|
-
remoteStepPlayback.executeStep(this._options, browser, step, this.
|
|
546
|
+
remoteStepService.listenToRemoteStep(this.testResultId, step => {
|
|
547
|
+
remoteStepPlayback.executeStep(this._options, browser, step, this.testResultId);
|
|
550
548
|
});
|
|
551
549
|
}
|
|
552
550
|
|
|
@@ -560,20 +558,20 @@ class TestRun {
|
|
|
560
558
|
return this.onRetry();
|
|
561
559
|
}
|
|
562
560
|
|
|
563
|
-
|
|
561
|
+
get retryCount() {
|
|
564
562
|
return this._retryCount;
|
|
565
563
|
}
|
|
566
564
|
|
|
567
|
-
|
|
565
|
+
get previousTestResultId() {
|
|
568
566
|
return this._previousTestResultId;
|
|
569
567
|
}
|
|
570
568
|
|
|
571
569
|
isAllowReportTestResultRetries() {
|
|
572
|
-
return Boolean(
|
|
570
|
+
return Boolean(this._options.company?.activePlan?.premiumFeatures?.allowReportTestResultRetries);
|
|
573
571
|
}
|
|
574
572
|
|
|
575
573
|
async onRetry() {
|
|
576
|
-
this._previousTestResultId = this.
|
|
574
|
+
this._previousTestResultId = this.testResultId;
|
|
577
575
|
|
|
578
576
|
if (!this.isAllowReportTestResultRetries()) {
|
|
579
577
|
return;
|