@testim/testim-cli 3.244.0 → 3.247.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 (36) hide show
  1. package/chromiumInstaller.js +92 -0
  2. package/cli.js +89 -87
  3. package/cliAgentMode.js +39 -114
  4. package/commons/chromedriverWrapper.js +4 -3
  5. package/commons/featureFlags.js +1 -0
  6. package/commons/getSessionPlayerRequire.js +2 -1
  7. package/commons/httpRequestCounters.test.js +9 -9
  8. package/commons/npmWrapper.js +3 -3
  9. package/commons/npmWrapper.test.js +1 -1
  10. package/commons/prepareRunner.js +2 -0
  11. package/commons/socket/baseSocketServiceSocketIO.js +2 -2
  12. package/commons/testimDesiredCapabilitiesBuilder.js +1 -3
  13. package/commons/testimServicesApi.js +45 -39
  14. package/errors.js +2 -1
  15. package/npm-shrinkwrap.json +467 -366
  16. package/package.json +2 -2
  17. package/player/WebDriverHttpRequest.js +45 -37
  18. package/player/chromeLauncherTestPlayer.js +2 -2
  19. package/player/services/playbackTimeoutCalculator.js +5 -1
  20. package/player/stepActions/RefreshStepAction.js +2 -4
  21. package/player/stepActions/inputFileStepAction.js +6 -2
  22. package/player/stepActions/sfdcStepAction.js +27 -0
  23. package/player/stepActions/stepActionRegistrar.js +12 -0
  24. package/player/utils/screenshotUtils.js +2 -2
  25. package/player/utils/windowUtils.js +3 -3
  26. package/player/webdriver.js +51 -52
  27. package/runOptions.js +3 -1
  28. package/runner.js +44 -41
  29. package/services/lambdatestService.js +2 -2
  30. package/services/lambdatestService.test.js +2 -1
  31. package/testRunHandler.js +7 -3
  32. package/testRunStatus.js +2 -1
  33. package/utils.js +54 -15
  34. package/utils.test.js +26 -0
  35. package/workers/BaseWorker.js +9 -7
  36. package/workers/BaseWorker.test.js +11 -12
@@ -78,7 +78,7 @@ async function installPackageLocally(rootPath, packageName, execOptions) {
78
78
  } catch (err) {
79
79
  // ignore error
80
80
  }
81
- return await exec(`npm i ${packageName} --no-save --no-package-lock --no-prune --prefer-offline --no-audit --legacy-peer-deps --progress=false`, { ...execOptions, cwd: rootPath }).catch(err => {
81
+ return await exec(`npm i ${packageName} --no-save --no-prune --prefer-offline --no-audit --progress=false`, { ...execOptions, cwd: rootPath }).catch(err => {
82
82
  const pathWithMissingPermissions = getPathWithMissingPermissions(err);
83
83
  const npmEightMissingPermissions = npmSevenAndEightMissingPermissions(err);
84
84
  if (pathWithMissingPermissions || npmEightMissingPermissions) {
@@ -110,7 +110,7 @@ Testim had missing write access to ${pathWithMissingPermissions || rootPath}
110
110
  }
111
111
  }
112
112
 
113
- const localNpmLocation = path.resolve(require.resolve('npm').replace('index.js',''), 'bin','npm-cli.js');
113
+ const localNpmLocation = path.resolve(require.resolve('npm').replace('index.js', ''), 'bin', 'npm-cli.js');
114
114
 
115
115
  function installPackages(prefix, packageNames, proxyUri, timeoutMs) {
116
116
  return new Promise((resolve, reject) => {
@@ -121,7 +121,7 @@ function installPackages(prefix, packageNames, proxyUri, timeoutMs) {
121
121
  let stdout = '';
122
122
  let stderr = '';
123
123
 
124
- const ops = '--no-save --no-package-lock --no-prune --prefer-offline --no-audit --legacy-peer-deps --progress=false'.split(' ');
124
+ const ops = '--no-save --no-package-lock --no-prune --prefer-offline --no-audit --progress=false'.split(' ');
125
125
  const npmInstall = spawn('node', [localNpmLocation, 'i', '--prefix', prefix, ...ops, ...packageNames, ...proxyFlag], envVars);
126
126
  npmInstall.stderr.pipe(process.stderr);
127
127
  npmInstall.stdout.pipe(process.stdout);
@@ -54,7 +54,7 @@ describe('npmWrapper', () => {
54
54
  fakeChildProcess.exec.yields(undefined, []); //resolve without errors
55
55
  const cwd = '/some/dir';
56
56
  const pkg = 'some-package';
57
- const expectedCmd = 'npm i some-package --no-save --no-package-lock --no-prune --prefer-offline --no-audit --legacy-peer-deps --progress=false';
57
+ const expectedCmd = 'npm i some-package --no-save --no-prune --prefer-offline --no-audit --progress=false';
58
58
  const expectedExecParams = { cwd };
59
59
 
60
60
  await npmWrapper.installPackageLocally(cwd, pkg);
@@ -10,6 +10,7 @@ const Ajv = require('ajv');
10
10
  const prepareRunnerAndTestimStartUtils = require('./prepareRunnerAndTestimStartUtils');
11
11
  const mockNetworkRuleFileSchema = require('./mockNetworkRuleFileSchema.json');
12
12
  const { initializeUserWithAuth } = require('./initializeUserWithAuth');
13
+ const { downloadAndInstallChromium } = require('../chromiumInstaller');
13
14
 
14
15
  const MAX_RULE_FILE_SIZE_IN_MB = 1;
15
16
  const PREPARE_MOCK_NETWORK_ERROR_PREFIX = 'JSON file supplied to --mock-network-pattern';
@@ -30,6 +31,7 @@ async function prepare(options) {
30
31
  const hasNoGrid = !options.host && !options.gridId && !options.grid && (!options.testPlan || options.testPlan.length === 0);
31
32
  const isTdkRun = options.files.length !== 0;
32
33
  if ((hasNoGrid && isTdkRun) || options.useLocalChromeDriver) {
34
+ options.chromeBinaryLocation = options.downloadBrowser ? await downloadAndInstallChromium() : options.chromeBinaryLocation;
33
35
  chromedriverPromise = prepareRunnerAndTestimStartUtils.prepareChromeDriver(
34
36
  { projectId: options.project, userId: options.user },
35
37
  { chromeBinaryLocation: options.chromeBinaryLocation },
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  const Promise = require('bluebird');
4
+ const pRetry = require('p-retry');
4
5
  const io = require('socket.io-client');
5
6
  const config = require('../config');
6
- const utils = require('../../utils');
7
7
 
8
8
  const MAX_SOCKET_RECONNECT_ATTEMPT = 50;
9
9
  const MAX_RECONNECT_ATTEMPT_BEFORE_SWITCH = 10;
@@ -163,7 +163,7 @@ class BaseSocketServiceSocketIO {
163
163
  };
164
164
 
165
165
  this.emitPromisQueue = (this.emitPromisQueue || Promise.resolve())
166
- .then(() => utils.runWithRetries(emitAndWait, 200, 3000))
166
+ .then(() => pRetry(emitAndWait, { retries: 200, minTimeout: 3000 }))
167
167
  .finally(() => {
168
168
  if (Object.keys(errorneousEvents).length > 0) {
169
169
  logger.error('Bad acknowledge from socket emit', { errorneousEvents });
@@ -536,13 +536,11 @@ function buildSeleniumOptions(browserOptions, testName, testRunConfig, gridInfo,
536
536
  );
537
537
 
538
538
  let predefinedTestimExtension = null;
539
- if (!browserOptions.ext && _.endsWith(gridInfo.host, '.testim.io') && !browserOptions.canary && browserOptions.mode === CLI_MODE.EXTENSION) {
539
+ if (!browserOptions.ext && !browserOptions.extensionPath && _.endsWith(gridInfo.host, '.testim.io') && !browserOptions.canary && browserOptions.mode === CLI_MODE.EXTENSION) {
540
540
  if (browser === 'chrome') {
541
541
  predefinedTestimExtension = '/opt/testim-headless';
542
542
  } else if (browser === 'edge-chromium') {
543
543
  predefinedTestimExtension = 'C:/selenium/testim-headless';
544
- } else if (browser === 'firefox') {
545
- predefinedTestimExtension = '/opt/testim-firefox-profile';
546
544
  }
547
545
  }
548
546
 
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const pako = require('pako');
4
+ const pRetry = require('p-retry');
4
5
  const _ = require('lodash');
5
6
  const testimCustomToken = require('./testimCustomToken');
6
7
  const constants = require('./constants');
@@ -66,13 +67,13 @@ function getWithAuth(url, query, options, timeout) {
66
67
  }
67
68
 
68
69
  function getS3Artifact(url, timeout) {
69
- return utils.runWithRetries(() => getWithAuth(`/storage${url}`, null, { isBinary: true }, timeout));
70
+ return pRetry(() => getWithAuth(`/storage${url}`, null, { isBinary: true }, timeout), { retries: DEFAULT_REQUEST_RETRY });
70
71
  }
71
72
 
72
73
  function getTestPlan(projectId, testPlanNames) {
73
74
  //TODO: need to be checked after 3 months to prevent users from using old version
74
75
  const parseProperty = (dataValue) => (dataValue == null ? [] : (typeof (dataValue) === 'string' && JSON.parse(dataValue)) || dataValue);
75
- return utils.runWithRetries(() => getWithAuth('/testPlan', { projectId, name: testPlanNames.join(',') }))
76
+ return pRetry((() => getWithAuth('/testPlan', { projectId, name: testPlanNames.join(',') }), { retries: DEFAULT_REQUEST_RETRY }))
76
77
  .then(body => body.map(testPlan => {
77
78
  testPlan.testConfigIds = parseProperty(testPlan.testConfigIds);
78
79
  testPlan.beforeAllLabels = parseProperty(testPlan.beforeAllLabels);
@@ -84,20 +85,20 @@ function getTestPlan(projectId, testPlanNames) {
84
85
 
85
86
 
86
87
  function loadTest({ testId, branch, projectId, skipSharedSteps = false, useBranchMap = true }) {
87
- return utils.runWithRetries(() => getWithAuth(`/test/${testId}`, {
88
+ return pRetry(() => getWithAuth(`/test/${testId}`, {
88
89
  projectId,
89
90
  branch,
90
91
  skipSharedSteps,
91
92
  useBranchMap,
92
- }));
93
+ }), { retries: DEFAULT_REQUEST_RETRY });
93
94
  }
94
95
 
95
96
  function saveTestPlanResult(projectId, testPlanId, result) {
96
- return utils.runWithRetries(() => postAuth({ url: '/testPlan/result', body: { projectId, testPlanId, result } }));
97
+ return pRetry(() => postAuth({ url: '/testPlan/result', body: { projectId, testPlanId, result } }), { retries: DEFAULT_REQUEST_RETRY });
97
98
  }
98
99
 
99
- function updateTestStatus(projectId, executionId, testId, resultId, status, data, apiRetries = DEFAULT_REQUEST_RETRY) {
100
- return utils.runWithRetries(() => putAuth('/result/run/test', {
100
+ function updateTestStatus(projectId, executionId, testId, resultId, status, data, retries = DEFAULT_REQUEST_RETRY) {
101
+ return pRetry(() => putAuth('/result/run/test', {
101
102
  runId: executionId,
102
103
  testId,
103
104
  resultId,
@@ -105,11 +106,11 @@ function updateTestStatus(projectId, executionId, testId, resultId, status, data
105
106
  projectId,
106
107
  runnerVersion,
107
108
  ...data,
108
- }), apiRetries);
109
+ }), { retries });
109
110
  }
110
111
 
111
112
  function updateExecutionTests(executionId, runnerStatuses, status, reason, success, startTime, endTime, projectId) {
112
- return utils.runWithRetries(() => putAuth('/result/run/tests', {
113
+ return pRetry(() => putAuth('/result/run/tests', {
113
114
  runId: executionId,
114
115
  runnerStatuses,
115
116
  status,
@@ -118,7 +119,7 @@ function updateExecutionTests(executionId, runnerStatuses, status, reason, succe
118
119
  startTime,
119
120
  endTime,
120
121
  projectId,
121
- }), DEFAULT_REQUEST_RETRY);
122
+ }), { retries: DEFAULT_REQUEST_RETRY });
122
123
  }
123
124
 
124
125
  function reportExecutionStarted({
@@ -162,7 +163,7 @@ function reportExecutionStarted({
162
163
  function reportExecutionFinished(status, executionId, projectId, success, tmsOptions = {}, remoteRunId, resultExtraData) {
163
164
  const endTime = Date.now();
164
165
 
165
- return utils.runWithRetries(() => putAuth('/result/run', {
166
+ return pRetry(() => putAuth('/result/run', {
166
167
  runId: executionId,
167
168
  projectId,
168
169
  endTime,
@@ -171,22 +172,22 @@ function reportExecutionFinished(status, executionId, projectId, success, tmsOpt
171
172
  tmsOptions,
172
173
  remoteRunId,
173
174
  resultExtraData,
174
- }), DEFAULT_REQUEST_RETRY);
175
+ }), { reties: DEFAULT_REQUEST_RETRY });
175
176
  }
176
177
 
177
178
  async function getTestPlanTestList(projectId, names, planIds, branch, intersections) {
178
- return utils.runWithRetries(() => postAuth({
179
+ return pRetry(() => postAuth({
179
180
  url: '/testPlan/list',
180
181
  body: { projectId, names, planIds, branch, intersections },
181
182
  // people who send insane lists get a timeout :(
182
183
  timeout: 120000,
183
- }));
184
+ }), { reties: DEFAULT_REQUEST_RETRY });
184
185
  }
185
186
 
186
187
  function getSuiteTestList({
187
188
  projectId, labels, testIds, testNames, testConfigNames, suiteNames, suiteIds, branch, rerunFailedByRunId, testConfigIds, intersections,
188
189
  }) {
189
- return utils.runWithRetries(() => postAuth({
190
+ return pRetry(() => postAuth({
190
191
  url: '/suite/v2/list',
191
192
  body: {
192
193
  projectId,
@@ -201,11 +202,11 @@ function getSuiteTestList({
201
202
  testConfigIds,
202
203
  intersections,
203
204
  },
204
- }));
205
+ }), { reties: DEFAULT_REQUEST_RETRY });
205
206
  }
206
207
 
207
208
  function getUsageForCurrentBillingPeriod(projectId) {
208
- return utils.runWithRetries(() => getWithAuth(`/plan/project/${projectId}/usage-current-billing-period`))
209
+ return pRetry(() => getWithAuth(`/plan/project/${projectId}/usage-current-billing-period`), { reties: DEFAULT_REQUEST_RETRY })
209
210
  .catch((error) => {
210
211
  logger.error('failed getting usage for current billing period', { projectId, error });
211
212
  return undefined;
@@ -213,11 +214,11 @@ function getUsageForCurrentBillingPeriod(projectId) {
213
214
  }
214
215
 
215
216
  function isTestResultCompleted(resultId, projectId, testRetryKey) {
216
- return utils.runWithRetries(() => getWithAuth(`/result/${resultId}/isComplete`, { projectId, testRetryKey }));
217
+ return pRetry(() => getWithAuth(`/result/${resultId}/isComplete`, { projectId, testRetryKey }), { reties: DEFAULT_REQUEST_RETRY });
217
218
  }
218
219
 
219
220
  function getTestResults(testId, resultId, projectId, branch) {
220
- return utils.runWithRetries(() => getWithAuth(`/test/v2/${testId}/result/${resultId}`, { projectId, branch }));
221
+ return pRetry(() => getWithAuth(`/test/v2/${testId}/result/${resultId}`, { projectId, branch }), { reties: DEFAULT_REQUEST_RETRY });
221
222
  }
222
223
 
223
224
  function keepAliveGrid(projectId, slots) {
@@ -245,19 +246,19 @@ function getHybridGridProvider(body) {
245
246
  }
246
247
 
247
248
  function getGridByName(companyId, projectId, gridName, browser, executionId) {
248
- return utils.runWithRetries(() => getWithAuth('/grid/name', {
249
+ return pRetry(() => getWithAuth('/grid/name', {
249
250
  companyId, projectId, name: gridName, browser, executionId, reqId: utils.guid(),
250
- }));
251
+ }), { reties: DEFAULT_REQUEST_RETRY });
251
252
  }
252
253
 
253
254
  function getGridById(companyId, projectId, gridId, browser, executionId) {
254
- return utils.runWithRetries(() => getWithAuth(`/grid/${gridId}`, { companyId, projectId, browser, executionId, reqId: utils.guid() }));
255
+ return pRetry(() => getWithAuth(`/grid/${gridId}`, { companyId, projectId, browser, executionId, reqId: utils.guid() }), { reties: DEFAULT_REQUEST_RETRY });
255
256
  }
256
257
 
257
258
 
258
259
  async function initializeUserWithAuth({ projectId, token, branchName, lightweightMode, localGrid }) {
259
260
  try {
260
- return await utils.runWithRetries(() => httpRequest.post({
261
+ return await pRetry(() => httpRequest.post({
261
262
  url: `${config.SERVICES_HOST}/executions/initialize`,
262
263
  body: {
263
264
  projectId,
@@ -266,7 +267,7 @@ async function initializeUserWithAuth({ projectId, token, branchName, lightweigh
266
267
  lightweightMode,
267
268
  localGrid,
268
269
  },
269
- }));
270
+ }), { reties: DEFAULT_REQUEST_RETRY });
270
271
  } catch (e) {
271
272
  logger.error('error initializing info from server', e);
272
273
  if (e && e.message && e.message.includes('Bad Request')) {
@@ -288,7 +289,7 @@ async function getEditorUrl() {
288
289
  return config.EDITOR_URL;
289
290
  }
290
291
  try {
291
- return await utils.runWithRetries(() => getWithAuth('/system-info/editor-url'));
292
+ return await pRetry(() => getWithAuth('/system-info/editor-url'), { reties: DEFAULT_REQUEST_RETRY });
292
293
  } catch (err) {
293
294
  logger.error('cannot retrieve editor-url from server');
294
295
  return 'https://app.testim.io';
@@ -296,20 +297,20 @@ async function getEditorUrl() {
296
297
  }
297
298
 
298
299
  function getAllGrids(companyId) {
299
- return utils.runWithRetries(() => getWithAuth('/grid', { companyId }));
300
+ return pRetry(() => getWithAuth('/grid', { companyId }), { reties: DEFAULT_REQUEST_RETRY });
300
301
  }
301
302
 
302
- const fetchLambdatestConfig = async () => utils.runWithRetries(() => getWithAuth('/grid/lt/config'));
303
+ const fetchLambdatestConfig = async () => pRetry(() => getWithAuth('/grid/lt/config'), { reties: DEFAULT_REQUEST_RETRY });
303
304
 
304
- const getLabFeaturesByProjectId = async (projectId) => utils.runWithRetries(() => getWithAuth(`/labFeature/v2/project/${projectId}`));
305
+ const getLabFeaturesByProjectId = async (projectId) => pRetry(() => getWithAuth(`/labFeature/v2/project/${projectId}`), { reties: DEFAULT_REQUEST_RETRY });
305
306
 
306
307
 
307
308
  function getRealData(projectId, channel, query) {
308
- return utils.runWithRetries(() => getWithAuth(`/real-data/${channel}?${query}&projectId=${projectId}`));
309
+ return pRetry(() => getWithAuth(`/real-data/${channel}?${query}&projectId=${projectId}`), { reties: DEFAULT_REQUEST_RETRY });
309
310
  }
310
311
 
311
312
  function updateTestResult(projectId, resultId, testId, testResult, remoteRunId) {
312
- return utils.runWithRetries(() => postAuth({
313
+ return pRetry(() => postAuth({
313
314
  url: '/result/test',
314
315
  body: {
315
316
  projectId,
@@ -318,11 +319,11 @@ function updateTestResult(projectId, resultId, testId, testResult, remoteRunId)
318
319
  testResult,
319
320
  remoteRunId,
320
321
  },
321
- }));
322
+ }), { reties: DEFAULT_REQUEST_RETRY });
322
323
  }
323
324
 
324
325
  function clearTestResult(projectId, resultId, testId, testResult) {
325
- return utils.runWithRetries(() => postAuth({
326
+ return pRetry(() => postAuth({
326
327
  url: '/result/test/clear',
327
328
  body: {
328
329
  projectId,
@@ -330,11 +331,11 @@ function clearTestResult(projectId, resultId, testId, testResult) {
330
331
  testId,
331
332
  testResult,
332
333
  },
333
- }));
334
+ }), { reties: DEFAULT_REQUEST_RETRY });
334
335
  }
335
336
 
336
337
  function saveRemoteStep(projectId, resultId, stepId, remoteStep) {
337
- return utils.runWithRetries(() => postAuth({
338
+ return pRetry(() => postAuth({
338
339
  url: '/remoteStep',
339
340
  body: {
340
341
  projectId,
@@ -342,7 +343,7 @@ function saveRemoteStep(projectId, resultId, stepId, remoteStep) {
342
343
  stepId,
343
344
  remoteStep,
344
345
  },
345
- }));
346
+ }), { reties: DEFAULT_REQUEST_RETRY });
346
347
  }
347
348
 
348
349
  function relativize(uri) {
@@ -381,9 +382,9 @@ function uploadArtifact(projectId, testId, testResultId, content, subType, mimeT
381
382
  },
382
383
  };
383
384
 
384
- return utils.runWithRetries(() => postAuthFormData(`/storage${storagePath}`, {}, files, {
385
+ return pRetry(() => postAuthFormData(`/storage${storagePath}`, {}, files, {
385
386
  'X-Asset-Encoding': 'gzip',
386
- })).then(() => storagePath);
387
+ }), { reties: DEFAULT_REQUEST_RETRY }).then(() => storagePath);
387
388
  }
388
389
 
389
390
  const uploadRunDataArtifact = _.memoize(async (projectId, testId, testResultId, runData) => {
@@ -422,7 +423,7 @@ function addTestRetry({
422
423
  previousTestResultId,
423
424
  testResult,
424
425
  }) {
425
- return utils.runWithRetries(() => postAuth({
426
+ return pRetry(() => postAuth({
426
427
  url: '/result/test/retry',
427
428
  body: {
428
429
  projectId,
@@ -433,7 +434,7 @@ function addTestRetry({
433
434
  runId,
434
435
  testResult,
435
436
  },
436
- }), DEFAULT_REQUEST_RETRY);
437
+ }), { reties: DEFAULT_REQUEST_RETRY });
437
438
  }
438
439
 
439
440
  /**
@@ -475,6 +476,10 @@ function deleteCloudflareTunnel(companyId, tunnelId) {
475
476
  }
476
477
  }
477
478
 
479
+ function updateRemoteRunFailure(body) {
480
+ return httpRequest.post({ url: `${config.SERVICES_HOST}/result/remoteRunFailure`, body });
481
+ }
482
+
478
483
  module.exports = {
479
484
  getS3Artifact,
480
485
  getTestPlan,
@@ -510,4 +515,5 @@ module.exports = {
510
515
  getCloudflareTunnel,
511
516
  forceUpdateCloudflareTunnelRoutes,
512
517
  deleteCloudflareTunnel,
518
+ updateRemoteRunFailure,
513
519
  };
package/errors.js CHANGED
@@ -1,3 +1,4 @@
1
+ const { AbortError } = require('p-retry');
1
2
  /**
2
3
  * NoArgsError - throws when arguments not passed to cli
3
4
  *
@@ -57,7 +58,7 @@ class GetBrowserError extends Error {
57
58
  }
58
59
  }
59
60
 
60
- class PageNotAvailableError extends Error {
61
+ class PageNotAvailableError extends AbortError {
61
62
  }
62
63
 
63
64
  class QuotaDepletedError extends Error { }