@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.
- package/OverrideTestDataBuilder.js +1 -1
- package/agent/routers/cliJsCode/index.js +4 -4
- package/agent/routers/cliJsCode/router.js +46 -42
- package/agent/routers/cliJsCode/service.js +18 -13
- package/agent/routers/codim/router.js +14 -17
- package/agent/routers/codim/router.test.js +19 -21
- package/agent/routers/codim/service.js +16 -16
- package/agent/routers/general/index.js +4 -8
- package/agent/routers/hybrid/registerRoutes.js +18 -18
- package/agent/routers/index.js +7 -7
- package/agent/routers/playground/router.js +11 -10
- package/agent/routers/playground/service.js +22 -23
- package/agent/routers/standalone-browser/registerRoutes.js +10 -10
- package/cdpTestRunner.js +4 -3
- package/chromiumInstaller.js +4 -5
- package/cli/onExit.js +2 -2
- package/cli.js +11 -10
- package/cliAgentMode.js +8 -8
- package/codim/codim-cli.js +20 -17
- package/codim/hybrid-utils.js +1 -1
- package/codim/measure-perf.js +9 -6
- package/codim/template.js/tests/examples/01-simple-text-validation.test.js +6 -6
- package/codim/template.js/tests/examples/02-using-locators.test.js +13 -15
- package/codim/template.js/tests/examples/03-using-hooks.test.js +17 -19
- package/codim/template.js/tests/examples/04-skip-and-only.test.js +16 -17
- package/codim/template.js/tests/examples/05-multiple-windows.test.js +16 -17
- package/codim/template.js/webpack.config.js +1 -1
- package/codim/template.ts/webpack.config.js +3 -3
- package/commons/AbortError.js +4 -4
- package/commons/detectDebugger.js +4 -2
- package/commons/featureFlags.js +8 -0
- package/commons/httpRequest.js +5 -1
- package/commons/httpRequestCounters.js +21 -10
- package/commons/lazyRequire.js +14 -12
- package/commons/logger.js +4 -4
- package/commons/performance-logger.js +14 -8
- package/commons/preloadTests.js +2 -2
- package/commons/prepareRunner.js +4 -2
- package/commons/prepareRunnerAndTestimStartUtils.js +40 -42
- package/commons/runnerFileCache.js +1 -1
- package/commons/socket/baseSocketServiceSocketIO.js +32 -34
- package/commons/socket/realDataService.js +6 -5
- package/commons/socket/realDataServiceSocketIO.js +4 -4
- package/commons/socket/remoteStepService.js +4 -3
- package/commons/socket/remoteStepServiceSocketIO.js +11 -12
- package/commons/socket/socketService.js +50 -52
- package/commons/socket/testResultServiceSocketIO.js +11 -11
- package/commons/testimDesiredCapabilitiesBuilder.js +3 -2
- package/commons/testimNgrok.js +2 -2
- package/commons/testimNgrok.test.js +1 -1
- package/commons/testimServicesApi.js +27 -20
- package/commons/testimTunnel.test.js +2 -1
- package/commons/xhr2.js +97 -100
- package/coverage/SummaryToObjectReport.js +0 -1
- package/coverage/jsCoverage.js +12 -10
- package/errors.js +5 -0
- package/fixLocalBuild.js +2 -0
- package/inputFileUtils.js +11 -9
- package/npm-shrinkwrap.json +2286 -1284
- package/package.json +9 -8
- package/player/appiumTestPlayer.js +1 -1
- package/player/chromeLauncherTestPlayer.js +0 -1
- package/player/services/tabService.js +15 -1
- package/player/services/tabServiceMock.js +166 -0
- package/player/stepActions/locateStepAction.js +2 -0
- package/player/stepActions/navigationStepAction.js +11 -10
- package/player/stepActions/sleepStepAction.js +4 -5
- package/player/stepActions/textStepAction.js +4 -11
- package/player/utils/imageCaptureUtils.js +81 -120
- package/player/utils/windowUtils.js +4 -3
- package/player/webdriver.js +26 -23
- package/processHandler.js +3 -3
- package/processHandler.test.js +1 -1
- package/reports/consoleReporter.js +3 -2
- package/reports/junitReporter.js +7 -9
- package/reports/reporter.js +34 -39
- package/runOptions.d.ts +260 -0
- package/runOptions.js +59 -44
- package/runner.js +14 -0
- package/runners/ParallelWorkerManager.js +9 -10
- package/runners/TestPlanRunner.js +142 -78
- package/runners/buildCodeTests.js +38 -37
- package/runners/runnerUtils.js +3 -3
- package/services/gridService.js +36 -40
- package/services/lambdatestService.js +3 -5
- package/stepPlayers/cliJsStepPlayback.js +22 -17
- package/testRunHandler.js +8 -0
- package/testRunStatus.js +9 -6
- package/utils/argsUtils.js +86 -0
- package/utils/argsUtils.test.js +32 -0
- package/utils/fsUtils.js +154 -0
- package/{utils.js → utils/index.js} +19 -262
- package/utils/promiseUtils.js +89 -0
- package/utils/stringUtils.js +98 -0
- package/utils/stringUtils.test.js +22 -0
- package/utils/timeUtils.js +25 -0
- package/utils/utils.test.js +27 -0
- package/workers/BaseWorker.js +16 -14
- package/workers/WorkerAppium.js +1 -1
- package/workers/WorkerExtension.js +6 -7
- package/workers/WorkerExtensionSingleBrowser.js +4 -4
- package/workers/WorkerSelenium.js +5 -2
- package/utils.test.js +0 -68
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
|
-
const
|
|
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
|
|
12
|
-
const
|
|
13
|
-
const
|
|
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.
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
+
});
|