@testim/testim-cli 3.235.0 → 3.238.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.235.0",
3
+ "version": "3.238.0",
4
4
  "description": "Command line interface for running Testing on your CI",
5
5
  "author": "Oren Rubin",
6
6
  "contributors": [{
@@ -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, [])
@@ -322,6 +324,7 @@ program
322
324
  // Agent mode
323
325
  .option('connect, --agent [enable-agent-mode]', 'enable Testim CLI agent mode', false)
324
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)
325
328
  .option('--agent-port [agent-port]', 'set agent port', Number, 42543)
326
329
  .option('--agent-bind [agent-host-bind]', 'set agent host bind', '127.0.0.1')
327
330
 
@@ -761,7 +764,9 @@ module.exports = {
761
764
 
762
765
  const timeoutWasGiven = Boolean(program.timeout);
763
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);
764
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);
765
770
 
766
771
 
767
772
  if (program.parallel > 1 && program.run && !program.gridId && !program.grid &&
@@ -816,10 +821,18 @@ module.exports = {
816
821
  throw new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>');
817
822
  }
818
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>');
826
+ }
827
+
819
828
  if (program.parallel <= 0 || _.isNaN(program.parallel)) {
820
829
  throw new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>');
821
830
  }
822
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>');
834
+ }
835
+
823
836
  if (![CLI_MODE.EXTENSION, CLI_MODE.SELENIUM].includes(program.mode)) {
824
837
  throw new ArgError(`runner mode <${program.mode}> is not supported`);
825
838
  }
@@ -865,6 +878,10 @@ module.exports = {
865
878
  );
866
879
  }
867
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');
883
+ }
884
+
868
885
  if (
869
886
  (program.testId.length ||
870
887
  isTestPlanSpecified ||
@@ -1088,7 +1105,9 @@ module.exports = {
1088
1105
  mode: program.mode,
1089
1106
  isRegressionBaselineRun: program.isRegressionBaselineRun,
1090
1107
  browser: program.browser,
1108
+ beforeParallel: program.beforeParallel,
1091
1109
  parallel: program.parallel,
1110
+ afterParallel: program.afterParallel,
1092
1111
  canary: program.canary,
1093
1112
  rerunFailedByRunId: program.rerunFailedByRunId,
1094
1113
  disableGridCheck: program.disableGridCheck,
@@ -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');
@@ -51,6 +50,8 @@ class ParallelWorkerManager {
51
50
  return undefined;
52
51
  }
53
52
 
53
+ let stoppedOnError = false;
54
+ let runningTests = 0;
54
55
  const runAndWaitToComplete = token => new Promise((resolve, reject) => {
55
56
  const projectId = options.project;
56
57
  const executionQueue = new ExecutionQueue(executionId, executionName, testList, options, branchToUse, testStatus);
@@ -69,6 +70,7 @@ class ParallelWorkerManager {
69
70
  const sessionType = utils.getSessionType(options);
70
71
 
71
72
  const onTestStarted = (wid, testId, resultId, isRerun, testRetryKey) => {
73
+ runningTests++;
72
74
  analyticsService.analyticsTestStart({
73
75
  authData,
74
76
  executionId,
@@ -89,6 +91,7 @@ class ParallelWorkerManager {
89
91
  };
90
92
 
91
93
  const onTestCompleted = async (wid, testId, testResult, sessionId, isRerun) => {
94
+ runningTests--;
92
95
  const update = {};
93
96
  if (lightweightMode && lightweightMode.onlyTestIdsNoSuite) {
94
97
  update.show = true;
@@ -151,11 +154,11 @@ class ParallelWorkerManager {
151
154
  isStartUp,
152
155
  });
153
156
  if (stopOnError && !testResult.success) {
154
- reject(new StopRunOnError());
155
- throw new StopRunOnError();
157
+ executionQueue.stop();
158
+ stoppedOnError = true;
156
159
  }
157
160
  const completedTests = Object.keys(combinedTestResults).length;
158
- if (completedTests === testCount) {
161
+ if (completedTests === testCount || (stoppedOnError && runningTests === 0)) {
159
162
  resolve(combinedTestResults);
160
163
  return undefined;
161
164
  }
@@ -165,8 +168,9 @@ class ParallelWorkerManager {
165
168
  const onTestIgnored = (wid, testResult) => {
166
169
  combinedTestResults[testResult.resultId] = testResult;
167
170
  testStatus.onTestIgnored(wid, testResult.resultId);
171
+ runningTests--;
168
172
  const completedTests = Object.keys(combinedTestResults).length;
169
- if (completedTests === testCount) {
173
+ if (completedTests === testCount || (stoppedOnError && runningTests === 0)) {
170
174
  resolve(combinedTestResults);
171
175
  }
172
176
  };
@@ -196,7 +200,11 @@ class ParallelWorkerManager {
196
200
 
197
201
  try {
198
202
  const token = await testimCustomToken.getCustomTokenV3();
199
- return await runAndWaitToComplete(token);
203
+ const result = await runAndWaitToComplete(token);
204
+ if (stoppedOnError) {
205
+ throw new StopRunOnError();
206
+ }
207
+ return result;
200
208
  } catch (err) {
201
209
  logger.error('failed running parallel workers', { executionId, err });
202
210
  throw err;
@@ -20,7 +20,6 @@ const { getSuite, calcTestResultStatus, validateConfig } = require('./runnerUtil
20
20
  const { StopRunOnError, ArgError } = require('../errors');
21
21
  const Logger = require('../commons/logger');
22
22
  const perf = require('../commons/performance-logger');
23
- const featureFlags = require('../commons/featureFlags');
24
23
 
25
24
  const guid = utils.guid;
26
25
  const logger = Logger.getLogger('test-plan-runner');
@@ -35,14 +34,14 @@ class TestPlanRunner {
35
34
  const executionResults = {};
36
35
  const authData = testimCustomToken.getTokenV3UserData();
37
36
 
38
- const runBeforeTests = (beforeTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData) => {
39
- const workerCount = 1;
37
+ const runBeforeTests = () => {
38
+ const workerCount = tpOptions.beforeParallel || 1;
40
39
  const stopOnError = true;
41
40
  return this.workerManager.runTests(beforeTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData, workerCount, stopOnError)
42
41
  .then(beforeTestsResults => Object.assign(executionResults, beforeTestsResults));
43
42
  };
44
43
 
45
- const runTestPlanTests = (tests, testStatus, executionId, executionName, tpOptions, branchToUse, authData) => {
44
+ const runTestPlanTests = () => {
46
45
  const workerCount = config.TESTIM_CONCURRENT_WORKER_COUNT || tpOptions.parallel;
47
46
  const stopOnError = false;
48
47
  perf.log('right before this.workerManager.runTests');
@@ -51,30 +50,30 @@ class TestPlanRunner {
51
50
  .then(testsResults => Object.assign(executionResults, testsResults));
52
51
  };
53
52
 
54
- const runAfterTests = (afterTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData) => {
55
- const workerCount = 1;
53
+ const runAfterTests = () => {
54
+ const workerCount = tpOptions.afterParallel || 1;
56
55
  const stopOnError = false;
57
56
  return this.workerManager.runTests(afterTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData, workerCount, stopOnError)
58
57
  .then(afterTestsResults => Object.assign(executionResults, afterTestsResults));
59
58
  };
60
59
 
61
- function catchBeforeTestsFailed(executionId) {
60
+ function catchBeforeTestsFailed() {
62
61
  return testStatus.markAllQueuedTests(executionId, constants.runnerTestStatus.ABORTED, 'aborted', false);
63
62
  }
64
63
 
65
64
  const sessionType = utils.getSessionType(tpOptions);
66
65
  analyticsService.analyticsExecsStart({ authData, executionId, projectId: tpOptions.project, sessionType });
67
66
  perf.log('right before runBeforeTests');
68
- return runBeforeTests(beforeTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData)
67
+ return runBeforeTests()
69
68
  .log('right before runTestPlanTests')
70
- .then(() => runTestPlanTests(tests, testStatus, executionId, executionName, tpOptions, branchToUse, authData))
69
+ .then(() => runTestPlanTests())
71
70
  .log('right after runTestPlanTests')
72
- .then(() => runAfterTests(afterTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData))
71
+ .then(() => runAfterTests())
73
72
  .then(() => executionResults)
74
73
  .catch(err => {
75
74
  logger.error('error running test plan', { err });
76
75
  if (err instanceof StopRunOnError) {
77
- return catchBeforeTestsFailed(executionId);
76
+ return catchBeforeTestsFailed();
78
77
  }
79
78
  throw err;
80
79
  })
@@ -1,13 +1,12 @@
1
1
  const os = require('os');
2
- const { spawn } = require('child_process');
2
+ const childProcess = require('child_process');
3
3
  const Promise = require('bluebird');
4
4
  const fse = require('fs-extra');
5
5
  const portfinder = require('portfinder');
6
6
  const ms = require('ms');
7
7
 
8
- const {
9
- downloadAndSave, unzipFile, guid, runWithRetries, isURL,
10
- } = require('../utils');
8
+ const { guid, isURL } = require('../utils');
9
+ const utils = require('../utils');
11
10
  const { gridTypes, CLI_MODE } = require('../commons/constants');
12
11
  const httpRequest = require('../commons/httpRequest');
13
12
  const { ArgError } = require('../errors');
@@ -86,8 +85,8 @@ class LambdatestService {
86
85
  throw new Error(`tunnel on ${os.platform() + os.arch()} platform is not supported.`);
87
86
  }
88
87
  const zipLocation = `${LT_TUNNEL_BINARY_DIRECTORY}.zip`;
89
- await downloadAndSave(`${LT_TUNNEL_BINARY_ORIGIN}/${downloadUrl}`, zipLocation);
90
- await unzipFile(zipLocation, LT_TUNNEL_BINARY_DIRECTORY);
88
+ await utils.downloadAndSave(`${LT_TUNNEL_BINARY_ORIGIN}/${downloadUrl}`, zipLocation);
89
+ await utils.unzipFile(zipLocation, LT_TUNNEL_BINARY_DIRECTORY);
91
90
  }
92
91
 
93
92
  static async connectTunnel(runnerOptions) {
@@ -140,7 +139,7 @@ class LambdatestService {
140
139
  tunnelArgs = [...tunnelArgs, '--mitm'];
141
140
  }
142
141
 
143
- LambdatestService.tunnel = spawn('./LT', tunnelArgs, { cwd: LT_TUNNEL_BINARY_DIRECTORY });
142
+ LambdatestService.tunnel = childProcess.spawn('./LT', tunnelArgs, { cwd: LT_TUNNEL_BINARY_DIRECTORY });
144
143
 
145
144
  let stdoutResult = '';
146
145
  let stderrResult = '';
@@ -155,7 +154,7 @@ class LambdatestService {
155
154
 
156
155
  // verify that LT tunnel strated successfully
157
156
  try {
158
- const ltInfo = await runWithRetries(() => httpRequest.get(`http://127.0.0.1:${infoAPIPort}/api/v1.0/info`, {}, {}, undefined, { skipProxy: true }), 30, 2000);
157
+ const ltInfo = await utils.runWithRetries(() => httpRequest.get(`http://127.0.0.1:${infoAPIPort}/api/v1.0/info`, {}, {}, undefined, { skipProxy: true }), 30, 2000);
159
158
  logger.info('LT tunnel info', ltInfo);
160
159
  } catch (err) {
161
160
  logger.error('Failed to start LT tunnel', { err, stdoutResult, stderrResult });
@@ -170,7 +169,7 @@ class LambdatestService {
170
169
  return new Promise((resolve, reject) => {
171
170
  LambdatestService.tunnel.on('close', (code) => {
172
171
  if (code) {
173
- reject();
172
+ reject(new Error(`tunnel process exited with code ${code}`));
174
173
  }
175
174
  resolve();
176
175
  });