@testim/testim-cli 3.252.0 → 3.254.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 (97) hide show
  1. package/OverrideTestDataBuilder.js +1 -1
  2. package/agent/routers/cliJsCode/index.js +4 -4
  3. package/agent/routers/cliJsCode/router.js +46 -42
  4. package/agent/routers/cliJsCode/service.js +18 -13
  5. package/agent/routers/codim/router.js +14 -17
  6. package/agent/routers/codim/router.test.js +15 -14
  7. package/agent/routers/codim/service.js +1 -1
  8. package/agent/routers/general/index.js +4 -8
  9. package/agent/routers/hybrid/registerRoutes.js +18 -18
  10. package/agent/routers/index.js +9 -8
  11. package/agent/routers/playground/router.js +12 -11
  12. package/agent/routers/playground/service.js +19 -18
  13. package/agent/routers/standalone-browser/registerRoutes.js +10 -10
  14. package/cdpTestRunner.js +4 -3
  15. package/chromiumInstaller.js +4 -5
  16. package/cli/onExit.js +2 -2
  17. package/cli.js +7 -6
  18. package/cliAgentMode.js +4 -5
  19. package/codim/codim-cli.js +11 -10
  20. package/codim/hybrid-utils.js +1 -1
  21. package/codim/measure-perf.js +9 -6
  22. package/codim/template.js/tests/examples/01-simple-text-validation.test.js +6 -6
  23. package/codim/template.js/tests/examples/02-using-locators.test.js +13 -15
  24. package/codim/template.js/tests/examples/03-using-hooks.test.js +17 -19
  25. package/codim/template.js/tests/examples/04-skip-and-only.test.js +16 -17
  26. package/codim/template.js/tests/examples/05-multiple-windows.test.js +16 -17
  27. package/codim/template.js/webpack.config.js +1 -1
  28. package/codim/template.ts/webpack.config.js +3 -3
  29. package/commons/AbortError.js +4 -4
  30. package/commons/chrome-launcher.js +6 -6
  31. package/commons/constants.js +2 -0
  32. package/commons/detectDebugger.js +4 -2
  33. package/commons/getSessionPlayerRequire.js +2 -20
  34. package/commons/initializeUserWithAuth.js +2 -2
  35. package/commons/lazyRequire.js +10 -9
  36. package/commons/logger.js +4 -4
  37. package/commons/performance-logger.js +14 -8
  38. package/commons/prepareRunnerAndTestimStartUtils.js +6 -7
  39. package/commons/socket/baseSocketServiceSocketIO.js +32 -34
  40. package/commons/socket/realDataService.js +6 -5
  41. package/commons/socket/realDataServiceSocketIO.js +4 -4
  42. package/commons/socket/remoteStepService.js +4 -3
  43. package/commons/socket/remoteStepServiceSocketIO.js +11 -12
  44. package/commons/socket/socketService.js +50 -52
  45. package/commons/socket/testResultServiceSocketIO.js +11 -11
  46. package/commons/testimDesiredCapabilitiesBuilder.js +44 -0
  47. package/commons/testimNgrok.js +2 -2
  48. package/commons/testimNgrok.test.js +1 -1
  49. package/commons/testimServicesApi.js +37 -21
  50. package/commons/xhr2.js +97 -100
  51. package/credentialsManager.js +17 -20
  52. package/errors.js +5 -0
  53. package/fixLocalBuild.js +2 -0
  54. package/npm-shrinkwrap.json +4455 -1576
  55. package/package.json +9 -7
  56. package/player/WebdriverioWebDriverApi.js +7 -2
  57. package/player/appiumTestPlayer.js +102 -0
  58. package/player/chromeLauncherTestPlayer.js +0 -1
  59. package/player/seleniumTestPlayer.js +3 -2
  60. package/player/services/frameLocator.js +2 -1
  61. package/player/services/mobileFrameLocatorMock.js +32 -0
  62. package/player/services/playbackTimeoutCalculator.js +1 -0
  63. package/player/services/portSelector.js +10 -8
  64. package/player/services/tabService.js +29 -0
  65. package/player/services/tabServiceMock.js +166 -0
  66. package/player/stepActions/navigationStepAction.js +11 -10
  67. package/player/stepActions/sleepStepAction.js +4 -5
  68. package/player/stepActions/stepAction.js +15 -1
  69. package/player/stepActions/textStepAction.js +4 -11
  70. package/player/utils/stepActionUtils.js +4 -2
  71. package/player/utils/windowUtils.js +139 -125
  72. package/player/webdriver.js +40 -26
  73. package/processHandler.js +3 -3
  74. package/processHandler.test.js +1 -1
  75. package/reports/consoleReporter.js +3 -2
  76. package/reports/debugReporter.js +41 -39
  77. package/reports/jsonReporter.js +53 -50
  78. package/reports/junitReporter.js +1 -2
  79. package/reports/reporter.js +135 -136
  80. package/runOptions.js +8 -7
  81. package/runner.js +13 -0
  82. package/runners/ParallelWorkerManager.js +2 -0
  83. package/runners/TestPlanRunner.js +142 -74
  84. package/runners/buildCodeTests.js +38 -37
  85. package/runners/runnerUtils.js +3 -3
  86. package/services/lambdatestService.js +3 -5
  87. package/stepPlayers/cliJsStepPlayback.js +22 -17
  88. package/testRunHandler.js +8 -0
  89. package/testRunStatus.js +458 -460
  90. package/{utils.js → utils/index.js} +25 -117
  91. package/utils/promiseUtils.js +78 -0
  92. package/utils/stringUtils.js +96 -0
  93. package/{utils.test.js → utils/utils.test.js} +2 -2
  94. package/workers/BaseWorker.js +29 -20
  95. package/workers/WorkerAppium.js +123 -0
  96. package/workers/WorkerExtensionSingleBrowser.js +4 -4
  97. package/workers/WorkerSelenium.js +5 -2
package/testRunStatus.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const constants = require('./commons/constants');
4
4
  const { TESTIM_CONCURRENT_WORKER_COUNT } = require('./commons/config');
5
- const utils = require('./utils.js');
5
+ const utils = require('./utils');
6
6
  const reporter = require('./reports/reporter.js');
7
7
  const servicesApi = require('./commons/testimServicesApi.js');
8
8
  const gridService = require('./services/gridService');
@@ -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;