@testim/testim-cli 3.255.0 → 3.257.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 (35) hide show
  1. package/agent/routers/cliJsCode/service.js +11 -8
  2. package/cli.js +2 -2
  3. package/cliAgentMode.js +7 -7
  4. package/codim/codim-cli.js +4 -1
  5. package/commons/featureFlags.js +21 -7
  6. package/commons/httpRequest.js +1 -1
  7. package/commons/initializeUserWithAuth.js +7 -4
  8. package/commons/preloadTests.js +5 -2
  9. package/commons/prepareRunner.js +5 -5
  10. package/commons/prepareRunnerAndTestimStartUtils.js +11 -4
  11. package/commons/runnerFileCache.js +9 -1
  12. package/commons/testimServicesApi.js +36 -5
  13. package/npm-shrinkwrap.json +59 -60
  14. package/package.json +1 -1
  15. package/player/stepActions/apiStepAction.js +49 -43
  16. package/player/stepActions/baseCliJsStepAction.js +19 -14
  17. package/player/stepActions/baseJsStepAction.js +9 -8
  18. package/player/stepActions/dropFileStepAction.js +1 -3
  19. package/player/stepActions/inputFileStepAction.js +10 -8
  20. package/player/stepActions/mouseStepAction.js +21 -22
  21. package/player/stepActions/nodePackageStepAction.js +34 -35
  22. package/player/stepActions/stepAction.js +1 -0
  23. package/player/utils/imageCaptureUtils.js +63 -63
  24. package/player/utils/screenshotUtils.js +16 -13
  25. package/player/utils/windowUtils.js +20 -8
  26. package/processHandler.js +4 -0
  27. package/runOptions.d.ts +27 -1
  28. package/runOptions.js +7 -7
  29. package/runner.js +62 -23
  30. package/runners/ParallelWorkerManager.js +3 -2
  31. package/runners/TestPlanRunner.js +9 -6
  32. package/runners/buildCodeTests.js +1 -0
  33. package/runners/runnerUtils.js +11 -2
  34. package/services/branchService.js +11 -5
  35. package/services/localRCASaver.js +4 -0
package/runner.js CHANGED
@@ -1,32 +1,34 @@
1
1
  'use strict';
2
2
 
3
3
  /* eslint-disable no-console */
4
- const { CLI_MODE } = require('./commons/constants');
5
4
  const _ = require('lodash');
6
- const { EDITOR_URL } = require('./commons/config');
7
- const tunnel = require('./commons/testimTunnel');
8
5
  const utils = require('./utils');
9
6
  const reporter = require('./reports/reporter');
10
- const testimCustomToken = require('./commons/testimCustomToken');
11
- const socketService = require('./commons/socket/socketService');
12
- const servicesApi = require('./commons/testimServicesApi.js');
13
- const npmDriver = require('./testimNpmDriver.js');
7
+ const npmDriver = require('./testimNpmDriver');
8
+ const tunnel = require('./commons/testimTunnel');
9
+ const perf = require('./commons/performance-logger');
10
+ const gridService = require('./services/gridService');
14
11
  const analytics = require('./commons/testimAnalytics');
12
+ const featureFlags = require('./commons/featureFlags');
15
13
  const branchService = require('./services/branchService');
16
- const gridService = require('./services/gridService');
14
+ const servicesApi = require('./commons/testimServicesApi');
15
+ const TestPlanRunner = require('./runners/TestPlanRunner');
16
+ const socketService = require('./commons/socket/socketService');
17
+ const testimCustomToken = require('./commons/testimCustomToken');
18
+ const labFeaturesService = require('./services/labFeaturesService');
19
+ const featureAvailabilityService = require('./commons/featureAvailabilityService');
20
+ const { getLogger } = require('./commons/logger');
21
+ const { EDITOR_URL } = require('./commons/config');
22
+ const { CLI_MODE } = require('./commons/constants');
17
23
  const { ArgError, QuotaDepletedError } = require('./errors');
18
- const featureFlags = require('./commons/featureFlags');
19
- const perf = require('./commons/performance-logger');
20
24
  const { prepareMockNetwork, initializeUserWithAuth } = require('./commons/prepareRunner');
21
25
 
22
26
  const FREE_PLAN_MINIMUM_BROWSER_TIMEOUT = 30 * 60 * 1000;
23
27
 
24
- const TestPlanRunner = require('./runners/TestPlanRunner');
25
- const labFeaturesService = require('./services/labFeaturesService');
26
- const featureAvailabilityService = require('./commons/featureAvailabilityService');
27
28
 
28
- const logger = require('./commons/logger').getLogger('runner');
29
+ const logger = getLogger('runner');
29
30
 
31
+ /** @param {import('./runOptions').RunnerOptions} options */
30
32
  function validateCLIRunsAreAllowed(options) {
31
33
  const hasCliAccess = _.get(options, 'company.activePlan.premiumFeatures.allowCLI');
32
34
 
@@ -34,14 +36,16 @@ function validateCLIRunsAreAllowed(options) {
34
36
  const projectId = options.project;
35
37
  analytics.track(options.authData.uid, 'cli-not-supported', { projectId });
36
38
  console.warn('Testim CLI is not supported in this plan');
39
+ // TODO: shouldn't this throw an error? 🤔
37
40
  }
38
41
  }
39
42
 
43
+ /** @param {import('./runOptions').RunnerOptions} options */
40
44
  async function validateProjectQuotaNotDepleted(options) {
41
45
  const projectId = options.project;
42
46
 
43
47
  const usage = await servicesApi.getUsageForCurrentBillingPeriod(projectId);
44
- const isExecutionBlocked = usage && usage.isExecutionBlocked;
48
+ const isExecutionBlocked = usage?.isExecutionBlocked;
45
49
  if (!isExecutionBlocked) {
46
50
  return;
47
51
  }
@@ -51,6 +55,10 @@ async function validateProjectQuotaNotDepleted(options) {
51
55
  throw new QuotaDepletedError();
52
56
  }
53
57
 
58
+ /**
59
+ * @param {import('./runOptions').RunnerOptions} options
60
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['companyByProjectId']} company
61
+ */
54
62
  function validateOptionsForCompany(options, company) {
55
63
  const optionsRetention = options.retentionDays;
56
64
  if (!optionsRetention) {
@@ -63,8 +71,9 @@ function validateOptionsForCompany(options, company) {
63
71
  }
64
72
  }
65
73
 
74
+ /** @param {import('./runOptions').RunnerOptions} options */
66
75
  async function validateCliAccount(options) {
67
- if (options.lightweightMode && options.lightweightMode.disableQuotaBlocking) {
76
+ if (options.lightweightMode?.disableQuotaBlocking) {
68
77
  return;
69
78
  }
70
79
  try {
@@ -73,13 +82,14 @@ async function validateCliAccount(options) {
73
82
  validateCLIRunsAreAllowed(options),
74
83
  ]);
75
84
  } catch (err) {
76
- if (err instanceof ArgError || err instanceof QuotaDepletedError) {
85
+ if ([ArgError, QuotaDepletedError].some(errType => err instanceof errType)) {
77
86
  throw err;
78
87
  }
79
88
  logger.error('could not validate cli account', { err });
80
89
  }
81
90
  }
82
91
 
92
+ /** @param {string} projectId */
83
93
  function analyticsIdentify(projectId) {
84
94
  const authData = testimCustomToken.getTokenV3UserData();
85
95
  return analytics.identify({
@@ -95,6 +105,7 @@ function analyticsIdentify(projectId) {
95
105
  });
96
106
  }
97
107
 
108
+ /** @param {string} projectId */
98
109
  function initSocketServices(projectId, { disableResults = false, disableRemoteStep = false }) {
99
110
  if (featureFlags.flags.useNewWSCLI.isEnabled() && !disableResults && !disableRemoteStep) {
100
111
  return socketService.connect(projectId);
@@ -110,6 +121,10 @@ function initSocketServices(projectId, { disableResults = false, disableRemoteSt
110
121
  return undefined;
111
122
  }
112
123
 
124
+ /**
125
+ * @param {import('./runOptions').RunnerOptions} options
126
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['branchName']} branchInfoFromServer
127
+ */
113
128
  function setBranch(options, branchInfoFromServer) {
114
129
  const { branch, autoDetect } = options;
115
130
  branchService.setCurrentBranch(branchInfoFromServer, autoDetect);
@@ -118,14 +133,19 @@ function setBranch(options, branchInfoFromServer) {
118
133
  }
119
134
  }
120
135
 
136
+ /** @param {import('./runOptions').RunnerOptions} options */
121
137
  async function setSfdcCredential(options) {
122
- const { projectData: { projectId } } = options;
138
+ const { projectData: { projectId } = {} } = options;
123
139
  const branch = branchService.getCurrentBranch();
124
140
  if (_.get(options, 'company.activePlan.premiumFeatures.ttaForSalesforce')) {
125
141
  options.sfdcCredential = await servicesApi.loadSfdcCredential({ projectId, branch });
126
142
  }
127
143
  }
128
144
 
145
+ /**
146
+ * @param {import('./runOptions').RunnerOptions} options
147
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['companyByProjectId']} company
148
+ */
129
149
  function setCompany(options, company) {
130
150
  const { onprem, id, storageBaseUrl, storageType, name, activePlan = {} } = company;
131
151
  if (onprem) {
@@ -161,6 +181,10 @@ function setCompany(options, company) {
161
181
  };
162
182
  }
163
183
 
184
+ /**
185
+ * @param {import('./runOptions').RunnerOptions} options
186
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['editorConfig']} editorConfig
187
+ */
164
188
  function setSystemInfo(options, editorConfig) {
165
189
  if (EDITOR_URL) {
166
190
  options.editorUrl = EDITOR_URL;
@@ -169,14 +193,26 @@ function setSystemInfo(options, editorConfig) {
169
193
  options.editorUrl = editorConfig.editorUrl;
170
194
  }
171
195
 
196
+ /**
197
+ * @param {import('./runOptions').RunnerOptions} options
198
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['allGrids']} allGrids
199
+ */
172
200
  function setAllGrids(options, allGrids) {
173
201
  options.allGrids = allGrids;
174
202
  }
175
203
 
204
+ /**
205
+ * @param {import('./runOptions').RunnerOptions} options
206
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['authData']} authData
207
+ */
176
208
  function setAuthData(options, authData) {
177
209
  options.authData = authData;
178
210
  }
179
211
 
212
+ /**
213
+ * @param {import('./runOptions').RunnerOptions} options
214
+ * @param {Awaited<ReturnType<initializeUserWithAuth>>['projectById']} project
215
+ */
180
216
  function setProject(options, project) {
181
217
  const { id, name, type, defaults } = project;
182
218
  featureFlags.setProjectId(id);
@@ -189,10 +225,12 @@ function setProject(options, project) {
189
225
  };
190
226
  }
191
227
 
228
+ /** @param {import('./runOptions').RunnerOptions} options */
192
229
  async function setGrid(options) {
193
230
  options.gridData = await gridService.getGridData(options);
194
231
  }
195
232
 
233
+ /** @param {import('./runOptions').RunnerOptions} options */
196
234
  async function setMockNetworkRules(options) {
197
235
  const { project } = options;
198
236
  const props = { projectId: project };
@@ -236,8 +274,9 @@ async function runRunner(options, customExtensionLocalLocation) {
236
274
  return results;
237
275
  }
238
276
 
277
+ /** @param {import('./runOptions').RunnerOptions} options */
239
278
  function showFreeGridRunWarningIfNeeded(options) {
240
- if (featureAvailabilityService.shouldShowFreeGridRunWarning(options.gridData && options.gridData.type)) {
279
+ if (featureAvailabilityService.shouldShowFreeGridRunWarning(options.gridData?.type)) {
241
280
  const CYAN = '\x1b[36m';
242
281
  const UNDERSCORE = '\x1b[4m';
243
282
  const RESET = '\x1b[0m';
@@ -252,15 +291,15 @@ function showFreeGridRunWarningIfNeeded(options) {
252
291
  * - Reporting the user to analytics
253
292
  * - Authenticating the user and exchanging their token for a jwt
254
293
  * - Sets the grids for the company and validates the user has permission to run the CLI
255
- * @param {Object} options - the run options passed to the CLI, namely the project and token
294
+ * @param {import('./runOptions').RunnerOptions} options - the run options passed to the CLI, namely the project and token
256
295
  */
257
296
  async function init(options) {
258
297
  perf.log('start runner init');
259
298
  const { project, lightweightMode, useChromeLauncher, mode, disableSockets } = options;
260
299
  const featureFlagsReady = featureFlags.fetch();
261
300
  const socketConnected = initSocketServices(project, {
262
- disableResults: disableSockets || Boolean(lightweightMode && lightweightMode.disableResults && (useChromeLauncher || mode !== 'extension')),
263
- disableRemoteStep: disableSockets || Boolean(lightweightMode && lightweightMode.disableRemoteStep),
301
+ disableResults: disableSockets || Boolean(lightweightMode?.disableResults && (useChromeLauncher || mode !== 'extension')),
302
+ disableRemoteStep: disableSockets || Boolean(lightweightMode?.disableRemoteStep),
264
303
  });
265
304
 
266
305
  featureFlagsReady.catch(() => {}); // suppress unhandled rejection
@@ -280,7 +319,7 @@ async function init(options) {
280
319
  setAuthData(options, authData);
281
320
  await setSfdcCredential(options);
282
321
 
283
- if (!(options.lightweightMode && options.lightweightMode.disableLabs)) {
322
+ if (!options.lightweightMode?.disableLabs) {
284
323
  await labFeaturesService.loadLabFeatures(projectById.id, companyByProjectId.activePlan);
285
324
  }
286
325
 
@@ -54,10 +54,11 @@ class ParallelWorkerManager {
54
54
 
55
55
  let stoppedOnError = false;
56
56
  let runningTests = 0;
57
- const runAndWaitToComplete = token => new Promise((resolve, reject) => {
57
+ const runAndWaitToComplete = token => new Promise((resolve) => {
58
58
  const projectId = options.project;
59
59
  const executionQueue = new ExecutionQueue(executionId, executionName, testList, options, branchToUse, testStatus);
60
60
 
61
+ /** @type {{ [testResultId: string]: any; }} */
61
62
  const combinedTestResults = {};
62
63
  const testCount = testList.length;
63
64
 
@@ -177,7 +178,7 @@ class ParallelWorkerManager {
177
178
  }
178
179
  };
179
180
 
180
- const onGridSlot = (executionId, resultId, gridInfo) => testStatus.onGridSlot(executionId, resultId, gridInfo);
181
+ const onGridSlot = (_executionId, resultId, gridInfo) => testStatus.onGridSlot(_executionId, resultId, gridInfo);
181
182
 
182
183
  options.userData = {
183
184
  loginData: Object.assign({}, testimCustomToken.getTokenV3UserData(), {
@@ -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
 
@@ -145,6 +146,7 @@ class TestPlanRunner {
145
146
  const childTestResults = {};
146
147
  realDataService.joinToTestResultsByRunId(runId, projectId);
147
148
  let isPromisePending = true;
149
+ /** @type {Promise<any[]>} */
148
150
  const promise = new Promise(_resolve => {
149
151
  const resolve = (val) => {
150
152
  isPromisePending = false;
@@ -226,17 +228,18 @@ class TestPlanRunner {
226
228
  Logger.setExecutionId(executionId);
227
229
  beforeTests.forEach(test => { test.isBeforeTestPlan = true; });
228
230
  afterTests.forEach(test => { test.isAfterTestPlan = true; });
229
- const testStatus = new TestRunStatus(_.concat(beforeTests, tests, afterTests), tpOptions, testPlanId, branch);
231
+ const testStatus = new TestRunStatus([].concat(beforeTests, tests, afterTests), tpOptions, testPlanId, branch);
230
232
 
231
- const configs = _.chain(_.concat(beforeTests, tests, afterTests))
233
+ const configs = _.chain([].concat(beforeTests, tests, afterTests))
232
234
  .map(test => test.overrideTestConfig?.name || '')
233
235
  .uniq()
234
236
  .compact()
235
237
  .value();
238
+ /** @type {string | null} */
236
239
  const configName = configs?.length === 1 ? configs[0] : null;
237
240
 
238
241
  const isCodeMode = tpOptions.files.length > 0;
239
- 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);
240
243
 
241
244
  const testListInfoPromise = tpOptions.lightweightMode?.onlyTestIdsNoSuite ?
242
245
  { beforeTests, tests, afterTests } :
@@ -375,13 +378,13 @@ class TestPlanRunner {
375
378
  suiteResult.runName = `rerun-${options.rerunFailedByRunId}`;
376
379
  }
377
380
  }
378
- 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(' ');
379
382
  const isAnonymous = true;
380
383
  perf.log('Right before validateConfig + runAnonymousTestPlan tests map');
381
384
  return await utils.promiseMap(suiteResult.tests, async suiteTests => { // array of results per execution
382
385
  //override result id for remote run mode and run only the first test data
383
386
  if (options.resultId) {
384
- const firstTest = _.first(suiteTests);
387
+ const firstTest = suiteTests[0];
385
388
  firstTest.resultId = options.resultId;
386
389
  suiteTests = [firstTest];
387
390
  }
@@ -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
  };
@@ -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;