@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/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 Promise = require('bluebird');
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 {*} test
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
- getTestStatus() {
65
+ get testStatus() {
66
66
  return this._testStatus;
67
67
  }
68
68
 
69
- getRunMode() {
69
+ get runMode() {
70
70
  return this._options.mode;
71
71
  }
72
72
 
73
- getAutomationMode() {
73
+ get automationMode() {
74
74
  return this._code ? 'codeful' : 'codeless';
75
75
  }
76
76
 
77
- getCode() {
77
+ get code() {
78
78
  return this._code;
79
79
  }
80
80
 
81
- getRunConfig() {
81
+ get runConfig() {
82
82
  return this._runConfig;
83
83
  }
84
84
 
85
- getTestResultId() {
85
+ get testResultId() {
86
86
  return this._testResultId;
87
87
  }
88
88
 
89
- getBaseUrl() {
89
+ get baseUrl() {
90
90
  return this._baseUrl;
91
91
  }
92
92
 
93
- getExecutionId() {
93
+ get executionId() {
94
94
  return this._executionId;
95
95
  }
96
96
 
97
- getExecutionName() {
97
+ get executionName() {
98
98
  return this._executionName;
99
99
  }
100
100
 
101
- getNativeAppData() {
102
- if (this._nativeApp && !this._options.baseUrl) {
103
- return this._nativeApp;
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
- const url = this._options.baseUrl || this.getBaseUrl();
107
- if (!url) {
108
- return null;
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
- const [packageName, activity] = url.split(':');
112
- return {
113
- packageName,
114
- activity,
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
- getBranch() {
137
+ get branch() {
119
138
  return this._branch;
120
139
  }
121
140
 
122
- getSfdcCredential() {
141
+ get sfdcCredential() {
123
142
  return this._options.sfdcCredential;
124
143
  }
125
144
 
126
- getRemoteRunId() {
145
+ get remoteRunId() {
127
146
  return this._remoteRunId;
128
147
  }
129
148
 
130
- getOverrideTestConfigId() {
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.getRemoteRunId(),
152
- previousTestResultId: this.getPreviousTestResultId(),
153
- testRetryCount: this.getRetryCount(),
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.getRunData();
198
+ const runData = this.runData;
195
199
  runRequestParams.lightweightMode.isRunDataSentInUrl = canSendRunDataOverUrl(runData);
196
200
  const stringifiedLength = JSON.stringify(runData).length;
197
- const testId = this.getTestId();
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.getTestId()}) run URL length: ${url.length}`);
222
+ logger.info(`Test (${this.testId}) run URL length: ${url.length}`);
231
223
  return url;
232
224
  }
233
225
 
234
- setSessionId(sessionId) {
226
+ set sessionId(sessionId) {
235
227
  this._sessionId = sessionId;
236
228
  }
237
229
 
238
- getSessionId() {
230
+ get sessionId() {
239
231
  return this._sessionId;
240
232
  }
241
233
 
242
- getTestId() {
234
+ get testId() {
243
235
  return this._testId;
244
236
  }
245
237
 
246
- getTestName() {
238
+ get testName() {
247
239
  return this._testName;
248
240
  }
249
241
 
250
- getRunParams() {
242
+ get runParams() {
251
243
  return this._options.runParams[this._testResultId] || {};
252
244
  }
253
245
 
254
- getRunData() {
246
+ get runData() {
255
247
  return {
256
- userParamsData: this.getRunParams(),
248
+ userParamsData: this.runParams,
257
249
  overrideTestConfigId: this._overrideTestConfigId || null,
258
250
  };
259
251
  }
260
252
 
261
- clearTestResult() {
262
- const runData = this.getRunData();
263
- if (this.getRunMode() === CLI_MODE.EXTENSION) {
264
- runData.code = this.getCode();
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 Promise.resolve();
265
+ return undefined;
274
266
  }
275
267
 
276
- this.clearTestResultFinished = testimServicesApi.uploadRunDataArtifact(this._options.project, this._testId, this._testResultId, runData)
277
- .catch(err => {
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
- .then(async (runDataUrl) => {
282
- // make sure the execution is created by now
283
- await this._testRunStatus.waitForExecutionStartedFinished();
284
- // we probably can save this backend call by initializing the execution
285
- return testimServicesApi.clearTestResult(this._options.project, this._testResultId, this._testId, {
286
- name: this._testName,
287
- resultId: this._testResultId,
288
- status: 'pending',
289
- retryCount: this._retryCount,
290
- runDataUrl, // links the run data url to the test.
291
- runData: runDataUrl ? undefined : runData, // put runData in mongo if we fail to upload to S3.
292
- testRetryKey: this.getRetryKey(),
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
- getRetryKey() {
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.getRetryKey());
355
+ return testResult.testRetryKey && (testResult.testRetryKey !== this.retryKey);
359
356
  }
360
357
 
361
358
  validateRunConfig() {
362
- const baseUrl = this.getBaseUrl();
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.getRetryKey()}`, {
392
- resultId: this.getTestResultId(),
393
- testId: this.getTestId(),
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._testResultId;
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.getTestResultId(),
403
- testId: this.getTestId(),
404
- testRetryKey: this.getRetryKey(),
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._testId;
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
- testimServicesApi.getTestResults(testId, resultId, projectId, branch).then(restResult => {
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
- }).catch((err) => {
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._testId;
442
- const resultId = this._testResultId;
443
- const projectId = this._options.project;
444
- const branch = this.getBranch();
445
- return testimServicesApi.getTestResults(testId, resultId, projectId, branch)
446
- .then(testResult => {
447
- const expectedStatuses = ['running', 'completed'];
448
- if (expectedStatuses.includes(testResult.status)) {
449
- logger.info(`get status: ${testResult.status} after not get test started status`, { testId, resultId, branch });
450
- return testResult;
451
- }
452
- logger.error(`test not start test status: ${testResult.status} (expected [${expectedStatuses.join(', ')}])`, { testId, resultId, branch });
453
- throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
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 (!this._options.disableSockets) {
463
- return Promise.resolve(testResultService.leaveTestResult(this._testResultId, this._testId));
452
+ async onCompletedCleanup() {
453
+ if (this._options.disableSockets) {
454
+ return undefined;
464
455
  }
465
- return Promise.resolve();
456
+ return await testResultService.leaveTestResult(this._testResultId, this._testId);
466
457
  }
467
458
 
468
- onCompleted() {
459
+ async onCompleted() {
469
460
  let onConnected;
470
- return new Promise(resolve => {
471
- if (this._wasCompletedOnStartedCheck && !this.isRetryKeyMismatch(this._wasCompletedOnStartedCheck)) {
472
- logger.info('test was already completed in on started check', {
473
- resultId: this.getTestResultId(),
474
- testId: this.getTestId(),
475
- });
476
- resolve(this._wasCompletedOnStartedCheck);
477
- return;
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
- if (!this._options.disableSockets) {
481
- testResultService.listenToTestResult(this._testResultId, this._testId, testResult => {
482
- if (this.isRetryKeyMismatch(testResult)) {
483
- logger.warn(`ignoring result update for on completed due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
484
- resultId: this.getTestResultId(),
485
- testId: this.getTestId(),
486
- });
487
- return;
488
- }
489
- if (testResult.status === 'completed') {
490
- testResult.resultId = this._testResultId;
491
- resolve(testResult);
492
- }
493
- });
494
- }
495
- const debounceDelay = this._options.disableSockets ? 0 : Math.floor(10000 + (Math.random() * 5000));
496
- const maxWait = this._options.disableSockets ? 0 : Math.floor(60000 + (Math.random() * 15000));
497
- onConnected = _.debounce(() => testimServicesApi.getTestResults(this._testId, this._testResultId, this._options.project, this.getBranch())
498
- .then(testResult => {
499
- if (this.isRetryKeyMismatch(testResult)) {
500
- logger.warn(`ignoring result update for on completed (in reconnect) due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
501
- resultId: this.getTestResultId(),
502
- testId: this.getTestId(),
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
- if (testResult && testResult.status === 'completed') {
507
- logger.info('Socket reconnected - Test complete', { testId: this._testId, resultId: this._testResultId, projectId: this._options.project });
508
- testResult.resultId = this._testResultId;
509
- resolve(testResult);
510
- return true;
511
- }
512
- return false;
513
- })
514
- .catch(err => logger.warn('Error while trying to check status on socket connect', err)), debounceDelay, { maxWait });
515
- if (!this._options.disableSockets) {
516
- testResultService.on('socket-connected', onConnected);
517
- } else {
518
- const waitForTestEnd = () => {
519
- setTimeout(async () => {
520
- try {
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
- } else {
527
+ } catch (err) {
528
+ logger.error('failed to check is complete', { err });
529
529
  waitForTestEnd();
530
530
  }
531
- } catch (err) {
532
- logger.error('failed to check is complete', { err });
533
- waitForTestEnd();
534
- }
535
- }, 3000);
536
- };
537
- waitForTestEnd();
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._testResultId, step => {
549
- remoteStepPlayback.executeStep(this._options, browser, step, this._testResultId);
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
- getRetryCount() {
561
+ get retryCount() {
564
562
  return this._retryCount;
565
563
  }
566
564
 
567
- getPreviousTestResultId() {
565
+ get previousTestResultId() {
568
566
  return this._previousTestResultId;
569
567
  }
570
568
 
571
569
  isAllowReportTestResultRetries() {
572
- return Boolean(_(this._options).get('company.activePlan.premiumFeatures.allowReportTestResultRetries'));
570
+ return Boolean(this._options.company?.activePlan?.premiumFeatures?.allowReportTestResultRetries);
573
571
  }
574
572
 
575
573
  async onRetry() {
576
- this._previousTestResultId = this._testResultId;
574
+ this._previousTestResultId = this.testResultId;
577
575
 
578
576
  if (!this.isAllowReportTestResultRetries()) {
579
577
  return;