@testim/testim-cli 3.254.0 → 3.255.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 (43) hide show
  1. package/agent/routers/codim/router.test.js +9 -12
  2. package/agent/routers/codim/service.js +16 -16
  3. package/agent/routers/playground/service.js +5 -7
  4. package/cli.js +4 -4
  5. package/cliAgentMode.js +4 -3
  6. package/codim/codim-cli.js +10 -8
  7. package/commons/featureFlags.js +8 -0
  8. package/commons/httpRequest.js +5 -1
  9. package/commons/httpRequestCounters.js +21 -10
  10. package/commons/lazyRequire.js +4 -3
  11. package/commons/preloadTests.js +2 -2
  12. package/commons/prepareRunner.js +4 -2
  13. package/commons/prepareRunnerAndTestimStartUtils.js +40 -41
  14. package/commons/runnerFileCache.js +1 -1
  15. package/commons/testimTunnel.test.js +2 -1
  16. package/coverage/SummaryToObjectReport.js +0 -1
  17. package/coverage/jsCoverage.js +12 -10
  18. package/inputFileUtils.js +11 -9
  19. package/npm-shrinkwrap.json +187 -444
  20. package/package.json +4 -3
  21. package/player/services/tabService.js +15 -1
  22. package/player/stepActions/locateStepAction.js +2 -0
  23. package/player/utils/imageCaptureUtils.js +81 -120
  24. package/player/webdriver.js +25 -22
  25. package/reports/junitReporter.js +6 -7
  26. package/reports/reporter.js +34 -39
  27. package/runOptions.d.ts +260 -0
  28. package/runOptions.js +53 -38
  29. package/runner.js +2 -1
  30. package/runners/ParallelWorkerManager.js +9 -10
  31. package/runners/TestPlanRunner.js +5 -9
  32. package/services/gridService.js +36 -40
  33. package/testRunStatus.js +8 -5
  34. package/utils/argsUtils.js +86 -0
  35. package/utils/argsUtils.test.js +32 -0
  36. package/utils/fsUtils.js +154 -0
  37. package/utils/index.js +10 -161
  38. package/utils/promiseUtils.js +13 -2
  39. package/utils/stringUtils.js +4 -2
  40. package/utils/stringUtils.test.js +22 -0
  41. package/utils/timeUtils.js +25 -0
  42. package/utils/utils.test.js +0 -41
  43. package/workers/WorkerExtension.js +6 -7
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const logger = require('../commons/logger').getLogger('reporter');
4
- const Promise = require('bluebird');
5
4
 
6
5
  class Reporter {
7
6
  setOptions(options, branchToUse) {
@@ -46,7 +45,7 @@ class Reporter {
46
45
  }
47
46
  }
48
47
 
49
- onTestPlanFinished(testResults, testPlanName, startTime, executionId, isAnonymous, isCodeMode, childTestResults) {
48
+ async onTestPlanFinished(testResults, testPlanName, startTime, executionId, isAnonymous, isCodeMode, childTestResults) {
50
49
  let results = {};
51
50
 
52
51
  // TODO: remove mutation of testResults from the Reporter
@@ -68,62 +67,55 @@ class Reporter {
68
67
  } else {
69
68
  results = testResults;
70
69
  }
71
-
72
- return Promise.each(this.reporters, reporter => {
70
+ for (const reporter of this.reporters) {
73
71
  if (reporter?.onTestPlanFinished) {
74
72
  const duration = Date.now() - (startTime || 0);
75
- return reporter.onTestPlanFinished(results, testPlanName, duration, executionId, isAnonymous, isCodeMode);
73
+ await reporter.onTestPlanFinished(results, testPlanName, duration, executionId, isAnonymous, isCodeMode);
76
74
  }
77
- return undefined;
78
- });
75
+ }
79
76
  }
80
77
 
81
- onTestPlanStarted(beforeTests, tests, afterTests, testPlanName, executionId, isAnonymous, configName, isCodeMode) {
82
- return Promise.each(this.reporters, reporter => {
78
+ async onTestPlanStarted(beforeTests, tests, afterTests, testPlanName, executionId, isAnonymous, configName, isCodeMode) {
79
+ for (const reporter of this.reporters) {
83
80
  if (reporter?.onTestPlanStarted) {
84
- return reporter.onTestPlanStarted(beforeTests, tests, afterTests, testPlanName, executionId, isAnonymous, configName, isCodeMode);
81
+ await reporter.onTestPlanStarted(beforeTests, tests, afterTests, testPlanName, executionId, isAnonymous, configName, isCodeMode);
85
82
  }
86
- return undefined;
87
- });
83
+ }
88
84
  }
89
85
 
90
- onGetSlot(workerId, browser) {
91
- return Promise.each(this.reporters, reporter => {
86
+ async onGetSlot(workerId, browser) {
87
+ for (const reporter of this.reporters) {
92
88
  if (reporter?.onGetSlot) {
93
- return reporter.onGetSlot(workerId, browser);
89
+ await reporter.onGetSlot(workerId, browser);
94
90
  }
95
- return undefined;
96
- });
91
+ }
97
92
  }
98
93
 
99
- onGetSession(workerId, testName, mode) {
100
- return Promise.each(this.reporters, reporter => {
94
+ async onGetSession(workerId, testName, mode) {
95
+ for (const reporter of this.reporters) {
101
96
  if (reporter?.onGetSession) {
102
- return reporter.onGetSession(workerId, testName, mode);
97
+ await reporter.onGetSession(workerId, testName, mode);
103
98
  }
104
- return undefined;
105
- });
99
+ }
106
100
  }
107
101
 
108
- onWaitToTestComplete(workerId, isCodeMode, debuggerAddress) {
109
- return Promise.each(this.reporters, reporter => {
102
+ async onWaitToTestComplete(workerId, isCodeMode, debuggerAddress) {
103
+ for (const reporter of this.reporters) {
110
104
  if (reporter?.onWaitToTestComplete) {
111
- return reporter.onWaitToTestComplete(workerId, isCodeMode, debuggerAddress);
105
+ await reporter.onWaitToTestComplete(workerId, isCodeMode, debuggerAddress);
112
106
  }
113
- return undefined;
114
- });
107
+ }
115
108
  }
116
109
 
117
- onWaitToTestStart(workerId) {
118
- return Promise.each(this.reporters, reporter => {
110
+ async onWaitToTestStart(workerId) {
111
+ for (const reporter of this.reporters) {
119
112
  if (reporter?.onWaitToTestStart) {
120
- return reporter.onWaitToTestStart(workerId);
113
+ await reporter.onWaitToTestStart(workerId);
121
114
  }
122
- return undefined;
123
- });
115
+ }
124
116
  }
125
117
 
126
- onAllTestPlansFinished(testPlanResults) {
118
+ async onAllTestPlansFinished(testPlanResults) {
127
119
  // TODO: remove mutation of testPlanResults from the Reporter
128
120
  for (const result of testPlanResults) {
129
121
  if (result.childTestResults) {
@@ -140,18 +132,21 @@ class Reporter {
140
132
  }
141
133
  }
142
134
 
143
- return Promise.each(this.reporters, reporter => {
135
+ for (const reporter of this.reporters) {
144
136
  if (reporter?.onAllTestPlansFinished) {
145
- return reporter.onAllTestPlansFinished(testPlanResults);
137
+ await reporter.onAllTestPlansFinished(testPlanResults);
146
138
  }
147
- return undefined;
148
- });
139
+ }
149
140
  }
150
141
  }
151
142
 
152
143
  function addHook(name) {
153
- Reporter.prototype[name] = function (...args) {
154
- return Promise.filter(this.reporters, reporter => reporter?.[name]).each(reporter => reporter[name](...args));
144
+ Reporter.prototype[name] = async function (...args) {
145
+ for (const reporter of this.reporters) {
146
+ if (reporter?.[name]) {
147
+ await reporter[name](...args);
148
+ }
149
+ }
155
150
  };
156
151
  }
157
152
 
@@ -0,0 +1,260 @@
1
+ interface LoginModeOptions {
2
+ loginMode: true;
3
+ }
4
+
5
+ interface InitModeOptions {
6
+ initCodimMode: true;
7
+ initTestProject: string;
8
+ }
9
+
10
+ interface AgentModeOptions {
11
+ agentMode: true;
12
+ project?: string;
13
+ token?: string;
14
+ agentPort: number;
15
+ agentBind?: string;
16
+ openEditor?: boolean;
17
+ installPlaygroundPlaywrightDeps: boolean;
18
+ installPlaygroundPuppeteerDeps: boolean;
19
+ installPlaygroundSeleniumDeps: boolean;
20
+ startTestimBrowser: boolean;
21
+ ext?: string;
22
+ extensionPath?: string;
23
+ playerLocation: string;
24
+ canary?: boolean;
25
+ playerPath?: string;
26
+ playerRequirePath?: string;
27
+ downloadBrowser: boolean;
28
+ }
29
+
30
+ interface TunnelDaemonOptions {
31
+ tunnelOnlyMode: true;
32
+ tunnelRoutes: string[];
33
+ tunnelRoutesOutput: string;
34
+ tunnelHostHeader?: string;
35
+ tunnelRegion?: string;
36
+ }
37
+ interface NgrokTunnelOptions {
38
+ tunnelPort: number;
39
+ tunnelDiagnostics?: boolean;
40
+ }
41
+
42
+ type TunnelOptions = (TunnelDaemonOptions | NgrokTunnelOptions) & {
43
+ tunnel: true;
44
+ tunnelUseHttpAddress?: boolean;
45
+ token: string;
46
+ project: string;
47
+ };
48
+
49
+ type CodeCoverageReporter = 'clover' | 'html' | 'json-summary' | 'json' | 'lcov' | 'lcovonly' | 'teamcity' | 'text-lcov' | 'text-summary' | 'text';
50
+
51
+ interface LightweightSettings {
52
+ type: 'lightweight' | 'turboMode';
53
+ general: boolean;
54
+ disableLabs: boolean;
55
+ disableFeatureFlags: boolean;
56
+ disableAssets: boolean;
57
+ disablePixelValidation: boolean;
58
+ disableResults: boolean;
59
+ disableStepDelay: boolean;
60
+ disableRemoteStep: boolean;
61
+ assumePreloadedSharedSteps: boolean;
62
+ disableVisibilityCheck: boolean;
63
+ disableLocators: boolean;
64
+ bypassSetup: boolean;
65
+ disableAutoImprove: boolean;
66
+ disableQuotaBlocking: boolean;
67
+ onlyTestIdsNoSuite: boolean;
68
+ uploadAssetsAndResultsOnFailure: boolean;
69
+ preloadTests: boolean;
70
+ disableProjectDefaults: boolean;
71
+ }
72
+
73
+ // TODO: consider typing better, based on the validations we do (example: must have one of grid/grid-id/host&port/test plan)
74
+ interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | 'tunnel'>> {
75
+ token: string;
76
+ project: string;
77
+ user?: string;
78
+ webpackConfig?: string;
79
+ headless?: boolean;
80
+ disableNativeEvents?: boolean;
81
+ baseUrl?: string;
82
+ branch: string;
83
+ autoDetect: boolean;
84
+ userParamsData: object;
85
+ mode: 'selenium' | 'extension';
86
+ isRegressionBaselineRun?: boolean;
87
+ canary?: boolean;
88
+ rerunFailedByRunId?: string;
89
+ disableTimeoutRetry?: boolean;
90
+ resultLabels: string[];
91
+ path?: string;
92
+ protocol?: string;
93
+ testobjectSauce: { testobjectApiKey?: string };
94
+ overrideExecutionName?: string;
95
+ passZeroTests: boolean;
96
+ runQuarantinedTests: boolean;
97
+ downloadBrowser: boolean;
98
+ disableSockets: boolean;
99
+ disableCookiesSameSiteNoneRequiresSecure: boolean;
100
+ shouldMonitorPerformance?: boolean;
101
+ exitCodeIgnoreFailingTests?: boolean;
102
+ seleniumCapsFileContent: object;
103
+ retentionDays?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
104
+ retries?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
105
+
106
+ // #region What to execute
107
+ testId: string[];
108
+ name: string[];
109
+ label: string[];
110
+ suites: string[];
111
+ suiteIds: string[];
112
+ testPlan: string[];
113
+ testPlanIds: string[];
114
+ files: string[];
115
+ // #endregion
116
+
117
+ // #region Test config to use
118
+ testConfigNames?: string[];
119
+ testConfigIds?: string[];
120
+ // #endregion
121
+
122
+ // #region Grid details
123
+ grid?: string;
124
+ gridId?: string;
125
+ host?: string;
126
+ port?: number;
127
+ gridUsername?: string;
128
+ gridPassword?: string;
129
+ useLocalChromeDriver?: boolean;
130
+ chromeBinaryLocation?: string;
131
+ useChromeLauncher?: boolean;
132
+
133
+ disableGridCheck: boolean;
134
+ browser?: 'ie11' | 'ie' | 'internet explorer' | 'edge' | 'edge-chromium' | 'firefox' | 'safari' | 'chrome';
135
+
136
+ proxyForGrid: string;
137
+
138
+ experitestToken?: string;
139
+ saucelabs: { username: string; accessKey: string };
140
+ perfecto: { securityToken?: string; location?: string };
141
+ browserstack: { 'browserstack.user': string; 'browserstack.key': string };
142
+ // #endregion
143
+
144
+ // #region Report settings
145
+ reportFile?: string;
146
+ reportFileClassname?: string;
147
+ reporters?: string[];
148
+ // #endregion
149
+
150
+ // #region Parallel settings
151
+ beforeParallel: number;
152
+ parallel: number;
153
+ afterParallel: number;
154
+ //#endregion
155
+
156
+ // #region TMS
157
+ tmsSuppressReporting: boolean;
158
+ tmsRunId?: string;
159
+ tmsCustomFields?: string;
160
+ // #endregion
161
+
162
+ // #region Extension debugging
163
+ ext?: string;
164
+ extensionLocation?: string[];
165
+ extensionPath?: string;
166
+ // #endregion
167
+
168
+ // #region Session Player debugging
169
+ playerLocation: string;
170
+ playerPath?: string;
171
+ playerRequirePath?: string;
172
+ // #endregion
173
+
174
+ // #region Tunnel
175
+ tunnel?: boolean;
176
+ externalLambdatestTunnelId?: string;
177
+ externalLambdatestUseWss?: string;
178
+ externalLambdatestDisableAutomationTunneling: boolean;
179
+ externalLambdatestMitm: boolean;
180
+ // #endregion
181
+
182
+ // #region Hooks
183
+ beforeTest?: Function;
184
+ afterTest?: Function;
185
+ beforeSuite?: Function;
186
+ afterSuite?: Function;
187
+ // #endregion
188
+
189
+ // #region Timeouts
190
+ testStartTimeout: number;
191
+ timeout: number;
192
+ timeoutWasGiven: boolean;
193
+ browserTimeout: number;
194
+ newBrowserWaitTimeout: number;
195
+ getBrowserTimeout: number;
196
+ getBrowserRetries: number;
197
+ getSessionTimeout: number;
198
+ getSessionRetries: number;
199
+ driverRequestTimeout: number;
200
+ driverRequestRetries: number;
201
+ // #endregion
202
+
203
+ // #region Mock network
204
+ overrideMappingFile?: string;
205
+ mockNetworkRules?: object;
206
+ disableMockNetwork?: boolean;
207
+ // #endregion
208
+
209
+ // #region Code coverage
210
+ codeCoverageUrlFilter?: string;
211
+ collectCodeCoverage?: boolean;
212
+ codeCoverageReportPath?: string;
213
+ codeCoverageSourceMapPath?: string;
214
+ codeCoverageReporter: CodeCoverageReporter[];
215
+ codeCoverageInclude: string[];
216
+ // #endregion
217
+
218
+ // #region Remote run options
219
+ executionId?: string;
220
+ remoteRunId?: string;
221
+ schedulerId?: string;
222
+ source?: string;
223
+ resultId?: string;
224
+ // #endregion
225
+
226
+ // #region Customer Extension
227
+ installCustomExtension?: string;
228
+ // #endregion
229
+
230
+ // #region Capabilities format
231
+ w3cCapabilities: boolean;
232
+ oldCapabilities: boolean;
233
+ // #endregion
234
+
235
+ // #region Chrome specific settings
236
+ chromeBlockLocation: boolean;
237
+ chromeUserDataDir: false | string;
238
+ chromeExtraPrefs: object;
239
+ chromeExtraArgs: string[];
240
+ // #endregion
241
+
242
+ // #region Lightweight / Turbo Mode
243
+ lightweightMode?: LightweightSettings;
244
+ createPrefechedData?: boolean;
245
+ saveRCALocally?: boolean | string;
246
+ // #endregion
247
+
248
+ // #region intersections
249
+ intersections: { labels?: string[]; suiteNames?: string[]; suiteIds?: string[] };
250
+ // #endregion
251
+ }
252
+
253
+ type Options = |
254
+ LoginModeOptions |
255
+ InitModeOptions |
256
+ AgentModeOptions |
257
+ TunnelOptions |
258
+ RunnerOptions;
259
+
260
+ export function process(): Promise<Options>;
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);
@@ -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