@testim/testim-cli 3.233.0 → 3.236.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testim/testim-cli",
3
- "version": "3.233.0",
3
+ "version": "3.236.0",
4
4
  "description": "Command line interface for running Testing on your CI",
5
5
  "author": "Oren Rubin",
6
6
  "contributors": [{
@@ -69,7 +69,6 @@
69
69
  "data-uri-to-buffer": "2.0.2",
70
70
  "decompress": "4.2.1",
71
71
  "express": "4.17.3",
72
- "find-root": "1.1.0",
73
72
  "fkill": "7.2.1",
74
73
  "form-data": "3.0.0",
75
74
  "fs-extra": "10.0.1",
@@ -4,7 +4,7 @@ const StepAction = require('./stepAction');
4
4
  const _ = require('lodash');
5
5
  const logger = require('../../commons/logger').getLogger('input-file-step-action');
6
6
  const { codeSnippets, utils } = require('../../commons/getSessionPlayerRequire');
7
- const { extractElementId } = require('../../utils');
7
+ const { extractElementId, download } = require('../../utils');
8
8
  const inputFileUtils = require('../../inputFileUtils');
9
9
  const featureFlagService = require('../../commons/featureFlags');
10
10
 
@@ -41,18 +41,15 @@ class InputFileStepAction extends StepAction {
41
41
  }
42
42
 
43
43
  async uploadFilesAndForceVisibility(gridLocalFiles, target) {
44
- const preUploadStep = this.driver.isSafari() ?
45
- // in safari we force the visibility ahead of time because making it visible the second time around doesn't work
46
- this.safariPreUploadActions(target) :
47
- Promise.resolve();
48
-
49
44
  try {
50
- await preUploadStep;
45
+ if (this.driver.isSafari()) {
46
+ await this.safariPreUploadActions(target);
47
+ }
51
48
  await this.uploadFiles(gridLocalFiles, target);
52
49
  } catch (err) {
53
50
  const edgeErrorEditableMessage = 'The element is not editable';
54
51
  const edgeErrorFocusableMessage = 'The element is not focusable';
55
- const safariErrorVisibleMessege = 'An element command could not be completed because the element is not visible on the page.';
52
+ const safariErrorVisibleMessage = 'An element command could not be completed because the element is not visible on the page.';
56
53
  const elementNotInteractable = 'element not interactable';
57
54
  const elementNotPointerOrKeyboardInteractable = 'element is not pointer- or keyboard interactable';
58
55
  const invalidStateMsg = 'invalid element state: Element is not currently interactable and may not be manipulated';
@@ -64,7 +61,7 @@ class InputFileStepAction extends StepAction {
64
61
  _.startsWith(errorMsg, mustBeVisibleMsg) ||
65
62
  _.startsWith(errorMsg, edgeErrorEditableMessage) ||
66
63
  _.startsWith(errorMsg, edgeErrorFocusableMessage) ||
67
- _.startsWith(errorMsg, safariErrorVisibleMessege) ||
64
+ _.startsWith(errorMsg, safariErrorVisibleMessage) ||
68
65
  _.includes(errorMsg, notReachableByKeyboard) ||
69
66
  _.includes(errorMsg, elementNotInteractable) ||
70
67
  _.includes(errorMsg, elementNotPointerOrKeyboardInteractable)
@@ -88,9 +85,18 @@ class InputFileStepAction extends StepAction {
88
85
  async performAction() {
89
86
  const target = this.context.data[this.step.targetId || 'targetId'];
90
87
  const overrideAzureStorageUrl = featureFlagService.flags.overrideAzureStorageUrl.isEnabled();
91
-
92
- const fileUrls = await utils.addTokenToFileUrl(this.context.project.id, this.step.fileUrls, this.stepActionUtils.testimServicesApi, overrideAzureStorageUrl, logger);
93
88
  const useJsInputCodeInSafari = featureFlagService.flags.useJsInputCodeInSafari.isEnabled();
89
+ const downloadToBase64 = featureFlagService.flags.downloadToBase64.isEnabled();
90
+
91
+ let fileUrls = await utils.addTokenToFileUrl(this.context.project.id, this.step.fileUrls, this.stepActionUtils.testimServicesApi, overrideAzureStorageUrl, logger);
92
+
93
+ if (downloadToBase64) {
94
+ fileUrls = await Promise.all(fileUrls.map(async ({ name, url }) => {
95
+ const res = await download(url);
96
+ return { name, url: `data:${res.type};base64,${Buffer.from(res.body).toString('base64')}` };
97
+ }));
98
+ }
99
+
94
100
  if (this.driver.isSafari() && (useJsInputCodeInSafari || fileUrls.length > 1)) {
95
101
  await this.driver.executeJSWithArray(`
96
102
  const getLocatedElement = ${codeSnippets.getLocatedElementCode};
@@ -165,15 +171,18 @@ function downloadAndUpload() {
165
171
  return new File([blob], name, { type: blob.type });
166
172
  }));
167
173
 
168
- fileList.item = function(ind) { return this[ind]; };
174
+ const dt = new DataTransfer();
175
+ for (const file of fileList) {
176
+ dt.items.add(file);
177
+ }
178
+ element.files = dt.files;
179
+
169
180
  let changeWasFired = false;
170
181
  const changeFiredHandler = (e) => {
171
182
  changeWasFired = true;
172
183
  };
173
184
 
174
185
  element.addEventListener("change", changeFiredHandler, true);
175
- Reflect.deleteProperty(element, 'files');
176
- Reflect.defineProperty(element, 'files', { get() { return fileList; }, configurable: true });
177
186
  await Promise.resolve(); // wait microtick
178
187
  element.dispatchEvent(new Event("input", { bubbles: true }));
179
188
  if (!changeWasFired) {
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+
3
4
  const sessionPlayer = require('../../commons/getSessionPlayerRequire');
4
5
 
5
6
  const StepAction = require('./stepAction');
@@ -7,6 +8,7 @@ const constants = sessionPlayer.commonConstants.stepResult;
7
8
  const paramEvaluator = sessionPlayer.stepParamExpressionEvaluator;
8
9
  const utils = sessionPlayer.utils;
9
10
 
11
+
10
12
  const Promise = require('bluebird');
11
13
 
12
14
  class TextValidationStepAction extends StepAction {
@@ -15,6 +17,7 @@ class TextValidationStepAction extends StepAction {
15
17
  var context = this.context;
16
18
  var target = this.getTarget();
17
19
  var frameHandler = this.frameHandler;
20
+
18
21
 
19
22
  return new Promise(resolve => {
20
23
  var onFail = resultInfo => {
@@ -59,6 +59,7 @@ class WebDriver extends WebDriverApi {
59
59
  this.cdpUrl = undefined;
60
60
  this.browserClosedCallbacks = [];
61
61
  this.browserClosedFailedKeepAlives = 0;
62
+ this.ignoreHiddenTagsText = false;
62
63
  }
63
64
 
64
65
  registerToClosedBrowser(callback) {
@@ -71,6 +72,7 @@ class WebDriver extends WebDriverApi {
71
72
 
72
73
  async init(browserOptions, testName, testRunConfig, gridInfo, customExtensionLocalLocation, executionId, testResultId, seleniumPerfStats = new SeleniumPerfStats(), fastInit = false, lambdatestService) {
73
74
  this.browserClosedFailedKeepAlives = 0;
75
+ this.ignoreHiddenTagsText = _(browserOptions).get('company.activePlan.premiumFeatures.ignoreHiddenTagsText');
74
76
  this.browserClosedCallbacks = [];
75
77
  const capabilities = desiredCapabilitiesBuilder.buildSeleniumOptions(browserOptions, testName, testRunConfig, gridInfo, customExtensionLocalLocation, executionId, testResultId, lambdatestService);
76
78
  if (capabilities.desiredCapabilities) {
@@ -360,7 +362,14 @@ class WebDriver extends WebDriverApi {
360
362
  return element.shadowRoot.textContent.replace(/(\r\n|\n|\r)/gm, "");
361
363
  }
362
364
  } catch (err) { }
363
- return element.textContent.replace(/(\r\n|\n|\r)/gm, "");
365
+ if (this.ignoreHiddenTagsText && Array.prototype.some.call(element.children, elem => elem.hidden)) {
366
+ const dupElement = element.cloneNode(true);
367
+ const hiddenChildren = Array.prototype.filter.call(dupElement.children, elem => elem.hidden);
368
+ hiddenChildren.forEach(child => {
369
+ dupElement.removeChild(child);
370
+ });
371
+ return dupElement.textContent.replace(/(\r\n|\n|\r)/gm, "");
372
+ } return element.textContent.replace(/(\r\n|\n|\r)/gm, "");
364
373
  }
365
374
 
366
375
  function getElementTextContent(element) {
package/processHandler.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable no-console */
2
+
1
3
  'use strict';
2
4
 
3
5
  const logger = require('./commons/logger').getLogger('process-handler');
@@ -6,9 +8,12 @@ const exitHooks = [];
6
8
  const Promise = require('bluebird');
7
9
 
8
10
  module.exports = function (onExit) {
9
- process.on('uncaughtException', async (err) => {
11
+ async function cleanup(err) {
10
12
  // give cleanup and socket reports a chance to run
11
13
  await Promise.all(exitHooks.map(x => x())).timeout(10000).catch(() => {});
14
+ onExit(err);
15
+ }
16
+ process.on('uncaughtException', async (err) => {
12
17
  logger.error('Caught exception', { err });
13
18
  console.log('Uncaught exception');
14
19
  if (err.message) {
@@ -17,7 +22,7 @@ module.exports = function (onExit) {
17
22
  if (err.reason) {
18
23
  console.log('Reason =', err.reason);
19
24
  }
20
- onExit(err);
25
+ await cleanup(err);
21
26
  });
22
27
 
23
28
  process.on('unhandledRejection', (reason) => {
@@ -40,7 +45,7 @@ module.exports = function (onExit) {
40
45
  const msg = 'Runner aborted - SIGTERM event';
41
46
  const err = new Error(msg);
42
47
  logger.error(msg);
43
- onExit(err);
48
+ cleanup(err);
44
49
  throw err;
45
50
  });
46
51
 
@@ -48,7 +53,7 @@ module.exports = function (onExit) {
48
53
  const msg = 'Runner aborted - SIGINT event';
49
54
  const err = new Error(msg);
50
55
  logger.error(msg);
51
- onExit(err);
56
+ cleanup(err);
52
57
  throw err;
53
58
  });
54
59
 
package/runOptions.js CHANGED
@@ -306,6 +306,7 @@ program
306
306
  .option('--external-lambdatest-tunnel-id [tunnel-id]', 'use existing lambdatest tunnel ID')
307
307
  .option('--external-lambdatest-use-wss', 'use wss instead of ssh for LT', false)
308
308
  .option('--external-lambdatest-disable-automation-tunneling', 'don\'t tunnel Testim calls in LT tunnel', true)
309
+ .option('--external-lambdatest-mitm', 'Turn on LT Man in the middle', false)
309
310
 
310
311
  .option('--w3c-capabilities [enable-w3c-caps-mode]', 'enable/disable w3c capabilities format (default enable)', JSON.parse, true)
311
312
  .option('--old-capabilities [enable-old-caps-mode]', 'enable/disable old capabilities format (default enable)', JSON.parse, true)
@@ -321,6 +322,7 @@ program
321
322
  // Agent mode
322
323
  .option('connect, --agent [enable-agent-mode]', 'enable Testim CLI agent mode', false)
323
324
  .option('start [enable-start]', 'Connect to testim and open the editor in a standalone browser', false)
325
+ .option('--download-browser', 'when used with the start option, downloads a fixed version to run Testim editor in', false)
324
326
  .option('--agent-port [agent-port]', 'set agent port', Number, 42543)
325
327
  .option('--agent-bind [agent-host-bind]', 'set agent host bind', '127.0.0.1')
326
328
 
@@ -494,7 +496,7 @@ module.exports = {
494
496
  }
495
497
 
496
498
  if (program.proxyForGrid && !program.proxy) {
497
- return Promise.reject(new ArgError('missing --proxy option'));
499
+ throw new ArgError('missing --proxy option');
498
500
  }
499
501
 
500
502
  if (runOptionsAgentFlow.isAgentFlow(program)) {
@@ -553,20 +555,20 @@ module.exports = {
553
555
  }
554
556
  }
555
557
 
556
- if (program.reporters && program.reporters.indexOf('junit') > -1 && !program.reportFile) {
558
+ if (program.reporters && program.reporters.includes('junit') && !program.reportFile) {
557
559
  console.log('Warning: please define --report-file option for JUnit reporter');
558
560
  }
559
561
 
560
562
  if (!program.tunnel && program.externalLambdatestTunnelId) {
561
- return Promise.reject(new ArgError('missing --tunnel parameter'));
563
+ throw new ArgError('missing --tunnel parameter');
562
564
  }
563
565
 
564
566
  if (!program.tunnel && program.externalLambdatestUseWss) {
565
- return Promise.reject(new ArgError('missing --tunnel parameter'));
567
+ throw new ArgError('missing --tunnel parameter');
566
568
  }
567
569
 
568
570
  if (!program.tunnel && [program.tunnelPort, program.tunnelHostHeader, program.tunnelRegion, program.tunnelDiagnostics].some(Boolean)) {
569
- return Promise.reject(new ArgError('missing --tunnel parameter'));
571
+ throw new ArgError('missing --tunnel parameter');
570
572
  }
571
573
  if (program.chromeExtraPrefs) {
572
574
  try {
@@ -609,7 +611,7 @@ module.exports = {
609
611
 
610
612
  // SauceLabs Options
611
613
  if ((program.sauceUser && !program.sauceKey) || (!program.sauceUser && program.sauceKey)) {
612
- return Promise.reject(new ArgError('missing --sauce-user <sauce-user> or --sauce-key <sauce-key>'));
614
+ throw new ArgError('missing --sauce-user <sauce-user> or --sauce-key <sauce-key>');
613
615
  }
614
616
 
615
617
  if (program.sauceUser && program.sauceKey) {
@@ -655,7 +657,7 @@ module.exports = {
655
657
 
656
658
  // BrowserStack options
657
659
  if ((program.browserstackUser && !program.browserstackKey) || (!program.browserstackUser && program.browserstackKey)) {
658
- return Promise.reject(new ArgError('missing --browserstack-user <browserstack-user> or --browserstack-key <browserstack-key>'));
660
+ throw new ArgError('missing --browserstack-user <browserstack-user> or --browserstack-key <browserstack-key>');
659
661
  }
660
662
  if (program.browserstackUser && program.browserstackKey) {
661
663
  setHostAndPortForBrowserStack();
@@ -726,7 +728,7 @@ module.exports = {
726
728
  if (projectId) {
727
729
  program.project = projectId;
728
730
  } else {
729
- return Promise.reject(new ArgError('missing project-id info, either --login to provide new credentials or use --project <project-id>'));
731
+ throw new ArgError('missing project-id info, either --login to provide new credentials or use --project <project-id>');
730
732
  }
731
733
  }
732
734
 
@@ -737,12 +739,12 @@ module.exports = {
737
739
 
738
740
  if (program.testConfig) {
739
741
  // convert single test config inputs to array (e.g. from configFile)
740
- program.testConfig = [].concat(program.testConfig);
742
+ program.testConfig = [program.testConfig].flat();
741
743
  }
742
744
 
743
745
  if (program.testConfigId) {
744
746
  // convert single test config inputs to array (e.g. from configFile)
745
- program.testConfigId = [].concat(program.testConfigId);
747
+ program.testConfigId = [program.testConfigId].flat();
746
748
  }
747
749
 
748
750
  program.retries = !program.retries || typeof program.retries === 'boolean' ? 1 : Number(program.retries) + 1;
@@ -764,21 +766,19 @@ module.exports = {
764
766
 
765
767
 
766
768
  if (program.parallel > 1 && program.run && !program.gridId && !program.grid &&
767
- ((!program.testPlan || program.testPlan.length === 0) && (!program.testPlanId || !program.testPlanId.length))) {
768
- if (process.stdout.isTTY && !program.headless && !process.env.TERM) {
769
- const prompts = require('prompts');
770
- const response = await prompts({
771
- type: 'toggle',
772
- name: 'isSure',
773
- message: 'Running in parallel without --headless flag will open several browsers on your computer. Are you sure?',
774
- initial: false,
775
- active: 'yes',
776
- inactive: 'no',
777
- });
778
-
779
- if (!response.isSure) {
780
- process.exit(0);
781
- }
769
+ ((!program.testPlan || program.testPlan.length === 0) && (!program.testPlanId || !program.testPlanId.length)) && process.stdout.isTTY && !program.headless && !process.env.TERM) {
770
+ const prompts = require('prompts');
771
+ const response = await prompts({
772
+ type: 'toggle',
773
+ name: 'isSure',
774
+ message: 'Running in parallel without --headless flag will open several browsers on your computer. Are you sure?',
775
+ initial: false,
776
+ active: 'yes',
777
+ inactive: 'no',
778
+ });
779
+
780
+ if (!response.isSure) {
781
+ process.exit(0);
782
782
  }
783
783
  }
784
784
 
@@ -787,11 +787,11 @@ module.exports = {
787
787
  program.port = program.port && Number(program.port);
788
788
 
789
789
  if (program.retries <= 0 || _.isNaN(program.retries)) {
790
- return Promise.reject(new ArgError('test failure retry count could not be a negative number or string, --retries <max_num_of_retries>'));
790
+ throw new ArgError('test failure retry count could not be a negative number or string, --retries <max_num_of_retries>');
791
791
  }
792
792
 
793
793
  if (program.retries > 21) {
794
- return Promise.reject(new ArgError('Max number of retries exceeded. Number cannot be greater than 20, --retries <max_num_of_retries>'));
794
+ throw new ArgError('Max number of retries exceeded. Number cannot be greater than 20, --retries <max_num_of_retries>');
795
795
  }
796
796
 
797
797
  if (!program.token) {
@@ -801,33 +801,32 @@ module.exports = {
801
801
  if (credentialToken) {
802
802
  program.token = credentialToken;
803
803
  } else {
804
- return Promise.reject(
805
- new ArgError('missing Testim Access Token, either --login to provide new credentials or use --token <testim-access-token>, contact info@testim.io if you need a new one.'));
804
+ throw new ArgError('missing Testim Access Token, either --login to provide new credentials or use --token <testim-access-token>, contact info@testim.io if you need a new one.');
806
805
  }
807
806
  }
808
807
 
809
808
  if (program.browserTimeout <= 0 || _.isNaN(program.browserTimeout)) {
810
- return Promise.reject(new ArgError('get browser timeout could not be a negative number, --browser-timeout <get-browser-timeout>'));
809
+ throw new ArgError('get browser timeout could not be a negative number, --browser-timeout <get-browser-timeout>');
811
810
  }
812
811
 
813
812
  if (program.newBrowserWaitTimeout <= 0 || _.isNaN(program.newBrowserWaitTimeout)) {
814
- return Promise.reject(new ArgError('max new browser wait timeout could not be a negative number, --new-browser-wait-timeout <max-wait-to-browser>'));
813
+ throw new ArgError('max new browser wait timeout could not be a negative number, --new-browser-wait-timeout <max-wait-to-browser>');
815
814
  }
816
815
 
817
816
  if (program.timeout <= 0 || _.isNaN(program.timeout)) {
818
- return Promise.reject(new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>'));
817
+ throw new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>');
819
818
  }
820
819
 
821
820
  if (program.parallel <= 0 || _.isNaN(program.parallel)) {
822
- return Promise.reject(new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>'));
821
+ throw new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>');
823
822
  }
824
823
 
825
- if ([CLI_MODE.EXTENSION, CLI_MODE.SELENIUM].indexOf(program.mode) === -1) {
826
- return Promise.reject(new ArgError(`runner mode <${program.mode}> is not supported`));
824
+ if (![CLI_MODE.EXTENSION, CLI_MODE.SELENIUM].includes(program.mode)) {
825
+ throw new ArgError(`runner mode <${program.mode}> is not supported`);
827
826
  }
828
827
 
829
828
  if ((program.mode !== CLI_MODE.SELENIUM) && program.disableNativeEvents) {
830
- return Promise.reject(new ArgError('disable-native-events is only applicable in selenium mode'));
829
+ throw new ArgError('disable-native-events is only applicable in selenium mode');
831
830
  }
832
831
 
833
832
  if (
@@ -848,9 +847,9 @@ module.exports = {
848
847
  !program.useChromeLauncher &&
849
848
  !program.createPrefechedData
850
849
  ) {
851
- return Promise.reject(new ArgError(
850
+ throw new ArgError(
852
851
  'missing remote grid address parameter, specify --host <host-name-or-ip> or --grid <grid-name> or --grid-id <grid-id>'
853
- ));
852
+ );
854
853
  }
855
854
  } else if (
856
855
  program.testId.length ||
@@ -862,9 +861,9 @@ module.exports = {
862
861
  program.useLocalChromeDriver ||
863
862
  program.useChromeLauncher
864
863
  ) {
865
- return Promise.reject(new ArgError(
864
+ throw new ArgError(
866
865
  'cannot set --testId, --label, --name, --browser, --test-config, --test-config-id, --use-local-chrome-driver --use-chrome-launcher or --suite with --test-plan option'
867
- ));
866
+ );
868
867
  }
869
868
 
870
869
  if (
@@ -875,17 +874,17 @@ module.exports = {
875
874
  isSuiteSpecified) &&
876
875
  program.file
877
876
  ) {
878
- return Promise.reject(new ArgError(
877
+ throw new ArgError(
879
878
  'Cannot pass codeful automation tests with --testId --label --name or --suite'
880
- ));
879
+ );
881
880
  }
882
881
 
883
882
  const numberOfDefinedHosts = [program.host, program.grid, program.gridId].filter(Boolean).length;
884
883
  if (numberOfDefinedHosts > 1) {
885
- return Promise.reject(new ArgError('please define exactly one of --grid or --grid-id or --host'));
884
+ throw new ArgError('please define exactly one of --grid or --grid-id or --host');
886
885
  }
887
886
 
888
- if (program.host && program.host.indexOf('/') !== -1) {
887
+ if (program.host && program.host.includes('/')) {
889
888
  if (!/^(f|ht)tps?:\/\//i.test(program.host)) {
890
889
  program.host = `http://${program.host}`;
891
890
  }
@@ -896,7 +895,7 @@ module.exports = {
896
895
  program.resultLabel = program.resultLabel.map(label => label.trim()).filter(Boolean);
897
896
  const invalidLabels = program.resultLabel.filter(label => label.length >= 250).filter(Boolean);
898
897
  if (invalidLabels.length) {
899
- return Promise.reject(new ArgError('A result label cannot exceed 250 characters'));
898
+ throw new ArgError('A result label cannot exceed 250 characters');
900
899
  }
901
900
  }
902
901
 
@@ -904,66 +903,66 @@ module.exports = {
904
903
  const playerUrl = runOptionsUtils.getPlayerUrl(program);
905
904
 
906
905
  if (!program.w3cCapabilities && !program.oldCapabilities) {
907
- return Promise.reject(new ArgError('cannot set --w3c-capabilities and --old-capabilities options as false'));
906
+ throw new ArgError('cannot set --w3c-capabilities and --old-capabilities options as false');
908
907
  }
909
908
  program.protocol = program.protocol || (program.port === 443 ? 'https' : 'http');
910
909
  if (!['http', 'https'].includes(program.protocol)) {
911
- return Promise.reject(new ArgError('invalid --protocol value, allow --protocol http or https'));
910
+ throw new ArgError('invalid --protocol value, allow --protocol http or https');
912
911
  }
913
912
 
914
913
  if (program.rerunFailedByRunId && program.branch) {
915
- return Promise.reject(new ArgError('It is not possible to use --branch with --rerun-failed-by-run-id. Tests will automatically run on the same branch that was used in the original run'));
914
+ throw new ArgError('It is not possible to use --branch with --rerun-failed-by-run-id. Tests will automatically run on the same branch that was used in the original run');
916
915
  }
917
916
 
918
917
  if (program.rerunFailedByRunId &&
919
918
  (isSuiteSpecified || program.name.length ||
920
919
  program.testId.length || program.label.length || isTestPlanSpecified)) {
921
- return Promise.reject(new ArgError('Re-running failed tests is not possible when suite (--suite),' +
922
- ' label (--label), plan (--test-plan), or other test flags (--test) are provided. Please remove these flags and try again'));
920
+ throw new ArgError('Re-running failed tests is not possible when suite (--suite),' +
921
+ ' label (--label), plan (--test-plan), or other test flags (--test) are provided. Please remove these flags and try again');
923
922
  }
924
923
 
925
924
  if (program.run.length) {
926
925
  const glob = require('glob');
927
926
  program.files = _.flatMap(program.run, files => glob.sync(files));
928
927
  if (program.files.length === 0) {
929
- return Promise.reject(new ArgError(`No files found at path '${program.run}'.`));
928
+ throw new ArgError(`No files found at path '${program.run}'.`);
930
929
  }
931
930
  } else {
932
931
  program.files = [];
933
932
  }
934
933
 
935
934
  if (program.setRetention && !_.inRange(_.parseInt(program.setRetention), 1, 11)) {
936
- return Promise.reject(new ArgError('Please provide the number of days that the test results will be retained for (--set-retention must be a whole number between 1 to 10)'));
935
+ throw new ArgError('Please provide the number of days that the test results will be retained for (--set-retention must be a whole number between 1 to 10)');
937
936
  }
938
937
  program.setRetention = program.setRetention && Number(program.setRetention);
939
938
 
940
939
  const mockNetworkDeprecationMsg = 'is no longer supported, please use --override-mapping-file';
941
940
  if (program.mockNetworkHar) {
942
- return Promise.reject(new ArgError(`--mock-network-har ${mockNetworkDeprecationMsg}`));
941
+ throw new ArgError(`--mock-network-har ${mockNetworkDeprecationMsg}`);
943
942
  }
944
943
  if (program.mockNetworkPattern) {
945
- return Promise.reject(new ArgError(`--mock-network-pattern ${mockNetworkDeprecationMsg}`));
944
+ throw new ArgError(`--mock-network-pattern ${mockNetworkDeprecationMsg}`);
946
945
  }
947
946
 
948
947
  if (program.disableMockNetwork && program.overrideMappingFile) {
949
- return Promise.reject(new ArgError('You can either use --disable-mock-network or --override-mapping-file'));
948
+ throw new ArgError('You can either use --disable-mock-network or --override-mapping-file');
950
949
  }
951
950
 
952
951
  if (!program.collectCodeCoverage && program.codeCoverageSourceMapPath) {
953
- return Promise.reject(new ArgError('cannot set --code-coverage-source-map-path without passing --collect-code-coverage'));
952
+ throw new ArgError('cannot set --code-coverage-source-map-path without passing --collect-code-coverage');
954
953
  }
955
954
 
956
955
  if (!program.collectCodeCoverage && program.codeCoverageReporter.length) {
957
- return Promise.reject(new ArgError('cannot set --code-coverage-reporter without passing --collect-code-coverage'));
956
+ throw new ArgError('cannot set --code-coverage-reporter without passing --collect-code-coverage');
958
957
  }
959
958
 
960
959
  if (!program.collectCodeCoverage && program.codeCoverageInclude.length) {
961
- return Promise.reject(new ArgError('cannot set --code-coverage-include without passing --collect-code-coverage'));
960
+ throw new ArgError('cannot set --code-coverage-include without passing --collect-code-coverage');
962
961
  }
963
962
 
964
963
  if (program.collectCodeCoverage && program.codeCoverageReporter && _.difference(program.codeCoverageReporter, CODE_COVERAGE_REPORTER_OPTIONS).length) {
965
964
  const diff = _.difference(program.codeCoverageReporter, CODE_COVERAGE_REPORTER_OPTIONS);
966
- return Promise.reject(new ArgError(`invalid --code-coverage-reporter parameters ${diff.join('/')}`));
965
+ throw new ArgError(`invalid --code-coverage-reporter parameters ${diff.join('/')}`);
967
966
  }
968
967
 
969
968
  program.codeCoverageReporter = program.codeCoverageReporter.length === 0 ? ['html', 'text'] : program.codeCoverageReporter;
@@ -982,7 +981,7 @@ module.exports = {
982
981
 
983
982
  if (program.mode !== CLI_MODE.EXTENSION && usedExtensionOptions.length) {
984
983
  const multi = usedExtensionOptions.length > 1 ? 'are' : 'is';
985
- return Promise.reject(new ArgError(`${usedExtensionOptions.map(key => extensionOnlyOptions[key]).join(' and ')} ${multi} only applicable in extension mode`));
984
+ throw new ArgError(`${usedExtensionOptions.map(key => extensionOnlyOptions[key]).join(' and ')} ${multi} only applicable in extension mode`);
986
985
  }
987
986
 
988
987
  if (program.tmsFieldFile) {
@@ -1054,18 +1053,18 @@ module.exports = {
1054
1053
  }
1055
1054
 
1056
1055
  if (typeof program.baseUrl === 'boolean') {
1057
- return Promise.reject(new ArgError('base url cannot be used as a flag, and must contain a string value'));
1056
+ throw new ArgError('base url cannot be used as a flag, and must contain a string value');
1058
1057
  }
1059
1058
 
1060
1059
  return ({
1061
- testId: [].concat(program.testId),
1062
- name: [].concat(program.name),
1063
- label: [].concat(program.label),
1064
- suites: [].concat(program.suite),
1065
- suiteIds: [].concat(program.suiteId),
1066
- testPlan: [].concat(program.testPlan),
1067
- testPlanIds: [].concat(program.testPlanId),
1068
- files: [].concat(program.files),
1060
+ testId: [program.testId].flat(),
1061
+ name: [program.name].flat(),
1062
+ label: [program.label].flat(),
1063
+ suites: [program.suite].flat(),
1064
+ suiteIds: [program.suiteId].flat(),
1065
+ testPlan: [program.testPlan].flat(),
1066
+ testPlanIds: [program.testPlanId].flat(),
1067
+ files: [program.files].flat(),
1069
1068
  webpackConfig: program.webpackConfig,
1070
1069
  reportFile: program.reportFile,
1071
1070
  reportFileClassname: program.overrideReportFileClassname,
@@ -1116,7 +1115,7 @@ module.exports = {
1116
1115
 
1117
1116
  // Extension
1118
1117
  ext: program.ext,
1119
- extensionLocation: [].concat(program.extensionPath || extHeadlessUrl),
1118
+ extensionLocation: [program.extensionPath || extHeadlessUrl].flat(),
1120
1119
  extensionPath: program.extensionPath,
1121
1120
 
1122
1121
  // Player
@@ -1136,6 +1135,7 @@ module.exports = {
1136
1135
  externalLambdatestTunnelId: program.externalLambdatestTunnelId,
1137
1136
  externalLambdatestUseWss: program.externalLambdatestUseWss || false,
1138
1137
  externalLambdatestDisableAutomationTunneling: Boolean(program.externalLambdatestDisableAutomationTunneling),
1138
+ externalLambdatestMitm: Boolean(program.externalLambdatestMitm),
1139
1139
 
1140
1140
  // Hooks
1141
1141
  beforeTest: program.beforeTest,
@@ -5,12 +5,16 @@
5
5
  const { ArgError } = require('./errors');
6
6
  const _ = require('lodash');
7
7
  const runOptionsUtils = require('./runOptionsUtils');
8
+ const analytics = require('./commons/testimAnalytics');
8
9
 
9
10
  /**
10
11
  *
11
12
  * @param {import("commander").CommanderStatic} program
12
13
  */
13
14
  function isAgentFlow(program) {
15
+ if (program.start) {
16
+ analytics.track(null, 'cli-start-command', { downloadBrowser: Boolean(program.downloadBrowser) });
17
+ }
14
18
  if (program.startV2 || program.start || program.agent) {
15
19
  return true;
16
20
  }
@@ -73,6 +77,7 @@ async function runAgentFlow(program) {
73
77
  canary: program.canary,
74
78
  playerPath: program.playerPath,
75
79
  playerRequirePath: program.playerRequirePath,
80
+ downloadBrowser: Boolean(program.downloadBrowser),
76
81
  };
77
82
  }
78
83
 
package/runner.js CHANGED
@@ -25,7 +25,6 @@ const FREE_PLAN_MINIMUM_BROWSER_TIMEOUT = 30 * 60 * 1000;
25
25
  const TestPlanRunner = require('./runners/TestPlanRunner');
26
26
  const labFeaturesService = require('./services/labFeaturesService');
27
27
  const featureAvailabilityService = require('./commons/featureAvailabilityService');
28
- const featureFlagService = require('./commons/featureFlags');
29
28
 
30
29
  const logger = require('./commons/logger').getLogger('runner');
31
30
 
@@ -269,7 +268,7 @@ async function init(options) {
269
268
  await labFeaturesService.loadLabFeatures(projectById.id, companyByProjectId.activePlan);
270
269
  }
271
270
 
272
- if (options.lightweightMode && options.lightweightMode.type === 'turboMode' && (featureFlagService.flags.highSpeedMode.getValue() === 'disabled' || options.company.planType === 'free')) {
271
+ if (options.lightweightMode && options.lightweightMode.type === 'turboMode' && (featureFlags.flags.highSpeedMode.getValue() === 'disabled' || options.company.planType === 'free')) {
273
272
  delete options.lightweightMode;
274
273
  }
275
274
 
@@ -8,7 +8,6 @@ const config = require('../commons/config');
8
8
  const ExecutionQueue = require('../executionQueue');
9
9
 
10
10
  const testimCustomToken = require('../commons/testimCustomToken');
11
- const testimServicesApi = require('../commons/testimServicesApi');
12
11
  const labFeaturesService = require('../services/labFeaturesService');
13
12
  const perf = require('../commons/performance-logger');
14
13
  const { StopRunOnError } = require('../errors');