@testim/testim-cli 3.253.0 → 3.255.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 (103) hide show
  1. package/OverrideTestDataBuilder.js +1 -1
  2. package/agent/routers/cliJsCode/index.js +4 -4
  3. package/agent/routers/cliJsCode/router.js +46 -42
  4. package/agent/routers/cliJsCode/service.js +18 -13
  5. package/agent/routers/codim/router.js +14 -17
  6. package/agent/routers/codim/router.test.js +19 -21
  7. package/agent/routers/codim/service.js +16 -16
  8. package/agent/routers/general/index.js +4 -8
  9. package/agent/routers/hybrid/registerRoutes.js +18 -18
  10. package/agent/routers/index.js +7 -7
  11. package/agent/routers/playground/router.js +11 -10
  12. package/agent/routers/playground/service.js +22 -23
  13. package/agent/routers/standalone-browser/registerRoutes.js +10 -10
  14. package/cdpTestRunner.js +4 -3
  15. package/chromiumInstaller.js +4 -5
  16. package/cli/onExit.js +2 -2
  17. package/cli.js +11 -10
  18. package/cliAgentMode.js +8 -8
  19. package/codim/codim-cli.js +20 -17
  20. package/codim/hybrid-utils.js +1 -1
  21. package/codim/measure-perf.js +9 -6
  22. package/codim/template.js/tests/examples/01-simple-text-validation.test.js +6 -6
  23. package/codim/template.js/tests/examples/02-using-locators.test.js +13 -15
  24. package/codim/template.js/tests/examples/03-using-hooks.test.js +17 -19
  25. package/codim/template.js/tests/examples/04-skip-and-only.test.js +16 -17
  26. package/codim/template.js/tests/examples/05-multiple-windows.test.js +16 -17
  27. package/codim/template.js/webpack.config.js +1 -1
  28. package/codim/template.ts/webpack.config.js +3 -3
  29. package/commons/AbortError.js +4 -4
  30. package/commons/detectDebugger.js +4 -2
  31. package/commons/featureFlags.js +8 -0
  32. package/commons/httpRequest.js +5 -1
  33. package/commons/httpRequestCounters.js +21 -10
  34. package/commons/lazyRequire.js +14 -12
  35. package/commons/logger.js +4 -4
  36. package/commons/performance-logger.js +14 -8
  37. package/commons/preloadTests.js +2 -2
  38. package/commons/prepareRunner.js +4 -2
  39. package/commons/prepareRunnerAndTestimStartUtils.js +40 -42
  40. package/commons/runnerFileCache.js +1 -1
  41. package/commons/socket/baseSocketServiceSocketIO.js +32 -34
  42. package/commons/socket/realDataService.js +6 -5
  43. package/commons/socket/realDataServiceSocketIO.js +4 -4
  44. package/commons/socket/remoteStepService.js +4 -3
  45. package/commons/socket/remoteStepServiceSocketIO.js +11 -12
  46. package/commons/socket/socketService.js +50 -52
  47. package/commons/socket/testResultServiceSocketIO.js +11 -11
  48. package/commons/testimDesiredCapabilitiesBuilder.js +3 -2
  49. package/commons/testimNgrok.js +2 -2
  50. package/commons/testimNgrok.test.js +1 -1
  51. package/commons/testimServicesApi.js +27 -20
  52. package/commons/testimTunnel.test.js +2 -1
  53. package/commons/xhr2.js +97 -100
  54. package/coverage/SummaryToObjectReport.js +0 -1
  55. package/coverage/jsCoverage.js +12 -10
  56. package/errors.js +5 -0
  57. package/fixLocalBuild.js +2 -0
  58. package/inputFileUtils.js +11 -9
  59. package/npm-shrinkwrap.json +2286 -1284
  60. package/package.json +9 -8
  61. package/player/appiumTestPlayer.js +1 -1
  62. package/player/chromeLauncherTestPlayer.js +0 -1
  63. package/player/services/tabService.js +15 -1
  64. package/player/services/tabServiceMock.js +166 -0
  65. package/player/stepActions/locateStepAction.js +2 -0
  66. package/player/stepActions/navigationStepAction.js +11 -10
  67. package/player/stepActions/sleepStepAction.js +4 -5
  68. package/player/stepActions/textStepAction.js +4 -11
  69. package/player/utils/imageCaptureUtils.js +81 -120
  70. package/player/utils/windowUtils.js +4 -3
  71. package/player/webdriver.js +26 -23
  72. package/processHandler.js +3 -3
  73. package/processHandler.test.js +1 -1
  74. package/reports/consoleReporter.js +3 -2
  75. package/reports/junitReporter.js +7 -9
  76. package/reports/reporter.js +34 -39
  77. package/runOptions.d.ts +260 -0
  78. package/runOptions.js +59 -44
  79. package/runner.js +14 -0
  80. package/runners/ParallelWorkerManager.js +9 -10
  81. package/runners/TestPlanRunner.js +142 -78
  82. package/runners/buildCodeTests.js +38 -37
  83. package/runners/runnerUtils.js +3 -3
  84. package/services/gridService.js +36 -40
  85. package/services/lambdatestService.js +3 -5
  86. package/stepPlayers/cliJsStepPlayback.js +22 -17
  87. package/testRunHandler.js +8 -0
  88. package/testRunStatus.js +9 -6
  89. package/utils/argsUtils.js +86 -0
  90. package/utils/argsUtils.test.js +32 -0
  91. package/utils/fsUtils.js +154 -0
  92. package/{utils.js → utils/index.js} +19 -262
  93. package/utils/promiseUtils.js +89 -0
  94. package/utils/stringUtils.js +98 -0
  95. package/utils/stringUtils.test.js +22 -0
  96. package/utils/timeUtils.js +25 -0
  97. package/utils/utils.test.js +27 -0
  98. package/workers/BaseWorker.js +16 -14
  99. package/workers/WorkerAppium.js +1 -1
  100. package/workers/WorkerExtension.js +6 -7
  101. package/workers/WorkerExtensionSingleBrowser.js +4 -4
  102. package/workers/WorkerSelenium.js +5 -2
  103. package/utils.test.js +0 -68
@@ -1,16 +1,17 @@
1
+ // @ts-check
2
+
1
3
  'use strict';
2
4
 
3
- const moment = require('moment');
4
- const pRetry = require('p-retry');
5
+ const os = require('os');
5
6
  const _ = require('lodash');
6
- const Promise = require('bluebird');
7
- const fs = Promise.promisifyAll(require('fs'));
8
- const { W3C_ELEMENT_ID } = require('./player/constants');
9
- const { sessionType, testStatus: testStatusConst } = require('./commons/constants');
10
7
  const path = require('path');
11
- const httpRequest = require('./commons/httpRequest');
12
- const decompress = require('decompress');
13
- const os = require('os');
8
+ const fsUtils = require('./fsUtils');
9
+ const argsUtils = require('./argsUtils');
10
+ const timeUtils = require('./timeUtils');
11
+ const stringUtils = require('./stringUtils');
12
+ const promiseUtils = require('./promiseUtils');
13
+ const httpRequest = require('../commons/httpRequest');
14
+ const { W3C_ELEMENT_ID } = require('../player/constants');
14
15
 
15
16
  const HOMEDIR = os.homedir();
16
17
  const TESTIM_BROWSER_DIR = path.join(HOMEDIR, '.testim-browser-profile');
@@ -43,6 +44,7 @@ const BROWSERS = [
43
44
  { browserName: 'Firefox', bs: { browser: 'Firefox', browser_version: '89' }, sl: { browserName: 'firefox', version: '89.0' }, browserValue: 'firefox' },
44
45
  { browserName: 'Safari', bs: { browser: 'Safari' }, sl: { browserName: 'safari' }, browserValue: 'safari' },
45
46
  { browserName: 'Edge', bs: { browser: 'Edge', browser_version: '18' }, sl: { browserName: 'MicrosoftEdge', version: '18.17763' }, browserValue: 'edge' },
47
+ // eslint-disable-next-line max-len
46
48
  { browserName: 'Edge Chromium', bs: { browser: 'Edge', browser_version: '94' }, sl: { browserName: 'MicrosoftEdge', version: '94' }, synonyms: ['edge-chromium'], browserValue: 'edge-chromium', seleniumName: 'MicrosoftEdge' },
47
49
  { browserName: 'Internet Explorer 11', bs: { browser: 'IE', browser_version: '11' }, sl: { browserName: 'internet explorer', version: '11.0' }, synonyms: ['ie11'], browserValue: 'ie11' },
48
50
  { browserName: 'Browser', bs: {}, sl: { browserName: 'Browser' }, browserValue: 'browser' },
@@ -53,7 +55,7 @@ const BROWSERS = [
53
55
 
54
56
  function getRunConfigByBrowserName(browser, saucelabs, browserstack) {
55
57
  browser = browser.toLowerCase();
56
- const selectedBrowser = BROWSERS.find(b => b.browserName.toLowerCase() === browser || browser.indexOf(b.synonyms) > -1) || BROWSERS[0];
58
+ const selectedBrowser = BROWSERS.find(b => b.browserName.toLowerCase() === browser || browser.includes(b.synonyms)) || BROWSERS[0];
57
59
 
58
60
  // BS and SL do not support Linux for newer browser, so use Windows instead.
59
61
  let selectedOS = OSS.find(x => x.osName === 'Windows 10');
@@ -75,82 +77,10 @@ function getRunConfigByBrowserName(browser, saucelabs, browserstack) {
75
77
  return _.merge(selectedBrowser, selectedOS);
76
78
  }
77
79
 
78
- function getTestUrl(editorUrl, projectId, testId, resultId, branch) {
79
- let testUrl = '';
80
- branch = branch ? encodeURIComponent(branch) : 'master';
81
- if (projectId && testId) {
82
- testUrl = `${editorUrl}/#/project/${projectId}/branch/${branch}/test/${testId}`;
83
- if (resultId) {
84
- testUrl += `?result-id=${resultId}`;
85
- }
86
- }
87
- return testUrl;
88
- }
89
-
90
- function isPromise(obj) {
91
- return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' && typeof obj.catch === 'function';
92
- }
93
-
94
- function getDuration(ms) {
95
- const duration = moment.duration(ms);
96
- return `${duration.hours()}:${duration.minutes()}:${duration.seconds()}.${duration.milliseconds()}`;
97
- }
98
-
99
- function getDurationSec(ms) {
100
- return moment.duration(ms).asSeconds();
101
- }
102
-
103
- function getRunnerVersion() {
104
- try {
105
- const pack = require(`${__dirname}/package.json`);
106
- return pack.version;
107
- } catch (err) {
108
- return '';
109
- }
110
- }
111
-
112
- function getEnginesVersion() {
113
- try {
114
- const pack = require(`${__dirname}/package.json`);
115
- return pack.engines.node;
116
- } catch (err) {
117
- return '';
118
- }
119
- }
120
-
121
- async function getEnginesVersionAsync() {
122
- try {
123
- const pack = JSON.parse(await fs.readFileAsync(`${__dirname}/package.json`));
124
- return pack.engines.node;
125
- } catch (err) {
126
- return '';
127
- }
128
- }
129
-
130
80
  function getEnvironmentGitBranch() {
131
81
  return process.env.GIT_BRANCH || process.env.CIRCLE_BRANCH || process.env.TRAVIS_BRANCH || process.env.CI_BRANCH;
132
82
  }
133
83
 
134
- function getUniqBrowsers(options, testList) {
135
- if ((options.testConfigNames.length || options.testConfigIds.length || options.testPlan.length || options.testPlanIds.length) && !options.browser) {
136
- return _.uniq(testList.map(t => t.runConfig.browserValue));
137
- }
138
- return [options.browser.toLowerCase()];
139
- }
140
-
141
- function randomString(length) {
142
- const a = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
143
- const index = (Math.random() * (a.length - 1)).toFixed(0);
144
- return length > 0 ? a[index] + randomString(length - 1) : '';
145
- }
146
-
147
- function blueBirdify(fn) {
148
- return new Promise((resolve, reject) =>
149
- fn()
150
- .then(x => resolve(x))
151
- .catch(y => reject(y)));
152
- }
153
-
154
84
  function removePropertyFromObject(obj, propName, cmpFunction) {
155
85
  for (const prop in obj) {
156
86
  if (obj.hasOwnProperty(prop)) {
@@ -163,133 +93,10 @@ function removePropertyFromObject(obj, propName, cmpFunction) {
163
93
  }
164
94
  }
165
95
 
166
- function getCliLocation() {
167
- let cliLocation;
168
- if (!require.main) { // we're in a REPL
169
- return process.cwd(); // fall back on the current working directory
170
- }
171
- if (require.main.filename.includes('/src') || require.main.filename.includes('\\src') || process.env.IS_UNIT_TEST) {
172
- cliLocation = path.resolve(__dirname, '../');
173
- } else {
174
- cliLocation = __dirname;
175
- }
176
-
177
- return cliLocation;
178
- }
179
-
180
- function buildBasicHeader(userName, password) {
181
- const userAndPasswordBase64 = Buffer.from(`${userName}:${password}`).toString('base64');
182
- return `Basic ${userAndPasswordBase64}`;
183
- }
184
-
185
96
  function extractElementId(element) {
186
97
  return element.ELEMENT || element[W3C_ELEMENT_ID];
187
98
  }
188
99
 
189
- function isURL(path) {
190
- const legacyPattern = /^(https?:\/\/)/i;
191
-
192
- // https://gist.github.com/dperini/729294 (validator.js based on).
193
- const pattern = new RegExp(
194
- '^' +
195
- // protocol identifier (optional)
196
- // short syntax // still required
197
- '(?:(?:(?:https?|ftp):)?\\/\\/)' +
198
- // user:pass BasicAuth (optional)
199
- '(?:\\S+(?::\\S*)?@)?' +
200
- '(?:' +
201
- // IP address exclusion
202
- // private & local networks
203
- '(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
204
- '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
205
- '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
206
- // IP address dotted notation octets
207
- // excludes loopback network 0.0.0.0
208
- // excludes reserved space >= 224.0.0.0
209
- // excludes network & broadcast addresses
210
- // (first & last IP address of each class)
211
- '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
212
- '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
213
- '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
214
- '|' +
215
- // host & domain names, may end with dot
216
- // can be replaced by a shortest alternative
217
- // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
218
- '(?:' +
219
- '(?:' +
220
- '[a-z0-9\\u00a1-\\uffff]' +
221
- '[a-z0-9\\u00a1-\\uffff_-]{0,62}' +
222
- ')?' +
223
- '[a-z0-9\\u00a1-\\uffff]\\.' +
224
- ')+' +
225
- // TLD identifier name, may end with dot
226
- '(?:[a-z\\u00a1-\\uffff]{2,}\\.?)' +
227
- ')' +
228
- // port number (optional)
229
- '(?::\\d{2,5})?' +
230
- // resource path (optional)
231
- '(?:[/?#]\\S*)?' +
232
- '$', 'i'
233
- );
234
-
235
- return legacyPattern.test(path) || pattern.test(path);
236
- }
237
-
238
- const DOWNLOAD_RETRY = 3;
239
- const download = async (url) => pRetry(() => httpRequest.download(url), { retries: DOWNLOAD_RETRY });
240
-
241
- const downloadAndSave = async (url, saveToLocation) => {
242
- const res = await download(url);
243
- return fs.writeFileAsync(saveToLocation, res.body);
244
- };
245
-
246
- const copy = async (readFile, destFile) => new Promise((resolve, reject) => {
247
- try {
248
- const file = fs.createWriteStream(destFile);
249
- fs.createReadStream(readFile).pipe(file);
250
- file.on('finish', () => {
251
- file.close(resolve);
252
- });
253
- } catch (err) {
254
- reject(err);
255
- }
256
- });
257
- function getSourcePath(location, fileName) {
258
- if (isURL(location)) {
259
- return fileName || path.join(process.cwd(), location.replace(/^.*[\\\/]/, ''));
260
- }
261
-
262
- return fileName || path.basename(location);
263
- }
264
-
265
- const getSource = async (location, fileName) => {
266
- const destFile = getSourcePath(location, fileName);
267
- if (isURL(location)) {
268
- return downloadAndSave(location, destFile);
269
- }
270
-
271
- return copy(location, destFile);
272
- };
273
-
274
- const getSourceAsBuffer = async (location) => {
275
- if (isURL(location)) {
276
- return download(location);
277
- }
278
- return fs.readFileAsync(location);
279
- };
280
-
281
- const unzipFile = async (srcZipFile, destZipPath) => await decompress(srcZipFile, destZipPath);
282
-
283
- const getLocalFileSizeInMB = (fileLocation) => {
284
- const stats = fs.statSync(fileLocation);
285
- const fileSizeInBytes = stats.size;
286
- return fileSizeInBytes / 1000000;
287
- };
288
-
289
- function getSessionType(options) {
290
- return options.files.length > 0 ? sessionType.CODEFUL : sessionType.CODELESS;
291
- }
292
-
293
100
  function getPlanType(plan) {
294
101
  plan = plan || {};
295
102
  const now = Date.now();
@@ -304,16 +111,6 @@ function getPlanType(plan) {
304
111
  return 'free';
305
112
  }
306
113
 
307
- /**
308
- * @param time {number} in ms
309
- * @returns {Promise}
310
- */
311
- function delay(time) {
312
- return new Promise(((resolve) => {
313
- setTimeout(resolve, time);
314
- }));
315
- }
316
-
317
114
  const calcPercentile = (arr, percentile) => {
318
115
  if (arr.length === 0) return 0;
319
116
  if (typeof percentile !== 'number') throw new TypeError('p must be a number');
@@ -327,17 +124,11 @@ const calcPercentile = (arr, percentile) => {
327
124
  return arr[index];
328
125
  };
329
126
 
330
- const hasTestPlanFlag = (options) => (options.testPlan && options.testPlan.length) || (options.testPlanIds && options.testPlanIds.length);
331
-
332
- const isRemoteRun = (options) => options.resultId && options.source === 'remote-run';
333
-
334
- const isQuarantineAndNotRemoteRun = (test, options) => test.testStatus === testStatusConst.QUARANTINE && !isRemoteRun(options) && !options.runQuarantinedTests;
335
-
336
127
  function groupTestsByRetries(testResults = []) { // NOTE: This duplicates a function in services (stream-data/result/resultService.js) since we can't share code between packages.
337
128
  return _.chain(testResults)
338
129
  .groupBy((tr) => tr.originalTestResultId || tr.resultId)
339
130
  .values()
340
- .reduce((all, current) => {
131
+ .reduce((/** @type {any[]} */ all, current) => {
341
132
  if (!current) {
342
133
  return all;
343
134
  }
@@ -356,7 +147,7 @@ function groupTestsByRetries(testResults = []) { // NOTE: This duplicates a func
356
147
  all.push(last);
357
148
  return all;
358
149
  }, [])
359
- .filter(Boolean)
150
+ .compact()
360
151
  .value();
361
152
  }
362
153
 
@@ -380,54 +171,20 @@ async function getCdpAddressForHost(browserInstanceHost, timeout) {
380
171
  }
381
172
  }
382
173
 
383
- function getArgsOnRemoteRunFailure() {
384
- const { argv: args } = process;
385
- if (!args.includes('--remoteRunId')) {
386
- return undefined;
387
- }
388
- return {
389
- remoteRunId: args[args.indexOf('--remoteRunId') + 1],
390
- projectId: args[args.indexOf('--project') + 1],
391
- token: args[args.indexOf('--token') + 1],
392
- };
393
- }
394
-
395
174
 
396
175
  module.exports = {
397
176
  TESTIM_BROWSER_DIR,
398
177
  removePropertyFromObject,
399
- getTestUrl,
400
- getDuration,
401
- getDurationSec,
402
- getRunnerVersion,
403
- getEnginesVersion,
404
- getEnginesVersionAsync,
405
- isPromise,
406
178
  getEnvironmentGitBranch,
407
- getUniqBrowsers,
408
- guid: (n = 16) => randomString(n),
409
179
  getRunConfigByBrowserName,
410
- blueBirdify,
411
- buildBasicHeader,
412
180
  extractElementId,
413
- getCliLocation,
414
- isURL,
415
- download,
416
- downloadAndSave,
417
- copy,
418
- unzipFile,
419
- getLocalFileSizeInMB,
420
- getSource,
421
- getSourceAsBuffer,
422
- getSessionType,
423
- getSourcePath,
424
181
  calcPercentile,
425
- hasTestPlanFlag,
426
- isRemoteRun,
427
- isQuarantineAndNotRemoteRun,
428
182
  groupTestsByRetries,
429
183
  getPlanType,
430
- delay,
431
184
  getCdpAddressForHost,
432
- getArgsOnRemoteRunFailure,
185
+ ...fsUtils,
186
+ ...argsUtils,
187
+ ...timeUtils,
188
+ ...promiseUtils,
189
+ ...stringUtils,
433
190
  };
@@ -0,0 +1,89 @@
1
+ // @ts-check
2
+
3
+ 'use strict';
4
+
5
+ const Bluebird = require('bluebird');
6
+ const { TimeoutError } = require('../errors');
7
+
8
+ function isPromise(obj) {
9
+ return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' && typeof obj.catch === 'function';
10
+ }
11
+
12
+ /**
13
+ * @deprecated This is horrifying. Please stop what you are doing and think about what you just considered!
14
+ * @template T
15
+ * @param {() => Promise<T>} fn
16
+ * @returns {Bluebird<T>}
17
+ */
18
+ function blueBirdify(fn) {
19
+ return new Bluebird((resolve, reject) =>
20
+ fn()
21
+ .then(x => resolve(x))
22
+ .catch(y => reject(y)));
23
+ }
24
+
25
+ /**
26
+ * @param time {number} in ms
27
+ * @returns {Promise}
28
+ */
29
+ function delay(time) {
30
+ return new Promise(((resolve) => {
31
+ setTimeout(resolve, time);
32
+ }));
33
+ }
34
+
35
+ /**
36
+ * @template T
37
+ * @param {Promise<T> | undefined} promise
38
+ * @param {number} timeout
39
+ * @param {string=} errMsg
40
+ */
41
+ function promiseTimeout(promise, timeout, errMsg = 'Timeout Error') {
42
+ return Promise.race([
43
+ promise,
44
+ delay(timeout).then(() => { throw new TimeoutError(errMsg); }),
45
+ ]);
46
+ }
47
+
48
+ /** @type {import('p-limit').default} */
49
+ let pLimit;
50
+
51
+ /**
52
+ * @template T, U
53
+ * @param {ArrayLike<T> | Iterable<T>} arr
54
+ * @param {(item: T, index: number) => PromiseLike<U>} handler
55
+ * @param {{ concurrency?: number }} options
56
+ */
57
+ async function promiseMap(arr, handler, { concurrency } = {}) {
58
+ pLimit = pLimit || (await import('p-limit')).default;
59
+ if (concurrency) {
60
+ const limit = pLimit(concurrency);
61
+ return await Promise.all(Array.from(arr, (item, index) => limit(() => handler(item, index))));
62
+ }
63
+ return await Promise.all(Array.from(arr, handler));
64
+ }
65
+
66
+ /**
67
+ * @template ErrT, T
68
+ * @param {(callback: (err: ErrT, result: T) => void) => void} resolver
69
+ * @returns {Promise<T>}
70
+ */
71
+ function promiseFromCallback(resolver) {
72
+ return new Promise((resolve, reject) => {
73
+ resolver((err, result) => {
74
+ if (err) {
75
+ return reject(err);
76
+ }
77
+ return resolve(result);
78
+ });
79
+ });
80
+ }
81
+
82
+ module.exports = {
83
+ isPromise,
84
+ blueBirdify,
85
+ delay,
86
+ promiseTimeout,
87
+ promiseMap,
88
+ promiseFromCallback,
89
+ };
@@ -0,0 +1,98 @@
1
+ // @ts-check
2
+
3
+ 'use strict';
4
+
5
+ /**
6
+ * @param {number} length
7
+ * @returns {string}
8
+ */
9
+ function randomString(length) {
10
+ const a = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
11
+ const index = (Math.random() * (a.length - 1)).toFixed(0);
12
+ return length > 0 ? a[index] + randomString(length - 1) : '';
13
+ }
14
+
15
+ /**
16
+ * @param {string} editorUrl
17
+ * @param {string} projectId
18
+ * @param {string} testId
19
+ * @param {string=} resultId
20
+ * @param {string=} branch
21
+ */
22
+ function getTestUrl(editorUrl, projectId, testId, resultId, branch) {
23
+ let testUrl = '';
24
+ branch = branch ? encodeURIComponent(branch) : 'master';
25
+ if (projectId && testId) {
26
+ testUrl = `${editorUrl}/#/project/${projectId}/branch/${branch}/test/${testId}`;
27
+ if (resultId) {
28
+ testUrl += `?result-id=${resultId}`;
29
+ }
30
+ }
31
+ return testUrl;
32
+ }
33
+
34
+ /**
35
+ * @param {string} userName
36
+ * @param {string} password
37
+ */
38
+ function buildBasicHeader(userName, password) {
39
+ const userAndPasswordBase64 = Buffer.from(`${userName}:${password}`).toString('base64');
40
+ return `Basic ${userAndPasswordBase64}`;
41
+ }
42
+
43
+ /** @param {string} path */
44
+ function isURL(path) {
45
+ const legacyPattern = /^(https?:\/\/)/i;
46
+
47
+ // https://gist.github.com/dperini/729294 (validator.js based on).
48
+ const pattern = new RegExp(
49
+ '^' +
50
+ // protocol identifier (optional)
51
+ // short syntax // still required
52
+ '(?:(?:(?:https?|ftp):)?\\/\\/)' +
53
+ // user:pass BasicAuth (optional)
54
+ '(?:\\S+(?::\\S*)?@)?' +
55
+ '(?:' +
56
+ // IP address exclusion
57
+ // private & local networks
58
+ '(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
59
+ '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
60
+ '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
61
+ // IP address dotted notation octets
62
+ // excludes loopback network 0.0.0.0
63
+ // excludes reserved space >= 224.0.0.0
64
+ // excludes network & broadcast addresses
65
+ // (first & last IP address of each class)
66
+ '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
67
+ '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
68
+ '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
69
+ '|' +
70
+ // host & domain names, may end with dot
71
+ // can be replaced by a shortest alternative
72
+ // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
73
+ '(?:' +
74
+ '(?:' +
75
+ '[a-z0-9\\u00a1-\\uffff]' +
76
+ '[a-z0-9\\u00a1-\\uffff_-]{0,62}' +
77
+ ')?' +
78
+ '[a-z0-9\\u00a1-\\uffff]\\.' +
79
+ ')+' +
80
+ // TLD identifier name, may end with dot
81
+ '(?:[a-z\\u00a1-\\uffff]{2,}\\.?)' +
82
+ ')' +
83
+ // port number (optional)
84
+ '(?::\\d{2,5})?' +
85
+ // resource path (optional)
86
+ '(?:[/?#]\\S*)?' +
87
+ '$', 'i'
88
+ );
89
+
90
+ return legacyPattern.test(path) || pattern.test(path);
91
+ }
92
+
93
+ module.exports = {
94
+ isURL,
95
+ getTestUrl,
96
+ buildBasicHeader,
97
+ guid: (n = 16) => randomString(n),
98
+ };
@@ -0,0 +1,22 @@
1
+ const chai = require('chai'); // eslint-disable-line import/no-extraneous-dependencies
2
+ const stringUtils = require('./stringUtils');
3
+
4
+ const expect = chai.expect;
5
+
6
+ describe('stringUtils', () => {
7
+ describe('getTestUrl', () => {
8
+ it('should create properly escaped test URL', () => {
9
+ expect(stringUtils.getTestUrl('http://localhost:8080', 'project', 'test')).to.equal('http://localhost:8080/#/project/project/branch/master/test/test');
10
+ expect(stringUtils.getTestUrl('http://localhost:8080', 'project', 'test', 'result')).to.equal('http://localhost:8080/#/project/project/branch/master/test/test?result-id=result');
11
+ expect(stringUtils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', null)).to.equal('http://localhost:8080/#/project/project/branch/master/test/test?result-id=result');
12
+ expect(stringUtils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'normal-branch-name'))
13
+ .to.equal('http://localhost:8080/#/project/project/branch/normal-branch-name/test/test?result-id=result');
14
+ expect(stringUtils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'branch/with/slashes'))
15
+ .to.equal('http://localhost:8080/#/project/project/branch/branch%2Fwith%2Fslashes/test/test?result-id=result');
16
+ expect(stringUtils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'branch with spaces'))
17
+ .to.equal('http://localhost:8080/#/project/project/branch/branch%20with%20spaces/test/test?result-id=result');
18
+ expect(stringUtils.getTestUrl('http://localhost:8080', 'project', 'test', 'result', 'encoded%20branch'))
19
+ .to.equal('http://localhost:8080/#/project/project/branch/encoded%2520branch/test/test?result-id=result');
20
+ });
21
+ });
22
+ });
@@ -0,0 +1,25 @@
1
+ //@ts-check
2
+
3
+ 'use strict';
4
+
5
+ const moment = require('moment');
6
+
7
+ /**
8
+ * @param {moment.DurationInputArg1} ms
9
+ */
10
+ function getDuration(ms) {
11
+ const duration = moment.duration(ms);
12
+ return `${duration.hours()}:${duration.minutes()}:${duration.seconds()}.${duration.milliseconds()}`;
13
+ }
14
+
15
+ /**
16
+ * @param {moment.DurationInputArg1} ms
17
+ */
18
+ function getDurationSec(ms) {
19
+ return moment.duration(ms).asSeconds();
20
+ }
21
+
22
+ module.exports = {
23
+ getDuration,
24
+ getDurationSec,
25
+ };
@@ -0,0 +1,27 @@
1
+ const chai = require('chai'); // eslint-disable-line import/no-extraneous-dependencies
2
+ const utils = require('./index');
3
+
4
+ const expect = chai.expect;
5
+
6
+ describe('utils', () => {
7
+ describe('calcPercentile', () => {
8
+ it('should calc some precentiles', () => {
9
+ // Arrange:
10
+ const arr = [4, 5, 1, 2, 7, 8, 3, 6, 9, 10];
11
+
12
+ // Act:
13
+ const p0 = utils.calcPercentile(arr, 0);
14
+ const p50 = utils.calcPercentile(arr, 50);
15
+ const p90 = utils.calcPercentile(arr, 90);
16
+ const p95 = utils.calcPercentile(arr, 95);
17
+ const p100 = utils.calcPercentile(arr, 100);
18
+
19
+ // Assert:
20
+ expect(p0).to.eql(1);
21
+ expect(p50).to.eql(5);
22
+ expect(p90).to.eql(9);
23
+ expect(p95).to.eql(10);
24
+ expect(p100).to.eql(10);
25
+ });
26
+ });
27
+ });