@testim/testim-cli 3.254.0 → 3.256.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 (61) hide show
  1. package/agent/routers/cliJsCode/service.js +11 -8
  2. package/agent/routers/codim/router.test.js +9 -12
  3. package/agent/routers/codim/service.js +16 -16
  4. package/agent/routers/playground/service.js +5 -7
  5. package/cli.js +6 -6
  6. package/cliAgentMode.js +11 -10
  7. package/codim/codim-cli.js +14 -9
  8. package/commons/featureFlags.js +29 -7
  9. package/commons/httpRequest.js +5 -1
  10. package/commons/httpRequestCounters.js +21 -10
  11. package/commons/initializeUserWithAuth.js +7 -4
  12. package/commons/lazyRequire.js +4 -3
  13. package/commons/preloadTests.js +6 -3
  14. package/commons/prepareRunner.js +7 -5
  15. package/commons/prepareRunnerAndTestimStartUtils.js +51 -45
  16. package/commons/runnerFileCache.js +10 -2
  17. package/commons/testimServicesApi.js +36 -5
  18. package/commons/testimTunnel.test.js +2 -1
  19. package/coverage/SummaryToObjectReport.js +0 -1
  20. package/coverage/jsCoverage.js +12 -10
  21. package/inputFileUtils.js +11 -9
  22. package/npm-shrinkwrap.json +214 -471
  23. package/package.json +4 -3
  24. package/player/services/tabService.js +15 -1
  25. package/player/stepActions/apiStepAction.js +49 -43
  26. package/player/stepActions/baseCliJsStepAction.js +19 -14
  27. package/player/stepActions/baseJsStepAction.js +9 -8
  28. package/player/stepActions/dropFileStepAction.js +1 -3
  29. package/player/stepActions/inputFileStepAction.js +10 -8
  30. package/player/stepActions/locateStepAction.js +2 -0
  31. package/player/stepActions/mouseStepAction.js +21 -22
  32. package/player/stepActions/nodePackageStepAction.js +34 -35
  33. package/player/stepActions/stepAction.js +1 -0
  34. package/player/utils/imageCaptureUtils.js +133 -172
  35. package/player/utils/screenshotUtils.js +16 -13
  36. package/player/utils/windowUtils.js +20 -8
  37. package/player/webdriver.js +25 -22
  38. package/processHandler.js +4 -0
  39. package/reports/junitReporter.js +6 -7
  40. package/reports/reporter.js +34 -39
  41. package/runOptions.d.ts +286 -0
  42. package/runOptions.js +60 -45
  43. package/runner.js +64 -24
  44. package/runners/ParallelWorkerManager.js +12 -12
  45. package/runners/TestPlanRunner.js +14 -15
  46. package/runners/buildCodeTests.js +1 -0
  47. package/runners/runnerUtils.js +11 -2
  48. package/services/branchService.js +11 -5
  49. package/services/gridService.js +36 -40
  50. package/services/localRCASaver.js +4 -0
  51. package/testRunStatus.js +8 -5
  52. package/utils/argsUtils.js +86 -0
  53. package/utils/argsUtils.test.js +32 -0
  54. package/utils/fsUtils.js +154 -0
  55. package/utils/index.js +10 -161
  56. package/utils/promiseUtils.js +13 -2
  57. package/utils/stringUtils.js +4 -2
  58. package/utils/stringUtils.test.js +22 -0
  59. package/utils/timeUtils.js +25 -0
  60. package/utils/utils.test.js +0 -41
  61. package/workers/WorkerExtension.js +6 -7
@@ -39,12 +39,13 @@ class TestPlanRunner {
39
39
  * @param {any[]} tests
40
40
  * @param {any[]} afterTests
41
41
  * @param {string} branchToUse
42
- * @param {*} tpOptions
42
+ * @param {import('../runOptions').RunnerOptions} tpOptions
43
43
  * @param {string} executionId
44
44
  * @param {string} executionName
45
45
  * @param {TestRunStatus} testStatus
46
46
  */
47
47
  async runTestAllPhases(beforeTests, tests, afterTests, branchToUse, tpOptions, executionId, executionName, testStatus) {
48
+ /** @type {{ [testResultId: string]: any; }} */
48
49
  const executionResults = {};
49
50
  const authData = testimCustomToken.getTokenV3UserData();
50
51
 
@@ -71,10 +72,6 @@ class TestPlanRunner {
71
72
  Object.assign(executionResults, afterTestsResults);
72
73
  };
73
74
 
74
- function catchBeforeTestsFailed() {
75
- return testStatus.markAllQueuedTests(executionId, constants.runnerTestStatus.ABORTED, 'aborted', false);
76
- }
77
-
78
75
  const sessionType = utils.getSessionType(tpOptions);
79
76
  analyticsService.analyticsExecsStart({ authData, executionId, projectId: tpOptions.project, sessionType });
80
77
  perf.log('right before runBeforeTests');
@@ -88,7 +85,7 @@ class TestPlanRunner {
88
85
  } catch (err) {
89
86
  logger.error('error running test plan', { err });
90
87
  if (err instanceof StopRunOnError) {
91
- return catchBeforeTestsFailed();
88
+ return testStatus.markAllQueuedTests(executionId, constants.runnerTestStatus.ABORTED, 'aborted', false);
92
89
  }
93
90
  throw err;
94
91
  } finally {
@@ -149,6 +146,7 @@ class TestPlanRunner {
149
146
  const childTestResults = {};
150
147
  realDataService.joinToTestResultsByRunId(runId, projectId);
151
148
  let isPromisePending = true;
149
+ /** @type {Promise<any[]>} */
152
150
  const promise = new Promise(_resolve => {
153
151
  const resolve = (val) => {
154
152
  isPromisePending = false;
@@ -218,7 +216,7 @@ class TestPlanRunner {
218
216
  * @param {any[]} beforeTests
219
217
  * @param {any[]} tests
220
218
  * @param {any[]} afterTests
221
- * @param {Awaited<ReturnType<typeof import('../runOptions')['process']>>} tpOptions
219
+ * @param {import('../runOptions').RunnerOptions} tpOptions
222
220
  * @param {string} testPlanName
223
221
  * @param {string | null} testPlanId
224
222
  * @param {string} branch
@@ -230,17 +228,18 @@ class TestPlanRunner {
230
228
  Logger.setExecutionId(executionId);
231
229
  beforeTests.forEach(test => { test.isBeforeTestPlan = true; });
232
230
  afterTests.forEach(test => { test.isAfterTestPlan = true; });
233
- const testStatus = new TestRunStatus(_.concat(beforeTests, tests, afterTests), tpOptions, testPlanId, branch);
231
+ const testStatus = new TestRunStatus([].concat(beforeTests, tests, afterTests), tpOptions, testPlanId, branch);
234
232
 
235
- const configs = _.chain(_.concat(beforeTests, tests, afterTests))
233
+ const configs = _.chain([].concat(beforeTests, tests, afterTests))
236
234
  .map(test => test.overrideTestConfig?.name || '')
237
235
  .uniq()
238
236
  .compact()
239
237
  .value();
238
+ /** @type {string | null} */
240
239
  const configName = configs?.length === 1 ? configs[0] : null;
241
240
 
242
241
  const isCodeMode = tpOptions.files.length > 0;
243
- const testNames = tpOptions.lightweightMode?.onlyTestIdsNoSuite ? [] : _.concat(beforeTests, tests, afterTests).map(test => test.name);
242
+ const testNames = tpOptions.lightweightMode?.onlyTestIdsNoSuite ? [] : [].concat(beforeTests, tests, afterTests).map(test => test.name);
244
243
 
245
244
  const testListInfoPromise = tpOptions.lightweightMode?.onlyTestIdsNoSuite ?
246
245
  { beforeTests, tests, afterTests } :
@@ -276,7 +275,7 @@ class TestPlanRunner {
276
275
 
277
276
  /**
278
277
  * @private
279
- * @param {Awaited<ReturnType<typeof import('../runOptions')['process']>>} options
278
+ * @param {import('../runOptions').RunnerOptions} options
280
279
  * @param {string} branchToUse
281
280
  */
282
281
  async runTestPlans(options, branchToUse) {
@@ -347,7 +346,7 @@ class TestPlanRunner {
347
346
 
348
347
  /**
349
348
  * @private
350
- * @param {Awaited<ReturnType<typeof import('../runOptions')['process']>>} options
349
+ * @param {import('../runOptions').RunnerOptions} options
351
350
  * @param {string} branchToUse
352
351
  */
353
352
  async runAnonymousTestPlan(options, branchToUse) {
@@ -379,13 +378,13 @@ class TestPlanRunner {
379
378
  suiteResult.runName = `rerun-${options.rerunFailedByRunId}`;
380
379
  }
381
380
  }
382
- const testPlanName = options.overrideExecutionName || suiteResult.runName || _.concat(options.label, options.name, options.suites).join(' ');
381
+ const testPlanName = options.overrideExecutionName || suiteResult.runName || [].concat(options.label, options.name, options.suites).join(' ');
383
382
  const isAnonymous = true;
384
383
  perf.log('Right before validateConfig + runAnonymousTestPlan tests map');
385
384
  return await utils.promiseMap(suiteResult.tests, async suiteTests => { // array of results per execution
386
385
  //override result id for remote run mode and run only the first test data
387
386
  if (options.resultId) {
388
- const firstTest = _.first(suiteTests);
387
+ const firstTest = suiteTests[0];
389
388
  firstTest.resultId = options.resultId;
390
389
  suiteTests = [firstTest];
391
390
  }
@@ -400,7 +399,7 @@ class TestPlanRunner {
400
399
  }
401
400
 
402
401
  /**
403
- * @param {Awaited<ReturnType<typeof import('../runOptions')['process']>>} options
402
+ * @param {import('../runOptions').RunnerOptions} options
404
403
  */
405
404
  async run(options) {
406
405
  const branchToUse = branchService.getCurrentBranch();
@@ -122,6 +122,7 @@ async function buildCodeTests(files, webpackConfig = { mode: 'development' }, ru
122
122
  throw new ArgError(`Compilation Webpack Error in tests: ${e.message}`);
123
123
  }
124
124
 
125
+ /** @type {{ code: string; name: string; }[]} */
125
126
  const fileResults = files.map((file, i) => ({ code: mfs.readFileSync(path.resolve('./dist', `${fileHashes[i]}.bundle.js`)), name: file })); // read all files
126
127
 
127
128
  suite.tests = [fileResults.map(({ code, name }) => ({
@@ -7,9 +7,14 @@ const analytics = require('../commons/testimAnalytics');
7
7
  const { ArgError } = require('../errors');
8
8
 
9
9
 
10
+ /**
11
+ * @param {import('../runOptions').RunnerOptions} options
12
+ * @param {string} branchToUse
13
+ * @returns
14
+ */
10
15
  async function getSuite(options, branchToUse) {
11
16
  if (options.lightweightMode?.onlyTestIdsNoSuite && options.testId) {
12
- return { tests: [options.testId.map(testId => ({ testId, testConfig: { }, resultId: utils.guid() }))] };
17
+ return { tests: [options.testId.map(testId => ({ testId, testConfig: {}, resultId: utils.guid() }))] };
13
18
  }
14
19
  // local code test
15
20
  if (options.files.length > 0) {
@@ -47,7 +52,11 @@ function calcTestResultStatus(tests) {
47
52
  }
48
53
 
49
54
 
50
-
55
+ /**
56
+ * @template T
57
+ * @param {import('../runOptions').RunnerOptions} options
58
+ * @param {T[]} testList
59
+ */
51
60
  async function validateConfig(options, testList) {
52
61
  const supportedBrowsers = options.mode === 'extension' ? [
53
62
  'edge-chromium', 'chrome',
@@ -1,12 +1,18 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
+ /** @type {string | undefined} */
3
4
  let currentBranch;
4
5
 
5
6
  function getCurrentBranch() {
6
- return currentBranch || "master";
7
+ return currentBranch || 'master';
7
8
  }
8
- function setCurrentBranch(branchData = "master", acknowledgeAutoDetect = "false") {
9
- if (branchData && branchData.branch && branchData.branch === 'master') {
9
+
10
+ /**
11
+ * @param {Awaited<ReturnType<import('../commons/initializeUserWithAuth')['initializeUserWithAuth']>>['branchName']} branchData
12
+ * @param {boolean | string} acknowledgeAutoDetect
13
+ */
14
+ function setCurrentBranch(branchData = 'master', acknowledgeAutoDetect = 'false') {
15
+ if (branchData?.branch && branchData.branch === 'master') {
10
16
  currentBranch = 'master';
11
17
  return;
12
18
  }
@@ -19,5 +25,5 @@ function setCurrentBranch(branchData = "master", acknowledgeAutoDetect = "false"
19
25
 
20
26
  module.exports = {
21
27
  getCurrentBranch,
22
- setCurrentBranch
28
+ setCurrentBranch,
23
29
  };
@@ -4,7 +4,7 @@ const _ = require('lodash');
4
4
  const Promise = require('bluebird');
5
5
 
6
6
  const { GridError, ArgError } = require('../errors');
7
- const { hasTestPlanFlag } = require('../utils');
7
+ const { hasTestPlanFlag, promiseMap } = require('../utils');
8
8
  const { gridMessages, gridTypes } = require('../commons/constants');
9
9
  const logger = require('../commons/logger').getLogger('grid-service');
10
10
  const servicesApi = require('../commons/testimServicesApi');
@@ -37,22 +37,22 @@ function extractHost(hostUrl) {
37
37
 
38
38
  function getSerializableObject(grid) {
39
39
  const host = grid && extractHost(grid.host);
40
- const port = grid && grid.port;
41
- const path = grid && grid.path;
40
+ const port = grid?.port;
41
+ const path = grid?.path;
42
42
  const protocol = grid && extractProtocol(grid);
43
- const accessToken = grid && grid.token;
44
- const slotId = grid && grid.slotId;
45
- const tunnel = grid && grid.hybrid && grid.hybrid.tunnel;
46
- const user = grid && grid.external && grid.external.user;
47
- const key = grid && grid.external && grid.external.key;
48
- const type = grid && grid.type;
43
+ const accessToken = grid?.token;
44
+ const slotId = grid?.slotId;
45
+ const tunnel = grid?.hybrid?.tunnel;
46
+ const user = grid?.external?.user;
47
+ const key = grid?.external?.key;
48
+ const type = grid?.type;
49
49
  const tunnelUser = type === gridTypes.HYBRID ?
50
50
  (tunnel && grid.hybrid.external && grid.hybrid.external[grid.hybrid.tunnel] && grid.hybrid.external[grid.hybrid.tunnel].user) : user;
51
51
  const tunnelKey = type === gridTypes.HYBRID ?
52
52
  (tunnel && grid.hybrid.external && grid.hybrid.external[grid.hybrid.tunnel] && grid.hybrid.external[grid.hybrid.tunnel].key) : key;
53
- const name = grid && grid.name;
53
+ const name = grid?.name;
54
54
  const gridId = grid && (grid._id || grid.gridId);
55
- const provider = grid && grid.provider;
55
+ const provider = grid?.provider;
56
56
 
57
57
  return {
58
58
  host, port, path, protocol, accessToken, slotId, gridId, tunnel, user, key, type, name, provider, tunnelUser, tunnelKey,
@@ -97,14 +97,16 @@ function addItemToGridCache(workerId, companyId, gridId, slotId, browser) {
97
97
  gridCache[workerId] = { gridId, companyId, slotId, browser };
98
98
  }
99
99
 
100
- function getHostAndPortById(workerId, companyId, projectId, gridId, browser, executionId, options) {
101
- return handleGetGridResponse(projectId, companyId, workerId, browser, () => servicesApi.getGridById(companyId, projectId, gridId, browser, executionId));
100
+ function getHostAndPortById(workerId, companyId, projectId, gridId, browser, executionId) {
101
+ return handleGetGridResponse(projectId, companyId, workerId, browser, () =>
102
+ servicesApi.getGridById(companyId, projectId, gridId, browser, executionId),
103
+ );
102
104
  }
103
105
 
104
106
  function getHostAndPortByName(workerId, companyId, projectId, gridName, browser, executionId, options) {
105
107
  const get = () => {
106
- const grid = options.allGrids && options.allGrids.find(grid => (grid.name || '').toLowerCase() === gridName.toLowerCase());
107
- if (grid && grid._id) {
108
+ const grid = options.allGrids?.find(g => (g.name || '').toLowerCase() === gridName.toLowerCase());
109
+ if (grid?._id) {
108
110
  return servicesApi.getGridById(companyId, projectId, grid._id, browser, executionId);
109
111
  }
110
112
  return servicesApi.getGridByName(companyId, projectId, gridName, browser, executionId);
@@ -116,26 +118,22 @@ function getAllGrids(companyId) {
116
118
  return servicesApi.getAllGrids(companyId);
117
119
  }
118
120
 
119
- function getGridDataByGridId(companyId, gridId, allGrids) {
120
- return Promise.resolve(allGrids || getAllGrids(companyId))
121
- .then(grids => {
122
- const grid = grids.find(grid => grid._id === gridId);
123
- if (!grid) {
124
- throw new ArgError(`Failed to find grid id: ${gridId}`);
125
- }
126
- return getSerializableObject(grid);
127
- });
121
+ async function getGridDataByGridId(companyId, gridId, allGrids) {
122
+ const grids = await Promise.resolve(allGrids || getAllGrids(companyId));
123
+ const grid = grids.find(g => g._id === gridId);
124
+ if (!grid) {
125
+ throw new ArgError(`Failed to find grid id: ${gridId}`);
126
+ }
127
+ return getSerializableObject(grid);
128
128
  }
129
129
 
130
- function getGridDataByGridName(companyId, gridName, allGrids) {
131
- return Promise.resolve(allGrids || getAllGrids(companyId))
132
- .then(grids => {
133
- const grid = grids.find(grid => (grid.name || '').toLowerCase() === gridName.toLowerCase());
134
- if (!grid) {
135
- throw new ArgError(`Failed to find grid name: ${gridName}`);
136
- }
137
- return getSerializableObject(grid);
138
- });
130
+ async function getGridDataByGridName(companyId, gridName, allGrids) {
131
+ const grids = await Promise.resolve(allGrids || getAllGrids(companyId));
132
+ const grid = grids.find(g => (g.name || '').toLowerCase() === gridName.toLowerCase());
133
+ if (!grid) {
134
+ throw new ArgError(`Failed to find grid name: ${gridName}`);
135
+ }
136
+ return getSerializableObject(grid);
139
137
  }
140
138
 
141
139
  function releaseGridSlot(workerId, projectId) {
@@ -184,7 +182,7 @@ function releaseAllSlots(projectId) {
184
182
  }
185
183
 
186
184
  logger.warn('not all slots released before end runner flow', { projectId });
187
- return Promise.map(workerIds, workerId => releaseGridSlot(workerId, projectId))
185
+ return promiseMap(workerIds, workerId => releaseGridSlot(workerId, projectId))
188
186
  .catch(err => logger.error('failed to release all slots', { err, projectId }));
189
187
  }
190
188
 
@@ -266,20 +264,18 @@ async function getGridData(options) {
266
264
  throw new GridError('Missing host or grid configuration');
267
265
  }
268
266
 
269
- const getGridSlot = Promise.method(_getGridSlot);
270
-
271
- async function _getGridSlot(browser, executionId, testResultId, onGridSlot, options, workerId) {
272
- const getGridDataFromServer = () => {
267
+ async function getGridSlot(browser, executionId, testResultId, onGridSlot, options, workerId) {
268
+ const getGridDataFromServer = async () => {
273
269
  const { host, project, grid, gridId, useLocalChromeDriver, useChromeLauncher, company = {} } = options;
274
270
  const companyId = company.companyId;
275
271
  if (useLocalChromeDriver || useChromeLauncher) {
276
272
  return { mode: 'local' };
277
273
  }
278
274
  if (host) {
279
- return Promise.resolve(getOptionGrid(options));
275
+ return getOptionGrid(options);
280
276
  }
281
277
  if (gridId) {
282
- return getHostAndPortById(workerId, companyId, project, gridId, browser, executionId, options);
278
+ return getHostAndPortById(workerId, companyId, project, gridId, browser, executionId);
283
279
  }
284
280
  if (grid) {
285
281
  return getHostAndPortByName(workerId, companyId, project, grid, browser, executionId, options);
@@ -37,6 +37,10 @@ function mapFilesToLocalDrive(test, logger) {
37
37
  }
38
38
  }
39
39
 
40
+ /**
41
+ * @param {Partial<Pick<import('../runOptions').AgentModeOptions, 'agentPort' | 'agentBind'> & Pick<import('../runOptions').RunnerOptions, 'saveRCALocally'>>} param0
42
+ * @returns {Promise<import('net').AddressInfo>}
43
+ */
40
44
  async function initServer({ agentPort, agentBind, saveRCALocally }) {
41
45
  const multer = await lazyRequire('multer');
42
46
  saveRCALocally = typeof saveRCALocally === 'string' ? saveRCALocally : DEFUALT_PATH;
package/testRunStatus.js CHANGED
@@ -17,6 +17,7 @@ const { calculateCoverage } = require('./coverage/jsCoverage');
17
17
  const featureAvailabilityService = require('./commons/featureAvailabilityService');
18
18
  const featureFlags = require('./commons/featureFlags');
19
19
  const { mapFilesToLocalDrive } = require('./services/localRCASaver');
20
+ const { promiseMap } = require('./utils');
20
21
 
21
22
  const gitBranch = utils.getEnvironmentGitBranch();
22
23
  const gitCommit = process.env.GIT_COMMIT || process.env.CIRCLE_SHA1 || process.env.TRAVIS_COMMIT;
@@ -400,7 +401,7 @@ class RunStatus {
400
401
 
401
402
  const reportExecutionStarted = () => {
402
403
  const testResults = _.cloneDeep(this.testRunStatus);
403
- return Promise.map(Object.keys(testResults), testResultId => {
404
+ return promiseMap(Object.keys(testResults), testResultId => {
404
405
  const test = testResults[testResultId];
405
406
  const testData = test.config?.testData;
406
407
  const testId = test.testId;
@@ -498,10 +499,10 @@ class RunStatus {
498
499
  });
499
500
  }
500
501
 
501
- markAllQueuedTests(executionId, status, failureReason, success) {
502
+ async markAllQueuedTests(executionId, status, failureReason, success) {
502
503
  const queuedResultIds = Object.keys(this.testRunStatus).filter(resultId => this.getTestResult(resultId).status === 'QUEUED');
503
504
 
504
- return servicesApi.updateExecutionTests(
505
+ await servicesApi.updateExecutionTests(
505
506
  executionId,
506
507
  ['QUEUED'],
507
508
  status,
@@ -510,12 +511,14 @@ class RunStatus {
510
511
  this.startTime,
511
512
  null,
512
513
  this.options.project
513
- ).then(() => Promise.each(queuedResultIds, resultId => {
514
+ );
515
+ for (const resultId of queuedResultIds) {
514
516
  const test = this.getTestResult(resultId);
515
517
  test.status = status;
516
518
  test.failureReason = failureReason;
517
519
  test.success = success;
518
- })).then(() => this.testRunStatus);
520
+ }
521
+ return this.testRunStatus;
519
522
  }
520
523
  }
521
524
 
@@ -0,0 +1,86 @@
1
+ // @ts-check
2
+
3
+ 'use strict';
4
+
5
+ const { sessionType, testStatus: testStatusConst } = require('../commons/constants');
6
+
7
+
8
+ /**
9
+ * @typedef {import('../runOptions').Options} Options
10
+ * @typedef {import('../runOptions').InitModeOptions} InitModeOptions
11
+ * @typedef {import('../runOptions').LoginModeOptions} LoginModeOptions
12
+ * @typedef {import('../runOptions').TunnelOptions} TunnelOptions
13
+ * @typedef {import('../runOptions').NgrokTunnelOptions} NgrokTunnelOptions
14
+ * @typedef {import('../runOptions').TunnelDaemonOptions} TunnelDaemonOptions
15
+ * @typedef {import('../runOptions').RunnerOptions} RunnerOptions
16
+ */
17
+
18
+ /** @param {RunnerOptions} options */
19
+ function getSessionType(options) {
20
+ return options.files.length > 0 ? sessionType.CODEFUL : sessionType.CODELESS;
21
+ }
22
+
23
+ /**
24
+ * @param {RunnerOptions} options
25
+ * @param {{ runConfig: { browserValue: string } }[]} testList
26
+ */
27
+ function getUniqBrowsers(options, testList) {
28
+ if ((options.testConfigNames?.length || options.testConfigIds?.length || options.testPlan.length || options.testPlanIds.length) && !options.browser) {
29
+ return [...new Set(testList.map(t => t.runConfig.browserValue))];
30
+ }
31
+ return [options.browser?.toLowerCase()];
32
+ }
33
+
34
+ /** @param {RunnerOptions} options */
35
+ const hasTestPlanFlag = (options) => Boolean(options.testPlan?.length || options.testPlanIds?.length);
36
+
37
+ /** @param {RunnerOptions} options */
38
+ const isRemoteRun = (options) => options.resultId && options.source === 'remote-run';
39
+
40
+ /**
41
+ * @param {{ testStatus?: string }} test
42
+ * @param {RunnerOptions} options
43
+ */
44
+ const isQuarantineAndNotRemoteRun = (test, options) => test.testStatus === testStatusConst.QUARANTINE && !isRemoteRun(options) && !options.runQuarantinedTests;
45
+
46
+ function getArgsOnRemoteRunFailure() {
47
+ const { argv: args } = process;
48
+ if (!args.includes('--remoteRunId')) {
49
+ return undefined;
50
+ }
51
+ return {
52
+ remoteRunId: args[args.indexOf('--remoteRunId') + 1],
53
+ projectId: args[args.indexOf('--project') + 1],
54
+ token: args[args.indexOf('--token') + 1],
55
+ };
56
+ }
57
+
58
+
59
+ /** @type {(options: Options) => options is InitModeOptions} options */
60
+ // @ts-ignore should be `as any` etc
61
+ const isInitCodimMode = (options) => Boolean(options.initCodimMode);
62
+
63
+ /** @type {(options: Options) => options is LoginModeOptions} options */
64
+ // @ts-ignore should be `as any` etc
65
+ const isLoginMode = (options) => Boolean(options.loginMode);
66
+
67
+ /** @type {(options: Options) => options is TunnelOptions} options */
68
+ // @ts-ignore should be `as any` etc
69
+ const isTunnelOnlyMode = (options) => Boolean(options.tunnelOnlyMode);
70
+
71
+ /** @type {(options: Options) => options is RunnerOptions & { createPrefechedData: true; }} options */
72
+ // @ts-ignore should be `as any` etc
73
+ const isCreatePrefetchedDataMode = (options) => Boolean(options.createPrefechedData);
74
+
75
+ module.exports = {
76
+ getSessionType,
77
+ getUniqBrowsers,
78
+ hasTestPlanFlag,
79
+ isRemoteRun,
80
+ isQuarantineAndNotRemoteRun,
81
+ getArgsOnRemoteRunFailure,
82
+ isInitCodimMode,
83
+ isLoginMode,
84
+ isTunnelOnlyMode,
85
+ isCreatePrefetchedDataMode,
86
+ };
@@ -0,0 +1,32 @@
1
+ const chai = require('chai'); // eslint-disable-line import/no-extraneous-dependencies
2
+ const argsUtils = require('./argsUtils');
3
+
4
+ const expect = chai.expect;
5
+
6
+ describe('argsUtils', () => {
7
+ describe('getArgsOnRemoteRunFailure', () => {
8
+ let originalArgv;
9
+
10
+ beforeEach(() => {
11
+ originalArgv = process.argv;
12
+ });
13
+
14
+ afterEach(() => {
15
+ process.argv = originalArgv;
16
+ });
17
+
18
+ it('should return undefined if no remote run is current', () => {
19
+ process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id'];
20
+ expect(argsUtils.getArgsOnRemoteRunFailure()).to.be.undefined;
21
+ });
22
+
23
+ it('should return details if remote run is current', () => {
24
+ process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id', '--remoteRunId', 'remote-run-id'];
25
+ expect(argsUtils.getArgsOnRemoteRunFailure()).to.eql({
26
+ remoteRunId: 'remote-run-id',
27
+ projectId: 'project-id',
28
+ token: 'token',
29
+ });
30
+ });
31
+ });
32
+ });
@@ -0,0 +1,154 @@
1
+ // @ts-check
2
+
3
+ 'use strict';
4
+
5
+ const path = require('path');
6
+ const pRetry = require('p-retry');
7
+ const decompress = require('decompress');
8
+ const stringUtils = require('./stringUtils');
9
+ const httpRequest = require('../commons/httpRequest');
10
+ const { promises: fsPromises, createReadStream, createWriteStream, statSync } = require('fs');
11
+
12
+ const DOWNLOAD_RETRY = 3;
13
+
14
+ function getCliLocation() {
15
+ let cliLocation;
16
+ if (!require.main) { // we're in a REPL
17
+ return process.cwd(); // fall back on the current working directory
18
+ }
19
+ if (require.main.filename.includes('/src') || require.main.filename.includes('\\src') || process.env.IS_UNIT_TEST) {
20
+ cliLocation = path.resolve(__dirname, '../../');
21
+ } else {
22
+ cliLocation = path.resolve(__dirname, '../');
23
+ }
24
+
25
+ return cliLocation;
26
+ }
27
+
28
+ /**
29
+ * @param {string} url
30
+ */
31
+ const download = async (url) => pRetry(() => httpRequest.download(url), { retries: DOWNLOAD_RETRY });
32
+
33
+ /**
34
+ * @param {string} url
35
+ * @param {import('fs').PathLike} saveToLocation
36
+ */
37
+ const downloadAndSave = async (url, saveToLocation) => {
38
+ const res = await download(url);
39
+ return fsPromises.writeFile(saveToLocation, res.body);
40
+ };
41
+
42
+ /**
43
+ * @param {import('fs').PathLike} readFile
44
+ * @param {import('fs').PathLike} destFile
45
+ * @return {Promise<void>}
46
+ */
47
+ const copy = async (readFile, destFile) => new Promise((resolve, reject) => {
48
+ try {
49
+ // TODO: can this use fsPromises.copyFile?
50
+ const file = createWriteStream(destFile);
51
+ createReadStream(readFile).pipe(file);
52
+ file.on('finish', () => {
53
+ file.close(() => resolve());
54
+ });
55
+ } catch (err) {
56
+ reject(err);
57
+ }
58
+ });
59
+
60
+ /**
61
+ * @param {string} location
62
+ * @param {string=} fileName
63
+ */
64
+ function getSourcePath(location, fileName) {
65
+ if (stringUtils.isURL(location)) {
66
+ return fileName || path.join(process.cwd(), location.replace(/^.*[\\\/]/, ''));
67
+ }
68
+
69
+ return fileName || path.basename(location);
70
+ }
71
+
72
+ /**
73
+ * @param {string} location
74
+ * @param {string} fileName
75
+ */
76
+ const getSource = async (location, fileName) => {
77
+ const destFile = getSourcePath(location, fileName);
78
+ if (stringUtils.isURL(location)) {
79
+ return downloadAndSave(location, destFile);
80
+ }
81
+
82
+ return copy(location, destFile);
83
+ };
84
+
85
+ /**
86
+ * @param {string} location
87
+ */
88
+ const getSourceAsBuffer = async (location) => {
89
+ if (stringUtils.isURL(location)) {
90
+ return download(location);
91
+ }
92
+ return fsPromises.readFile(location);
93
+ };
94
+
95
+ /**
96
+ * @param {string} srcZipFile
97
+ * @param {string} destZipPath
98
+ */
99
+ const unzipFile = async (srcZipFile, destZipPath) => await decompress(srcZipFile, destZipPath);
100
+
101
+ /**
102
+ * @param {import('fs').PathLike} fileLocation
103
+ */
104
+ const getLocalFileSizeInMB = (fileLocation) => {
105
+ const stats = statSync(fileLocation);
106
+ const fileSizeInBytes = stats.size;
107
+ return fileSizeInBytes / 1000000;
108
+ };
109
+
110
+
111
+ function getRunnerVersion() {
112
+ try {
113
+ /** @type {import('../../package.json')} */
114
+ const pack = require(`${__dirname}/../package.json`); // eslint-disable-line import/no-dynamic-require
115
+ return pack.version;
116
+ } catch (err) {
117
+ return '';
118
+ }
119
+ }
120
+
121
+ function getEnginesVersion() {
122
+ try {
123
+ /** @type {import('../../package.json')} */
124
+ const pack = require(`${__dirname}/../package.json`); // eslint-disable-line import/no-dynamic-require
125
+ return pack.engines.node;
126
+ } catch (err) {
127
+ return '';
128
+ }
129
+ }
130
+
131
+ async function getEnginesVersionAsync() {
132
+ try {
133
+ /** @type {import('../../package.json')} */
134
+ const pack = JSON.parse(await fsPromises.readFile(`${__dirname}/../package.json`, 'utf8'));
135
+ return pack.engines.node;
136
+ } catch (err) {
137
+ return '';
138
+ }
139
+ }
140
+
141
+ module.exports = {
142
+ getCliLocation,
143
+ download,
144
+ downloadAndSave,
145
+ copy,
146
+ getSourcePath,
147
+ getSource,
148
+ getSourceAsBuffer,
149
+ unzipFile,
150
+ getLocalFileSizeInMB,
151
+ getRunnerVersion,
152
+ getEnginesVersion,
153
+ getEnginesVersionAsync,
154
+ };