@testim/testim-cli 3.234.0 → 3.237.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.234.0",
3
+ "version": "3.237.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) {
@@ -330,7 +332,7 @@ class WebDriver extends WebDriverApi {
330
332
  }
331
333
 
332
334
  getElementTextJS(locatedElement) {
333
- function extractTextCode(locatedElement) {
335
+ function extractTextCode(locatedElement, ignoreHiddenTagsText) {
334
336
  // copy of utils.getElementTextContent to run inside content script
335
337
  // sadly .children doesn't work for SVG elements in IE11
336
338
  function clearTitleTags(node) {
@@ -360,7 +362,15 @@ 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 (ignoreHiddenTagsText && Array.prototype.some.call(element.children, function (elem) { return elem.hidden; })) {
366
+ var dupElement = element.cloneNode(true);
367
+ var hiddenChildren = Array.prototype.filter.call(dupElement.children, function (elem) { return elem.hidden; });
368
+ hiddenChildren.forEach(function (child) {
369
+ dupElement.removeChild(child);
370
+ });
371
+ return dupElement.textContent.replace(/(\r\n|\n|\r)/gm, '');
372
+ }
373
+ return element.textContent.replace(/(\r\n|\n|\r)/gm, '');
364
374
  }
365
375
 
366
376
  function getElementTextContent(element) {
@@ -390,7 +400,7 @@ class WebDriver extends WebDriverApi {
390
400
  var getLocatedElement = ${codeSnippets().getLocatedElementCode};
391
401
  var extractText = ${extractTextCode.toString()};
392
402
  return extractText.apply(null, arguments)
393
- `, locatedElement)
403
+ `, locatedElement, this.ignoreHiddenTagsText)
394
404
  .then(result => result.value);
395
405
  }
396
406
 
package/runOptions.js CHANGED
@@ -187,6 +187,8 @@ program
187
187
  .option('--token [token]', 'identification token to testim')
188
188
  .option('--is-regression-baseline-run', 'save doms and run results as regression baseline data')
189
189
  .option('--parallel [number-of-tests]', 'number of tests to run on parallel')
190
+ .option('--before-parallel [number-of-tests]', 'number of tests to run on parallel in the before phase of a test plan')
191
+ .option('--after-parallel [number-of-tests]', 'number of tests to run on parallel in the after phase of a test plan')
190
192
  .option('--canary [canary-mode]', 'enable canary mode', false)
191
193
  .option('--test-plan [test-plan-name]', 'test plan to run', collect, [])
192
194
  .option('--test-plan-id [test-plan-id]', 'test plan to run', collect, [])
@@ -306,6 +308,7 @@ program
306
308
  .option('--external-lambdatest-tunnel-id [tunnel-id]', 'use existing lambdatest tunnel ID')
307
309
  .option('--external-lambdatest-use-wss', 'use wss instead of ssh for LT', false)
308
310
  .option('--external-lambdatest-disable-automation-tunneling', 'don\'t tunnel Testim calls in LT tunnel', true)
311
+ .option('--external-lambdatest-mitm', 'Turn on LT Man in the middle', false)
309
312
 
310
313
  .option('--w3c-capabilities [enable-w3c-caps-mode]', 'enable/disable w3c capabilities format (default enable)', JSON.parse, true)
311
314
  .option('--old-capabilities [enable-old-caps-mode]', 'enable/disable old capabilities format (default enable)', JSON.parse, true)
@@ -321,6 +324,7 @@ program
321
324
  // Agent mode
322
325
  .option('connect, --agent [enable-agent-mode]', 'enable Testim CLI agent mode', false)
323
326
  .option('start [enable-start]', 'Connect to testim and open the editor in a standalone browser', false)
327
+ .option('--download-browser', 'when used with the start option, downloads a fixed version to run Testim editor in', false)
324
328
  .option('--agent-port [agent-port]', 'set agent port', Number, 42543)
325
329
  .option('--agent-bind [agent-host-bind]', 'set agent host bind', '127.0.0.1')
326
330
 
@@ -494,7 +498,7 @@ module.exports = {
494
498
  }
495
499
 
496
500
  if (program.proxyForGrid && !program.proxy) {
497
- return Promise.reject(new ArgError('missing --proxy option'));
501
+ throw new ArgError('missing --proxy option');
498
502
  }
499
503
 
500
504
  if (runOptionsAgentFlow.isAgentFlow(program)) {
@@ -553,20 +557,20 @@ module.exports = {
553
557
  }
554
558
  }
555
559
 
556
- if (program.reporters && program.reporters.indexOf('junit') > -1 && !program.reportFile) {
560
+ if (program.reporters && program.reporters.includes('junit') && !program.reportFile) {
557
561
  console.log('Warning: please define --report-file option for JUnit reporter');
558
562
  }
559
563
 
560
564
  if (!program.tunnel && program.externalLambdatestTunnelId) {
561
- return Promise.reject(new ArgError('missing --tunnel parameter'));
565
+ throw new ArgError('missing --tunnel parameter');
562
566
  }
563
567
 
564
568
  if (!program.tunnel && program.externalLambdatestUseWss) {
565
- return Promise.reject(new ArgError('missing --tunnel parameter'));
569
+ throw new ArgError('missing --tunnel parameter');
566
570
  }
567
571
 
568
572
  if (!program.tunnel && [program.tunnelPort, program.tunnelHostHeader, program.tunnelRegion, program.tunnelDiagnostics].some(Boolean)) {
569
- return Promise.reject(new ArgError('missing --tunnel parameter'));
573
+ throw new ArgError('missing --tunnel parameter');
570
574
  }
571
575
  if (program.chromeExtraPrefs) {
572
576
  try {
@@ -609,7 +613,7 @@ module.exports = {
609
613
 
610
614
  // SauceLabs Options
611
615
  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>'));
616
+ throw new ArgError('missing --sauce-user <sauce-user> or --sauce-key <sauce-key>');
613
617
  }
614
618
 
615
619
  if (program.sauceUser && program.sauceKey) {
@@ -655,7 +659,7 @@ module.exports = {
655
659
 
656
660
  // BrowserStack options
657
661
  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>'));
662
+ throw new ArgError('missing --browserstack-user <browserstack-user> or --browserstack-key <browserstack-key>');
659
663
  }
660
664
  if (program.browserstackUser && program.browserstackKey) {
661
665
  setHostAndPortForBrowserStack();
@@ -726,7 +730,7 @@ module.exports = {
726
730
  if (projectId) {
727
731
  program.project = projectId;
728
732
  } else {
729
- return Promise.reject(new ArgError('missing project-id info, either --login to provide new credentials or use --project <project-id>'));
733
+ throw new ArgError('missing project-id info, either --login to provide new credentials or use --project <project-id>');
730
734
  }
731
735
  }
732
736
 
@@ -737,12 +741,12 @@ module.exports = {
737
741
 
738
742
  if (program.testConfig) {
739
743
  // convert single test config inputs to array (e.g. from configFile)
740
- program.testConfig = [].concat(program.testConfig);
744
+ program.testConfig = [program.testConfig].flat();
741
745
  }
742
746
 
743
747
  if (program.testConfigId) {
744
748
  // convert single test config inputs to array (e.g. from configFile)
745
- program.testConfigId = [].concat(program.testConfigId);
749
+ program.testConfigId = [program.testConfigId].flat();
746
750
  }
747
751
 
748
752
  program.retries = !program.retries || typeof program.retries === 'boolean' ? 1 : Number(program.retries) + 1;
@@ -760,25 +764,25 @@ module.exports = {
760
764
 
761
765
  const timeoutWasGiven = Boolean(program.timeout);
762
766
  program.timeout = !program.timeout || typeof program.timeout === 'boolean' ? 10 * 60 * 1000 : Number(program.timeout);
767
+ program.beforeParallel = !program.beforeParallel || typeof program.beforeParallel === 'boolean' ? 1 : Number(program.beforeParallel);
763
768
  program.parallel = !program.parallel || typeof program.parallel === 'boolean' ? 1 : Number(program.parallel);
769
+ program.afterParallel = !program.afterParallel || typeof program.afterParallel === 'boolean' ? 1 : Number(program.afterParallel);
764
770
 
765
771
 
766
772
  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
- }
773
+ ((!program.testPlan || program.testPlan.length === 0) && (!program.testPlanId || !program.testPlanId.length)) && process.stdout.isTTY && !program.headless && !process.env.TERM) {
774
+ const prompts = require('prompts');
775
+ const response = await prompts({
776
+ type: 'toggle',
777
+ name: 'isSure',
778
+ message: 'Running in parallel without --headless flag will open several browsers on your computer. Are you sure?',
779
+ initial: false,
780
+ active: 'yes',
781
+ inactive: 'no',
782
+ });
783
+
784
+ if (!response.isSure) {
785
+ process.exit(0);
782
786
  }
783
787
  }
784
788
 
@@ -787,11 +791,11 @@ module.exports = {
787
791
  program.port = program.port && Number(program.port);
788
792
 
789
793
  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>'));
794
+ throw new ArgError('test failure retry count could not be a negative number or string, --retries <max_num_of_retries>');
791
795
  }
792
796
 
793
797
  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>'));
798
+ throw new ArgError('Max number of retries exceeded. Number cannot be greater than 20, --retries <max_num_of_retries>');
795
799
  }
796
800
 
797
801
  if (!program.token) {
@@ -801,33 +805,40 @@ module.exports = {
801
805
  if (credentialToken) {
802
806
  program.token = credentialToken;
803
807
  } 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.'));
808
+ 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
809
  }
807
810
  }
808
811
 
809
812
  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>'));
813
+ throw new ArgError('get browser timeout could not be a negative number, --browser-timeout <get-browser-timeout>');
811
814
  }
812
815
 
813
816
  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>'));
817
+ throw new ArgError('max new browser wait timeout could not be a negative number, --new-browser-wait-timeout <max-wait-to-browser>');
815
818
  }
816
819
 
817
820
  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>'));
821
+ throw new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>');
822
+ }
823
+
824
+ if (program.beforeParallel <= 0 || _.isNaN(program.beforeParallel)) {
825
+ throw new ArgError('before-parallel could not be a negative number or not number, --before-parallel <number-of-tests>');
819
826
  }
820
827
 
821
828
  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>'));
829
+ throw new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>');
830
+ }
831
+
832
+ if (program.afterParallel <= 0 || _.isNaN(program.afterParallel)) {
833
+ throw new ArgError('after-parallel could not be a negative number or not number, --after-parallel <number-of-tests>');
823
834
  }
824
835
 
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`));
836
+ if (![CLI_MODE.EXTENSION, CLI_MODE.SELENIUM].includes(program.mode)) {
837
+ throw new ArgError(`runner mode <${program.mode}> is not supported`);
827
838
  }
828
839
 
829
840
  if ((program.mode !== CLI_MODE.SELENIUM) && program.disableNativeEvents) {
830
- return Promise.reject(new ArgError('disable-native-events is only applicable in selenium mode'));
841
+ throw new ArgError('disable-native-events is only applicable in selenium mode');
831
842
  }
832
843
 
833
844
  if (
@@ -848,9 +859,9 @@ module.exports = {
848
859
  !program.useChromeLauncher &&
849
860
  !program.createPrefechedData
850
861
  ) {
851
- return Promise.reject(new ArgError(
862
+ throw new ArgError(
852
863
  'missing remote grid address parameter, specify --host <host-name-or-ip> or --grid <grid-name> or --grid-id <grid-id>'
853
- ));
864
+ );
854
865
  }
855
866
  } else if (
856
867
  program.testId.length ||
@@ -862,9 +873,13 @@ module.exports = {
862
873
  program.useLocalChromeDriver ||
863
874
  program.useChromeLauncher
864
875
  ) {
865
- return Promise.reject(new ArgError(
876
+ throw new ArgError(
866
877
  '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
- ));
878
+ );
879
+ }
880
+
881
+ if (!isTestPlanSpecified && (program.beforeParallel !== 1 || program.afterParallel !== 1)) {
882
+ throw new ArgError('cannot set --before-parallel or --after-parallel without --test-plan option');
868
883
  }
869
884
 
870
885
  if (
@@ -875,17 +890,17 @@ module.exports = {
875
890
  isSuiteSpecified) &&
876
891
  program.file
877
892
  ) {
878
- return Promise.reject(new ArgError(
893
+ throw new ArgError(
879
894
  'Cannot pass codeful automation tests with --testId --label --name or --suite'
880
- ));
895
+ );
881
896
  }
882
897
 
883
898
  const numberOfDefinedHosts = [program.host, program.grid, program.gridId].filter(Boolean).length;
884
899
  if (numberOfDefinedHosts > 1) {
885
- return Promise.reject(new ArgError('please define exactly one of --grid or --grid-id or --host'));
900
+ throw new ArgError('please define exactly one of --grid or --grid-id or --host');
886
901
  }
887
902
 
888
- if (program.host && program.host.indexOf('/') !== -1) {
903
+ if (program.host && program.host.includes('/')) {
889
904
  if (!/^(f|ht)tps?:\/\//i.test(program.host)) {
890
905
  program.host = `http://${program.host}`;
891
906
  }
@@ -896,7 +911,7 @@ module.exports = {
896
911
  program.resultLabel = program.resultLabel.map(label => label.trim()).filter(Boolean);
897
912
  const invalidLabels = program.resultLabel.filter(label => label.length >= 250).filter(Boolean);
898
913
  if (invalidLabels.length) {
899
- return Promise.reject(new ArgError('A result label cannot exceed 250 characters'));
914
+ throw new ArgError('A result label cannot exceed 250 characters');
900
915
  }
901
916
  }
902
917
 
@@ -904,66 +919,66 @@ module.exports = {
904
919
  const playerUrl = runOptionsUtils.getPlayerUrl(program);
905
920
 
906
921
  if (!program.w3cCapabilities && !program.oldCapabilities) {
907
- return Promise.reject(new ArgError('cannot set --w3c-capabilities and --old-capabilities options as false'));
922
+ throw new ArgError('cannot set --w3c-capabilities and --old-capabilities options as false');
908
923
  }
909
924
  program.protocol = program.protocol || (program.port === 443 ? 'https' : 'http');
910
925
  if (!['http', 'https'].includes(program.protocol)) {
911
- return Promise.reject(new ArgError('invalid --protocol value, allow --protocol http or https'));
926
+ throw new ArgError('invalid --protocol value, allow --protocol http or https');
912
927
  }
913
928
 
914
929
  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'));
930
+ 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
931
  }
917
932
 
918
933
  if (program.rerunFailedByRunId &&
919
934
  (isSuiteSpecified || program.name.length ||
920
935
  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'));
936
+ throw new ArgError('Re-running failed tests is not possible when suite (--suite),' +
937
+ ' label (--label), plan (--test-plan), or other test flags (--test) are provided. Please remove these flags and try again');
923
938
  }
924
939
 
925
940
  if (program.run.length) {
926
941
  const glob = require('glob');
927
942
  program.files = _.flatMap(program.run, files => glob.sync(files));
928
943
  if (program.files.length === 0) {
929
- return Promise.reject(new ArgError(`No files found at path '${program.run}'.`));
944
+ throw new ArgError(`No files found at path '${program.run}'.`);
930
945
  }
931
946
  } else {
932
947
  program.files = [];
933
948
  }
934
949
 
935
950
  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)'));
951
+ 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
952
  }
938
953
  program.setRetention = program.setRetention && Number(program.setRetention);
939
954
 
940
955
  const mockNetworkDeprecationMsg = 'is no longer supported, please use --override-mapping-file';
941
956
  if (program.mockNetworkHar) {
942
- return Promise.reject(new ArgError(`--mock-network-har ${mockNetworkDeprecationMsg}`));
957
+ throw new ArgError(`--mock-network-har ${mockNetworkDeprecationMsg}`);
943
958
  }
944
959
  if (program.mockNetworkPattern) {
945
- return Promise.reject(new ArgError(`--mock-network-pattern ${mockNetworkDeprecationMsg}`));
960
+ throw new ArgError(`--mock-network-pattern ${mockNetworkDeprecationMsg}`);
946
961
  }
947
962
 
948
963
  if (program.disableMockNetwork && program.overrideMappingFile) {
949
- return Promise.reject(new ArgError('You can either use --disable-mock-network or --override-mapping-file'));
964
+ throw new ArgError('You can either use --disable-mock-network or --override-mapping-file');
950
965
  }
951
966
 
952
967
  if (!program.collectCodeCoverage && program.codeCoverageSourceMapPath) {
953
- return Promise.reject(new ArgError('cannot set --code-coverage-source-map-path without passing --collect-code-coverage'));
968
+ throw new ArgError('cannot set --code-coverage-source-map-path without passing --collect-code-coverage');
954
969
  }
955
970
 
956
971
  if (!program.collectCodeCoverage && program.codeCoverageReporter.length) {
957
- return Promise.reject(new ArgError('cannot set --code-coverage-reporter without passing --collect-code-coverage'));
972
+ throw new ArgError('cannot set --code-coverage-reporter without passing --collect-code-coverage');
958
973
  }
959
974
 
960
975
  if (!program.collectCodeCoverage && program.codeCoverageInclude.length) {
961
- return Promise.reject(new ArgError('cannot set --code-coverage-include without passing --collect-code-coverage'));
976
+ throw new ArgError('cannot set --code-coverage-include without passing --collect-code-coverage');
962
977
  }
963
978
 
964
979
  if (program.collectCodeCoverage && program.codeCoverageReporter && _.difference(program.codeCoverageReporter, CODE_COVERAGE_REPORTER_OPTIONS).length) {
965
980
  const diff = _.difference(program.codeCoverageReporter, CODE_COVERAGE_REPORTER_OPTIONS);
966
- return Promise.reject(new ArgError(`invalid --code-coverage-reporter parameters ${diff.join('/')}`));
981
+ throw new ArgError(`invalid --code-coverage-reporter parameters ${diff.join('/')}`);
967
982
  }
968
983
 
969
984
  program.codeCoverageReporter = program.codeCoverageReporter.length === 0 ? ['html', 'text'] : program.codeCoverageReporter;
@@ -982,7 +997,7 @@ module.exports = {
982
997
 
983
998
  if (program.mode !== CLI_MODE.EXTENSION && usedExtensionOptions.length) {
984
999
  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`));
1000
+ throw new ArgError(`${usedExtensionOptions.map(key => extensionOnlyOptions[key]).join(' and ')} ${multi} only applicable in extension mode`);
986
1001
  }
987
1002
 
988
1003
  if (program.tmsFieldFile) {
@@ -1054,18 +1069,18 @@ module.exports = {
1054
1069
  }
1055
1070
 
1056
1071
  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'));
1072
+ throw new ArgError('base url cannot be used as a flag, and must contain a string value');
1058
1073
  }
1059
1074
 
1060
1075
  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),
1076
+ testId: [program.testId].flat(),
1077
+ name: [program.name].flat(),
1078
+ label: [program.label].flat(),
1079
+ suites: [program.suite].flat(),
1080
+ suiteIds: [program.suiteId].flat(),
1081
+ testPlan: [program.testPlan].flat(),
1082
+ testPlanIds: [program.testPlanId].flat(),
1083
+ files: [program.files].flat(),
1069
1084
  webpackConfig: program.webpackConfig,
1070
1085
  reportFile: program.reportFile,
1071
1086
  reportFileClassname: program.overrideReportFileClassname,
@@ -1090,7 +1105,9 @@ module.exports = {
1090
1105
  mode: program.mode,
1091
1106
  isRegressionBaselineRun: program.isRegressionBaselineRun,
1092
1107
  browser: program.browser,
1108
+ beforeParallel: program.beforeParallel,
1093
1109
  parallel: program.parallel,
1110
+ afterParallel: program.afterParallel,
1094
1111
  canary: program.canary,
1095
1112
  rerunFailedByRunId: program.rerunFailedByRunId,
1096
1113
  disableGridCheck: program.disableGridCheck,
@@ -1116,7 +1133,7 @@ module.exports = {
1116
1133
 
1117
1134
  // Extension
1118
1135
  ext: program.ext,
1119
- extensionLocation: [].concat(program.extensionPath || extHeadlessUrl),
1136
+ extensionLocation: [program.extensionPath || extHeadlessUrl].flat(),
1120
1137
  extensionPath: program.extensionPath,
1121
1138
 
1122
1139
  // Player
@@ -1136,6 +1153,7 @@ module.exports = {
1136
1153
  externalLambdatestTunnelId: program.externalLambdatestTunnelId,
1137
1154
  externalLambdatestUseWss: program.externalLambdatestUseWss || false,
1138
1155
  externalLambdatestDisableAutomationTunneling: Boolean(program.externalLambdatestDisableAutomationTunneling),
1156
+ externalLambdatestMitm: Boolean(program.externalLambdatestMitm),
1139
1157
 
1140
1158
  // Hooks
1141
1159
  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