@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
@@ -40,10 +40,6 @@ function runCode(transactionId, incomingParams, context, code, packageLocalLocat
40
40
  return all;
41
41
  }, '');
42
42
 
43
- if (fileDataUrl === 'data:') { // fix chrome/safari bug that creates malformed datauri for empty files
44
- fileDataUrl = 'data:,';
45
- }
46
-
47
43
  const fileDataUrlToFileBuffer = !fileDataUrl ? 'var fileBuffer = null;' :
48
44
  `
49
45
  ${dataUriToBuffer.toString()}
@@ -297,10 +293,6 @@ function runCodeWithWorkerThread(transactionId, incomingParams, context, code, p
297
293
  return all;
298
294
  }, '');
299
295
 
300
- if (fileDataUrl === 'data:') { // fix chrome/safari bug that creates malformed datauri for empty files
301
- fileDataUrl = 'data:,';
302
- }
303
-
304
296
  const fileDataUrlToFileBuffer = !fileDataUrl ? 'var fileBuffer = null;' :
305
297
  `
306
298
  ${dataUriToBuffer.toString()}
@@ -581,6 +573,17 @@ function runCodeWithPackages(code, stepId, incomingParams, context, testResultId
581
573
  }
582
574
  }
583
575
 
576
+ const emptyFileDataUrl = 'data:,';
577
+ if (fileDataUrl === 'data:') { // Fix chrome/safari bug that creates malformed data-uri for empty files
578
+ logger.debug('runCodeWithPackages, fileDataUrl was an empty string ', { fileDataUrl });
579
+ fileDataUrl = emptyFileDataUrl;
580
+ }
581
+
582
+ if (Buffer.isBuffer(fileDataUrl)) { // Fix, S3 is returning a buffer and not a string on empty files
583
+ logger.debug('runCodeWithPackages, fileDataUrl was an empty Buffer ', { fileDataUrl });
584
+ fileDataUrl = emptyFileDataUrl;
585
+ }
586
+
584
587
  if (workerThreads && featureFlags.flags.enableWorkerThreadsCliCodeExecution.isEnabled()) {
585
588
  return runCodeWithWorkerThread(transactionId, incomingParams, context, code, packageLocalLocations, timeout, fileDataUrl);
586
589
  }
@@ -1,20 +1,16 @@
1
1
  'use strict';
2
2
 
3
3
  const router = require('./router');
4
-
5
4
  const path = require('path');
6
5
  const os = require('os');
7
6
  const compression = require('compression');
8
7
  const express = require('express');
9
-
10
- const app = express();
11
8
  const bodyParser = require('body-parser');
12
- const Promise = require('bluebird');
13
- const { promiseFromCallback } = require('../../../utils');
14
9
  const superagent = require('superagent');
10
+ const fs = require('fs');
15
11
  const { expect } = require('chai'); // eslint-disable-line import/no-extraneous-dependencies
16
12
 
17
- const fs = Promise.promisifyAll(require('fs'));
13
+ const app = express();
18
14
 
19
15
  app.use(bodyParser.urlencoded({ extended: false, limit: '50mb' }));
20
16
  app.use(compression());
@@ -25,25 +21,26 @@ app.use('/files', router.router);
25
21
  describe('codim router', () => {
26
22
  let listener;
27
23
  async function saveLocators(locatorsObject) {
28
- const request = superagent.post(`http://localhost:${listener.address().port}/files/locators`)
29
- .send(locatorsObject);
30
- await promiseFromCallback((callback) => request.end(callback));
24
+ const request = superagent.post(`http://localhost:${listener.address().port}/files/locators`).send(locatorsObject);
25
+ await request;
31
26
  }
32
27
 
33
28
  async function loadLocators() {
34
29
  const request = superagent.get(`http://localhost:${listener.address().port}/files/locators`);
35
- return await promiseFromCallback((callback) => request.end(callback)).then(x => x.text).then(JSON.parse);
30
+ const response = await request;
31
+ return JSON.parse(response.text);
36
32
  }
37
33
 
38
34
  let tmpDir;
35
+
39
36
  before((done) => {
40
37
  tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tdk-test-'));
41
38
  process.chdir(tmpDir);
42
- listener = app.listen(0, done);
39
+ listener = app.listen(0, () => done());
43
40
  });
44
41
 
45
42
  after(async () => {
46
- await fs.unlinkAsync(tmpDir).catch(() => {});
43
+ await fs.promises.unlink(tmpDir).catch(() => {});
47
44
  });
48
45
 
49
46
  it('saves locators', async () => {
@@ -2,17 +2,16 @@
2
2
 
3
3
  const _ = require('lodash');
4
4
  const path = require('path');
5
- const Promise = require('bluebird');
6
- const fs = Promise.promisifyAll(require('fs'));
5
+ const fsPromises = require('fs/promises');
7
6
  const { fromPairs } = require('lodash');
8
7
  const { buildCodeTests } = require('../../../runners/buildCodeTests');
9
8
  const { AbortError } = require('../../../commons/AbortError');
10
9
 
11
10
  const findTestFolder = _.memoize(async (fromFolder) => {
12
- const files = await fs.readdirAsync(fromFolder);
11
+ const files = await fsPromises.readdir(fromFolder);
13
12
  // this is either invoked by running the Testim CLI from inside the tests folder or from inside the `init` folder
14
13
  // so deal with the case we're inside tests.
15
- const isInProjectFolder = files.includes('tests') && (await fs.statAsync(path.join(fromFolder, 'tests'))).isDirectory();
14
+ const isInProjectFolder = files.includes('tests') && (await fsPromises.stat(path.join(fromFolder, 'tests'))).isDirectory();
16
15
  if (isInProjectFolder) {
17
16
  return path.join(fromFolder, 'tests');
18
17
  }
@@ -27,7 +26,7 @@ async function getLocalLocators() {
27
26
  // eslint-disable-next-line no-eval
28
27
  return eval(buffer.toString().replace(/require/g, '(x => /locator\.(.*)\.json/.exec(x)[1])'));
29
28
  }
30
- const locators = await fs.readFileAsync(locatorsFilePath).then(parseLocators, () => ({}));
29
+ const locators = await fsPromises.readFile(locatorsFilePath).then(parseLocators, () => ({}));
31
30
  return _(Object.keys(locators)).map((id) => {
32
31
  const escapedId = id.replace(/"/g, '\\"');
33
32
  return [escapedId, locators[id]];
@@ -36,7 +35,7 @@ async function getLocalLocators() {
36
35
 
37
36
  async function findTests(folder = process.cwd()) {
38
37
  const testFolder = await findTestFolder(folder);
39
- const filesWithStat = await fs.promises.readdir(testFolder, { withFileTypes: true });
38
+ const filesWithStat = await fsPromises.readdir(testFolder, { withFileTypes: true });
40
39
 
41
40
  // things we know are not tests but end in js
42
41
  const excluded = ['webpack.config.js', 'tsconfig.js', '.DS_Store', 'functions.js'];
@@ -70,13 +69,14 @@ async function getLocalLocatorContents(locators, full = false, originFolder = pr
70
69
  if (full) {
71
70
  const folder = await findTestFolder(originFolder);
72
71
  for (const key of Object.values(locators)) {
73
- props[key] = fs.promises.readFile(path.join(folder, 'locators', `locator.${key}.json`)).then(JSON.parse);
72
+ props[key] = fsPromises.readFile(path.join(folder, 'locators', `locator.${key}.json`)).then(JSON.parse);
74
73
  }
75
74
  }
76
75
  try {
77
76
  const contents = await promiseFromProps(props);
78
77
  return contents;
79
78
  } catch (e) {
79
+ // eslint-disable-next-line no-console
80
80
  console.error(e);
81
81
  return {};
82
82
  }
@@ -104,10 +104,10 @@ async function saveTest({
104
104
  if (filename.endsWith('locators/locators.js')) {
105
105
  throw new Error('Cannot override locators file from the internet as it is evaluated by the runner');
106
106
  }
107
- await fs.writeFileAsync(filename, body);
108
- await fs.mkdirAsync(path.join(folder, 'locators')).catch(() => {});
107
+ await fsPromises.writeFile(filename, body);
108
+ await fsPromises.mkdir(path.join(folder, 'locators')).catch(() => {});
109
109
  for (const { id, body } of locators) {
110
- await fs.writeFileAsync(path.join(folder, 'locators', `locator.${id}.json`), JSON.stringify(body));
110
+ await fsPromises.writeFile(path.join(folder, 'locators', `locator.${id}.json`), JSON.stringify(body));
111
111
  }
112
112
  const locatorMap = fromPairs(locators.map(({ name, id }) => [name, id]));
113
113
  const localLocatorMap = await getLocalLocators();
@@ -120,15 +120,15 @@ async function writeLocators(locatorsFilePath, locatorMap) {
120
120
  content += ` "${key}": require('./locator.${value}.json'),\n`;
121
121
  }
122
122
  content += '};';
123
- await fs.writeFileAsync(locatorsFilePath, content);
123
+ await fsPromises.writeFile(locatorsFilePath, content);
124
124
  }
125
125
  async function saveLocators(locators, { mergeIntoExisting } = { mergeIntoExisting: false }) {
126
126
  const folder = await findTestFolder(process.cwd());
127
127
  const locatorsFilePath = path.join(folder, 'locators', 'locators.js');
128
- await fs.mkdirAsync(path.join(folder, 'locators')).catch(() => {});
128
+ await fsPromises.mkdir(path.join(folder, 'locators')).catch(() => {});
129
129
 
130
130
  for (const { name, id, elementLocator } of locators) {
131
- await fs.writeFileAsync(path.join(folder, 'locators', `locator.${id}.json`), JSON.stringify({ name, id, elementLocator }));
131
+ await fsPromises.writeFile(path.join(folder, 'locators', `locator.${id}.json`), JSON.stringify({ name, id, elementLocator }));
132
132
  }
133
133
  const locatorMap = fromPairs(locators.map(({ name, id }) => [name, id]));
134
134
  if (mergeIntoExisting) {
@@ -139,14 +139,14 @@ async function saveLocators(locators, { mergeIntoExisting } = { mergeIntoExistin
139
139
  await writeLocators(locatorsFilePath, locatorMap);
140
140
  }
141
141
 
142
- async function compileFunctionsLibrary({ fileSystem, bypassWebpack } = {}, optionalAbortSignal) {
142
+ async function compileFunctionsLibrary({ fileSystem, bypassWebpack } = {}, optionalAbortSignal = undefined) {
143
143
  const folder = await findTestFolder(process.cwd());
144
- if (optionalAbortSignal && optionalAbortSignal.aborted) {
144
+ if (optionalAbortSignal?.aborted) {
145
145
  throw new AbortError();
146
146
  }
147
147
 
148
148
  const functionsFile = path.join(folder, 'functions.js');
149
- if (bypassWebpack && bypassWebpack.testim) {
149
+ if (bypassWebpack?.testim) {
150
150
  const Module = require('module');
151
151
  // attempt to require without webpack compile - useful for puppeteer/selenium hybrid
152
152
  const originalRequire = Module.prototype.require;
@@ -1,11 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const Promise = require('bluebird');
4
- const util = require('util');
5
- const writeFileAsync = util.promisify(require('fs').writeFile);
3
+ const fs = require('fs');
6
4
  const path = require('path');
7
- const { fork } = require('child_process');
8
5
  const os = require('os');
6
+ const { fork } = require('child_process');
9
7
  const { ClientError, PlaygroundCodeError } = require('../../../errors');
10
8
 
11
9
  const CODE_TYPES = ['playwright', 'selenium', 'puppeteer'];
@@ -14,13 +12,13 @@ const runForks = {};
14
12
 
15
13
  async function createTempFile(fileName, data, encoding = 'utf8') {
16
14
  const fullPath = path.join(os.tmpdir(), fileName);
17
- await writeFileAsync(fullPath, data, encoding);
15
+ await fs.promises.writeFile(fullPath, data, encoding);
18
16
  return fullPath;
19
17
  }
20
18
 
21
19
  const forkAsync = (fileFullPath) => {
22
20
  let gotResolved;
23
- const promise = new Promise(resolve => gotResolved = resolve);
21
+ const promise = new Promise(resolve => { gotResolved = resolve; });
24
22
 
25
23
  const child = fork(fileFullPath, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'] });
26
24
  promise.child = child;
@@ -30,7 +28,7 @@ const forkAsync = (fileFullPath) => {
30
28
  }
31
29
  const { type, error } = message;
32
30
  if (error && ['uncaughtException', 'unhandledRejection'].includes(type)) {
33
- return gotResolved({ error: Object.assign(new PlaygroundCodeError(), { innerStack: message.error.stack }) });
31
+ gotResolved({ error: Object.assign(new PlaygroundCodeError(), { innerStack: message.error.stack }) });
34
32
  }
35
33
  });
36
34
  child.on('error', (error) => {
package/cli.js CHANGED
@@ -68,14 +68,14 @@ async function main() {
68
68
  require('./commons/logger').setProjectId(processedOptions.project);
69
69
  require('./commons/runnerFileCache').setEncryptKey(typeof processedOptions.token === 'string' ? processedOptions.token : 'anonymous_encrypt_key');
70
70
 
71
- if (processedOptions.initCodimMode) {
71
+ if (utils.isInitCodimMode(processedOptions)) {
72
72
  const codimCli = require('./codim/codim-cli');
73
73
  return codimCli.init(processedOptions.initTestProject);
74
74
  }
75
- if (processedOptions.loginMode) {
75
+ if (utils.isLoginMode(processedOptions)) {
76
76
  return undefined;
77
77
  }
78
- if (processedOptions.createPrefechedData) {
78
+ if (utils.isCreatePrefetchedDataMode(processedOptions)) {
79
79
  const runnerFileCache = require('./commons/runnerFileCache');
80
80
  await runnerFileCache.clear();
81
81
  await prepareRunner.initializeUserWithAuth(processedOptions);
@@ -85,14 +85,14 @@ async function main() {
85
85
  }
86
86
  const res = await runnerFileCache.waitForSave();
87
87
  if (res.success) {
88
- console.log(`created prefeched data at ${runnerFileCache.getCacheFileLocation()}`);
88
+ console.log(`created prefetched data at ${runnerFileCache.getCacheFileLocation()}`);
89
89
  } else {
90
- console.error('failed to create prefech data', res.error);
90
+ console.error('failed to create prefetch data', res.error);
91
91
  }
92
92
  return undefined;
93
93
  }
94
94
 
95
- if (processedOptions.tunnelOnlyMode) {
95
+ if (utils.isTunnelOnlyMode(processedOptions)) {
96
96
  await testRunner.init(processedOptions);
97
97
  await require('./commons/testimTunnel').serveTunneling(processedOptions);
98
98
  return undefined;
package/cliAgentMode.js CHANGED
@@ -32,16 +32,14 @@ module.exports = {
32
32
  };
33
33
 
34
34
  /**
35
- * @param {{ agentMode: boolean; }} options
35
+ * @type {(options: import('./runOptions').Options) => options is import('./runOptions').AgentModeOptions}
36
36
  */
37
37
  function shouldStartAgentMode(options) {
38
+ // @ts-ignore should be `as any`
38
39
  return options.agentMode;
39
40
  }
40
41
 
41
- /**
42
- *
43
- * @param {*} options
44
- */
42
+ /** @param {import('./runOptions').AgentModeOptions} options */
45
43
  async function runAgentMode(options) {
46
44
  let testimStandaloneBrowser;
47
45
 
@@ -53,7 +51,7 @@ async function runAgentMode(options) {
53
51
  // Consider moving that into the agent server and add endpoint to start browser?
54
52
  testimStandaloneBrowser = await startTestimStandaloneBrowser(options);
55
53
  } catch (e) {
56
- if (e && e.message && e.message.includes('user data directory is already in use')) {
54
+ if (e?.message?.includes('user data directory is already in use')) {
57
55
  throw new ArgError('Please close all chrome browsers that were opened with "testim start" and try again');
58
56
  }
59
57
  throw e;
@@ -62,7 +60,7 @@ async function runAgentMode(options) {
62
60
 
63
61
  const agentServer = require('./agent/server');
64
62
 
65
- if (testimStandaloneBrowser && testimStandaloneBrowser.webdriverApi) {
63
+ if (testimStandaloneBrowser?.webdriverApi) {
66
64
  // if we're starting the agent here, pre-load the sessionPlayer so it loads faster
67
65
  // on first play
68
66
  const LOAD_PLAYER_DELAY = 6000;
@@ -168,6 +166,7 @@ async function startFixedVersionChromium(options, extensionBase64, downloadedExt
168
166
  };
169
167
  }
170
168
 
169
+ /** @param {import('./runOptions').AgentModeOptions} options */
171
170
  async function startTestimStandaloneBrowser(options) {
172
171
  // After next clickim release we will have also testim-full.zip
173
172
  // const fullExtensionUrl = "https://testimstatic.blob.core.windows.net/extension/testim-full-master.zip";
@@ -221,9 +220,11 @@ async function startTestimStandaloneBrowser(options) {
221
220
  }
222
221
  await prepareUtils.prepareChromeDriver(
223
222
  { projectId: options.project },
223
+ // @ts-expect-error not clear where chromeBinaryLocations comes from
224
224
  { chromeBinaryLocation: options.chromeBinaryLocations },
225
225
  );
226
226
 
227
+ // @ts-expect-error not clear where chromeBinaryLocations comes from
227
228
  const seleniumOptions = buildSeleniumOptions(USER_DATA_DIR, extensionBase64, options.extensionPath, options.chromeBinaryLocations);
228
229
 
229
230
  const WebDriver = require('./player/webdriver');
@@ -263,7 +264,7 @@ async function startTestimStandaloneBrowser(options) {
263
264
 
264
265
  /**
265
266
  * @param {string} userDataDir
266
- * @param {string} fullExtensionPath
267
+ * @param {string | null} fullExtensionPath
267
268
  */
268
269
  function buildSeleniumOptions(userDataDir, fullExtensionPath, unpackedExtensionPath, chromeBinaryPath) {
269
270
  const extensions = fullExtensionPath ? [fullExtensionPath] : [];
@@ -355,7 +356,7 @@ async function readAndValidateChromedriverDevToolsActivePortFile() {
355
356
 
356
357
  /**
357
358
  * @param {string | import("url").URL} webSocketDebuggerUrl
358
- * @param {number?} timeout
359
+ * @param {number=} timeout
359
360
  */
360
361
  async function tryToCloseBrowserWithCDPUrl(webSocketDebuggerUrl, timeout = 100) {
361
362
  const websocketConnection = await wsConnectAndOpen(webSocketDebuggerUrl, timeout);
@@ -370,7 +371,7 @@ async function tryToCloseBrowserWithCDPUrl(webSocketDebuggerUrl, timeout = 100)
370
371
 
371
372
  /**
372
373
  * @param {string | import("url").URL} webSocketDebuggerUrl
373
- * @param {number?} timeout
374
+ * @param {number=} timeout
374
375
  */
375
376
  async function wsConnectAndOpen(webSocketDebuggerUrl, timeout = 100) {
376
377
  const websocket = new WebSocket(webSocketDebuggerUrl, { timeout });
@@ -1,14 +1,19 @@
1
+ // @ts-check
2
+
1
3
  /* eslint-disable no-console */
2
4
 
3
5
  'use strict';
4
6
 
5
- const { promisifyAll } = require('bluebird');
6
- const fse = require('fs-extra');
7
- const exec = promisifyAll(require('child_process')).execAsync;
7
+ const fs = require('fs');
8
+ const childProcess = require('child_process');
8
9
  const path = require('path');
9
10
  const validateNpmPackageName = require('validate-npm-package-name');
10
- const ArgError = require('../errors.js').ArgError;
11
+ const { promisify } = require('util');
12
+ const { ArgError } = require('../errors.js');
13
+
14
+ const exec = promisify(childProcess.exec);
11
15
 
16
+ /** @param {string} name */
12
17
  module.exports.init = async function init(name) {
13
18
  const ora = require('ora');
14
19
  const prompts = require('prompts');
@@ -25,7 +30,7 @@ module.exports.init = async function init(name) {
25
30
 
26
31
  const fullPath = path.resolve(name);
27
32
 
28
- if (fse.existsSync(fullPath) && fse.readdirSync(fullPath).length !== 0) {
33
+ if (fs.existsSync(fullPath) && fs.readdirSync(fullPath).length !== 0) {
29
34
  console.log(`${fullPath} is not empty. Quitting...`);
30
35
  process.exit(1);
31
36
  }
@@ -57,20 +62,20 @@ module.exports.init = async function init(name) {
57
62
 
58
63
  let spinner = ora(`Creating new test project in ${dest}`).start();
59
64
 
60
- await fse.copy(source, dest);
65
+ await fs.promises.copyFile(source, dest);
61
66
 
62
67
  const sourcePackageJson = path.join(__dirname, sourceFolder, 'package.json');
63
68
  const destPackageJson = path.join(process.cwd(), name, 'package.json');
64
69
 
65
- const packageContents = await fse.readFile(sourcePackageJson);
70
+ const packageContents = await fs.promises.readFile(sourcePackageJson);
66
71
 
67
72
  const newPackageJson = packageContents.toString().replace('~testim-codeful-test-project~', packageName);
68
73
 
69
- await fse.writeFile(destPackageJson, newPackageJson);
74
+ await fs.promises.writeFile(destPackageJson, newPackageJson);
70
75
 
71
76
  const gitIgnore = 'node_modules';
72
77
  const gitIgnoreFilePath = path.join(process.cwd(), name, '.gitignore');
73
- await fse.writeFile(gitIgnoreFilePath, gitIgnore);
78
+ await fs.promises.writeFile(gitIgnoreFilePath, gitIgnore);
74
79
 
75
80
  spinner.succeed();
76
81
  spinner = ora('Installing dependencies').start();
@@ -1,9 +1,11 @@
1
1
  'use strict';
2
2
 
3
- const { ROLLOUT_KEY, IS_ON_PREM, GATEWAY_URL } = require('./config');
4
- const logger = require('./logger').getLogger('FeatureFlagsService');
5
- const Promise = require('bluebird');
6
3
  const Rox = require('rox-node');
4
+ const { getLogger } = require('./logger');
5
+ const { promiseTimeout } = require('../utils');
6
+ const { ROLLOUT_KEY, IS_ON_PREM, GATEWAY_URL } = require('./config');
7
+
8
+ const logger = getLogger('FeatureFlagsService');
7
9
 
8
10
  // IS_UNIT_TEST = disable rollout if code run in unit test mode to ignore mocha process stuck on running
9
11
  const USE_FEATURE_FLAGS = !IS_ON_PREM && !process.env.IS_UNIT_TEST && !GATEWAY_URL;
@@ -11,6 +13,7 @@ const USE_FEATURE_FLAGS = !IS_ON_PREM && !process.env.IS_UNIT_TEST && !GATEWAY_U
11
13
  const FORCE_FETCH_TIMEOUT_MS = 20000; // rollout sometimes takes up to 15 seconds to load
12
14
  const SEC_IN_DAY = 60 * 60 * 24;
13
15
 
16
+ /** @type {['labs', 'disabled', 'enabled'] as const} */
14
17
  const LAB_FEATURE_FLAG_VALUES = ['labs', 'disabled', 'enabled'];
15
18
 
16
19
  class LabFeatureFlag extends Rox.Variant {
@@ -18,6 +21,7 @@ class LabFeatureFlag extends Rox.Variant {
18
21
  super(initialValue, LAB_FEATURE_FLAG_VALUES);
19
22
  }
20
23
 
24
+ /** @returns {(typeof LAB_FEATURE_FLAG_VALUES)[number]} */
21
25
  getValue() {
22
26
  const value = super.getValue();
23
27
  if (!LAB_FEATURE_FLAG_VALUES.includes(value)) {
@@ -63,33 +67,51 @@ class FeatureFlagsService {
63
67
  Rox.register('default', this.flags);
64
68
  }
65
69
 
70
+ /** @param {string} projectId */
66
71
  setProjectId(projectId) {
67
72
  Rox.setCustomStringProperty('projectId', projectId);
68
73
  }
69
74
 
75
+ /** @param {string} projectType */
76
+ setProjectType(projectType) {
77
+ Rox.setCustomStringProperty('projectType', projectType);
78
+ }
79
+
80
+ /** @param {string} productType */
81
+ setCompanyProductType(productType) {
82
+ Rox.setCustomStringProperty('productType', productType);
83
+ }
84
+
85
+ /** @param {string} companyId */
70
86
  setCompanyId(companyId) {
71
87
  Rox.setCustomStringProperty('companyId', companyId);
72
88
  }
73
89
 
90
+ /** @param {string} planType */
74
91
  setPlanType(planType) {
75
92
  Rox.setCustomStringProperty('planType', planType);
76
93
  }
77
94
 
95
+ /** @param {boolean} isPOC */
78
96
  setIsPOC(isPOC) {
79
97
  Rox.setCustomBooleanProperty('isPOC', isPOC);
80
98
  }
99
+
100
+ /** @param {boolean} isStartUp */
81
101
  setIsStartUp(isStartUp) {
82
102
  Rox.setCustomBooleanProperty('isStartUp', isStartUp);
83
103
  }
84
104
 
105
+ /** @param {string} mode */
85
106
  setRunnerMode(mode) {
86
107
  Rox.setCustomStringProperty('runnerMode', mode);
87
108
  }
88
109
 
89
- fetch() {
110
+ async fetch() {
90
111
  if (!USE_FEATURE_FLAGS) {
91
- return Promise.resolve();
112
+ return undefined;
92
113
  }
114
+ /** @type {Rox.RoxSetupOptions} */
93
115
  const opts = {
94
116
  fetchIntervalInSec: SEC_IN_DAY, // we don't actually want to refresh feature flags in the CLI,
95
117
  disableNetworkFetch: false,
@@ -102,8 +124,8 @@ class FeatureFlagsService {
102
124
  opts.httpAgent = agent;
103
125
  }
104
126
 
105
- return Promise.resolve(Rox.setup(ROLLOUT_KEY, opts))
106
- .timeout(FORCE_FETCH_TIMEOUT_MS).catch(err => logger.error('failed to get feature flag status', err));
127
+ return promiseTimeout(Rox.setup(ROLLOUT_KEY, opts), FORCE_FETCH_TIMEOUT_MS)
128
+ .catch(err => logger.error('failed to get feature flag status', err));
107
129
  }
108
130
  }
109
131
 
@@ -79,7 +79,7 @@ function post({
79
79
  .catch(logErrorAndRethrow('failed to post request', { url }));
80
80
  }
81
81
 
82
- function postFullRes(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT, retry) {
82
+ function postFullRes(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT, retry = 0) {
83
83
  const request = superagent
84
84
  .post(url)
85
85
  .send(body)
@@ -215,6 +215,10 @@ function put(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT) {
215
215
  .catch(logErrorAndRethrow('failed to put request', { url }));
216
216
  }
217
217
 
218
+ /**
219
+ * @param {string} url
220
+ * @returns {Promise<Omit<superagent.Response, 'body'> & { body: Buffer }>}
221
+ */
218
222
  function download(url) {
219
223
  logger.info('start to download', { url });
220
224
 
@@ -1,10 +1,11 @@
1
- 'use strit';
1
+ 'use strict';
2
2
 
3
- const { sum } = require('lodash');
4
- const Bluebird = require('bluebird');
5
- const dns = require('dns');
6
3
  const _ = require('lodash');
7
4
  const config = require('./config');
5
+ const dns = require('dns').promises;
6
+ const Bluebird = require('bluebird');
7
+ const { sum } = require('lodash');
8
+ const { promiseMap } = require('../utils/promiseUtils');
8
9
 
9
10
  const logger = require('./logger').getLogger('http-request-counters');
10
11
 
@@ -18,7 +19,7 @@ const testNetworkConnectivity = async () => {
18
19
  const hostnames = ['www.google.com', 'www.facebook.com', 'www.microsoft.com', 'testim.io'];
19
20
  try {
20
21
  // If any of these domains resolve we consider the connectivity to be ok
21
- const result = Boolean(await Bluebird.any(hostnames.map(host => dns.promises.lookup(host))));
22
+ const result = Boolean(await promiseMap(hostnames, host => dns.lookup(host)));
22
23
  if (!result) {
23
24
  networkConnectivityTestFailed = true;
24
25
  }
@@ -40,18 +41,28 @@ const ttl = 60 * 1000 * 15;
40
41
 
41
42
  module.exports.makeCounters = () => {
42
43
  const counters = {
43
- call: new Map(),
44
- success: new Map(),
45
- fail: new Map(),
44
+ /** @type {Map<string, number>} */ call: new Map(),
45
+ /** @type {Map<string, number>} */ success: new Map(),
46
+ /** @type {Map<string, number>} */ fail: new Map(),
46
47
  };
48
+ /**
49
+ * @param {counters[keyof counters]} counter
50
+ * @param {string} key
51
+ */
47
52
  function update(counter, key) {
48
53
  const result = counter.get(key) || 0;
49
54
  counter.set(key, result + 1);
50
55
  setTimeout(() => {
51
- const result = counter.get(key) || 1;
52
- counter.set(key, result - 1);
56
+ const _result = counter.get(key) || 1;
57
+ counter.set(key, _result - 1);
53
58
  }, ttl);
54
59
  }
60
+ /**
61
+ * @template T, TArgs
62
+ * @param {(...args: TArgs) => T} fn
63
+ * @param {string=} name
64
+ * @return {(...args: TArgs) => Bluebird<Awaited<T>>}
65
+ */
55
66
  function wrapWithMonitoring(fn, name = fn.name) {
56
67
  return Bluebird.method(async function (...args) {
57
68
  update(counters.call, name);
@@ -1,7 +1,7 @@
1
1
 
2
2
  const perf = require('./performance-logger');
3
3
  const localRunnerCache = require('./runnerFileCache');
4
- const servicesApi = require('./testimServicesApi.js');
4
+ const servicesApi = require('./testimServicesApi');
5
5
  const testimCustomToken = require('./testimCustomToken');
6
6
  const { CLI_MODE } = require('./constants');
7
7
 
@@ -24,12 +24,15 @@ function preloadSlowRequires(mode) {
24
24
  });
25
25
  }
26
26
 
27
+ /**
28
+ * @param {import('../runOptions').RunnerOptions} options
29
+ */
27
30
  async function initializeUserWithAuth(options) {
28
31
  const { project, token, lightweightMode, useLocalChromeDriver, useChromeLauncher, mode } = options;
29
32
 
30
- const lightweightModeGenral = Boolean(lightweightMode && lightweightMode.general);
33
+ const lightweightModeGeneral = Boolean(lightweightMode?.general);
31
34
  const localGrid = Boolean(useLocalChromeDriver || useChromeLauncher);
32
- const memoizationTTL = lightweightModeGenral ? TEN_HOURS_MS : FIVE_MINUTES_MS;
35
+ const memoizationTTL = lightweightModeGeneral ? TEN_HOURS_MS : FIVE_MINUTES_MS;
33
36
 
34
37
  perf.log('before initializeUserWithAuth');
35
38
  const result = await localRunnerCache.memoize(async () => {
@@ -41,7 +44,7 @@ async function initializeUserWithAuth(options) {
41
44
  lightweightMode,
42
45
  localGrid,
43
46
  });
44
- }, 'initializeUserWithAuth', memoizationTTL, { project, token, branchName: options.branch, lightweightModeGenral, localGrid })();
47
+ }, 'initializeUserWithAuth', memoizationTTL, { project, token, branchName: options.branch, lightweightModeGeneral, localGrid })();
45
48
  perf.log('after initializeUserWithAuth');
46
49
 
47
50
  testimCustomToken.initFromData(result.authData, options.project, options.token);
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const Bluebird = require('bluebird');
4
3
  const npmWrapper = require('./npmWrapper');
5
4
  const ora = require('ora');
6
5
  const path = require('path');
@@ -76,10 +75,12 @@ async function lazyRequireImpl(dependency) {
76
75
  return requireWithFallback(dependency);
77
76
  }
78
77
 
79
- function installAllLazyDependencies() {
78
+ async function installAllLazyDependencies() {
80
79
  const allLazyDependencies = Object.keys(packageJson.lazyDependencies);
81
80
 
82
- return Bluebird.each(allLazyDependencies, dep => lazyRequireImpl(dep));
81
+ for (const dep of allLazyDependencies) {
82
+ await lazyRequireImpl(dep);
83
+ }
83
84
  }
84
85
 
85
86
  if (require.main === module) {