@testim/testim-cli 3.261.0 → 3.262.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 (32) hide show
  1. package/commons/testimServicesApi.js +10 -7
  2. package/executionQueue.js +9 -1
  3. package/npm-shrinkwrap.json +2 -2
  4. package/package.json +1 -1
  5. package/player/stepActions/RefreshStepAction.js +7 -4
  6. package/player/stepActions/baseJsStepAction.js +45 -46
  7. package/player/stepActions/dropFileStepAction.js +11 -12
  8. package/player/stepActions/evaluateExpressionStepAction.js +32 -33
  9. package/player/stepActions/extensionOnlyStepAction.js +3 -4
  10. package/player/stepActions/extractTextStepAction.js +8 -10
  11. package/player/stepActions/hoverStepAction.js +3 -3
  12. package/player/stepActions/locateStepAction.js +39 -34
  13. package/player/stepActions/mouseStepAction.js +36 -34
  14. package/player/stepActions/navigationStepAction.js +7 -8
  15. package/player/stepActions/scrollStepAction.js +22 -22
  16. package/player/stepActions/stepAction.js +21 -21
  17. package/player/stepActions/stepActionRegistrar.js +63 -58
  18. package/player/stepActions/submitStepAction.js +2 -3
  19. package/player/stepActions/textStepAction.js +14 -14
  20. package/player/stepActions/textValidationStepAction.js +50 -38
  21. package/player/stepActions/wheelStepAction.js +5 -11
  22. package/processHandler.js +2 -0
  23. package/reports/junitReporter.js +18 -1
  24. package/runOptions.d.ts +4 -0
  25. package/runOptions.js +8 -1
  26. package/runners/TestPlanRunner.js +20 -19
  27. package/runners/runnerUtils.js +1 -2
  28. package/testRunHandler.js +18 -9
  29. package/testRunStatus.js +205 -157
  30. package/workers/BaseWorker.js +11 -0
  31. package/workers/WorkerExtension.js +117 -91
  32. package/workers/WorkerSelenium.js +8 -3
package/testRunStatus.js CHANGED
@@ -1,41 +1,50 @@
1
1
  'use strict';
2
2
 
3
- const constants = require('./commons/constants');
4
- const { TESTIM_CONCURRENT_WORKER_COUNT } = require('./commons/config');
3
+ const _ = require('lodash');
5
4
  const utils = require('./utils');
6
- const reporter = require('./reports/reporter.js');
7
- const servicesApi = require('./commons/testimServicesApi.js');
5
+ const reporter = require('./reports/reporter');
6
+ const constants = require('./commons/constants');
8
7
  const gridService = require('./services/gridService');
9
- const logger = require('./commons/logger').getLogger('test-run-status');
10
- const { ArgError } = require('./errors');
8
+ const featureFlags = require('./commons/featureFlags');
9
+ const servicesApi = require('./commons/testimServicesApi');
11
10
  const OverrideTestDataBuilder = require('./OverrideTestDataBuilder');
12
- const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
13
- const Promise = require('bluebird');
14
- const _ = require('lodash');
11
+ const featureAvailabilityService = require('./commons/featureAvailabilityService');
12
+ const { ArgError } = require('./errors');
13
+ const { getLogger } = require('./commons/logger');
15
14
  const { registerExitHook } = require('./processHandler');
16
15
  const { calculateCoverage } = require('./coverage/jsCoverage');
17
- const featureAvailabilityService = require('./commons/featureAvailabilityService');
18
- const featureFlags = require('./commons/featureFlags');
16
+ const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
19
17
  const { mapFilesToLocalDrive } = require('./services/localRCASaver');
20
- const { promiseMap } = require('./utils');
18
+ const { TESTIM_CONCURRENT_WORKER_COUNT } = require('./commons/config');
21
19
 
20
+ const logger = getLogger('test-run-status');
22
21
  const gitBranch = utils.getEnvironmentGitBranch();
23
22
  const gitCommit = process.env.GIT_COMMIT || process.env.CIRCLE_SHA1 || process.env.TRAVIS_COMMIT;
24
23
  const gitRepoUrl = process.env.GIT_URL || process.env.CIRCLE_REPOSITORY_URL;
25
24
  const runnerVersion = utils.getRunnerVersion();
26
25
 
27
26
 
28
- function runHook(fn, ...args) {
27
+ async function runHook(fn, ...args) {
29
28
  if (!fn || typeof fn !== 'function') {
30
- return Promise.resolve();
29
+ return undefined;
31
30
  }
32
- return Promise.try(() => fn(...args) || {}).catch(err => {
31
+
32
+ try {
33
+ const res = await fn(...args);
34
+ return res || {};
35
+ } catch (err) {
33
36
  logger.warn('failed to run hook', { err });
34
37
  throw new ArgError(`failed to run hook promise ${err.message}`);
35
- });
38
+ }
36
39
  }
37
40
 
38
41
  class RunStatus {
42
+ /**
43
+ * @param {any[]} testInfoList
44
+ * @param {import('./runOptions').RunnerOptions} options
45
+ * @param {string} testPlanId
46
+ * @param {string} branchToUse
47
+ */
39
48
  constructor(testInfoList, options, testPlanId, branchToUse) {
40
49
  this.options = options;
41
50
  this.options.runParams = this.options.runParams || {};
@@ -65,7 +74,7 @@ class RunStatus {
65
74
  testPlanId,
66
75
  testPlans: options.testPlan,
67
76
  testLabels: options.label,
68
- testSuites: _.uniq(testInfoList.flatMap(test => test.testSuites)),
77
+ testSuites: [...new Set(testInfoList.flatMap(test => test.testSuites))],
69
78
  testNames: options.name,
70
79
  testIds: options.testId,
71
80
  testConfigs: options.testConfigNames,
@@ -104,7 +113,18 @@ class RunStatus {
104
113
  }) {
105
114
  const orgTestResult = this.testRunStatus[originalTestResultId] || {};
106
115
  const {
107
- config, isTestsContainer, testId, name, testStatus,
116
+ config,
117
+ isTestsContainer,
118
+ testId,
119
+ name,
120
+ testStatus,
121
+ testCreatorName,
122
+ testCreatorEmail,
123
+ testOwnerName,
124
+ testOwnerEmail,
125
+ testLabels,
126
+ testSuites,
127
+ allLabels,
108
128
  } = orgTestResult;
109
129
 
110
130
  const newTestResult = {
@@ -120,7 +140,15 @@ class RunStatus {
120
140
  testStatus,
121
141
  };
122
142
 
123
- this.testRunStatus[newResultId] = newTestResult;
143
+ this.testRunStatus[newResultId] = Object.assign({}, newTestResult, {
144
+ testCreatorName,
145
+ testCreatorEmail,
146
+ testOwnerName,
147
+ testOwnerEmail,
148
+ testLabels,
149
+ testSuites,
150
+ allLabels,
151
+ });
124
152
 
125
153
  return servicesApi.addTestRetry({
126
154
  projectId,
@@ -146,55 +174,58 @@ class RunStatus {
146
174
  return test;
147
175
  }
148
176
 
149
- updateTestStatusRunning(test, executionId, testRetryKey) {
177
+ async updateTestStatusRunning(test, executionId, testRetryKey) {
150
178
  const { project: projectId, remoteRunId, projectData } = this.options;
151
179
  if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
152
180
  return this.executionStartedPromise;
153
181
  }
154
182
 
155
- return servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults)
156
- .catch(err => {
157
- logger.error('failed to upload test data artifact (runner)', { err });
158
- return '';
159
- })
160
- .then(async (testDataUrl) => {
161
- const testConfig = _.cloneDeep(test.config);
162
- delete testConfig.testData;
163
- testConfig.testDataUrl = testDataUrl;
164
- await this.executionStartedPromise;
165
- return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
166
- });
183
+ let testDataUrl = '';
184
+
185
+ try {
186
+ testDataUrl = await servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults);
187
+ } catch (err) {
188
+ logger.error('failed to upload test data artifact (runner)', { err });
189
+ }
190
+ const testConfig = _.cloneDeep(test.config);
191
+ delete testConfig.testData;
192
+ testConfig.testDataUrl = testDataUrl;
193
+ await this.executionStartedPromise;
194
+ return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
167
195
  }
168
196
 
169
- testStartReport(test, executionId, testRetryKey) {
197
+ async testStartReport(test, executionId, testRetryKey) {
170
198
  if (utils.isQuarantineAndNotRemoteRun(test, this.options)) {
171
- return Promise.resolve();
199
+ return undefined;
200
+ }
201
+ const globalParameters = this.exportsGlobal;
202
+ try {
203
+ const params = await runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: globalParameters, globalParameters }), this.options.userData.loginData.token);
204
+
205
+ // Temporary Sapiens log (SUP-3192)
206
+ if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
207
+ logger.info('testRunStatus - testStartReport', {
208
+ 'test.config.testData': test.config.testData,
209
+ 'this.exportsGlobal': this.exportsGlobal,
210
+ 'this.fileUserParamsData': this.fileUserParamsData,
211
+ 'this.beforeSuiteParams': this.beforeSuiteParams,
212
+ params,
213
+ executionId,
214
+ 'test.testId': test.testId,
215
+ 'test.resultId': test.resultId,
216
+ });
217
+ }
218
+
219
+ test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
220
+ this.options.runParams[test.resultId] = test.config.testData;
221
+ test.startTime = Date.now();
222
+ await this.updateTestStatusRunning(test, executionId, testRetryKey);
223
+
224
+ return test;
225
+ } catch (err) {
226
+ logger.error('Failed to start test', { err });
227
+ throw err;
172
228
  }
173
- return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }), this.options.userData.loginData.token)
174
- .then(async (params) => {
175
- // Temporary Sapiens log (SUP-3192)
176
- if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
177
- logger.info('testRunStatus - testStartReport', {
178
- 'test.config.testData': test.config.testData,
179
- 'this.exportsGlobal': this.exportsGlobal,
180
- 'this.fileUserParamsData': this.fileUserParamsData,
181
- 'this.beforeSuiteParams': this.beforeSuiteParams,
182
- params,
183
- executionId,
184
- 'test.testId': test.testId,
185
- 'test.resultId': test.resultId,
186
- });
187
- }
188
- test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
189
- this.options.runParams[test.resultId] = test.config.testData;
190
- test.startTime = Date.now();
191
- await this.updateTestStatusRunning(test, executionId, testRetryKey);
192
-
193
- return test;
194
- }).catch(err => {
195
- logger.error('Failed to start test', { err });
196
- throw err;
197
- });
198
229
  }
199
230
 
200
231
  testStartAndReport(wid, executionId, resultId, isRerun, testRetryKey) {
@@ -219,16 +250,14 @@ class RunStatus {
219
250
  reporter.onTestPassed(name);
220
251
  return;
221
252
  }
222
- reporter.onTestFailed(test,
253
+ reporter.onTestFailed(
254
+ test,
223
255
  test.failureReason,
224
- utils.getTestUrl(this.options.editorUrl,
225
- this.options.project,
226
- testId,
227
- resultId,
228
- this.branchToUse),
256
+ utils.getTestUrl(this.options.editorUrl, this.options.project, testId, resultId, this.branchToUse),
229
257
  testId,
230
258
  isRerun,
231
- resultId);
259
+ resultId,
260
+ );
232
261
  }
233
262
 
234
263
  calcResultText(result) {
@@ -298,7 +327,7 @@ class RunStatus {
298
327
  const globalParameters = result.exportsGlobal;
299
328
  try {
300
329
  try {
301
- await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }), this.options.userData.loginData.token);
330
+ await runHook(this.options.afterTest, Object.assign({}, test, { exportsGlobal: globalParameters, globalParameters }), this.options.userData.loginData.token);
302
331
  } catch (err) {
303
332
  logger.error('HOOK threw an error', { test: test.testId, err });
304
333
  // eslint-disable-next-line no-console
@@ -364,7 +393,7 @@ class RunStatus {
364
393
  }, {});
365
394
  }
366
395
 
367
- executionStart(executionId, projectId, startTime, testPlanName, testNames) {
396
+ async executionStart(executionId, projectId, startTime, testPlanName, testNames) {
368
397
  logger.info('execution started', { executionId });
369
398
  const { options } = this;
370
399
  const { remoteRunId, projectData } = options;
@@ -383,66 +412,74 @@ class RunStatus {
383
412
  ]));
384
413
 
385
414
  this.startTime = startTime || Date.now();
386
- const runHooksProps = { projectId, executionId };
387
- if (featureFlags.flags.testNamesToBeforeSuiteHook.isEnabled()) {
388
- runHooksProps.testNames = testNames;
415
+ const runHooksProps = {
416
+ projectId,
417
+ executionId,
418
+ ...(featureFlags.flags.testNamesToBeforeSuiteHook.isEnabled() && { testNames }),
419
+ };
420
+ const params = await runHook(options.beforeSuite, runHooksProps);
421
+ const overrideTestDataBuilder = new OverrideTestDataBuilder(params, _.cloneDeep(this.testInfoList), projectId);
422
+ this.testInfoList = overrideTestDataBuilder.overrideTestData();
423
+ this.calcTestRunStatus();
424
+ this.beforeSuiteParams = params;
425
+
426
+ const { testInfoList } = this;
427
+ const beforeTests = [];
428
+ const tests = [];
429
+ const afterTests = [];
430
+ for (const test of testInfoList) {
431
+ if (test.isBeforeTestPlan) {
432
+ beforeTests.push(test);
433
+ continue;
434
+ }
435
+ if (test.isAfterTestPlan) {
436
+ afterTests.push(test);
437
+ continue;
438
+ }
439
+ tests.push(test);
389
440
  }
390
- return runHook(options.beforeSuite, runHooksProps)
391
- .then(params => {
392
- const overrideTestDataBuilder = new OverrideTestDataBuilder(params, _.cloneDeep(this.testInfoList), projectId);
393
- this.testInfoList = overrideTestDataBuilder.overrideTestData();
394
- this.calcTestRunStatus();
395
- this.beforeSuiteParams = params;
396
-
397
- const { testInfoList } = this;
398
- const beforeTests = testInfoList.filter(test => test.isBeforeTestPlan);
399
- const tests = testInfoList.filter(test => !test.isBeforeTestPlan && !test.isAfterTestPlan);
400
- const afterTests = testInfoList.filter(test => test.isAfterTestPlan);
401
-
402
- const reportExecutionStarted = () => {
403
- const testResults = _.cloneDeep(this.testRunStatus);
404
- return promiseMap(Object.keys(testResults), testResultId => {
405
- const test = testResults[testResultId];
406
- const testData = test.config?.testData;
407
- const testId = test.testId;
408
- return servicesApi.updateTestDataArtifact(projectId, testId, testResultId, testData, projectData.defaults)
409
- .then((testDataUrl) => {
410
- if (!testDataUrl) {
411
- return;
412
- }
413
- delete test.config.testData;
414
- test.config.testDataUrl = testDataUrl;
415
- });
416
- }).then(() => {
417
- const isLocalRun = Boolean(options.useLocalChromeDriver || options.useChromeLauncher);
418
- const data = {
419
- executionId,
420
- projectId,
421
- labels: testPlanName || [],
422
- startTime,
423
- executions: testResults,
424
- config: this.execConfig,
425
- resultLabels: options.resultLabels,
426
- remoteRunId: options.remoteRunId,
427
- localRunUserId: options.user,
428
- isLocalRun,
429
- intersections: options.intersections,
430
- };
431
- const ret = servicesApi.reportExecutionStarted(data);
432
- this.executionStartedPromise = ret;
433
- ret.catch(e => logger.error(e));
434
- return ret;
435
- });
436
- };
437
-
438
- return reportExecutionStarted()
439
- .catch(err => {
440
- logger.error('Failed to start suite', { err });
441
- // eslint-disable-next-line no-console
442
- console.error('Failed to start test run. Please contact support@testim.io');
443
- })
444
- .then(() => ({ beforeTests, tests, afterTests }));
441
+
442
+ const reportExecutionStarted = async () => {
443
+ const testResults = _.cloneDeep(this.testRunStatus);
444
+ await utils.promiseMap(Object.keys(testResults), async testResultId => {
445
+ const test = testResults[testResultId];
446
+ const testData = test.config?.testData;
447
+ const testId = test.testId;
448
+ const testDataUrl = await servicesApi.updateTestDataArtifact(projectId, testId, testResultId, testData, projectData.defaults);
449
+ if (!testDataUrl) {
450
+ return;
451
+ }
452
+ delete test.config.testData;
453
+ test.config.testDataUrl = testDataUrl;
445
454
  });
455
+ const isLocalRun = Boolean(options.useLocalChromeDriver || options.useChromeLauncher);
456
+ const data = {
457
+ executionId,
458
+ projectId,
459
+ labels: testPlanName || [],
460
+ startTime,
461
+ executions: testResults,
462
+ config: this.execConfig,
463
+ resultLabels: options.resultLabels,
464
+ remoteRunId: options.remoteRunId,
465
+ localRunUserId: options.user,
466
+ isLocalRun,
467
+ intersections: options.intersections,
468
+ };
469
+ const ret = servicesApi.reportExecutionStarted(data);
470
+ this.executionStartedPromise = ret;
471
+ ret.catch(e => logger.error(e));
472
+ return ret;
473
+ };
474
+
475
+ try {
476
+ await reportExecutionStarted();
477
+ } catch (err) {
478
+ logger.error('Failed to start suite', { err });
479
+ // eslint-disable-next-line no-console
480
+ console.error('Failed to start test run. Please contact support@testim.io');
481
+ }
482
+ return { beforeTests, tests, afterTests };
446
483
  }
447
484
 
448
485
  concatSeleniumPerfMarks(marks) {
@@ -456,47 +493,58 @@ class RunStatus {
456
493
  .value();
457
494
  }
458
495
 
459
- executionEnd(executionId) {
496
+ async executionEnd(executionId) {
460
497
  const tests = utils.groupTestsByRetries(this.testRunStatus);
461
498
  const total = tests.length;
462
- const passed = tests.filter(({ status }) => status === constants.runnerTestStatus.PASSED).length;
463
- const skipped = tests.filter(({ status }) => status === constants.runnerTestStatus.SKIPPED).length;
464
- const failedInEvaluatingStatus = tests.filter(({ status, testStatus }) => status === constants.runnerTestStatus.FAILED && testStatus === constants.testStatus.EVALUATING).length;
465
499
 
466
- const resultExtraData = { ...this.seleniumPerfStats.getStats() };
467
- delete resultExtraData.seleniumPerfMarks;
500
+ let passed = 0;
501
+ let skipped = 0;
502
+ let failedInEvaluatingStatus = 0;
503
+ for (const { status, testStatus } of tests) {
504
+ if (status === constants.runnerTestStatus.PASSED) {
505
+ passed++;
506
+ }
507
+ if (status === constants.runnerTestStatus.SKIPPED) {
508
+ skipped++;
509
+ }
510
+ if (status === constants.runnerTestStatus.FAILED && testStatus === constants.testStatus.EVALUATING) {
511
+ failedInEvaluatingStatus++;
512
+ }
513
+ }
514
+
515
+ const { seleniumPerfMarks, ...resultExtraData } = this.seleniumPerfStats.getStats();
468
516
 
469
- return runHook(this.options.afterSuite, {
517
+ await runHook(this.options.afterSuite, {
470
518
  exportsGlobal: this.exportsGlobal,
471
519
  tests,
472
520
  total,
473
521
  passed,
474
522
  skipped,
475
- })
476
- .then(() => calculateCoverage(this.options, this.branchToUse, total, executionId))
477
- .then((coverageSummary) => {
478
- resultExtraData.coverageSummary = coverageSummary;
523
+ });
524
+ const coverageSummary = await calculateCoverage(this.options, this.branchToUse, total, executionId);
525
+ resultExtraData.coverageSummary = coverageSummary;
479
526
 
480
- if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
481
- return undefined;
482
- }
483
- return servicesApi.reportExecutionFinished(
484
- 'FINISHED',
485
- executionId,
486
- this.options.project,
487
- total === (passed + skipped + failedInEvaluatingStatus),
488
- {
489
- tmsSuppressReporting: this.options.tmsSuppressReporting,
490
- tmsRunId: this.options.tmsRunId,
491
- tmsCustomFields: this.options.tmsCustomFields,
492
- },
493
- this.options.remoteRunId,
494
- resultExtraData
495
- ).catch(err => {
496
- logger.error('Failed to update suite finished', { err });
497
- throw err;
498
- });
499
- });
527
+ if (this.options.lightweightMode?.onlyTestIdsNoSuite) {
528
+ return undefined;
529
+ }
530
+ try {
531
+ return await servicesApi.reportExecutionFinished(
532
+ 'FINISHED',
533
+ executionId,
534
+ this.options.project,
535
+ total === (passed + skipped + failedInEvaluatingStatus),
536
+ {
537
+ tmsSuppressReporting: this.options.tmsSuppressReporting,
538
+ tmsRunId: this.options.tmsRunId,
539
+ tmsCustomFields: this.options.tmsCustomFields,
540
+ },
541
+ this.options.remoteRunId,
542
+ resultExtraData
543
+ );
544
+ } catch (err) {
545
+ logger.error('Failed to update suite finished', { err });
546
+ throw err;
547
+ }
500
548
  }
501
549
 
502
550
  async markAllQueuedTests(executionId, status, failureReason, success) {
@@ -38,6 +38,17 @@ function buildFailureResult(testId, testName, resultId, reason) {
38
38
  }
39
39
 
40
40
  class BaseWorker {
41
+ /**
42
+ * @param {import('../executionQueue')} executionQueue
43
+ * @param {import('../runOptions').RunnerOptions} options
44
+ * @param {string=} customExtensionLocalLocation
45
+ * @param {string} executionId
46
+ * @param {Function} onTestStarted
47
+ * @param {Function} onTestCompleted
48
+ * @param {Function} onGridSlot
49
+ * @param {Function} onTestIgnored
50
+ * @param {boolean=} releaseSlotOnTestFinished
51
+ */
41
52
  constructor(executionQueue, options, customExtensionLocalLocation, executionId, onTestStarted, onTestCompleted, onGridSlot, onTestIgnored, releaseSlotOnTestFinished = true) {
42
53
  this.lambdatestService = new LambdatestService();
43
54