@testim/testim-cli 3.226.0 → 3.229.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,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const _ = require('lodash');
3
4
  const testimCustomToken = require('./commons/testimCustomToken');
4
5
  const remoteStepService = require('./commons/socket/remoteStepService');
5
6
  const testResultService = require('./commons/socket/testResultService');
@@ -9,7 +10,6 @@ const logger = require('./commons/logger').getLogger('test-run-handler');
9
10
  const perf = require('./commons/performance-logger');
10
11
  const { URL } = require('url');
11
12
  const Promise = require('bluebird');
12
- const _ = require('lodash');
13
13
  const remoteStepPlayback = require('./stepPlayers/remoteStepPlayback');
14
14
  const utils = require('./utils');
15
15
  const config = require('./commons/config');
@@ -21,555 +21,560 @@ const RETRIES_ON_TIMEOUT = 3;
21
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
22
  const canSendRunDataOverUrl = (runData) => JSON.stringify(runData).length < MAX_LIGHTWEIGHT_MODE_RUN_DATA_SIZE;
23
23
 
24
- const TestRun = function (executionId, executionName, test, options, branchToUse, testRunStatus) {
25
- this._executionId = executionId;
26
- this._executionName = executionName;
27
- this._testStatus = test.testStatus;
28
- this._testId = test.testId;
29
- this._testName = test.name;
30
- this._testResultId = test.resultId;
31
- this._code = test.code;
32
- this._baseUrl = options.baseUrl || test.baseUrl || test.testConfig.baseUrl;
33
- this._nativeApp = test.nativeApp;
34
- this._overrideTestConfigId = test.overrideTestConfig && test.overrideTestConfig.id;
35
- this._options = options;
36
- this._branch = branchToUse;
37
- this._maxRetryCount = options.retries;
38
- this._remoteRunId = options.remoteRunId;
39
- this._retryCount = 1;
40
- this._timeoutRetryCount = 1;
41
- this._totalRetryCount = 1;
42
-
43
- this._testRunStatus = testRunStatus;
44
- const shouldUpdateConfig = !(test.runConfig && test.runConfig.isMobileWeb) && options.browser;
45
- this._runConfig = shouldUpdateConfig ? utils.getRunConfigByBrowserName(options.browser, options.saucelabs, options.browserstack) : test.runConfig;
46
- this.clearTestResultFinished = Promise.resolve();
47
-
48
- this.seleniumPerfStats = new SeleniumPerfStats();
49
- };
50
-
51
- TestRun.prototype.waitForExecutionStartedFinished = function () {
52
- return this._testRunStatus.waitForExecutionStartedFinished() && this.clearTestResultFinished;
53
- };
54
-
55
- TestRun.prototype.getTestStatus = function () {
56
- return this._testStatus;
57
- };
58
-
59
- TestRun.prototype.getRunMode = function () {
60
- return this._options.mode;
61
- };
62
- TestRun.prototype.getAutomationMode = function () {
63
- return this._code ? 'codeful' : 'codeless';
64
- };
65
-
66
- TestRun.prototype.getCode = function () {
67
- return this._code;
68
- };
69
-
70
- TestRun.prototype.getRunConfig = function () {
71
- return this._runConfig;
72
- };
73
-
74
- TestRun.prototype.getTestResultId = function () {
75
- return this._testResultId;
76
- };
77
-
78
- TestRun.prototype.getBaseUrl = function () {
79
- return this._baseUrl;
80
- };
81
-
82
- TestRun.prototype.getExecutionId = function () {
83
- return this._executionId;
84
- };
85
-
86
- TestRun.prototype.getExecutionName = function () {
87
- return this._executionName;
88
- };
89
-
90
- TestRun.prototype.getNativeAppData = function () {
91
- if (this._nativeApp && !this._options.baseUrl) {
92
- return this._nativeApp;
93
- }
94
-
95
- const url = this._options.baseUrl || this.getBaseUrl();
96
- if (!url) {
97
- return null;
98
- }
99
-
100
- const [packageName, activity] = url.split(':');
101
- return {
102
- packageName,
103
- activity,
104
- };
105
- };
106
-
107
- TestRun.prototype.getBranch = function () {
108
- return this._branch;
109
- };
110
-
111
- TestRun.prototype.getRemoteRunId = function () {
112
- return this._remoteRunId;
113
- };
114
-
115
- TestRun.prototype.getOverrideTestConfigId = function () {
116
- return this._overrideTestConfigId;
117
- };
118
-
119
- TestRun.prototype.markClearBrowser = function () {
120
- this.clearBrowser = true;
121
- };
122
-
123
-
124
- TestRun.prototype.getRunRequestParams = async function () {
125
- const customTokenV3 = await testimCustomToken.getCustomTokenV3();
126
- const runRequestParams = {
127
- tokenV3: customTokenV3,
128
- refreshToken: testimCustomToken.getRefreshToken(),
129
- projectId: this._options.project,
130
- executionId: this._executionId,
131
- executionName: this._executionName,
132
- testId: this._testId,
133
- resultId: this._testResultId,
134
- baseUrl: this._baseUrl,
135
- branch: this._branch,
136
- servicesUrl: config.EXTENSION_SERVICES_HOST,
137
- remoteRunId: this.getRemoteRunId(),
138
- previousTestResultId: this.getPreviousTestResultId(),
139
- testRetryCount: this.getRetryCount(),
140
- };
141
-
142
- if (this._code) {
143
- runRequestParams.isCodeMode = true;
144
- runRequestParams.testName = this._testName;
145
- }
146
-
147
- if (this._options.shouldMonitorPerformance) {
148
- runRequestParams.shouldMonitorPerformance = true;
149
- }
150
-
151
- if (this._options.company) {
152
- runRequestParams.companyId = this._options.company.companyId;
153
- runRequestParams.onprem = this._options.company.onprem;
154
- runRequestParams.storageBaseUrl = this._options.company.storageBaseUrl;
155
- runRequestParams.storageType = this._options.company.storageType;
156
- runRequestParams.planType = this._options.company.planType;
157
- runRequestParams.isPOC = this._options.company.isPOC;
158
- runRequestParams.isStartUp = this._options.company.isStartUp;
159
- }
160
-
161
- if (this._options.collectCodeCoverage) {
162
- if (this._options.codeCoverageUrlFilter) {
163
- runRequestParams.codeCoverageUrlFilter = this._options.codeCoverageUrlFilter;
164
- } else {
165
- runRequestParams.codeCoverageUrlFilter = `${this._baseUrl}*`;
166
- }
24
+ class TestRun {
25
+ constructor(executionId, executionName, test, options, branchToUse, testRunStatus) {
26
+ this._executionId = executionId;
27
+ this._executionName = executionName;
28
+ this._testStatus = test.testStatus;
29
+ this._testId = test.testId;
30
+ this._testName = test.name;
31
+ this._testResultId = test.resultId;
32
+ this._code = test.code;
33
+ this._baseUrl = options.baseUrl || test.baseUrl || test.testConfig.baseUrl;
34
+ this._nativeApp = test.nativeApp;
35
+ this._overrideTestConfigId = test.overrideTestConfig && test.overrideTestConfig.id;
36
+ this._options = options;
37
+ this._branch = branchToUse;
38
+ this._maxRetryCount = options.retries;
39
+ this._remoteRunId = options.remoteRunId;
40
+ this._retryCount = 1;
41
+ this._timeoutRetryCount = 1;
42
+ this._totalRetryCount = 1;
43
+
44
+ this._testRunStatus = testRunStatus;
45
+ const shouldUpdateConfig = !(test.runConfig && test.runConfig.isMobileWeb) && options.browser;
46
+ this._runConfig = shouldUpdateConfig ? utils.getRunConfigByBrowserName(options.browser, options.saucelabs, options.browserstack) : test.runConfig;
47
+ this.clearTestResultFinished = Promise.resolve();
48
+
49
+ this.seleniumPerfStats = new SeleniumPerfStats();
167
50
  }
168
51
 
169
- if (this._options.disableMockNetwork) {
170
- runRequestParams.disableMockNetwork = this._options.disableMockNetwork;
171
- analytics.trackWithCIUser('user-disable-mock');
52
+ waitForExecutionStartedFinished() {
53
+ return this._testRunStatus.waitForExecutionStartedFinished() && this.clearTestResultFinished;
172
54
  }
173
55
 
174
- if (this._options.lightweightMode) {
175
- runRequestParams.lightweightMode = this._options.lightweightMode;
56
+ getTestStatus() {
57
+ return this._testStatus;
176
58
  }
177
59
 
178
- if (this._options.lightweightMode && this._options.lightweightMode.general) {
179
- runRequestParams.company = this._options.company;
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)`);
60
+ getRunMode() {
61
+ return this._options.mode;
62
+ }
63
+
64
+ getAutomationMode() {
65
+ return this._code ? 'codeful' : 'codeless';
66
+ }
67
+
68
+ getCode() {
69
+ return this._code;
70
+ }
71
+
72
+ getRunConfig() {
73
+ return this._runConfig;
74
+ }
75
+
76
+ getTestResultId() {
77
+ return this._testResultId;
78
+ }
79
+
80
+ getBaseUrl() {
81
+ return this._baseUrl;
82
+ }
83
+
84
+ getExecutionId() {
85
+ return this._executionId;
86
+ }
87
+
88
+ getExecutionName() {
89
+ return this._executionName;
90
+ }
91
+
92
+ getNativeAppData() {
93
+ if (this._nativeApp && !this._options.baseUrl) {
94
+ return this._nativeApp;
95
+ }
96
+
97
+ const url = this._options.baseUrl || this.getBaseUrl();
98
+ if (!url) {
99
+ return null;
187
100
  }
188
- runRequestParams.isLocalRun = Boolean(this._options.useLocalChromeDriver || this._options.useChromeLauncher);
101
+
102
+ const [packageName, activity] = url.split(':');
103
+ return {
104
+ packageName,
105
+ activity,
106
+ };
107
+ }
108
+
109
+ getBranch() {
110
+ return this._branch;
189
111
  }
190
112
 
191
- if (this._options.lightweightMode && this._options.lightweightMode.preloadTests && this._options.useChromeLauncher) {
192
- const preloadedTests = await preloadTests(this._options);
193
- runRequestParams.preloadedTest = preloadedTests[runRequestParams.testId];
113
+ getRemoteRunId() {
114
+ return this._remoteRunId;
194
115
  }
195
116
 
196
- if (this.clearBrowser) {
197
- runRequestParams.clearBrowser = true;
117
+ getOverrideTestConfigId() {
118
+ return this._overrideTestConfigId;
198
119
  }
199
120
 
200
- if (this._options.localRCASaver) {
201
- runRequestParams.localRCASaver = this._options.localRCASaver;
121
+ markClearBrowser() {
122
+ this.clearBrowser = true;
202
123
  }
203
124
 
204
- return runRequestParams;
205
- };
125
+ async getRunRequestParams() {
126
+ const customTokenV3 = await testimCustomToken.getCustomTokenV3();
127
+ const runRequestParams = {
128
+ tokenV3: customTokenV3,
129
+ refreshToken: testimCustomToken.getRefreshToken(),
130
+ projectId: this._options.project,
131
+ executionId: this._executionId,
132
+ executionName: this._executionName,
133
+ testId: this._testId,
134
+ resultId: this._testResultId,
135
+ baseUrl: this._baseUrl,
136
+ branch: this._branch,
137
+ servicesUrl: config.EXTENSION_SERVICES_HOST,
138
+ remoteRunId: this.getRemoteRunId(),
139
+ previousTestResultId: this.getPreviousTestResultId(),
140
+ testRetryCount: this.getRetryCount(),
141
+ };
206
142
 
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;
212
- };
143
+ if (this._code) {
144
+ runRequestParams.isCodeMode = true;
145
+ runRequestParams.testName = this._testName;
146
+ }
213
147
 
214
- TestRun.prototype.setSessionId = function (sessionId) {
215
- this._sessionId = sessionId;
216
- };
148
+ if (this._options.shouldMonitorPerformance) {
149
+ runRequestParams.shouldMonitorPerformance = true;
150
+ }
151
+
152
+ if (this._options.company) {
153
+ runRequestParams.companyId = this._options.company.companyId;
154
+ runRequestParams.onprem = this._options.company.onprem;
155
+ runRequestParams.storageBaseUrl = this._options.company.storageBaseUrl;
156
+ runRequestParams.storageType = this._options.company.storageType;
157
+ runRequestParams.planType = this._options.company.planType;
158
+ runRequestParams.isPOC = this._options.company.isPOC;
159
+ runRequestParams.isStartUp = this._options.company.isStartUp;
160
+ }
161
+
162
+ if (this._options.collectCodeCoverage) {
163
+ if (this._options.codeCoverageUrlFilter) {
164
+ runRequestParams.codeCoverageUrlFilter = this._options.codeCoverageUrlFilter;
165
+ } else {
166
+ runRequestParams.codeCoverageUrlFilter = `${this._baseUrl}*`;
167
+ }
168
+ }
217
169
 
218
- TestRun.prototype.getSessionId = function () {
219
- return this._sessionId;
220
- };
170
+ if (this._options.disableMockNetwork) {
171
+ runRequestParams.disableMockNetwork = this._options.disableMockNetwork;
172
+ analytics.trackWithCIUser('user-disable-mock');
173
+ }
221
174
 
222
- TestRun.prototype.getTestId = function () {
223
- return this._testId;
224
- };
175
+ if (this._options.lightweightMode) {
176
+ runRequestParams.lightweightMode = this._options.lightweightMode;
177
+ }
225
178
 
226
- TestRun.prototype.getTestName = function () {
227
- return this._testName;
228
- };
179
+ if (this._options.lightweightMode && this._options.lightweightMode.general) {
180
+ runRequestParams.company = this._options.company;
181
+ const runData = this.getRunData();
182
+ runRequestParams.lightweightMode.isRunDataSentInUrl = canSendRunDataOverUrl(runData);
183
+ if (runRequestParams.lightweightMode.isRunDataSentInUrl) {
184
+ runRequestParams.runData = runData;
185
+ logger.info(`Run data sent as URL param, test id: ${this.getTestId()} run data length: ${JSON.stringify(runData).length}`);
186
+ } else {
187
+ 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)`);
188
+ }
189
+ runRequestParams.isLocalRun = Boolean(this._options.useLocalChromeDriver || this._options.useChromeLauncher);
190
+ }
229
191
 
230
- TestRun.prototype.getRunParams = function () {
231
- return this._options.runParams[this._testResultId] || {};
232
- };
192
+ if (this._options.lightweightMode && this._options.lightweightMode.preloadTests && this._options.useChromeLauncher) {
193
+ const preloadedTests = await preloadTests(this._options);
194
+ runRequestParams.preloadedTest = preloadedTests[runRequestParams.testId];
195
+ }
233
196
 
197
+ if (this.clearBrowser) {
198
+ runRequestParams.clearBrowser = true;
199
+ }
234
200
 
235
- TestRun.prototype.getRunData = function () {
236
- return {
237
- userParamsData: this.getRunParams(),
238
- overrideTestConfigId: this._overrideTestConfigId || null,
239
- };
240
- };
201
+ if (this._options.localRCASaver) {
202
+ runRequestParams.localRCASaver = this._options.localRCASaver;
203
+ }
241
204
 
242
- TestRun.prototype.clearTestResult = function () {
243
- const runData = this.getRunData();
244
- if (this.getRunMode() === CLI_MODE.EXTENSION) {
245
- runData.code = this.getCode();
205
+ return runRequestParams;
246
206
  }
247
207
 
248
- if (this._options.mockNetworkRules) {
249
- runData.mockNetworkRules = this._options.mockNetworkRules;
208
+ async getRunTestUrl() {
209
+ const runRequestParams = await this.getRunRequestParams();
210
+ const url = `https://run.testim.io/?params=${encodeURIComponent(JSON.stringify(runRequestParams))}`;
211
+ logger.info(`Test (${this.getTestId()}) run URL length: ${url.length}`);
212
+ return url;
250
213
  }
251
- const mustClearPreviousStepResults = !this.isAllowReportTestResultRetries() && (this._timeoutRetryCount > 1 || this._retryCount > 1);
252
214
 
253
- if (this._options.lightweightMode && this._options.lightweightMode.disableResults &&
254
- !mustClearPreviousStepResults && canSendRunDataOverUrl(runData)) {
255
- return Promise.resolve();
215
+ setSessionId(sessionId) {
216
+ this._sessionId = sessionId;
256
217
  }
257
218
 
258
- this.clearTestResultFinished = testimServicesApi.uploadRunDataArtifact(this._options.project, this._testId, this._testResultId, runData)
259
- .catch(err => {
260
- logger.error('failed to upload run data artifact (runner)', { err });
261
- return '';
262
- })
263
- .then(async (runDataUrl) => {
264
- // make sure the execution is created by now
265
- await this._testRunStatus.waitForExecutionStartedFinished();
266
- // we probably can save this backend call by initializing the execution
267
- return testimServicesApi.clearTestResult(this._options.project, this._testResultId, this._testId, {
268
- name: this._testName,
269
- resultId: this._testResultId,
270
- status: 'pending',
271
- retryCount: this._retryCount,
272
- runDataUrl, // links the run data url to the test.
273
- runData: runDataUrl ? undefined : runData, // put runData in mongo if we fail to upload to S3.
274
- testRetryKey: this.getRetryKey(),
275
- });
276
- });
277
- return this.clearTestResultFinished;
278
- };
279
-
280
- TestRun.prototype.hasMoreRetries = function () {
281
- return this._retryCount < this._maxRetryCount;
282
- };
283
-
284
- TestRun.prototype.getRetryKey = function () {
285
- return `${this._retryCount}:${this._timeoutRetryCount}`;
286
- };
287
-
288
- TestRun.prototype.startNewRetry = function () {
289
- this._retryCount++;
290
- this._timeoutRetryCount = 1;
291
- return this.onRetry();
292
- };
293
-
294
- TestRun.prototype.runTestUsingCDP = async function (cdpTestRunner) {
295
- perf.log('runTestUsingCDP');
296
- const { targetInfos } = await cdpTestRunner.cdpCommand('Target.getTargets') || { targetInfos: [] };
297
- const { targetId: extensionTargetId } = targetInfos.find(target => target.type === 'background_page' && target.title === 'Testim Editor') || {};
298
- const { targetId: AUTTargetId } = targetInfos.find(target => target.type === 'page') || {};
299
- if (!extensionTargetId) {
300
- throw new Error('testim extension not found');
301
- }
302
- if (!AUTTargetId) {
303
- throw new Error('AUT target not found');
304
- }
305
-
306
- try {
307
- perf.log('before Target.attachToTarget');
308
- const [extensionSession, runRequestParams] = await Promise.all([
309
- cdpTestRunner.cdpCommand('Target.attachToTarget', { targetId: extensionTargetId, flatten: true }),
310
- this.getRunRequestParams(),
311
- ]);
312
- const { sessionId: extensionSessionId } = extensionSession || {};
313
- perf.log('before Runtime.evaluate');
314
-
315
- await utils.runWithRetries(async () => {
316
- const { result } = await cdpTestRunner.cdpCommand('Runtime.evaluate', { expression: 'typeof runTestimTest !== \'undefined\'', returnByValue: true }, extensionSessionId);
317
- if (!result.value) {
318
- throw new Error('runTestimTest not available on global scope');
319
- }
320
- }, 100, 30);
219
+ getSessionId() {
220
+ return this._sessionId;
221
+ }
222
+
223
+ getTestId() {
224
+ return this._testId;
225
+ }
226
+
227
+ getTestName() {
228
+ return this._testName;
229
+ }
230
+
231
+ getRunParams() {
232
+ return this._options.runParams[this._testResultId] || {};
233
+ }
234
+
235
+ getRunData() {
236
+ return {
237
+ userParamsData: this.getRunParams(),
238
+ overrideTestConfigId: this._overrideTestConfigId || null,
239
+ };
240
+ }
241
+
242
+ clearTestResult() {
243
+ const runData = this.getRunData();
244
+ if (this.getRunMode() === CLI_MODE.EXTENSION) {
245
+ runData.code = this.getCode();
246
+ }
321
247
 
322
- perf.log('after wait for runTestimTest function');
323
- const { result } = await cdpTestRunner.cdpCommand('Runtime.evaluate', { expression: `runTestimTest(${JSON.stringify(runRequestParams)})`, awaitPromise: true, returnByValue: true }, extensionSessionId);
324
- if (result.subtype === 'error') {
325
- throw new Error(result.description);
248
+ if (this._options.mockNetworkRules) {
249
+ runData.mockNetworkRules = this._options.mockNetworkRules;
326
250
  }
327
- perf.log('after Runtime.evaluate');
328
- return result.value;
329
- } catch (err) {
330
- logger.error('error running test using CDP', { err });
331
- throw new Error('Error running test using CDP');
251
+ const mustClearPreviousStepResults = !this.isAllowReportTestResultRetries() && (this._timeoutRetryCount > 1 || this._retryCount > 1);
252
+
253
+ if (this._options.lightweightMode && this._options.lightweightMode.disableResults &&
254
+ !mustClearPreviousStepResults && canSendRunDataOverUrl(runData)) {
255
+ return Promise.resolve();
256
+ }
257
+
258
+ this.clearTestResultFinished = testimServicesApi.uploadRunDataArtifact(this._options.project, this._testId, this._testResultId, runData)
259
+ .catch(err => {
260
+ logger.error('failed to upload run data artifact (runner)', { err });
261
+ return '';
262
+ })
263
+ .then(async (runDataUrl) => {
264
+ // make sure the execution is created by now
265
+ await this._testRunStatus.waitForExecutionStartedFinished();
266
+ // we probably can save this backend call by initializing the execution
267
+ return testimServicesApi.clearTestResult(this._options.project, this._testResultId, this._testId, {
268
+ name: this._testName,
269
+ resultId: this._testResultId,
270
+ status: 'pending',
271
+ retryCount: this._retryCount,
272
+ runDataUrl, // links the run data url to the test.
273
+ runData: runDataUrl ? undefined : runData, // put runData in mongo if we fail to upload to S3.
274
+ testRetryKey: this.getRetryKey(),
275
+ });
276
+ });
277
+ return this.clearTestResultFinished;
278
+ }
279
+
280
+ hasMoreRetries() {
281
+ return this._retryCount < this._maxRetryCount;
332
282
  }
333
- };
334
283
 
335
- TestRun.prototype.isRetryKeyMismatch = function (testResult) {
336
- return testResult.testRetryKey && (testResult.testRetryKey !== this.getRetryKey());
337
- };
284
+ getRetryKey() {
285
+ return `${this._retryCount}:${this._timeoutRetryCount}`;
286
+ }
338
287
 
339
- TestRun.prototype.validateRunConfig = function () {
340
- const baseUrl = this.getBaseUrl();
341
- const { browserValue } = this.getRunConfig();
288
+ startNewRetry() {
289
+ this._retryCount++;
290
+ this._timeoutRetryCount = 1;
291
+ return this.onRetry();
292
+ }
293
+
294
+ async runTestUsingCDP(cdpTestRunner) {
295
+ perf.log('runTestUsingCDP');
296
+ const { targetInfos } = await cdpTestRunner.cdpCommand('Target.getTargets') || { targetInfos: [] };
297
+ const { targetId: extensionTargetId } = targetInfos.find(target => target.type === 'background_page' && target.title === 'Testim Editor') || {};
298
+ const { targetId: AUTTargetId } = targetInfos.find(target => target.type === 'page') || {};
299
+ if (!extensionTargetId) {
300
+ throw new Error('testim extension not found');
301
+ }
302
+ if (!AUTTargetId) {
303
+ throw new Error('AUT target not found');
304
+ }
342
305
 
343
- if (baseUrl && browserValue === 'safari') {
344
- let parsedUrl;
345
306
  try {
346
- parsedUrl = new URL(baseUrl);
307
+ perf.log('before Target.attachToTarget');
308
+ const [extensionSession, runRequestParams] = await Promise.all([
309
+ cdpTestRunner.cdpCommand('Target.attachToTarget', { targetId: extensionTargetId, flatten: true }),
310
+ this.getRunRequestParams(),
311
+ ]);
312
+ const { sessionId: extensionSessionId } = extensionSession || {};
313
+ perf.log('before Runtime.evaluate');
314
+
315
+ await utils.runWithRetries(async () => {
316
+ const { result } = await cdpTestRunner.cdpCommand('Runtime.evaluate', { expression: 'typeof runTestimTest !== \'undefined\'', returnByValue: true }, extensionSessionId);
317
+ if (!result.value) {
318
+ throw new Error('runTestimTest not available on global scope');
319
+ }
320
+ }, 100, 30);
321
+
322
+ perf.log('after wait for runTestimTest function');
323
+ const { result } = await cdpTestRunner.cdpCommand(
324
+ 'Runtime.evaluate',
325
+ { expression: `runTestimTest(${JSON.stringify(runRequestParams)})`, awaitPromise: true, returnByValue: true },
326
+ extensionSessionId,
327
+ );
328
+ if (result.subtype === 'error') {
329
+ throw new Error(result.description);
330
+ }
331
+ perf.log('after Runtime.evaluate');
332
+ return result.value;
347
333
  } catch (err) {
348
- // ignore invalid URLs (missing http:// or https:// prefix)
349
- return;
334
+ logger.error('error running test using CDP', { err });
335
+ throw new Error('Error running test using CDP');
350
336
  }
351
- const { username, password } = parsedUrl;
337
+ }
352
338
 
353
- if (username || password) {
354
- throw new Error('Basic authentication in URL is not supported in Safari');
355
- }
339
+ isRetryKeyMismatch(testResult) {
340
+ return testResult.testRetryKey && (testResult.testRetryKey !== this.getRetryKey());
356
341
  }
357
- };
358
342
 
359
- TestRun.prototype.onStarted = function (startTimeout) {
360
- return new Promise(resolve => {
361
- // We can't leave the test result as it may remove other listeners as well
362
- // We need to implement an .off(resultId, listener) method
363
- let reportedStart = false;
364
- const resolveResult = testResult => {
365
- if (reportedStart) {
366
- return;
367
- }
368
- if (this.isRetryKeyMismatch(testResult)) {
369
- logger.warn(`ignoring result update for on started due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
370
- resultId: this.getTestResultId(),
371
- testId: this.getTestId(),
372
- });
343
+ validateRunConfig() {
344
+ const baseUrl = this.getBaseUrl();
345
+ const { browserValue } = this.getRunConfig();
346
+
347
+ if (baseUrl && browserValue === 'safari') {
348
+ let parsedUrl;
349
+ try {
350
+ parsedUrl = new URL(baseUrl);
351
+ } catch (err) {
352
+ // ignore invalid URLs (missing http:// or https:// prefix)
373
353
  return;
374
354
  }
375
- if (['running', 'completed'].includes(testResult.status)) {
376
- testResult.resultId = this._testResultId;
377
- if (testResult.status === 'completed') {
378
- logger.info('setting _wasCompletedOnStartedCheck to true', {
379
- testResult,
380
- resultId: this.getTestResultId(),
381
- testId: this.getTestId(),
382
- testRetryKey: this.getRetryKey(),
383
- });
384
- this._wasCompletedOnStartedCheck = testResult;
385
- }
386
- reportedStart = true;
387
- resolve(testResult);
388
- }
389
- };
390
- if (this._options.disableSockets) {
391
- const timeLimit = Date.now() + startTimeout;
392
- const checkIfDone = () => {
393
- if (Date.now() > timeLimit) {
394
- return;
395
- }
396
- const testId = this._testId;
397
- const resultId = this._testResultId;
398
- const projectId = this._options.project;
399
- const branch = this.getBranch();
400
-
401
- testimServicesApi.getTestResults(testId, resultId, projectId, branch).then(restResult => {
402
- resolveResult(restResult);
403
- if (!reportedStart) {
404
- setTimeout(checkIfDone, 3000);
405
- }
406
- }).catch((err) => {
407
- logger.error('failed to check if done', { err });
408
- setTimeout(checkIfDone, 3000);
409
- });
410
- };
411
- setTimeout(checkIfDone, 3000);
412
- } else {
413
- testResultService.listenToTestResult(this._testResultId, this._testId, resolveResult);
414
- }
415
- });
416
- };
417
-
418
- TestRun.prototype.checkViaRestAPIIfTestStarted = function () {
419
- const testId = this._testId;
420
- const resultId = this._testResultId;
421
- const projectId = this._options.project;
422
- const branch = this.getBranch();
423
- return testimServicesApi.getTestResults(testId, resultId, projectId, branch)
424
- .then(testResult => {
425
- const expectedStatuses = ['running', 'completed'];
426
- if (expectedStatuses.includes(testResult.status)) {
427
- logger.info(`get status: ${testResult.status} after not get test started status`, { testId, resultId, branch });
428
- return testResult;
355
+ const { username, password } = parsedUrl;
356
+
357
+ if (username || password) {
358
+ throw new Error('Basic authentication in URL is not supported in Safari');
429
359
  }
430
- logger.error(`test not start test status: ${testResult.status} (expected [${expectedStatuses.join(', ')}])`, { testId, resultId, branch });
431
- throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
432
- })
433
- .catch(err => {
434
- logger.error('failed to get test result after test start timeout', { err, testId, resultId, branch });
435
- throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
436
- });
437
- };
438
-
439
- TestRun.prototype.onCompletedCleanup = function () {
440
- if (!this._options.disableSockets) {
441
- return Promise.resolve(testResultService.leaveTestResult(this._testResultId, this._testId));
442
- }
443
- return Promise.resolve();
444
- };
445
-
446
- TestRun.prototype.onCompleted = function () {
447
- let onConnected;
448
- return new Promise(resolve => {
449
- if (this._wasCompletedOnStartedCheck && !this.isRetryKeyMismatch(this._wasCompletedOnStartedCheck)) {
450
- logger.info('test was already completed in on started check', {
451
- resultId: this.getTestResultId(),
452
- testId: this.getTestId(),
453
- });
454
- resolve(this._wasCompletedOnStartedCheck);
455
- return;
456
360
  }
361
+ }
457
362
 
458
- if (!this._options.disableSockets) {
459
- testResultService.listenToTestResult(this._testResultId, this._testId, testResult => {
363
+ onStarted(startTimeout) {
364
+ return new Promise(resolve => {
365
+ // We can't leave the test result as it may remove other listeners as well
366
+ // We need to implement an .off(resultId, listener) method
367
+ let reportedStart = false;
368
+ const resolveResult = testResult => {
369
+ if (reportedStart) {
370
+ return;
371
+ }
460
372
  if (this.isRetryKeyMismatch(testResult)) {
461
- logger.warn(`ignoring result update for on completed due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
373
+ logger.warn(`ignoring result update for on started due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
462
374
  resultId: this.getTestResultId(),
463
375
  testId: this.getTestId(),
464
376
  });
465
377
  return;
466
378
  }
467
- if (testResult.status === 'completed') {
379
+ if (['running', 'completed'].includes(testResult.status)) {
468
380
  testResult.resultId = this._testResultId;
381
+ if (testResult.status === 'completed') {
382
+ logger.info('setting _wasCompletedOnStartedCheck to true', {
383
+ testResult,
384
+ resultId: this.getTestResultId(),
385
+ testId: this.getTestId(),
386
+ testRetryKey: this.getRetryKey(),
387
+ });
388
+ this._wasCompletedOnStartedCheck = testResult;
389
+ }
390
+ reportedStart = true;
469
391
  resolve(testResult);
470
392
  }
471
- });
472
- }
473
- const debounceDelay = this._options.disableSockets ? 0 : Math.floor(10000 + (Math.random() * 5000));
474
- const maxWait = this._options.disableSockets ? 0 : Math.floor(60000 + (Math.random() * 15000));
475
- onConnected = _.debounce(() => testimServicesApi.getTestResults(this._testId, this._testResultId, this._options.project, this.getBranch())
476
- .then(testResult => {
477
- if (this.isRetryKeyMismatch(testResult)) {
478
- logger.warn(`ignoring result update for on completed (in reconnect) due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
479
- resultId: this.getTestResultId(),
480
- testId: this.getTestId(),
393
+ };
394
+ if (this._options.disableSockets) {
395
+ const timeLimit = Date.now() + startTimeout;
396
+ const checkIfDone = () => {
397
+ if (Date.now() > timeLimit) {
398
+ return;
399
+ }
400
+ const testId = this._testId;
401
+ const resultId = this._testResultId;
402
+ const projectId = this._options.project;
403
+ const branch = this.getBranch();
404
+
405
+ testimServicesApi.getTestResults(testId, resultId, projectId, branch).then(restResult => {
406
+ resolveResult(restResult);
407
+ if (!reportedStart) {
408
+ setTimeout(checkIfDone, 3000);
409
+ }
410
+ }).catch((err) => {
411
+ logger.error('failed to check if done', { err });
412
+ setTimeout(checkIfDone, 3000);
481
413
  });
482
- return false;
483
- }
484
- if (testResult && testResult.status === 'completed') {
485
- logger.info('Socket reconnected - Test complete', { testId: this._testId, resultId: this._testResultId, projectId: this._options.project });
486
- testResult.resultId = this._testResultId;
487
- resolve(testResult);
488
- return true;
414
+ };
415
+ setTimeout(checkIfDone, 3000);
416
+ } else {
417
+ testResultService.listenToTestResult(this._testResultId, this._testId, resolveResult);
418
+ }
419
+ });
420
+ }
421
+
422
+ checkViaRestAPIIfTestStarted() {
423
+ const testId = this._testId;
424
+ const resultId = this._testResultId;
425
+ const projectId = this._options.project;
426
+ const branch = this.getBranch();
427
+ return testimServicesApi.getTestResults(testId, resultId, projectId, branch)
428
+ .then(testResult => {
429
+ const expectedStatuses = ['running', 'completed'];
430
+ if (expectedStatuses.includes(testResult.status)) {
431
+ logger.info(`get status: ${testResult.status} after not get test started status`, { testId, resultId, branch });
432
+ return testResult;
489
433
  }
490
- return false;
434
+ logger.error(`test not start test status: ${testResult.status} (expected [${expectedStatuses.join(', ')}])`, { testId, resultId, branch });
435
+ throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
491
436
  })
492
- .catch(err => logger.warn('Error while trying to check status on socket connect', err)), debounceDelay, { maxWait });
437
+ .catch(err => {
438
+ logger.error('failed to get test result after test start timeout', { err, testId, resultId, branch });
439
+ throw new Error(timeoutMessages.TEST_START_TIMEOUT_MSG);
440
+ });
441
+ }
442
+
443
+ onCompletedCleanup() {
493
444
  if (!this._options.disableSockets) {
494
- testResultService.on('socket-connected', onConnected);
495
- } else {
496
- const waitForTestEnd = () => {
497
- setTimeout(async () => {
498
- try {
499
- const { isComplete } = await testimServicesApi.isTestResultCompleted(this._testResultId, this._options.project, this.getRetryKey());
500
- if (isComplete) {
501
- const isDone = await onConnected();
502
- if (!isDone) {
503
- logger.warn('onConnected returned false even though isComplete was true');
445
+ return Promise.resolve(testResultService.leaveTestResult(this._testResultId, this._testId));
446
+ }
447
+ return Promise.resolve();
448
+ }
449
+
450
+ onCompleted() {
451
+ let onConnected;
452
+ return new Promise(resolve => {
453
+ if (this._wasCompletedOnStartedCheck && !this.isRetryKeyMismatch(this._wasCompletedOnStartedCheck)) {
454
+ logger.info('test was already completed in on started check', {
455
+ resultId: this.getTestResultId(),
456
+ testId: this.getTestId(),
457
+ });
458
+ resolve(this._wasCompletedOnStartedCheck);
459
+ return;
460
+ }
461
+
462
+ if (!this._options.disableSockets) {
463
+ testResultService.listenToTestResult(this._testResultId, this._testId, testResult => {
464
+ if (this.isRetryKeyMismatch(testResult)) {
465
+ logger.warn(`ignoring result update for on completed due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
466
+ resultId: this.getTestResultId(),
467
+ testId: this.getTestId(),
468
+ });
469
+ return;
470
+ }
471
+ if (testResult.status === 'completed') {
472
+ testResult.resultId = this._testResultId;
473
+ resolve(testResult);
474
+ }
475
+ });
476
+ }
477
+ const debounceDelay = this._options.disableSockets ? 0 : Math.floor(10000 + (Math.random() * 5000));
478
+ const maxWait = this._options.disableSockets ? 0 : Math.floor(60000 + (Math.random() * 15000));
479
+ onConnected = _.debounce(() => testimServicesApi.getTestResults(this._testId, this._testResultId, this._options.project, this.getBranch())
480
+ .then(testResult => {
481
+ if (this.isRetryKeyMismatch(testResult)) {
482
+ logger.warn(`ignoring result update for on completed (in reconnect) due to retry key mismatch, got ${testResult.testRetryKey}, current is ${this.getRetryKey()}`, {
483
+ resultId: this.getTestResultId(),
484
+ testId: this.getTestId(),
485
+ });
486
+ return false;
487
+ }
488
+ if (testResult && testResult.status === 'completed') {
489
+ logger.info('Socket reconnected - Test complete', { testId: this._testId, resultId: this._testResultId, projectId: this._options.project });
490
+ testResult.resultId = this._testResultId;
491
+ resolve(testResult);
492
+ return true;
493
+ }
494
+ return false;
495
+ })
496
+ .catch(err => logger.warn('Error while trying to check status on socket connect', err)), debounceDelay, { maxWait });
497
+ if (!this._options.disableSockets) {
498
+ testResultService.on('socket-connected', onConnected);
499
+ } else {
500
+ const waitForTestEnd = () => {
501
+ setTimeout(async () => {
502
+ try {
503
+ const { isComplete } = await testimServicesApi.isTestResultCompleted(this._testResultId, this._options.project, this.getRetryKey());
504
+ if (isComplete) {
505
+ const isDone = await onConnected();
506
+ if (!isDone) {
507
+ logger.warn('onConnected returned false even though isComplete was true');
508
+ waitForTestEnd();
509
+ }
510
+ } else {
504
511
  waitForTestEnd();
505
512
  }
506
- } else {
513
+ } catch (err) {
514
+ logger.error('failed to check is complete', { err });
507
515
  waitForTestEnd();
508
516
  }
509
- } catch (err) {
510
- logger.error('failed to check is complete', { err });
511
- waitForTestEnd();
512
- }
513
- }, 3000);
514
- };
515
- waitForTestEnd();
517
+ }, 3000);
518
+ };
519
+ waitForTestEnd();
520
+ }
521
+ })
522
+ .tap(() => this.onCompletedCleanup())
523
+ .finally(() => onConnected && !this._options.disableSockets && testResultService.off('socket-connected', onConnected));
524
+ }
525
+
526
+ listenToRemoteStep(browser) {
527
+ remoteStepService.listenToRemoteStep(this._testResultId, step => {
528
+ remoteStepPlayback.executeStep(this._options, browser, step, this._testResultId);
529
+ });
530
+ }
531
+
532
+ hasMoreTimeoutRetries() {
533
+ const maxRetryCount = this._options.disableTimeoutRetry ? 1 : RETRIES_ON_TIMEOUT;
534
+ return this._timeoutRetryCount < maxRetryCount;
535
+ }
536
+
537
+ startNewTimeoutRetry() {
538
+ this._timeoutRetryCount++;
539
+ return this.onRetry();
540
+ }
541
+
542
+ getRetryCount() {
543
+ return this._retryCount;
544
+ }
545
+
546
+ getPreviousTestResultId() {
547
+ return this._previousTestResultId;
548
+ }
549
+
550
+ isAllowReportTestResultRetries() {
551
+ return Boolean(_(this._options).get('company.activePlan.premiumFeatures.allowReportTestResultRetries'));
552
+ }
553
+
554
+ async onRetry() {
555
+ this._previousTestResultId = this._testResultId;
556
+
557
+ if (!this.isAllowReportTestResultRetries()) {
558
+ return;
559
+ }
560
+
561
+ this._totalRetryCount++;
562
+ this._originalTestResultId = this._originalTestResultId || this._previousTestResultId;
563
+ this._testResultId = utils.guid();
564
+
565
+ if (this._options.lightweightMode && this._options.lightweightMode.onlyTestIdsNoSuite) {
566
+ return;
516
567
  }
517
- })
518
- .tap(() => this.onCompletedCleanup())
519
- .finally(() => onConnected && !this._options.disableSockets && testResultService.off('socket-connected', onConnected));
520
- };
521
-
522
- TestRun.prototype.listenToRemoteStep = function (browser) {
523
- remoteStepService.listenToRemoteStep(this._testResultId, step => {
524
- remoteStepPlayback.executeStep(this._options, browser, step, this._testResultId);
525
- });
526
- };
527
-
528
- TestRun.prototype.hasMoreTimeoutRetries = function () {
529
- const maxRetryCount = this._options.disableTimeoutRetry ? 1 : RETRIES_ON_TIMEOUT;
530
- return this._timeoutRetryCount < maxRetryCount;
531
- };
532
-
533
- TestRun.prototype.startNewTimeoutRetry = function () {
534
- this._timeoutRetryCount++;
535
- return this.onRetry();
536
- };
537
-
538
- TestRun.prototype.getRetryCount = function () {
539
- return this._retryCount;
540
- };
541
-
542
- TestRun.prototype.getPreviousTestResultId = function () {
543
- return this._previousTestResultId;
544
- };
545
-
546
- TestRun.prototype.isAllowReportTestResultRetries = function () {
547
- return Boolean(_(this._options).get('company.activePlan.premiumFeatures.allowReportTestResultRetries'));
548
- };
549
-
550
- TestRun.prototype.onRetry = async function () {
551
- this._previousTestResultId = this._testResultId;
552
-
553
- if (!this.isAllowReportTestResultRetries()) {
554
- return;
555
- }
556
-
557
- this._totalRetryCount++;
558
- this._originalTestResultId = this._originalTestResultId || this._previousTestResultId;
559
- this._testResultId = utils.guid();
560
-
561
- if (this._options.lightweightMode && this._options.lightweightMode.onlyTestIdsNoSuite) {
562
- return;
563
- }
564
-
565
- await this._testRunStatus.addRetryTestResult({
566
- retryCount: this._totalRetryCount,
567
- executionId: this._executionId,
568
- projectId: this._options.project,
569
- newResultId: this._testResultId,
570
- originalTestResultId: this._originalTestResultId,
571
- previousTestResultId: this._previousTestResultId,
572
- });
573
- };
568
+
569
+ await this._testRunStatus.addRetryTestResult({
570
+ retryCount: this._totalRetryCount,
571
+ executionId: this._executionId,
572
+ projectId: this._options.project,
573
+ newResultId: this._testResultId,
574
+ originalTestResultId: this._originalTestResultId,
575
+ previousTestResultId: this._previousTestResultId,
576
+ });
577
+ }
578
+ }
574
579
 
575
580
  module.exports = TestRun;