@testim/testim-cli 3.254.0 → 3.256.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 (61) hide show
  1. package/agent/routers/cliJsCode/service.js +11 -8
  2. package/agent/routers/codim/router.test.js +9 -12
  3. package/agent/routers/codim/service.js +16 -16
  4. package/agent/routers/playground/service.js +5 -7
  5. package/cli.js +6 -6
  6. package/cliAgentMode.js +11 -10
  7. package/codim/codim-cli.js +14 -9
  8. package/commons/featureFlags.js +29 -7
  9. package/commons/httpRequest.js +5 -1
  10. package/commons/httpRequestCounters.js +21 -10
  11. package/commons/initializeUserWithAuth.js +7 -4
  12. package/commons/lazyRequire.js +4 -3
  13. package/commons/preloadTests.js +6 -3
  14. package/commons/prepareRunner.js +7 -5
  15. package/commons/prepareRunnerAndTestimStartUtils.js +51 -45
  16. package/commons/runnerFileCache.js +10 -2
  17. package/commons/testimServicesApi.js +36 -5
  18. package/commons/testimTunnel.test.js +2 -1
  19. package/coverage/SummaryToObjectReport.js +0 -1
  20. package/coverage/jsCoverage.js +12 -10
  21. package/inputFileUtils.js +11 -9
  22. package/npm-shrinkwrap.json +214 -471
  23. package/package.json +4 -3
  24. package/player/services/tabService.js +15 -1
  25. package/player/stepActions/apiStepAction.js +49 -43
  26. package/player/stepActions/baseCliJsStepAction.js +19 -14
  27. package/player/stepActions/baseJsStepAction.js +9 -8
  28. package/player/stepActions/dropFileStepAction.js +1 -3
  29. package/player/stepActions/inputFileStepAction.js +10 -8
  30. package/player/stepActions/locateStepAction.js +2 -0
  31. package/player/stepActions/mouseStepAction.js +21 -22
  32. package/player/stepActions/nodePackageStepAction.js +34 -35
  33. package/player/stepActions/stepAction.js +1 -0
  34. package/player/utils/imageCaptureUtils.js +133 -172
  35. package/player/utils/screenshotUtils.js +16 -13
  36. package/player/utils/windowUtils.js +20 -8
  37. package/player/webdriver.js +25 -22
  38. package/processHandler.js +4 -0
  39. package/reports/junitReporter.js +6 -7
  40. package/reports/reporter.js +34 -39
  41. package/runOptions.d.ts +286 -0
  42. package/runOptions.js +60 -45
  43. package/runner.js +64 -24
  44. package/runners/ParallelWorkerManager.js +12 -12
  45. package/runners/TestPlanRunner.js +14 -15
  46. package/runners/buildCodeTests.js +1 -0
  47. package/runners/runnerUtils.js +11 -2
  48. package/services/branchService.js +11 -5
  49. package/services/gridService.js +36 -40
  50. package/services/localRCASaver.js +4 -0
  51. package/testRunStatus.js +8 -5
  52. package/utils/argsUtils.js +86 -0
  53. package/utils/argsUtils.test.js +32 -0
  54. package/utils/fsUtils.js +154 -0
  55. package/utils/index.js +10 -161
  56. package/utils/promiseUtils.js +13 -2
  57. package/utils/stringUtils.js +4 -2
  58. package/utils/stringUtils.test.js +22 -0
  59. package/utils/timeUtils.js +25 -0
  60. package/utils/utils.test.js +0 -41
  61. package/workers/WorkerExtension.js +6 -7
package/runOptions.js CHANGED
@@ -2,22 +2,20 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const { CLI_MODE } = require('./commons/constants');
6
- const { EDGE_CHROMIUM_MIN_VERSION } = require('./player/constants');
7
- const program = require('commander');
8
5
  const fs = require('fs');
9
6
  const ms = require('ms');
10
- const Promise = require('bluebird');
11
- const NoArgsError = require('./errors.js').NoArgsError;
12
- const ArgError = require('./errors.js').ArgError;
13
7
  const url = require('url');
14
8
  const _ = require('lodash');
15
9
  const path = require('path');
10
+ const chalk = require('chalk');
11
+ const program = require('commander');
16
12
  const utils = require('./utils');
17
- const runOptionsAgentFlow = require('./runOptionsAgentFlow');
18
13
  const runOptionsUtils = require('./runOptionsUtils');
14
+ const runOptionsAgentFlow = require('./runOptionsAgentFlow');
19
15
  const localRunnerCache = require('./commons/runnerFileCache');
20
- const chalk = require('chalk');
16
+ const { CLI_MODE } = require('./commons/constants');
17
+ const { NoArgsError, ArgError } = require('./errors');
18
+ const { EDGE_CHROMIUM_MIN_VERSION } = require('./player/constants');
21
19
 
22
20
  const camelizeHyphenValues = (prop) => prop.replace(/-([a-z])/g, (m, w) => w.toUpperCase());
23
21
 
@@ -114,6 +112,10 @@ const printUsage = () => {
114
112
  return line.includes('--high-speed'); // high speed mode was renamed to turbo mode
115
113
  }
116
114
 
115
+ function isTestStartTimeout(line) {
116
+ return line.includes('--test-start-timeout');
117
+ }
118
+
117
119
  program.help((txt) => {
118
120
  const lines = txt.split('\n');
119
121
  return lines
@@ -129,7 +131,8 @@ const printUsage = () => {
129
131
  !isWebdriverTimeout(ln) &&
130
132
  !isSaveRCALocally(ln) &&
131
133
  !isExitCodeIgnoreFailingTests(ln) &&
132
- !isDeprecatedHighSpeed(ln)
134
+ !isDeprecatedHighSpeed(ln) &&
135
+ !isTestStartTimeout(ln)
133
136
  )
134
137
  .join('\n');
135
138
  });
@@ -217,17 +220,18 @@ program
217
220
  .option('--file-cache-location [directory]', ' internal CLI file caching location')
218
221
 
219
222
  // Timeout
220
- .option('--timeout [test-timeout]', 'test run timeout in milliseconds')
221
- .option('--browser-timeout [open-browser-timeout]', 'get browser from grid timeout in milliseconds')
222
- .option('--new-browser-wait-timeout [max-wait-to-browser]', 'maximum get browser wait in minutes')
223
+ .option('--test-start-timeout [test-start-timeout]', 'The time to wait for a test to start after getting a browser session', Number, Number(process.env.TESTIM_TEST_START_TIMEOUT) || (2 * 60 * 1000))
224
+ .option('--timeout [test-timeout]', 'Test run timeout in milliseconds', Number)
225
+ .option('--browser-timeout [open-browser-timeout]', 'Get browser from grid timeout in milliseconds', Number)
226
+ .option('--new-browser-wait-timeout [max-wait-to-browser]', 'Maximum get browser wait in minutes', Number)
223
227
 
224
228
  // New Timeouts
225
- .option('--get-browser-timeout [get-browser-timeout]', 'Timeout for a single attempt to get browser from the grid configured in the project\'s plan') // getBrowserTimeout
226
- .option('--get-browser-retries [get-browser-retries]', 'Number of attempts to get browser from the grid configured in the project\'s plan') // getBrowserRetries
227
- .option('--get-session-timeout [get-session-timeout]', 'Timeout for "/session" request to the selenium server', ms('90s')) // getSessionTimeout
228
- .option('--get-session-retries [get-session-retries]', 'Retries for "/session" request to the selenium server', 3) // getSessionRetries
229
- .option('--driver-request-timeout [driver-request-timeout]', 'Timeout for any WebDriver request to the grid server', ms('90s')) // driverRequestTimeout
230
- .option('--driver-request-retries [driver-request-retries]', 'Retries for any WebDriver request to the grid server', 3) // driverRequestRetries
229
+ .option('--get-browser-timeout [get-browser-timeout]', 'Timeout for a single attempt to get browser from the grid configured in the project\'s plan', Number) // getBrowserTimeout
230
+ .option('--get-browser-retries [get-browser-retries]', 'Number of attempts to get browser from the grid configured in the project\'s plan', Number) // getBrowserRetries
231
+ .option('--get-session-timeout [get-session-timeout]', 'Timeout for "/session" request to the selenium server', Number, ms('90s')) // getSessionTimeout
232
+ .option('--get-session-retries [get-session-retries]', 'Retries for "/session" request to the selenium server', Number, 3) // getSessionRetries
233
+ .option('--driver-request-timeout [driver-request-timeout]', 'Timeout for any WebDriver request to the grid server', Number, ms('90s')) // driverRequestTimeout
234
+ .option('--driver-request-retries [driver-request-retries]', 'Retries for any WebDriver request to the grid server', Number, 3) // driverRequestRetries
231
235
 
232
236
  // Run chrome ext mode locally
233
237
  .option('--use-local-chrome-driver [use-local-chrome-driver]', 'use a local ChromeDriver instance instead of a selenium grid')
@@ -510,8 +514,10 @@ module.exports = {
510
514
  try {
511
515
  let options = {};
512
516
  if (program.configFile) {
517
+ // eslint-disable-next-line import/no-dynamic-require
513
518
  options = require(path.join(process.cwd(), program.configFile)).config;
514
519
  } else if (program.optionsFile) {
520
+ // eslint-disable-next-line import/no-dynamic-require
515
521
  options = require(path.join(process.cwd(), program.optionsFile));
516
522
  }
517
523
 
@@ -546,19 +552,20 @@ module.exports = {
546
552
  }
547
553
 
548
554
 
549
- const isTestConfigSpecified = (program.testConfig && program.testConfig.length) || (program.testConfigId && program.testConfigId.length);
550
- const isTestPlanSpecified = (program.testPlan && program.testPlan.length) || (program.testPlanId && program.testPlanId.length);
551
- const isSuiteSpecified = (program.suite && program.suite.length) || (program.suiteId && program.suiteId.length);
555
+ const isTestConfigSpecified = program.testConfig?.length || program.testConfigId?.length;
556
+ const isTestPlanSpecified = program.testPlan?.length || program.testPlanId?.length;
557
+ const isSuiteSpecified = program.suite?.length || program.suiteId?.length;
552
558
 
553
559
  if (program.seleniumCapsFile) {
554
560
  try {
561
+ // eslint-disable-next-line import/no-dynamic-require
555
562
  seleniumCapsFileContent = require(path.join(process.cwd(), program.seleniumCapsFile));
556
563
  } catch (err) {
557
- return Promise.reject(new ArgError(`Failed to parse selenium caps file file error: ${err.message}`));
564
+ throw new ArgError(`Failed to parse selenium caps file file error: ${err.message}`);
558
565
  }
559
566
  }
560
567
 
561
- if (program.reporters && program.reporters.includes('junit') && !program.reportFile) {
568
+ if (program.reporters?.includes('junit') && !program.reportFile) {
562
569
  console.log('Warning: please define --report-file option for JUnit reporter');
563
570
  }
564
571
 
@@ -575,9 +582,10 @@ module.exports = {
575
582
  }
576
583
  if (program.chromeExtraPrefs) {
577
584
  try {
585
+ // eslint-disable-next-line import/no-dynamic-require
578
586
  chromeExtraPrefs = require(path.join(process.cwd(), program.chromeExtraPrefs));
579
587
  } catch (err) {
580
- return Promise.reject(new ArgError(`Failed to read/open chrome extra prefs file error: ${err.message}`));
588
+ throw new ArgError(`Failed to read/open chrome extra prefs file error: ${err.message}`);
581
589
  }
582
590
  }
583
591
 
@@ -598,9 +606,10 @@ module.exports = {
598
606
 
599
607
  if (program.paramsFile) {
600
608
  try {
609
+ // eslint-disable-next-line import/no-dynamic-require
601
610
  userParamsData = Object.assign({}, userParamsData, require(path.join(process.cwd(), program.paramsFile)));
602
611
  } catch (err) {
603
- return Promise.reject(new ArgError(`Failed to read/open params file error: ${err.message}`));
612
+ throw new ArgError(`Failed to read/open params file error: ${err.message}`);
604
613
  }
605
614
  }
606
615
 
@@ -608,7 +617,7 @@ module.exports = {
608
617
  try {
609
618
  userParamsData = Object.assign({}, userParamsData, JSON.parse(program.params));
610
619
  } catch (err) {
611
- return Promise.reject(new ArgError(`Failed to parse params string error: ${err.message}`));
620
+ throw new ArgError(`Failed to parse params string error: ${err.message}`);
612
621
  }
613
622
  }
614
623
 
@@ -627,6 +636,7 @@ module.exports = {
627
636
 
628
637
  if (program.sauceOptions) {
629
638
  try {
639
+ // eslint-disable-next-line import/no-dynamic-require
630
640
  const sOptions = require(path.join(process.cwd(), program.sauceOptions));
631
641
  const isMobile = sOptions.platformName && ['ios', 'android'].includes(sOptions.platformName.toLowerCase());
632
642
  if (sOptions.browserName) {
@@ -649,12 +659,12 @@ module.exports = {
649
659
 
650
660
  const isBadVersion = parseFloat(sOptions.version) < 50 && !['dev', 'beta'].includes(sOptions.version);
651
661
  if (!isMobile && program.browser === 'chrome' && isBadVersion) {
652
- return Promise.reject(new ArgError('The minimum chrome supported version is 50.0'));
662
+ throw new ArgError('The minimum chrome supported version is 50.0');
653
663
  }
654
664
 
655
665
  program.saucelabs = Object.assign({}, program.saucelabs, sOptions);
656
666
  } catch (err) {
657
- return Promise.reject(new ArgError(`Failed to parse saucelabs options file error: ${err.message}`));
667
+ throw new ArgError(`Failed to parse saucelabs options file error: ${err.message}`);
658
668
  }
659
669
  }
660
670
 
@@ -672,6 +682,7 @@ module.exports = {
672
682
 
673
683
  if (program.browserstackOptions) {
674
684
  try {
685
+ // eslint-disable-next-line import/no-dynamic-require
675
686
  const bsOptions = require(path.join(process.cwd(), program.browserstackOptions));
676
687
  const isMobile = bsOptions.platform && ['mac', 'android'].includes(bsOptions.platform.toLowerCase());
677
688
  if (bsOptions.browserName) {
@@ -683,12 +694,12 @@ module.exports = {
683
694
  }
684
695
 
685
696
  if (!isMobile && parseFloat(bsOptions.browser_version) < 50 && program.browser === 'chrome') {
686
- return Promise.reject(new ArgError('The minimum chrome supported version is 50.0'));
697
+ throw new ArgError('The minimum chrome supported version is 50.0');
687
698
  }
688
699
 
689
700
  program.browserstack = Object.assign({}, program.browserstack, bsOptions);
690
701
  } catch (err) {
691
- return Promise.reject(new ArgError(`Failed to parse browserstack options file error: ${err.message}`));
702
+ throw new ArgError(`Failed to parse browserstack options file error: ${err.message}`);
692
703
  }
693
704
  }
694
705
 
@@ -700,11 +711,12 @@ module.exports = {
700
711
 
701
712
  if (program.perfectoOptions) {
702
713
  try {
714
+ // eslint-disable-next-line import/no-dynamic-require
703
715
  const perfectoOptions = require(path.join(process.cwd(), program.perfectoOptions));
704
716
  const DEFAULTS = { location: 'US East', securityToken: program.perfectoToken };
705
717
  program.perfecto = Object.assign({}, DEFAULTS, perfectoOptions);
706
718
  } catch (err) {
707
- return Promise.reject(new ArgError(`Failed to parse perfecto options file error: ${err.message}`));
719
+ throw new ArgError(`Failed to parse perfecto options file error: ${err.message}`);
708
720
  }
709
721
  }
710
722
 
@@ -716,11 +728,12 @@ module.exports = {
716
728
 
717
729
  if (program.testobjectOptions) {
718
730
  try {
731
+ // eslint-disable-next-line import/no-dynamic-require
719
732
  const testobjectOptions = require(path.join(process.cwd(), program.testobjectOptions));
720
733
  const DEFAULTS = { testobjectApiKey: program.testobjectKey };
721
734
  program.testobjectSauce = Object.assign({}, DEFAULTS, testobjectOptions);
722
735
  } catch (err) {
723
- return Promise.reject(new ArgError(`Failed to parse test object options file error: ${err.message}`));
736
+ throw new ArgError(`Failed to parse test object options file error: ${err.message}`);
724
737
  }
725
738
  }
726
739
 
@@ -751,8 +764,8 @@ module.exports = {
751
764
  }
752
765
 
753
766
  program.retries = !program.retries || typeof program.retries === 'boolean' ? 1 : Number(program.retries) + 1;
754
- program.browserTimeout = !program.browserTimeout || typeof program.browserTimeout === 'boolean' ? 60 * 1000 : Number(program.browserTimeout);
755
- program.newBrowserWaitTimeout = !program.newBrowserWaitTimeout || typeof program.newBrowserWaitTimeout === 'boolean' ? 10 * 60 * 1000 : Number(program.newBrowserWaitTimeout * 60 * 1000);
767
+ program.browserTimeout = !program.browserTimeout || typeof program.browserTimeout === 'boolean' ? 60 * 1000 : program.browserTimeout;
768
+ program.newBrowserWaitTimeout = !program.newBrowserWaitTimeout || typeof program.newBrowserWaitTimeout === 'boolean' ? 10 * 60 * 1000 : program.newBrowserWaitTimeout * 60 * 1000;
756
769
 
757
770
  if (!program.getBrowserTimeout) {
758
771
  program.getBrowserTimeout = program.browserTimeout;
@@ -764,7 +777,7 @@ module.exports = {
764
777
  program.driverRequestTimeout = program.browserTimeout < program.driverRequestTimeout ? program.driverRequestTimeout : program.browserTimeout;
765
778
 
766
779
  const timeoutWasGiven = Boolean(program.timeout);
767
- program.timeout = !program.timeout || typeof program.timeout === 'boolean' ? 10 * 60 * 1000 : Number(program.timeout);
780
+ program.timeout = !program.timeout || typeof program.timeout === 'boolean' ? 10 * 60 * 1000 : program.timeout;
768
781
  program.beforeParallel = !program.beforeParallel || typeof program.beforeParallel === 'boolean' ? 1 : Number(program.beforeParallel);
769
782
  program.parallel = !program.parallel || typeof program.parallel === 'boolean' ? 1 : Number(program.parallel);
770
783
  program.afterParallel = !program.afterParallel || typeof program.afterParallel === 'boolean' ? 1 : Number(program.afterParallel);
@@ -791,7 +804,7 @@ module.exports = {
791
804
 
792
805
  program.port = program.port && Number(program.port);
793
806
 
794
- if (program.retries <= 0 || _.isNaN(program.retries)) {
807
+ if (program.retries <= 0 || Number.isNaN(program.retries)) {
795
808
  throw new ArgError('test failure retry count could not be a negative number or string, --retries <max_num_of_retries>');
796
809
  }
797
810
 
@@ -810,27 +823,27 @@ module.exports = {
810
823
  }
811
824
  }
812
825
 
813
- if (program.browserTimeout <= 0 || _.isNaN(program.browserTimeout)) {
826
+ if (program.browserTimeout <= 0 || Number.isNaN(program.browserTimeout)) {
814
827
  throw new ArgError('get browser timeout could not be a negative number, --browser-timeout <get-browser-timeout>');
815
828
  }
816
829
 
817
- if (program.newBrowserWaitTimeout <= 0 || _.isNaN(program.newBrowserWaitTimeout)) {
830
+ if (program.newBrowserWaitTimeout <= 0 || Number.isNaN(program.newBrowserWaitTimeout)) {
818
831
  throw new ArgError('max new browser wait timeout could not be a negative number, --new-browser-wait-timeout <max-wait-to-browser>');
819
832
  }
820
833
 
821
- if (program.timeout <= 0 || _.isNaN(program.timeout)) {
834
+ if (program.timeout <= 0 || Number.isNaN(program.timeout)) {
822
835
  throw new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>');
823
836
  }
824
837
 
825
- if (program.beforeParallel <= 0 || _.isNaN(program.beforeParallel)) {
838
+ if (program.beforeParallel <= 0 || Number.isNaN(program.beforeParallel)) {
826
839
  throw new ArgError('before-parallel could not be a negative number or not number, --before-parallel <number-of-tests>');
827
840
  }
828
841
 
829
- if (program.parallel <= 0 || _.isNaN(program.parallel)) {
842
+ if (program.parallel <= 0 || Number.isNaN(program.parallel)) {
830
843
  throw new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>');
831
844
  }
832
845
 
833
- if (program.afterParallel <= 0 || _.isNaN(program.afterParallel)) {
846
+ if (program.afterParallel <= 0 || Number.isNaN(program.afterParallel)) {
834
847
  throw new ArgError('after-parallel could not be a negative number or not number, --after-parallel <number-of-tests>');
835
848
  }
836
849
 
@@ -901,7 +914,7 @@ module.exports = {
901
914
  throw new ArgError('please define exactly one of --grid or --grid-id or --host');
902
915
  }
903
916
 
904
- if (program.host && program.host.includes('/')) {
917
+ if (program.host?.includes('/')) {
905
918
  if (!/^(f|ht)tps?:\/\//i.test(program.host)) {
906
919
  program.host = `http://${program.host}`;
907
920
  }
@@ -1009,7 +1022,7 @@ module.exports = {
1009
1022
  const fileContent = fs.readFileSync(program.tmsFieldFile);
1010
1023
  program.tmsCustomFields = JSON.parse(fileContent);
1011
1024
  } catch (err) {
1012
- return Promise.reject(new ArgError(`failed to parse field file error: ${err.message}`));
1025
+ throw new ArgError(`failed to parse field file error: ${err.message}`);
1013
1026
  }
1014
1027
  }
1015
1028
 
@@ -1046,7 +1059,7 @@ module.exports = {
1046
1059
  const lightweightModeOptions = typeof program.lightweightMode === 'string' ? JSON.parse(program.lightweightMode) : {};
1047
1060
  program.lightweightMode = Object.assign({}, DEFAULTS, lightweightModeOptions);
1048
1061
  } catch (err) {
1049
- return Promise.reject(new ArgError(`failed to parse lightweightMode settings error: ${err.message}`));
1062
+ throw new ArgError(`failed to parse lightweightMode settings error: ${err.message}`);
1050
1063
  }
1051
1064
  } else if (program.turboMode && program.mode === CLI_MODE.EXTENSION) {
1052
1065
  program.lightweightMode = {
@@ -1179,6 +1192,8 @@ module.exports = {
1179
1192
  driverRequestTimeout: program.driverRequestTimeout,
1180
1193
  driverRequestRetries: program.driverRequestRetries,
1181
1194
 
1195
+ testStartTimeout: program.testStartTimeout,
1196
+
1182
1197
  testConfigNames: program.testConfig,
1183
1198
  testConfigIds: program.testConfigId,
1184
1199
 
package/runner.js CHANGED
@@ -1,32 +1,34 @@
1
1
  'use strict';
2
2
 
3
3
  /* eslint-disable no-console */
4
- const { CLI_MODE } = require('./commons/constants');
5
4
  const _ = require('lodash');
6
- const { EDITOR_URL } = require('./commons/config');
7
- const tunnel = require('./commons/testimTunnel');
8
5
  const utils = require('./utils');
9
6
  const reporter = require('./reports/reporter');
10
- const testimCustomToken = require('./commons/testimCustomToken');
11
- const socketService = require('./commons/socket/socketService');
12
- const servicesApi = require('./commons/testimServicesApi.js');
13
- const npmDriver = require('./testimNpmDriver.js');
7
+ const npmDriver = require('./testimNpmDriver');
8
+ const tunnel = require('./commons/testimTunnel');
9
+ const perf = require('./commons/performance-logger');
10
+ const gridService = require('./services/gridService');
14
11
  const analytics = require('./commons/testimAnalytics');
12
+ const featureFlags = require('./commons/featureFlags');
15
13
  const branchService = require('./services/branchService');
16
- const gridService = require('./services/gridService');
14
+ const servicesApi = require('./commons/testimServicesApi');
15
+ const TestPlanRunner = require('./runners/TestPlanRunner');
16
+ const socketService = require('./commons/socket/socketService');
17
+ const testimCustomToken = require('./commons/testimCustomToken');
18
+ const labFeaturesService = require('./services/labFeaturesService');
19
+ const featureAvailabilityService = require('./commons/featureAvailabilityService');
20
+ const { getLogger } = require('./commons/logger');
21
+ const { EDITOR_URL } = require('./commons/config');
22
+ const { CLI_MODE } = require('./commons/constants');
17
23
  const { ArgError, QuotaDepletedError } = require('./errors');
18
- const featureFlags = require('./commons/featureFlags');
19
- const perf = require('./commons/performance-logger');
20
24
  const { prepareMockNetwork, initializeUserWithAuth } = require('./commons/prepareRunner');
21
25
 
22
26
  const FREE_PLAN_MINIMUM_BROWSER_TIMEOUT = 30 * 60 * 1000;
23
27
 
24
- const TestPlanRunner = require('./runners/TestPlanRunner');
25
- const labFeaturesService = require('./services/labFeaturesService');
26
- const featureAvailabilityService = require('./commons/featureAvailabilityService');
27
28
 
28
- const logger = require('./commons/logger').getLogger('runner');
29
+ const logger = getLogger('runner');
29
30
 
31
+ /** @param {import('./runOptions').RunnerOptions} options */
30
32
  function validateCLIRunsAreAllowed(options) {
31
33
  const hasCliAccess = _.get(options, 'company.activePlan.premiumFeatures.allowCLI');
32
34
 
@@ -34,14 +36,16 @@ function validateCLIRunsAreAllowed(options) {
34
36
  const projectId = options.project;
35
37
  analytics.track(options.authData.uid, 'cli-not-supported', { projectId });
36
38
  console.warn('Testim CLI is not supported in this plan');
39
+ // TODO: shouldn't this throw an error? 🤔
37
40
  }
38
41
  }
39
42
 
43
+ /** @param {import('./runOptions').RunnerOptions} options */
40
44
  async function validateProjectQuotaNotDepleted(options) {
41
45
  const projectId = options.project;
42
46
 
43
47
  const usage = await servicesApi.getUsageForCurrentBillingPeriod(projectId);
44
- const isExecutionBlocked = usage && usage.isExecutionBlocked;
48
+ const isExecutionBlocked = usage?.isExecutionBlocked;
45
49
  if (!isExecutionBlocked) {
46
50
  return;
47
51
  }
@@ -51,6 +55,10 @@ async function validateProjectQuotaNotDepleted(options) {
51
55
  throw new QuotaDepletedError();
52
56
  }
53
57
 
58
+ /**
59
+ * @param {import('./runOptions').RunnerOptions} options
60
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['companyByProjectId']} company
61
+ */
54
62
  function validateOptionsForCompany(options, company) {
55
63
  const optionsRetention = options.retentionDays;
56
64
  if (!optionsRetention) {
@@ -63,8 +71,9 @@ function validateOptionsForCompany(options, company) {
63
71
  }
64
72
  }
65
73
 
74
+ /** @param {import('./runOptions').RunnerOptions} options */
66
75
  async function validateCliAccount(options) {
67
- if (options.lightweightMode && options.lightweightMode.disableQuotaBlocking) {
76
+ if (options.lightweightMode?.disableQuotaBlocking) {
68
77
  return;
69
78
  }
70
79
  try {
@@ -73,13 +82,14 @@ async function validateCliAccount(options) {
73
82
  validateCLIRunsAreAllowed(options),
74
83
  ]);
75
84
  } catch (err) {
76
- if (err instanceof ArgError || err instanceof QuotaDepletedError) {
85
+ if ([ArgError, QuotaDepletedError].some(errType => err instanceof errType)) {
77
86
  throw err;
78
87
  }
79
88
  logger.error('could not validate cli account', { err });
80
89
  }
81
90
  }
82
91
 
92
+ /** @param {string} projectId */
83
93
  function analyticsIdentify(projectId) {
84
94
  const authData = testimCustomToken.getTokenV3UserData();
85
95
  return analytics.identify({
@@ -95,6 +105,7 @@ function analyticsIdentify(projectId) {
95
105
  });
96
106
  }
97
107
 
108
+ /** @param {string} projectId */
98
109
  function initSocketServices(projectId, { disableResults = false, disableRemoteStep = false }) {
99
110
  if (featureFlags.flags.useNewWSCLI.isEnabled() && !disableResults && !disableRemoteStep) {
100
111
  return socketService.connect(projectId);
@@ -110,6 +121,10 @@ function initSocketServices(projectId, { disableResults = false, disableRemoteSt
110
121
  return undefined;
111
122
  }
112
123
 
124
+ /**
125
+ * @param {import('./runOptions').RunnerOptions} options
126
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['branchName']} branchInfoFromServer
127
+ */
113
128
  function setBranch(options, branchInfoFromServer) {
114
129
  const { branch, autoDetect } = options;
115
130
  branchService.setCurrentBranch(branchInfoFromServer, autoDetect);
@@ -118,14 +133,19 @@ function setBranch(options, branchInfoFromServer) {
118
133
  }
119
134
  }
120
135
 
136
+ /** @param {import('./runOptions').RunnerOptions} options */
121
137
  async function setSfdcCredential(options) {
122
- const { projectData: { projectId } } = options;
138
+ const { projectData: { projectId } = {} } = options;
123
139
  const branch = branchService.getCurrentBranch();
124
140
  if (_.get(options, 'company.activePlan.premiumFeatures.ttaForSalesforce')) {
125
141
  options.sfdcCredential = await servicesApi.loadSfdcCredential({ projectId, branch });
126
142
  }
127
143
  }
128
144
 
145
+ /**
146
+ * @param {import('./runOptions').RunnerOptions} options
147
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['companyByProjectId']} company
148
+ */
129
149
  function setCompany(options, company) {
130
150
  const { onprem, id, storageBaseUrl, storageType, name, activePlan = {} } = company;
131
151
  if (onprem) {
@@ -161,6 +181,10 @@ function setCompany(options, company) {
161
181
  };
162
182
  }
163
183
 
184
+ /**
185
+ * @param {import('./runOptions').RunnerOptions} options
186
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['editorConfig']} editorConfig
187
+ */
164
188
  function setSystemInfo(options, editorConfig) {
165
189
  if (EDITOR_URL) {
166
190
  options.editorUrl = EDITOR_URL;
@@ -169,17 +193,30 @@ function setSystemInfo(options, editorConfig) {
169
193
  options.editorUrl = editorConfig.editorUrl;
170
194
  }
171
195
 
196
+ /**
197
+ * @param {import('./runOptions').RunnerOptions} options
198
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['allGrids']} allGrids
199
+ */
172
200
  function setAllGrids(options, allGrids) {
173
201
  options.allGrids = allGrids;
174
202
  }
175
203
 
204
+ /**
205
+ * @param {import('./runOptions').RunnerOptions} options
206
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['authData']} authData
207
+ */
176
208
  function setAuthData(options, authData) {
177
209
  options.authData = authData;
178
210
  }
179
211
 
212
+ /**
213
+ * @param {import('./runOptions').RunnerOptions} options
214
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['projectById']} project
215
+ */
180
216
  function setProject(options, project) {
181
217
  const { id, name, type, defaults } = project;
182
218
  featureFlags.setProjectId(id);
219
+ featureFlags.setProjectType(type);
183
220
  options.projectData = {
184
221
  projectId: id,
185
222
  type,
@@ -188,10 +225,12 @@ function setProject(options, project) {
188
225
  };
189
226
  }
190
227
 
228
+ /** @param {import('./runOptions').RunnerOptions} options */
191
229
  async function setGrid(options) {
192
230
  options.gridData = await gridService.getGridData(options);
193
231
  }
194
232
 
233
+ /** @param {import('./runOptions').RunnerOptions} options */
195
234
  async function setMockNetworkRules(options) {
196
235
  const { project } = options;
197
236
  const props = { projectId: project };
@@ -203,7 +242,7 @@ async function setMockNetworkRules(options) {
203
242
  }
204
243
 
205
244
  /**
206
- * @param {Awaited<ReturnType<typeof import('./runOptions')['process']>>} options
245
+ * @param {import('./runOptions').RunnerOptions} options
207
246
  * @param {string=} customExtensionLocalLocation
208
247
  */
209
248
  async function runRunner(options, customExtensionLocalLocation) {
@@ -235,8 +274,9 @@ async function runRunner(options, customExtensionLocalLocation) {
235
274
  return results;
236
275
  }
237
276
 
277
+ /** @param {import('./runOptions').RunnerOptions} options */
238
278
  function showFreeGridRunWarningIfNeeded(options) {
239
- if (featureAvailabilityService.shouldShowFreeGridRunWarning(options.gridData && options.gridData.type)) {
279
+ if (featureAvailabilityService.shouldShowFreeGridRunWarning(options.gridData?.type)) {
240
280
  const CYAN = '\x1b[36m';
241
281
  const UNDERSCORE = '\x1b[4m';
242
282
  const RESET = '\x1b[0m';
@@ -251,15 +291,15 @@ function showFreeGridRunWarningIfNeeded(options) {
251
291
  * - Reporting the user to analytics
252
292
  * - Authenticating the user and exchanging their token for a jwt
253
293
  * - Sets the grids for the company and validates the user has permission to run the CLI
254
- * @param {Object} options - the run options passed to the CLI, namely the project and token
294
+ * @param {import('./runOptions').RunnerOptions} options - the run options passed to the CLI, namely the project and token
255
295
  */
256
296
  async function init(options) {
257
297
  perf.log('start runner init');
258
298
  const { project, lightweightMode, useChromeLauncher, mode, disableSockets } = options;
259
299
  const featureFlagsReady = featureFlags.fetch();
260
300
  const socketConnected = initSocketServices(project, {
261
- disableResults: disableSockets || Boolean(lightweightMode && lightweightMode.disableResults && (useChromeLauncher || mode !== 'extension')),
262
- disableRemoteStep: disableSockets || Boolean(lightweightMode && lightweightMode.disableRemoteStep),
301
+ disableResults: disableSockets || Boolean(lightweightMode?.disableResults && (useChromeLauncher || mode !== 'extension')),
302
+ disableRemoteStep: disableSockets || Boolean(lightweightMode?.disableRemoteStep),
263
303
  });
264
304
 
265
305
  featureFlagsReady.catch(() => {}); // suppress unhandled rejection
@@ -279,7 +319,7 @@ async function init(options) {
279
319
  setAuthData(options, authData);
280
320
  await setSfdcCredential(options);
281
321
 
282
- if (!(options.lightweightMode && options.lightweightMode.disableLabs)) {
322
+ if (!options.lightweightMode?.disableLabs) {
283
323
  await labFeaturesService.loadLabFeatures(projectById.id, companyByProjectId.activePlan);
284
324
  }
285
325
 
@@ -54,20 +54,21 @@ class ParallelWorkerManager {
54
54
 
55
55
  let stoppedOnError = false;
56
56
  let runningTests = 0;
57
- const runAndWaitToComplete = token => new Promise((resolve, reject) => {
57
+ const runAndWaitToComplete = token => new Promise((resolve) => {
58
58
  const projectId = options.project;
59
59
  const executionQueue = new ExecutionQueue(executionId, executionName, testList, options, branchToUse, testStatus);
60
60
 
61
+ /** @type {{ [testResultId: string]: any; }} */
61
62
  const combinedTestResults = {};
62
63
  const testCount = testList.length;
63
64
 
64
- const companyId = options.company && options.company.companyId;
65
- const companyName = options.company && options.company.name;
65
+ const companyId = options.company?.companyId;
66
+ const companyName = options.company?.name;
66
67
  const source = options.source || 'cli';
67
68
  const user = options.user;
68
- const companyPlan = options.company && options.company.planType;
69
- const isStartUp = options.company && options.company.isStartUp;
70
- const projectName = options.projectData && options.projectData.name;
69
+ const companyPlan = options.company?.planType;
70
+ const isStartUp = options.company?.isStartUp;
71
+ const projectName = options.projectData?.name;
71
72
  const lightweightMode = options.lightweightMode;
72
73
  const sessionType = utils.getSessionType(options);
73
74
 
@@ -95,7 +96,7 @@ class ParallelWorkerManager {
95
96
  const onTestCompleted = async (wid, testId, testResult, sessionId, isRerun) => {
96
97
  runningTests--;
97
98
  const update = {};
98
- if (lightweightMode && lightweightMode.onlyTestIdsNoSuite) {
99
+ if (lightweightMode?.onlyTestIdsNoSuite) {
99
100
  update.show = true;
100
101
  }
101
102
  if (testResult.seleniumStats) {
@@ -115,9 +116,9 @@ class ParallelWorkerManager {
115
116
  update.gridHost = options.host;
116
117
  }
117
118
  if (options.grid || options.gridId) {
118
- update.gridName = options.grid || (options.gridData && options.gridData.name);
119
- update.gridType = options.gridData && options.gridData.type;
120
- update.gridProvider = options.gridData && options.gridData.provider;
119
+ update.gridName = options.grid || options.gridData?.name;
120
+ update.gridType = options.gridData?.type;
121
+ update.gridProvider = options.gridData?.provider;
121
122
  } else if (options.useLocalChromeDriver) {
122
123
  update.gridName = 'local-chrome-driver-from-options';
123
124
  update.gridType = 'local-chrome';
@@ -177,7 +178,7 @@ class ParallelWorkerManager {
177
178
  }
178
179
  };
179
180
 
180
- const onGridSlot = (executionId, resultId, gridInfo) => testStatus.onGridSlot(executionId, resultId, gridInfo);
181
+ const onGridSlot = (_executionId, resultId, gridInfo) => testStatus.onGridSlot(_executionId, resultId, gridInfo);
181
182
 
182
183
  options.userData = {
183
184
  loginData: Object.assign({}, testimCustomToken.getTokenV3UserData(), {
@@ -214,7 +215,6 @@ class ParallelWorkerManager {
214
215
  }
215
216
  }
216
217
 
217
-
218
218
  function schedule(fn, index) {
219
219
  if (index === 0) {
220
220
  fn();