@testim/testim-cli 3.255.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 (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 +41 -41
  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
@@ -16,16 +16,6 @@ class RectIsOutsideOfImageError extends Error {
16
16
  }
17
17
  }
18
18
 
19
- /**
20
- * @param {never} tabId
21
- * @param {import('./windowUtils')} windowUtils
22
- * @param {import('./screenshotUtils')} screenshotUtils
23
- * */
24
- const ImageCaptureUtils = function (tabId, windowUtils, screenshotUtils) {
25
- this.windowUtils = windowUtils;
26
- this.screenshotUtils = screenshotUtils;
27
- };
28
-
29
19
  async function cropImageFromImageData(imageData, imageInfo) {
30
20
  const _image = imageInfo.image || imageInfo;
31
21
  const pixelRatio = imageInfo.devicePixelRatio;
@@ -114,6 +104,7 @@ function stitchImage(fullSize, parts) {
114
104
  return chromeStitchImage(fullSize, parts);
115
105
  }
116
106
 
107
+ // ???????????
117
108
  function uploadDataUrl() {
118
109
  return Promise.resolve();
119
110
  }
@@ -148,74 +139,80 @@ function getElementAbsoluteRectangle(elementRect, pixelRatio) {
148
139
  };
149
140
  }
150
141
 
151
- ImageCaptureUtils.prototype = {
152
- takeViewPortImage() {
153
- return this.screenshotUtils.takeScreenshot()
154
- .then((imageInfo) => ((typeof imageInfo === 'string') ? imageInfo : imageInfo.image));
155
- },
142
+ class ImageCaptureUtils {
143
+ /**
144
+ * @param {unknown} _unusedId
145
+ * @param {import('./windowUtils')} windowUtils
146
+ * @param {import('./screenshotUtils')} screenshotUtils
147
+ * */
148
+ constructor(_unusedId, windowUtils, screenshotUtils) {
149
+ this.windowUtils = windowUtils;
150
+ this.screenshotUtils = screenshotUtils;
151
+ }
152
+
153
+ async takeViewPortImage() {
154
+ const imageInfo = await this.screenshotUtils.takeScreenshot();
155
+ return ((typeof imageInfo === 'string') ? imageInfo : imageInfo.image);
156
+ }
156
157
 
157
158
  takeImageForComparison() {
158
159
  return this.takeViewPortImage();
159
- },
160
+ }
160
161
 
161
- takeAreaDataUrl(areas, format) {
162
+ async takeAreaDataUrl(areas, format) {
162
163
  // Future changes in clickim will pass parameters to this function as a single object
163
164
  if (areas.areas) {
164
165
  areas = areas.areas;
165
166
  }
166
167
 
167
- return this.screenshotUtils.takeScreenshot(format)
168
- .then((imageInfo) => cropImageFromImageData(areas, imageInfo).then((result) => {
169
- result.screenImage = imageInfo.image;
170
- result.absoluteScreenHighlight = getElementAbsoluteRectangle(areas.elementRect, imageInfo.devicePixelRatio);
171
- return result;
172
- }));
173
- },
168
+ const imageInfo = await this.screenshotUtils.takeScreenshot(format);
169
+ const result = await cropImageFromImageData(areas, imageInfo);
170
+ result.screenImage = imageInfo.image;
171
+ result.absoluteScreenHighlight = getElementAbsoluteRectangle(areas.elementRect, imageInfo.devicePixelRatio);
172
+ return result;
173
+ }
174
174
 
175
- takeArea(areas) {
175
+ async takeArea(areas) {
176
176
  // Future changes in clickim will pass parameters to this function as a single object
177
177
  if (areas.areas) {
178
178
  areas = areas.areas;
179
179
  }
180
180
 
181
- return this.screenshotUtils.takeScreenshot()
182
- .then((imageInfo) => {
183
- const result = {};
184
- result.screenImage = imageInfo.image;
185
- result.absoluteScreenHighlight = getElementAbsoluteRectangle(areas.elementRect, imageInfo.devicePixelRatio);
186
- return result;
187
- }).then(uploadAllDataUrls);
188
- },
181
+ const imageInfo = await this.screenshotUtils.takeScreenshot();
182
+ return uploadAllDataUrls({
183
+ screenImage: imageInfo.image,
184
+ absoluteScreenHighlight: getElementAbsoluteRectangle(areas.elementRect, imageInfo.devicePixelRatio),
185
+ });
186
+ }
189
187
 
190
188
  forcePixelRatio(forceRatio) {
191
189
  return this.screenshotUtils.forcePixelRatio(forceRatio);
192
- },
190
+ }
193
191
 
194
192
  getCurrentDevicePixelRatio() {
195
193
  return this.screenshotUtils.getCurrentDevicePixelRatio();
196
- },
194
+ }
197
195
 
198
196
  async takeStitchedDataUrl(useImprovedScreenshotStitching) {
199
- const windowUtil = this.windowUtils;
200
- const getCurrentScrollPosition = windowUtil.getCurrentScrollPosition.bind(windowUtil);
197
+ const { windowUtils, screenshotUtils } = this;
201
198
 
202
- const that = this;
203
199
  const stabilize = () => utils.delay(250);
204
200
  const usingImprovedStitching = Boolean(useImprovedScreenshotStitching);
205
- const scroll = usingImprovedStitching ?
206
- (pos) => windowUtil.scrollToPositionWithoutAnimation.bind(windowUtil)(pos) :
207
- (pos) => windowUtil.scrollToPosition.bind(windowUtil)(pos);
208
-
209
- function createPart(position, crop) {
210
- return scroll(position)
211
- .then(stabilize)
212
- .then(() => that.screenshotUtils.takeScreenshot())
213
- .then(imageInfo => cropImageFromImageData({ elementRect: crop }, imageInfo))
214
- .then(cropResult => ({
215
- position: { left: position.x + crop.left, top: position.y + crop.top },
216
- size: { width: crop.width, height: crop.height },
217
- image: cropResult.elementImage,
218
- }));
201
+
202
+ async function createPart(position, crop) {
203
+ if (usingImprovedStitching) {
204
+ await windowUtils.scrollToPositionWithoutAnimation(position);
205
+ } else {
206
+ await windowUtils.scrollToPosition(position);
207
+ }
208
+ await stabilize();
209
+ const imageInfo = await screenshotUtils.takeScreenshot();
210
+ const cropResult = await cropImageFromImageData({ elementRect: crop }, imageInfo);
211
+ return ({
212
+ position: { left: position.x + crop.left, top: position.y + crop.top },
213
+ size: { width: crop.width, height: crop.height },
214
+ image: cropResult.elementImage,
215
+ });
219
216
  }
220
217
 
221
218
  async function takeAllParts(positionsData) {
@@ -232,36 +229,39 @@ ImageCaptureUtils.prototype = {
232
229
  const VPW = viewPortSize.width;
233
230
  const FPH = Math.max(fullPageSize.height, viewPortSize.height);
234
231
  const VPH = viewPortSize.height;
235
- const Ws = (Array.apply(null, new Array(Math.ceil(FPW / VPW)))).map((_, i) => ({
232
+ const Ws = Array.from({ length: Math.ceil(FPW / VPW) }, (__, i) => ({
236
233
  scrollX: Math.min(i * VPW, FPW - VPW),
237
234
  cropX: i * VPW - Math.min(i * VPW, FPW - VPW),
238
235
  cropW: VPW - (i * VPW - Math.min(i * VPW, FPW - VPW)),
239
236
  }));
240
- const Hs = (Array.apply(null, new Array(Math.ceil(FPH / VPH)))).map((_, i) => ({
237
+ const Hs = Array.from({ length: Math.ceil(FPH / VPH) }, (__, i) => ({
241
238
  scrollY: Math.min(i * VPH, FPH - VPH),
242
239
  cropY: i * VPH - Math.min(i * VPH, FPH - VPH),
243
240
  cropH: VPH - (i * VPH - Math.min(i * VPH, FPH - VPH)),
244
241
  }));
245
- const positions = Ws.reduce((posList, w) => posList.concat(Hs.map((h) => ({
242
+ return Ws.flatMap(w => Hs.map(h => ({
246
243
  scrollPos: { x: w.scrollX, y: h.scrollY },
247
244
  cropData: {
248
- top: h.cropY, left: w.cropX, width: w.cropW, height: h.cropH,
245
+ top: h.cropY,
246
+ left: w.cropX,
247
+ width: w.cropW,
248
+ height: h.cropH,
249
249
  },
250
- }))), []);
251
- return positions;
250
+ }))
251
+ );
252
252
  }
253
253
 
254
254
  async function createStitchImage(fullPageSize, viewPortSize) {
255
- const originalPosition = await getCurrentScrollPosition();
255
+ const originalPosition = await windowUtils.getCurrentScrollPosition();
256
256
  const positions = getPartsPositions(fullPageSize, viewPortSize);
257
257
  const parts = await takeAllParts(positions);
258
- await windowUtil.scrollToPosition(originalPosition);
258
+ await windowUtils.scrollToPosition(originalPosition);
259
259
  return stitchImage(fullPageSize, parts);
260
260
  }
261
261
 
262
- const [fullPageSize, viewPortSize] = await Promise.all([windowUtil.getFullPageSize(), windowUtil.getViewportSize()]);
262
+ const [fullPageSize, viewPortSize] = await Promise.all([windowUtils.getFullPageSize(), windowUtils.getViewportSize()]);
263
263
  return await createStitchImage(fullPageSize, viewPortSize);
264
- },
265
- };
264
+ }
265
+ }
266
266
 
267
267
  module.exports = ImageCaptureUtils;
@@ -1,9 +1,15 @@
1
+ // @ts-check
2
+
1
3
  'use strict';
2
4
 
3
5
  const Promise = require('bluebird');
4
6
  const pRetry = require('p-retry');
5
7
 
6
8
  class ScreenshotUtils {
9
+ /**
10
+ * @param {any} tabId
11
+ * @param {import('../webdriver')} driver
12
+ */
7
13
  constructor(tabId, driver, options = { takeScreenshots: true }) {
8
14
  this.tabId = tabId;
9
15
  this.driver = driver;
@@ -21,23 +27,21 @@ class ScreenshotUtils {
21
27
  return this.options.takeScreenshots;
22
28
  }
23
29
 
24
- takeScreenshot() {
30
+ async takeScreenshot() {
25
31
  if (!this.shouldTakeScreenshots()) {
26
- return Promise.resolve({ devicePixelRatio: 1, image: '' });
32
+ return { devicePixelRatio: 1, image: '' };
27
33
  }
28
34
  const MAX_RETRY_COUNT = 3;
29
35
  const SCREENSHOT_RETRY_DELAY = 2000;
30
36
  const devicePixelRatioPromise = this.currentDevicePixelRatio ? Promise.resolve(this.currentDevicePixelRatio) : this.getDevicePixelRatio();
31
37
  const getScreenshot = () => Promise.all([devicePixelRatioPromise, this.driver.takeScreenshot()]);
32
- return pRetry(getScreenshot, { retries: MAX_RETRY_COUNT, minTimeout: SCREENSHOT_RETRY_DELAY })
33
- .then(([devicePixelRatio, image]) => {
34
- const base64 = image ? image.value : '';
35
- const dataUrl = `data:image/png;base64,${this.base64AddPadding(base64.replace(/[\r\n]/g, ''))}`;
36
- return {
37
- image: dataUrl,
38
- devicePixelRatio,
39
- };
40
- });
38
+ const [devicePixelRatio, image] = await pRetry(getScreenshot, { retries: MAX_RETRY_COUNT, minTimeout: SCREENSHOT_RETRY_DELAY });
39
+ const base64 = image ? image.value : '';
40
+ const dataUrl = `data:image/png;base64,${this.base64AddPadding(base64.replace(/[\r\n]/g, ''))}`;
41
+ return {
42
+ image: dataUrl,
43
+ devicePixelRatio,
44
+ };
41
45
  }
42
46
 
43
47
  getDevicePixelRatio() {
@@ -49,7 +53,7 @@ class ScreenshotUtils {
49
53
  }
50
54
  }
51
55
 
52
- return this.driver.executeJS(devicePixelRatioJS).then(result => Promise.resolve(result.value));
56
+ return this.driver.executeJS(devicePixelRatioJS).then(result => result.value);
53
57
  }
54
58
 
55
59
  forcePixelRatio(forceRatio = 1) {
@@ -63,4 +67,3 @@ class ScreenshotUtils {
63
67
  }
64
68
 
65
69
  module.exports = ScreenshotUtils;
66
-
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  'use strict';
2
4
 
3
5
  const Promise = require('bluebird');
@@ -7,6 +9,10 @@ const { PageNotAvailableError } = require('../../errors');
7
9
  const logger = require('../../commons/logger').getLogger('window-utils');
8
10
 
9
11
  class WindowUtils {
12
+ /**
13
+ * @param {number} id tab or worker id
14
+ * @param {import('../webdriver')} driver
15
+ * */
10
16
  constructor(id, driver) {
11
17
  this.id = id;
12
18
  this.driver = driver;
@@ -131,12 +137,12 @@ class WindowUtils {
131
137
  });
132
138
  }
133
139
 
134
- setViewportSize(size) {
135
- return this.driver.setViewportSize(size.width, size.height)
136
- .then(() => this.checkSize(size));
140
+ async setViewportSize(size) {
141
+ await this.driver.setViewportSize(size.width, size.height);
142
+ return await this.checkSize(size);
137
143
  }
138
144
 
139
- validatePageIsAvailable() {
145
+ async validatePageIsAvailable() {
140
146
  /* eslint-disable */
141
147
  function pageIsAvailable() {
142
148
  var locationObj;
@@ -153,7 +159,8 @@ class WindowUtils {
153
159
  }
154
160
  /* eslint-enable */
155
161
 
156
- return this.driver.executeJS(pageIsAvailable).then(result => (result.value ? Promise.resolve() : Promise.reject(new PageNotAvailableError())));
162
+ const result = await this.driver.executeJS(pageIsAvailable);
163
+ return await (result.value ? Promise.resolve() : Promise.reject(new PageNotAvailableError()));
157
164
  }
158
165
 
159
166
  focusTab() {
@@ -164,9 +171,14 @@ class WindowUtils {
164
171
  return undefined;
165
172
  }
166
173
 
167
- getOsAndBrowser() {
168
- return pRetry(() => this.driver.getBrowserAndOS(), { retries: 3 })
169
- .then(osAndBrowser => ({ uaBrowserName: osAndBrowser.browser, uaOs: osAndBrowser.os, userAgent: osAndBrowser.userAgent, browserVersion: osAndBrowser.browserVersion }));
174
+ async getOsAndBrowser() {
175
+ const osAndBrowser = await pRetry(() => this.driver.getBrowserAndOS(), { retries: 3 });
176
+ return {
177
+ uaBrowserName: osAndBrowser.browser,
178
+ uaOs: osAndBrowser.os,
179
+ userAgent: osAndBrowser.userAgent,
180
+ browserVersion: osAndBrowser.browserVersion,
181
+ };
170
182
  }
171
183
 
172
184
  getUserAgentInfo() {
package/processHandler.js CHANGED
@@ -7,6 +7,10 @@ const logger = require('./commons/logger').getLogger('process-handler');
7
7
 
8
8
  const exitHooks = [];
9
9
 
10
+ /**
11
+ * @param {(e: any) => void} onExit
12
+ * @param {NodeJS.Process=} _process
13
+ */
10
14
  module.exports = function (onExit, _process = process) {
11
15
  async function cleanup(err) {
12
16
  // give cleanup and socket reports a chance to run
package/runOptions.d.ts CHANGED
@@ -70,6 +70,11 @@ interface LightweightSettings {
70
70
  disableProjectDefaults: boolean;
71
71
  }
72
72
 
73
+ type InitUserWithAuth = Awaited<ReturnType<typeof import('./commons/testimServicesApi')['initializeUserWithAuth']>>;
74
+ interface ProjectData extends Pick<InitUserWithAuth['projectById'], 'type' | 'name' | 'defaults'> {
75
+ projectId: string;
76
+ }
77
+
73
78
  // TODO: consider typing better, based on the validations we do (example: must have one of grid/grid-id/host&port/test plan)
74
79
  interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | 'tunnel'>> {
75
80
  token: string;
@@ -103,6 +108,26 @@ interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | '
103
108
  retentionDays?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
104
109
  retries?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
105
110
 
111
+ // #region Data set based on values from server
112
+ company: {
113
+ companyId: string;
114
+ onprem: boolean;
115
+ storageBaseUrl: string;
116
+ storageType: string;
117
+ name: string;
118
+ planType: 'pro' | 'free' | 'trial';
119
+ isPOC: boolean;
120
+ isStartUp: boolean;
121
+ activePlan: import('services/src/commons/mongo/db-dto-definitions/DbCompanyProjects').Plan;
122
+ };
123
+ gridData?: Awaited<ReturnType<typeof import('./services/gridService')['getGridData']>>;
124
+ sfdcCredential?: string;
125
+ editorUrl?: string;
126
+ allGrids?: InitUserWithAuth['allGrids'];
127
+ authData?: InitUserWithAuth['authData'];
128
+ projectData?: ProjectData;
129
+ // #endregion
130
+
106
131
  // #region What to execute
107
132
  testId: string[];
108
133
  name: string[];
@@ -161,7 +186,7 @@ interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | '
161
186
 
162
187
  // #region Extension debugging
163
188
  ext?: string;
164
- extensionLocation?: string[];
189
+ extensionLocation: string[];
165
190
  extensionPath?: string;
166
191
  // #endregion
167
192
 
@@ -243,6 +268,7 @@ interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | '
243
268
  lightweightMode?: LightweightSettings;
244
269
  createPrefechedData?: boolean;
245
270
  saveRCALocally?: boolean | string;
271
+ localRCASaver?: string;
246
272
  // #endregion
247
273
 
248
274
  // #region intersections
package/runOptions.js CHANGED
@@ -804,7 +804,7 @@ module.exports = {
804
804
 
805
805
  program.port = program.port && Number(program.port);
806
806
 
807
- if (program.retries <= 0 || _.isNaN(program.retries)) {
807
+ if (program.retries <= 0 || Number.isNaN(program.retries)) {
808
808
  throw new ArgError('test failure retry count could not be a negative number or string, --retries <max_num_of_retries>');
809
809
  }
810
810
 
@@ -823,27 +823,27 @@ module.exports = {
823
823
  }
824
824
  }
825
825
 
826
- if (program.browserTimeout <= 0 || _.isNaN(program.browserTimeout)) {
826
+ if (program.browserTimeout <= 0 || Number.isNaN(program.browserTimeout)) {
827
827
  throw new ArgError('get browser timeout could not be a negative number, --browser-timeout <get-browser-timeout>');
828
828
  }
829
829
 
830
- if (program.newBrowserWaitTimeout <= 0 || _.isNaN(program.newBrowserWaitTimeout)) {
830
+ if (program.newBrowserWaitTimeout <= 0 || Number.isNaN(program.newBrowserWaitTimeout)) {
831
831
  throw new ArgError('max new browser wait timeout could not be a negative number, --new-browser-wait-timeout <max-wait-to-browser>');
832
832
  }
833
833
 
834
- if (program.timeout <= 0 || _.isNaN(program.timeout)) {
834
+ if (program.timeout <= 0 || Number.isNaN(program.timeout)) {
835
835
  throw new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>');
836
836
  }
837
837
 
838
- if (program.beforeParallel <= 0 || _.isNaN(program.beforeParallel)) {
838
+ if (program.beforeParallel <= 0 || Number.isNaN(program.beforeParallel)) {
839
839
  throw new ArgError('before-parallel could not be a negative number or not number, --before-parallel <number-of-tests>');
840
840
  }
841
841
 
842
- if (program.parallel <= 0 || _.isNaN(program.parallel)) {
842
+ if (program.parallel <= 0 || Number.isNaN(program.parallel)) {
843
843
  throw new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>');
844
844
  }
845
845
 
846
- if (program.afterParallel <= 0 || _.isNaN(program.afterParallel)) {
846
+ if (program.afterParallel <= 0 || Number.isNaN(program.afterParallel)) {
847
847
  throw new ArgError('after-parallel could not be a negative number or not number, --after-parallel <number-of-tests>');
848
848
  }
849
849
 
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