@testim/testim-cli 3.251.0 → 3.253.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.
Files changed (34) hide show
  1. package/agent/routers/index.js +2 -1
  2. package/agent/routers/playground/router.js +1 -1
  3. package/commons/chrome-launcher.js +6 -6
  4. package/commons/constants.js +2 -0
  5. package/commons/getSessionPlayerRequire.js +2 -20
  6. package/commons/initializeUserWithAuth.js +2 -2
  7. package/commons/mockNetworkRuleFileSchema.json +40 -38
  8. package/commons/testimDesiredCapabilitiesBuilder.js +43 -0
  9. package/commons/testimServicesApi.js +11 -2
  10. package/credentialsManager.js +17 -20
  11. package/npm-shrinkwrap.json +2104 -444
  12. package/package.json +4 -2
  13. package/player/WebdriverioWebDriverApi.js +7 -2
  14. package/player/appiumTestPlayer.js +102 -0
  15. package/player/seleniumTestPlayer.js +3 -2
  16. package/player/services/frameLocator.js +2 -1
  17. package/player/services/mobileFrameLocatorMock.js +32 -0
  18. package/player/services/playbackTimeoutCalculator.js +1 -0
  19. package/player/services/portSelector.js +10 -8
  20. package/player/services/tabService.js +29 -0
  21. package/player/stepActions/sfdcRecordedStepAction.js +2 -2
  22. package/player/stepActions/sfdcStepAction.js +2 -2
  23. package/player/stepActions/stepAction.js +15 -1
  24. package/player/utils/stepActionUtils.js +4 -2
  25. package/player/utils/windowUtils.js +138 -125
  26. package/player/webdriver.js +39 -25
  27. package/reports/debugReporter.js +41 -39
  28. package/reports/jsonReporter.js +53 -50
  29. package/reports/reporter.js +135 -136
  30. package/runOptions.js +2 -1
  31. package/runners/ParallelWorkerManager.js +2 -0
  32. package/testRunStatus.js +457 -459
  33. package/workers/BaseWorker.js +13 -6
  34. package/workers/WorkerAppium.js +123 -0
package/testRunStatus.js CHANGED
@@ -34,491 +34,489 @@ function runHook(fn, ...args) {
34
34
  });
35
35
  }
36
36
 
37
- const RunStatus = function (testInfoList, options, testPlanId, branchToUse) {
38
- this.options = options;
39
- this.options.runParams = this.options.runParams || {};
40
- this.startTime = null;
41
- this.fileUserParamsData = this.options.userParamsData;
42
- this.beforeSuiteParams = {};
43
- this.branchToUse = branchToUse;
44
- this.exportsGlobal = {};
45
- this.testInfoList = testInfoList;
46
-
47
- this.executionStartedPromise = Promise.resolve();
48
-
49
- const browserNames = utils.getUniqBrowsers(options, testInfoList);
50
- const runnerMode = options.lightweightMode ? options.lightweightMode.type : options.mode;
51
- this.execConfig = {
52
- parallel: TESTIM_CONCURRENT_WORKER_COUNT || options.parallel || 1,
53
- browser: browserNames,
54
- gitBranch,
55
- gitCommit,
56
- gitRepoUrl,
57
- runnerVersion,
58
- gridHost: options.host || options.gridData.host,
59
- testimBranch: branchToUse,
60
- canaryMode: options.canary,
61
- source: options.source,
62
- schedulerId: options.schedulerId,
63
- testPlanId,
64
- testPlans: options.testPlan,
65
- testLabels: options.label,
66
- testSuites: _.uniq(testInfoList.flatMap(test => test.testSuites)),
67
- testNames: options.name,
68
- testIds: options.testId,
69
- testConfigs: options.testConfigNames,
70
- testConfigIds: options.testConfigIds,
71
- port: options.port,
72
- browserTimeout: options.browserTimeout,
73
- timeout: options.timeout,
74
- newBrowserWaitTimeout: options.newBrowserWaitTimeout,
75
- tunnel: options.tunnel,
76
- tunnelPort: options.tunnelPort,
77
- tunnelHostHeader: options.tunnelHostHeader,
78
- runnerMode,
79
- gridId: options.gridId || options.gridData.gridId,
80
- gridName: options.grid || options.gridData.name,
81
- gridType: options.gridData.type,
82
- retentionDays: options.retentionDays,
83
- codeCoverageReportPath: options.codeCoverageReportPath,
84
- collectCodeCoverage: options.codeCoverageUrlFilter || options.collectCodeCoverage,
85
- sessionType: utils.getSessionType(options),
86
- };
87
-
88
- this.seleniumPerfStats = new SeleniumPerfStats();
89
- this.calcTestRunStatus();
90
- };
91
-
92
- RunStatus.prototype.waitForExecutionStartedFinished = function () {
93
- return this.executionStartedPromise;
94
- };
95
- RunStatus.prototype.getTestResult = function (resultId) {
96
- return this.testRunStatus[resultId];
97
- };
98
-
99
- RunStatus.prototype.addRetryTestResult = async function ({
100
- newResultId,
101
- originalTestResultId,
102
- previousTestResultId,
103
- projectId,
104
- executionId,
105
- retryCount = 1,
106
- }) {
107
- const orgTestResult = this.testRunStatus[originalTestResultId] || {};
108
- const {
109
- config, isTestsContainer, testId, name, testStatus,
110
- } = orgTestResult;
111
-
112
- const newTestResult = {
113
- originalTestResultId,
114
- previousTestResultId,
115
- config: _.cloneDeep(config),
116
- testId,
117
- status: 'QUEUED',
118
- name,
119
- resultId: newResultId,
120
- isTestsContainer,
121
- retryCount,
122
- testStatus,
123
- };
124
-
125
- this.testRunStatus[newResultId] = newTestResult;
126
-
127
- return servicesApi.addTestRetry({
128
- projectId,
129
- runId: executionId,
130
- testId,
131
- newResultId,
132
- originalTestResultId,
133
- previousTestResultId,
134
- testResult: newTestResult,
135
- });
136
- };
137
-
138
- RunStatus.prototype.getAllTestResults = function () {
139
- return this.testRunStatus;
140
- };
141
-
142
- RunStatus.prototype.testStart = function (wid, executionId, resultId, isRerun) {
143
- const test = this.getTestResult(resultId);
144
- test.workerId = wid;
145
- const isCodeMode = this.options.files.length > 0;
146
- reporter.onTestStarted(test, wid, isRerun, isCodeMode, resultId);
37
+ class RunStatus {
38
+ constructor(testInfoList, options, testPlanId, branchToUse) {
39
+ this.options = options;
40
+ this.options.runParams = this.options.runParams || {};
41
+ this.startTime = null;
42
+ this.fileUserParamsData = this.options.userParamsData;
43
+ this.beforeSuiteParams = {};
44
+ this.branchToUse = branchToUse;
45
+ this.exportsGlobal = {};
46
+ this.testInfoList = testInfoList;
47
+
48
+ this.executionStartedPromise = Promise.resolve();
49
+
50
+ const browserNames = utils.getUniqBrowsers(options, testInfoList);
51
+ const runnerMode = options.lightweightMode ? options.lightweightMode.type : options.mode;
52
+ this.execConfig = {
53
+ parallel: TESTIM_CONCURRENT_WORKER_COUNT || options.parallel || 1,
54
+ browser: browserNames,
55
+ gitBranch,
56
+ gitCommit,
57
+ gitRepoUrl,
58
+ runnerVersion,
59
+ gridHost: options.host || options.gridData.host,
60
+ testimBranch: branchToUse,
61
+ canaryMode: options.canary,
62
+ source: options.source,
63
+ schedulerId: options.schedulerId,
64
+ testPlanId,
65
+ testPlans: options.testPlan,
66
+ testLabels: options.label,
67
+ testSuites: _.uniq(testInfoList.flatMap(test => test.testSuites)),
68
+ testNames: options.name,
69
+ testIds: options.testId,
70
+ testConfigs: options.testConfigNames,
71
+ testConfigIds: options.testConfigIds,
72
+ port: options.port,
73
+ browserTimeout: options.browserTimeout,
74
+ timeout: options.timeout,
75
+ newBrowserWaitTimeout: options.newBrowserWaitTimeout,
76
+ tunnel: options.tunnel,
77
+ tunnelPort: options.tunnelPort,
78
+ tunnelHostHeader: options.tunnelHostHeader,
79
+ runnerMode,
80
+ gridId: options.gridId || options.gridData.gridId,
81
+ gridName: options.grid || options.gridData.name,
82
+ gridType: options.gridData.type,
83
+ retentionDays: options.retentionDays,
84
+ codeCoverageReportPath: options.codeCoverageReportPath,
85
+ collectCodeCoverage: options.codeCoverageUrlFilter || options.collectCodeCoverage,
86
+ sessionType: utils.getSessionType(options),
87
+ };
147
88
 
148
- return test;
149
- };
89
+ this.seleniumPerfStats = new SeleniumPerfStats();
90
+ this.calcTestRunStatus();
91
+ }
150
92
 
151
- RunStatus.prototype.updateTestStatusRunning = function (test, executionId, testRetryKey) {
152
- const { project: projectId, remoteRunId, projectData } = this.options;
153
- if (this.options.lightweightMode && this.options.lightweightMode.onlyTestIdsNoSuite) {
93
+ waitForExecutionStartedFinished() {
154
94
  return this.executionStartedPromise;
155
95
  }
156
96
 
157
- return servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults)
158
- .catch(err => {
159
- logger.error('failed to upload test data artifact (runner)', { err });
160
- return '';
161
- })
162
- .then(async (testDataUrl) => {
163
- const testConfig = _.cloneDeep(test.config);
164
- delete testConfig.testData;
165
- testConfig.testDataUrl = testDataUrl;
166
- await this.executionStartedPromise;
167
- return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
97
+ getTestResult(resultId) {
98
+ return this.testRunStatus[resultId];
99
+ }
100
+
101
+ async addRetryTestResult({
102
+ newResultId, originalTestResultId, previousTestResultId, projectId, executionId, retryCount = 1,
103
+ }) {
104
+ const orgTestResult = this.testRunStatus[originalTestResultId] || {};
105
+ const {
106
+ config, isTestsContainer, testId, name, testStatus,
107
+ } = orgTestResult;
108
+
109
+ const newTestResult = {
110
+ originalTestResultId,
111
+ previousTestResultId,
112
+ config: _.cloneDeep(config),
113
+ testId,
114
+ status: 'QUEUED',
115
+ name,
116
+ resultId: newResultId,
117
+ isTestsContainer,
118
+ retryCount,
119
+ testStatus,
120
+ };
121
+
122
+ this.testRunStatus[newResultId] = newTestResult;
123
+
124
+ return servicesApi.addTestRetry({
125
+ projectId,
126
+ runId: executionId,
127
+ testId,
128
+ newResultId,
129
+ originalTestResultId,
130
+ previousTestResultId,
131
+ testResult: newTestResult,
168
132
  });
169
- };
133
+ }
170
134
 
171
- RunStatus.prototype.testStartReport = function (test, executionId, testRetryKey) {
172
- if (utils.isQuarantineAndNotRemoteRun(test, this.options)) {
173
- return Promise.resolve();
135
+ getAllTestResults() {
136
+ return this.testRunStatus;
174
137
  }
175
- return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }), this.options.userData.loginData.token)
176
- .then(async params => {
177
- // Temporary Sapiens log (SUP-3192)
178
- if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
179
- logger.info('testRunStatus - testStartReport', {
180
- 'test.config.testData': test.config.testData,
181
- 'this.exportsGlobal': this.exportsGlobal,
182
- 'this.fileUserParamsData': this.fileUserParamsData,
183
- 'this.beforeSuiteParams': this.beforeSuiteParams,
184
- params,
185
- executionId,
186
- 'test.testId': test.testId,
187
- 'test.resultId': test.resultId,
188
- });
189
- }
190
- test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
191
- this.options.runParams[test.resultId] = test.config.testData;
192
- test.startTime = Date.now();
193
- await this.updateTestStatusRunning(test, executionId, testRetryKey);
194
-
195
- return test;
196
- }).catch(err => {
197
- logger.error('Failed to start test', { err });
198
- throw err;
199
- });
200
- };
201
-
202
- RunStatus.prototype.testStartAndReport = function (wid, executionId, resultId, isRerun, testRetryKey) {
203
- const test = this.testStart(wid, executionId, resultId, isRerun);
204
- return this.testStartReport(test, executionId, testRetryKey);
205
- };
206
-
207
- RunStatus.prototype.onGridSlot = function (executionId, resultId, gridInfo) {
208
- const test = this.getTestResult(resultId);
209
- test.config.gridInfo = Object.assign({}, gridInfo, { key: undefined, user: undefined });
210
- logger.info('on get grid info', { gridInfo: test.config.gridInfo });
211
- };
212
-
213
- RunStatus.prototype.reportTestStatus = function (workerId, result, test, isRerun) {
214
- const { name, testId, testStatus } = test;
215
- const { resultId, success } = result;
216
- if (testStatus === constants.testStatus.EVALUATING && featureAvailabilityService.isTestStatusEnabled) {
217
- reporter.onTestIgnored(workerId, test, `test in ${constants.testStatus.EVALUATING} status`);
218
- return;
138
+
139
+ testStart(wid, executionId, resultId, isRerun) {
140
+ const test = this.getTestResult(resultId);
141
+ test.workerId = wid;
142
+ const isCodeMode = this.options.files.length > 0;
143
+ reporter.onTestStarted(test, wid, isRerun, isCodeMode, resultId);
144
+
145
+ return test;
219
146
  }
220
- if (success) {
221
- reporter.onTestPassed(name);
222
- return;
147
+
148
+ updateTestStatusRunning(test, executionId, testRetryKey) {
149
+ const { project: projectId, remoteRunId, projectData } = this.options;
150
+ if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
151
+ return this.executionStartedPromise;
152
+ }
153
+
154
+ return servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults)
155
+ .catch(err => {
156
+ logger.error('failed to upload test data artifact (runner)', { err });
157
+ return '';
158
+ })
159
+ .then(async (testDataUrl) => {
160
+ const testConfig = _.cloneDeep(test.config);
161
+ delete testConfig.testData;
162
+ testConfig.testDataUrl = testDataUrl;
163
+ await this.executionStartedPromise;
164
+ return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
165
+ });
223
166
  }
224
- reporter.onTestFailed(test,
225
- test.failureReason,
226
- utils.getTestUrl(this.options.editorUrl,
227
- this.options.project,
228
- testId,
229
- resultId,
230
- this.branchToUse),
231
- testId,
232
- isRerun,
233
- resultId);
234
- };
235
-
236
- RunStatus.prototype.calcResultText = function (result) {
237
- return result.success ? constants.runnerTestStatus.PASSED : constants.runnerTestStatus.FAILED;
238
- };
239
-
240
- RunStatus.prototype.onTestIgnored = function (wid, resultId) {
241
- const test = this.getTestResult(resultId);
242
- reporter.onTestIgnored(wid, test, `test in ${constants.testStatus.QUARANTINE}`);
243
- };
244
-
245
- RunStatus.prototype.testEnd = function (wid, result, executionId, sessionId, isRerun) {
246
- const test = this.testRunStatus[result.resultId];
247
-
248
- const duration = (result.endTime - result.startTime) || 0;
249
- test.sessionId = sessionId;
250
- test.startTime = result.startTime || test.startTime || Date.now();
251
- test.duration = duration;
252
- result.duration = duration;
253
- test.failureReason = result.failureReason || result.reason;
254
- result.failureReason = test.failureReason;
255
- test.failurePath = result.failurePath;
256
- test.resultId = result.resultId;
257
- test.success = result.success;
258
-
259
- if (this.options.saveRCALocally) {
260
- mapFilesToLocalDrive(test, logger);
167
+
168
+ testStartReport(test, executionId, testRetryKey) {
169
+ if (utils.isQuarantineAndNotRemoteRun(test, this.options)) {
170
+ return Promise.resolve();
171
+ }
172
+ return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }), this.options.userData.loginData.token)
173
+ .then(async (params) => {
174
+ // Temporary Sapiens log (SUP-3192)
175
+ if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
176
+ logger.info('testRunStatus - testStartReport', {
177
+ 'test.config.testData': test.config.testData,
178
+ 'this.exportsGlobal': this.exportsGlobal,
179
+ 'this.fileUserParamsData': this.fileUserParamsData,
180
+ 'this.beforeSuiteParams': this.beforeSuiteParams,
181
+ params,
182
+ executionId,
183
+ 'test.testId': test.testId,
184
+ 'test.resultId': test.resultId,
185
+ });
186
+ }
187
+ test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
188
+ this.options.runParams[test.resultId] = test.config.testData;
189
+ test.startTime = Date.now();
190
+ await this.updateTestStatusRunning(test, executionId, testRetryKey);
191
+
192
+ return test;
193
+ }).catch(err => {
194
+ logger.error('Failed to start test', { err });
195
+ throw err;
196
+ });
261
197
  }
262
198
 
263
- test.resultUrl = utils.getTestUrl(this.options.editorUrl, this.options.project, test.testId, test.resultId, this.branchToUse);
264
- test.status = this.calcResultText(result);
265
-
266
- result.status = test.status;
267
- result.name = test.name;
268
- result.testStatus = test.testStatus;
269
- result.testId = result.testId || test.testId;
270
- result.testCreatorName = test.testCreatorName;
271
- result.testCreatorEmail = test.testCreatorEmail;
272
- result.testOwnerName = test.testOwnerName;
273
- result.testOwnerEmail = test.testOwnerEmail;
274
- result.testData = test.config && typeof test.config.testDataTotal === 'number' ? {
275
- total: test.config.testDataTotal,
276
- index: test.config.testDataIndex,
277
- } : {};
278
-
279
- this.reportTestStatus(wid, result, test, isRerun);
280
- const isCodeMode = this.options.files.length > 0;
281
- reporter.onTestFinished(test, wid, isRerun, isCodeMode);
282
-
283
- const afterMerge = Object.assign({}, this.exportsGlobal, result.exportsGlobal);
284
- // Temporary Sapiens log (SUP-3192)
285
- if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
286
- logger.info('testRunStatus - testEnd', {
287
- 'this.exportsGlobal': this.exportsGlobal,
288
- 'result.exportsGlobal': result.exportsGlobal,
289
- afterMerge,
290
- executionId,
291
- 'test.testId': test.testId,
292
- 'test.resultId': test.resultId,
293
- });
199
+ testStartAndReport(wid, executionId, resultId, isRerun, testRetryKey) {
200
+ const test = this.testStart(wid, executionId, resultId, isRerun);
201
+ return this.testStartReport(test, executionId, testRetryKey);
294
202
  }
295
- this.exportsGlobal = afterMerge;
296
- return test;
297
- };
298
203
 
299
- RunStatus.prototype.testEndReport = async function (test, executionId, result, testResultUpdates) {
300
- const globalParameters = result.exportsGlobal;
301
- try {
302
- try {
303
- await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }), this.options.userData.loginData.token);
304
- } catch (err) {
305
- logger.error('HOOK threw an error', { test: test.testId, err });
306
- // eslint-disable-next-line no-console
307
- console.error('HOOK threw an error', err); // show the customer that his hook failed.
204
+ onGridSlot(executionId, resultId, gridInfo) {
205
+ const test = this.getTestResult(resultId);
206
+ test.config.gridInfo = Object.assign({}, gridInfo, { key: undefined, user: undefined });
207
+ logger.info('on get grid info', { gridInfo: test.config.gridInfo });
208
+ }
209
+
210
+ reportTestStatus(workerId, result, test, isRerun) {
211
+ const { name, testId, testStatus } = test;
212
+ const { resultId, success } = result;
213
+ if (testStatus === constants.testStatus.EVALUATING && featureAvailabilityService.isTestStatusEnabled) {
214
+ reporter.onTestIgnored(workerId, test, `test in ${constants.testStatus.EVALUATING} status`);
215
+ return;
308
216
  }
309
- if (this.options.lightweightMode && this.options.lightweightMode.onlyTestIdsNoSuite) {
310
- return undefined;
217
+ if (success) {
218
+ reporter.onTestPassed(name);
219
+ return;
311
220
  }
221
+ reporter.onTestFailed(test,
222
+ test.failureReason,
223
+ utils.getTestUrl(this.options.editorUrl,
224
+ this.options.project,
225
+ testId,
226
+ resultId,
227
+ this.branchToUse),
228
+ testId,
229
+ isRerun,
230
+ resultId);
231
+ }
312
232
 
313
- return await servicesApi.updateTestStatus(this.options.project, executionId, test.testId, test.resultId, 'FINISHED', {
314
- startTime: test.startTime,
315
- endTime: result.endTime,
316
- success: test.success,
317
- failureReason: test.failureReason,
318
- remoteRunId: this.options.remoteRunId,
319
- ...testResultUpdates,
320
- }, 5);
321
- } catch (err) {
322
- logger.error('Failed to update test finished', { err });
323
- throw err;
233
+ calcResultText(result) {
234
+ return result.success ? constants.runnerTestStatus.PASSED : constants.runnerTestStatus.FAILED;
324
235
  }
325
- };
326
-
327
- RunStatus.prototype.testEndAndReport = function (wid, result, executionId, sessionId, isRerun, testResultUpdates) {
328
- const test = this.testEnd(wid, result, executionId, sessionId, isRerun);
329
- return this.testEndReport(test, executionId, result, testResultUpdates);
330
- };
331
-
332
- RunStatus.prototype.calcTestRunStatus = function () {
333
- const { options, testInfoList } = this;
334
- const companyId = options.company.companyId;
335
- this.testRunStatus = testInfoList.reduce((resultStatus, testInfo) => {
336
- resultStatus[testInfo.resultId] = {
337
- testId: testInfo.testId,
338
- status: utils.isQuarantineAndNotRemoteRun(testInfo, options) ? constants.runnerTestStatus.SKIPPED : constants.runnerTestStatus.QUEUED,
339
- name: testInfo.name,
340
- resultId: testInfo.resultId,
341
- isTestsContainer: testInfo.isTestsContainer,
342
- testStatus: testInfo.testStatus || constants.testStatus.DRAFT,
343
- testCreatorName: testInfo.creatorName,
344
- testCreatorEmail: testInfo.creatorEmail,
345
- testOwnerName: testInfo.testOwnerName,
346
- testOwnerEmail: testInfo.testOwnerEmail,
347
- testLabels: testInfo.testLabels,
348
- testSuites: testInfo.testSuites,
349
- allLabels: testInfo.allLabels,
350
- };
351
236
 
352
- const runConfig = options.browser ? utils.getRunConfigByBrowserName(options.browser, options.saucelabs, options.browserstack) : testInfo.runConfig;
237
+ onTestIgnored(wid, resultId) {
238
+ const test = this.getTestResult(resultId);
239
+ reporter.onTestIgnored(wid, test, `test in ${constants.testStatus.QUARANTINE}`);
240
+ }
353
241
 
354
- resultStatus[testInfo.resultId].config = Object.assign({}, this.execConfig, {
355
- companyId,
356
- testData: testInfo.testData && testInfo.testData.value ? testInfo.testData.value : null,
357
- });
358
- resultStatus[testInfo.resultId].config.isBeforeTestPlan = testInfo.isBeforeTestPlan;
359
- resultStatus[testInfo.resultId].config.isAfterTestPlan = testInfo.isAfterTestPlan;
360
- resultStatus[testInfo.resultId].config.testDataTotal = testInfo.testData && testInfo.testData.total ? testInfo.testData.total : null;
361
- resultStatus[testInfo.resultId].config.testDataIndex = testInfo.testData && testInfo.testData.index ? testInfo.testData.index : null;
362
- resultStatus[testInfo.resultId].config.baseUrl = options.baseUrl || testInfo.baseUrl || testInfo.testConfig.baseUrl;
363
- resultStatus[testInfo.resultId].config.testConfig = testInfo.overrideTestConfig || testInfo.testConfig;
364
- resultStatus[testInfo.resultId].config.browser = runConfig.browserValue.toLowerCase();
365
- return resultStatus;
366
- }, {});
367
- };
368
-
369
- RunStatus.prototype.executionStart = function (executionId, projectId, startTime, testPlanName, testNames) {
370
- logger.info('execution started', { executionId });
371
- const { options } = this;
372
- const { remoteRunId, projectData } = options;
373
-
374
- registerExitHook(() => Promise.all([
375
- gridService.keepAlive.end(projectId),
376
- servicesApi.reportExecutionFinished(
377
- 'ABORTED',
378
- executionId,
379
- projectId,
380
- false,
381
- undefined,
382
- remoteRunId,
383
- undefined,
384
- ),
385
- ]));
386
-
387
- this.startTime = startTime || Date.now();
388
- const runHooksProps = { projectId, executionId };
389
- if (featureFlags.flags.testNamesToBeforeSuiteHook.isEnabled()) {
390
- runHooksProps.testNames = testNames;
242
+ testEnd(wid, result, executionId, sessionId, isRerun) {
243
+ const test = this.testRunStatus[result.resultId];
244
+
245
+ const duration = (result.endTime - result.startTime) || 0;
246
+ test.sessionId = sessionId;
247
+ test.startTime = result.startTime || test.startTime || Date.now();
248
+ test.duration = duration;
249
+ result.duration = duration;
250
+ test.failureReason = result.failureReason || result.reason;
251
+ result.failureReason = test.failureReason;
252
+ test.failurePath = result.failurePath;
253
+ test.resultId = result.resultId;
254
+ test.success = result.success;
255
+
256
+ if (this.options.saveRCALocally) {
257
+ mapFilesToLocalDrive(test, logger);
258
+ }
259
+
260
+ test.resultUrl = utils.getTestUrl(this.options.editorUrl, this.options.project, test.testId, test.resultId, this.branchToUse);
261
+ test.status = this.calcResultText(result);
262
+
263
+ result.status = test.status;
264
+ result.name = test.name;
265
+ result.testStatus = test.testStatus;
266
+ result.testId = result.testId || test.testId;
267
+ result.testCreatorName = test.testCreatorName;
268
+ result.testCreatorEmail = test.testCreatorEmail;
269
+ result.testOwnerName = test.testOwnerName;
270
+ result.testOwnerEmail = test.testOwnerEmail;
271
+ result.testData = test.config && typeof test.config.testDataTotal === 'number' ? {
272
+ total: test.config.testDataTotal,
273
+ index: test.config.testDataIndex,
274
+ } : {};
275
+
276
+ this.reportTestStatus(wid, result, test, isRerun);
277
+ const isCodeMode = this.options.files.length > 0;
278
+ reporter.onTestFinished(test, wid, isRerun, isCodeMode);
279
+
280
+ const afterMerge = Object.assign({}, this.exportsGlobal, result.exportsGlobal);
281
+ // Temporary Sapiens log (SUP-3192)
282
+ if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
283
+ logger.info('testRunStatus - testEnd', {
284
+ 'this.exportsGlobal': this.exportsGlobal,
285
+ 'result.exportsGlobal': result.exportsGlobal,
286
+ afterMerge,
287
+ executionId,
288
+ 'test.testId': test.testId,
289
+ 'test.resultId': test.resultId,
290
+ });
291
+ }
292
+ this.exportsGlobal = afterMerge;
293
+ return test;
391
294
  }
392
- return runHook(options.beforeSuite, runHooksProps)
393
- .then(params => {
394
- const overrideTestDataBuilder = new OverrideTestDataBuilder(params, _.cloneDeep(this.testInfoList), projectId);
395
- this.testInfoList = overrideTestDataBuilder.overrideTestData();
396
- this.calcTestRunStatus();
397
- this.beforeSuiteParams = params;
398
-
399
- const { testInfoList } = this;
400
- const beforeTests = testInfoList.filter(test => test.isBeforeTestPlan);
401
- const tests = testInfoList.filter(test => !test.isBeforeTestPlan && !test.isAfterTestPlan);
402
- const afterTests = testInfoList.filter(test => test.isAfterTestPlan);
403
-
404
- const reportExecutionStarted = () => {
405
- const testResults = _.cloneDeep(this.testRunStatus);
406
- return Promise.map(Object.keys(testResults), testResultId => {
407
- const test = testResults[testResultId];
408
- const testData = test.config && test.config.testData;
409
- const testId = test.testId;
410
- return servicesApi.updateTestDataArtifact(projectId, testId, testResultId, testData, projectData.defaults)
411
- .then((testDataUrl) => {
412
- if (!testDataUrl) {
413
- return;
414
- }
415
- delete test.config.testData;
416
- test.config.testDataUrl = testDataUrl;
417
- });
418
- }).then(() => {
419
- const isLocalRun = Boolean(options.useLocalChromeDriver || options.useChromeLauncher);
420
- const data = {
421
- executionId,
422
- projectId,
423
- labels: testPlanName || [],
424
- startTime,
425
- executions: testResults,
426
- config: this.execConfig,
427
- resultLabels: options.resultLabels,
428
- remoteRunId: options.remoteRunId,
429
- localRunUserId: options.user,
430
- isLocalRun,
431
- intersections: options.intersections,
432
- };
433
- const ret = servicesApi.reportExecutionStarted(data);
434
- this.executionStartedPromise = ret;
435
- ret.catch(e => logger.error(e));
436
- return ret;
437
- });
438
- };
439
295
 
440
- return reportExecutionStarted()
441
- .catch(err => {
442
- logger.error('Failed to start suite', { err });
443
- // eslint-disable-next-line no-console
444
- console.error('Failed to start test run. Please contact support@testim.io');
445
- })
446
- .then(() => ({ beforeTests, tests, afterTests }));
447
- });
448
- };
449
-
450
- RunStatus.prototype.concatSeleniumPerfMarks = function (marks) {
451
- _.chain(marks)
452
- .keys()
453
- .each((key) => {
454
- if (this.seleniumPerfStats.marks[key]) {
455
- this.seleniumPerfStats.marks[key] = [...this.seleniumPerfStats.marks[key], ...marks[key]];
296
+ async testEndReport(test, executionId, result, testResultUpdates) {
297
+ const globalParameters = result.exportsGlobal;
298
+ try {
299
+ try {
300
+ await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }), this.options.userData.loginData.token);
301
+ } catch (err) {
302
+ logger.error('HOOK threw an error', { test: test.testId, err });
303
+ // eslint-disable-next-line no-console
304
+ console.error('HOOK threw an error', err); // show the customer that his hook failed.
456
305
  }
457
- })
458
- .value();
459
- };
460
-
461
- RunStatus.prototype.executionEnd = function (executionId) {
462
- const tests = utils.groupTestsByRetries(this.testRunStatus);
463
- const total = tests.length;
464
- const passed = tests.filter(({ status }) => status === constants.runnerTestStatus.PASSED).length;
465
- const skipped = tests.filter(({ status }) => status === constants.runnerTestStatus.SKIPPED).length;
466
- const failedInEvaluatingStatus = tests.filter(({ status, testStatus }) => status === constants.runnerTestStatus.FAILED && testStatus === constants.testStatus.EVALUATING).length;
467
-
468
- const resultExtraData = { ...this.seleniumPerfStats.getStats() };
469
- delete resultExtraData.seleniumPerfMarks;
470
-
471
- return runHook(this.options.afterSuite, {
472
- exportsGlobal: this.exportsGlobal,
473
- tests,
474
- total,
475
- passed,
476
- skipped,
477
- })
478
- .then(() => calculateCoverage(this.options, this.branchToUse, total, executionId))
479
- .then((coverageSummary) => {
480
- resultExtraData.coverageSummary = coverageSummary;
481
-
482
- if (this.options.lightweightMode && this.options.lightweightMode.onlyTestIdsNoSuite) {
306
+ if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
483
307
  return undefined;
484
308
  }
485
- return servicesApi.reportExecutionFinished(
486
- 'FINISHED',
309
+
310
+ return await servicesApi.updateTestStatus(this.options.project, executionId, test.testId, test.resultId, 'FINISHED', {
311
+ startTime: test.startTime,
312
+ endTime: result.endTime,
313
+ success: test.success,
314
+ failureReason: test.failureReason,
315
+ remoteRunId: this.options.remoteRunId,
316
+ ...testResultUpdates,
317
+ }, 5);
318
+ } catch (err) {
319
+ logger.error('Failed to update test finished', { err });
320
+ throw err;
321
+ }
322
+ }
323
+
324
+ testEndAndReport(wid, result, executionId, sessionId, isRerun, testResultUpdates) {
325
+ const test = this.testEnd(wid, result, executionId, sessionId, isRerun);
326
+ return this.testEndReport(test, executionId, result, testResultUpdates);
327
+ }
328
+
329
+ calcTestRunStatus() {
330
+ const { options, testInfoList } = this;
331
+ const companyId = options.company.companyId;
332
+ this.testRunStatus = testInfoList.reduce((resultStatus, testInfo) => {
333
+ resultStatus[testInfo.resultId] = {
334
+ testId: testInfo.testId,
335
+ status: utils.isQuarantineAndNotRemoteRun(testInfo, options) ? constants.runnerTestStatus.SKIPPED : constants.runnerTestStatus.QUEUED,
336
+ name: testInfo.name,
337
+ resultId: testInfo.resultId,
338
+ isTestsContainer: testInfo.isTestsContainer,
339
+ testStatus: testInfo.testStatus || constants.testStatus.DRAFT,
340
+ testCreatorName: testInfo.creatorName,
341
+ testCreatorEmail: testInfo.creatorEmail,
342
+ testOwnerName: testInfo.testOwnerName,
343
+ testOwnerEmail: testInfo.testOwnerEmail,
344
+ testLabels: testInfo.testLabels,
345
+ testSuites: testInfo.testSuites,
346
+ allLabels: testInfo.allLabels,
347
+ };
348
+
349
+ const runConfig = options.browser ? utils.getRunConfigByBrowserName(options.browser, options.saucelabs, options.browserstack) : testInfo.runConfig;
350
+
351
+ resultStatus[testInfo.resultId].config = Object.assign({}, this.execConfig, {
352
+ companyId,
353
+ testData: testInfo.testData?.value ? testInfo.testData.value : null,
354
+ });
355
+ resultStatus[testInfo.resultId].config.isBeforeTestPlan = testInfo.isBeforeTestPlan;
356
+ resultStatus[testInfo.resultId].config.isAfterTestPlan = testInfo.isAfterTestPlan;
357
+ resultStatus[testInfo.resultId].config.testDataTotal = testInfo.testData?.total || null;
358
+ resultStatus[testInfo.resultId].config.testDataIndex = testInfo.testData?.index || null;
359
+ resultStatus[testInfo.resultId].config.baseUrl = options.baseUrl || testInfo.baseUrl || testInfo.testConfig.baseUrl;
360
+ resultStatus[testInfo.resultId].config.testConfig = testInfo.overrideTestConfig || testInfo.testConfig;
361
+ resultStatus[testInfo.resultId].config.browser = runConfig.browserValue.toLowerCase();
362
+ return resultStatus;
363
+ }, {});
364
+ }
365
+
366
+ executionStart(executionId, projectId, startTime, testPlanName, testNames) {
367
+ logger.info('execution started', { executionId });
368
+ const { options } = this;
369
+ const { remoteRunId, projectData } = options;
370
+
371
+ registerExitHook(() => Promise.all([
372
+ gridService.keepAlive.end(projectId),
373
+ servicesApi.reportExecutionFinished(
374
+ 'ABORTED',
487
375
  executionId,
488
- this.options.project,
489
- total === (passed + skipped + failedInEvaluatingStatus),
490
- {
491
- tmsSuppressReporting: this.options.tmsSuppressReporting,
492
- tmsRunId: this.options.tmsRunId,
493
- tmsCustomFields: this.options.tmsCustomFields,
494
- },
495
- this.options.remoteRunId,
496
- resultExtraData,
497
- ).catch(err => {
498
- logger.error('Failed to update suite finished', { err });
499
- throw err;
376
+ projectId,
377
+ false,
378
+ undefined,
379
+ remoteRunId,
380
+ undefined
381
+ ),
382
+ ]));
383
+
384
+ this.startTime = startTime || Date.now();
385
+ const runHooksProps = { projectId, executionId };
386
+ if (featureFlags.flags.testNamesToBeforeSuiteHook.isEnabled()) {
387
+ runHooksProps.testNames = testNames;
388
+ }
389
+ return runHook(options.beforeSuite, runHooksProps)
390
+ .then(params => {
391
+ const overrideTestDataBuilder = new OverrideTestDataBuilder(params, _.cloneDeep(this.testInfoList), projectId);
392
+ this.testInfoList = overrideTestDataBuilder.overrideTestData();
393
+ this.calcTestRunStatus();
394
+ this.beforeSuiteParams = params;
395
+
396
+ const { testInfoList } = this;
397
+ const beforeTests = testInfoList.filter(test => test.isBeforeTestPlan);
398
+ const tests = testInfoList.filter(test => !test.isBeforeTestPlan && !test.isAfterTestPlan);
399
+ const afterTests = testInfoList.filter(test => test.isAfterTestPlan);
400
+
401
+ const reportExecutionStarted = () => {
402
+ const testResults = _.cloneDeep(this.testRunStatus);
403
+ return Promise.map(Object.keys(testResults), testResultId => {
404
+ const test = testResults[testResultId];
405
+ const testData = test.config?.testData;
406
+ const testId = test.testId;
407
+ return servicesApi.updateTestDataArtifact(projectId, testId, testResultId, testData, projectData.defaults)
408
+ .then((testDataUrl) => {
409
+ if (!testDataUrl) {
410
+ return;
411
+ }
412
+ delete test.config.testData;
413
+ test.config.testDataUrl = testDataUrl;
414
+ });
415
+ }).then(() => {
416
+ const isLocalRun = Boolean(options.useLocalChromeDriver || options.useChromeLauncher);
417
+ const data = {
418
+ executionId,
419
+ projectId,
420
+ labels: testPlanName || [],
421
+ startTime,
422
+ executions: testResults,
423
+ config: this.execConfig,
424
+ resultLabels: options.resultLabels,
425
+ remoteRunId: options.remoteRunId,
426
+ localRunUserId: options.user,
427
+ isLocalRun,
428
+ intersections: options.intersections,
429
+ };
430
+ const ret = servicesApi.reportExecutionStarted(data);
431
+ this.executionStartedPromise = ret;
432
+ ret.catch(e => logger.error(e));
433
+ return ret;
434
+ });
435
+ };
436
+
437
+ return reportExecutionStarted()
438
+ .catch(err => {
439
+ logger.error('Failed to start suite', { err });
440
+ // eslint-disable-next-line no-console
441
+ console.error('Failed to start test run. Please contact support@testim.io');
442
+ })
443
+ .then(() => ({ beforeTests, tests, afterTests }));
500
444
  });
501
- });
502
- };
503
-
504
- RunStatus.prototype.markAllQueuedTests = function (executionId, status, failureReason, success) {
505
- const queuedResultIds = Object.keys(this.testRunStatus).filter(resultId => this.getTestResult(resultId).status === 'QUEUED');
506
-
507
- return servicesApi.updateExecutionTests(
508
- executionId,
509
- ['QUEUED'],
510
- status,
511
- failureReason,
512
- success,
513
- this.startTime,
514
- null,
515
- this.options.project
516
- ).then(() => Promise.each(queuedResultIds, resultId => {
517
- const test = this.getTestResult(resultId);
518
- test.status = status;
519
- test.failureReason = failureReason;
520
- test.success = success;
521
- })).then(() => this.testRunStatus);
522
- };
445
+ }
446
+
447
+ concatSeleniumPerfMarks(marks) {
448
+ _.chain(marks)
449
+ .keys()
450
+ .each((key) => {
451
+ if (this.seleniumPerfStats.marks[key]) {
452
+ this.seleniumPerfStats.marks[key] = [...this.seleniumPerfStats.marks[key], ...marks[key]];
453
+ }
454
+ })
455
+ .value();
456
+ }
457
+
458
+ executionEnd(executionId) {
459
+ const tests = utils.groupTestsByRetries(this.testRunStatus);
460
+ const total = tests.length;
461
+ const passed = tests.filter(({ status }) => status === constants.runnerTestStatus.PASSED).length;
462
+ const skipped = tests.filter(({ status }) => status === constants.runnerTestStatus.SKIPPED).length;
463
+ const failedInEvaluatingStatus = tests.filter(({ status, testStatus }) => status === constants.runnerTestStatus.FAILED && testStatus === constants.testStatus.EVALUATING).length;
464
+
465
+ const resultExtraData = { ...this.seleniumPerfStats.getStats() };
466
+ delete resultExtraData.seleniumPerfMarks;
467
+
468
+ return runHook(this.options.afterSuite, {
469
+ exportsGlobal: this.exportsGlobal,
470
+ tests,
471
+ total,
472
+ passed,
473
+ skipped,
474
+ })
475
+ .then(() => calculateCoverage(this.options, this.branchToUse, total, executionId))
476
+ .then((coverageSummary) => {
477
+ resultExtraData.coverageSummary = coverageSummary;
478
+
479
+ if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
480
+ return undefined;
481
+ }
482
+ return servicesApi.reportExecutionFinished(
483
+ 'FINISHED',
484
+ executionId,
485
+ this.options.project,
486
+ total === (passed + skipped + failedInEvaluatingStatus),
487
+ {
488
+ tmsSuppressReporting: this.options.tmsSuppressReporting,
489
+ tmsRunId: this.options.tmsRunId,
490
+ tmsCustomFields: this.options.tmsCustomFields,
491
+ },
492
+ this.options.remoteRunId,
493
+ resultExtraData
494
+ ).catch(err => {
495
+ logger.error('Failed to update suite finished', { err });
496
+ throw err;
497
+ });
498
+ });
499
+ }
500
+
501
+ markAllQueuedTests(executionId, status, failureReason, success) {
502
+ const queuedResultIds = Object.keys(this.testRunStatus).filter(resultId => this.getTestResult(resultId).status === 'QUEUED');
503
+
504
+ return servicesApi.updateExecutionTests(
505
+ executionId,
506
+ ['QUEUED'],
507
+ status,
508
+ failureReason,
509
+ success,
510
+ this.startTime,
511
+ null,
512
+ this.options.project
513
+ ).then(() => Promise.each(queuedResultIds, resultId => {
514
+ const test = this.getTestResult(resultId);
515
+ test.status = status;
516
+ test.failureReason = failureReason;
517
+ test.success = success;
518
+ })).then(() => this.testRunStatus);
519
+ }
520
+ }
523
521
 
524
522
  module.exports = RunStatus;