@testim/testim-cli 3.252.0 → 3.254.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 +15 -14
- package/agent/routers/codim/service.js +1 -1
- package/agent/routers/general/index.js +4 -8
- package/agent/routers/hybrid/registerRoutes.js +18 -18
- package/agent/routers/index.js +9 -8
- package/agent/routers/playground/router.js +12 -11
- package/agent/routers/playground/service.js +19 -18
- 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 +7 -6
- package/cliAgentMode.js +4 -5
- package/codim/codim-cli.js +11 -10
- 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/chrome-launcher.js +6 -6
- package/commons/constants.js +2 -0
- package/commons/detectDebugger.js +4 -2
- package/commons/getSessionPlayerRequire.js +2 -20
- package/commons/initializeUserWithAuth.js +2 -2
- package/commons/lazyRequire.js +10 -9
- package/commons/logger.js +4 -4
- package/commons/performance-logger.js +14 -8
- package/commons/prepareRunnerAndTestimStartUtils.js +6 -7
- 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 +44 -0
- package/commons/testimNgrok.js +2 -2
- package/commons/testimNgrok.test.js +1 -1
- package/commons/testimServicesApi.js +37 -21
- package/commons/xhr2.js +97 -100
- package/credentialsManager.js +17 -20
- package/errors.js +5 -0
- package/fixLocalBuild.js +2 -0
- package/npm-shrinkwrap.json +4455 -1576
- package/package.json +9 -7
- package/player/WebdriverioWebDriverApi.js +7 -2
- package/player/appiumTestPlayer.js +102 -0
- package/player/chromeLauncherTestPlayer.js +0 -1
- package/player/seleniumTestPlayer.js +3 -2
- package/player/services/frameLocator.js +2 -1
- package/player/services/mobileFrameLocatorMock.js +32 -0
- package/player/services/playbackTimeoutCalculator.js +1 -0
- package/player/services/portSelector.js +10 -8
- package/player/services/tabService.js +29 -0
- package/player/services/tabServiceMock.js +166 -0
- package/player/stepActions/navigationStepAction.js +11 -10
- package/player/stepActions/sleepStepAction.js +4 -5
- package/player/stepActions/stepAction.js +15 -1
- package/player/stepActions/textStepAction.js +4 -11
- package/player/utils/stepActionUtils.js +4 -2
- package/player/utils/windowUtils.js +139 -125
- package/player/webdriver.js +40 -26
- package/processHandler.js +3 -3
- package/processHandler.test.js +1 -1
- package/reports/consoleReporter.js +3 -2
- package/reports/debugReporter.js +41 -39
- package/reports/jsonReporter.js +53 -50
- package/reports/junitReporter.js +1 -2
- package/reports/reporter.js +135 -136
- package/runOptions.js +8 -7
- package/runner.js +13 -0
- package/runners/ParallelWorkerManager.js +2 -0
- package/runners/TestPlanRunner.js +142 -74
- package/runners/buildCodeTests.js +38 -37
- package/runners/runnerUtils.js +3 -3
- package/services/lambdatestService.js +3 -5
- package/stepPlayers/cliJsStepPlayback.js +22 -17
- package/testRunHandler.js +8 -0
- package/testRunStatus.js +458 -460
- package/{utils.js → utils/index.js} +25 -117
- package/utils/promiseUtils.js +78 -0
- package/utils/stringUtils.js +96 -0
- package/{utils.test.js → utils/utils.test.js} +2 -2
- package/workers/BaseWorker.js +29 -20
- package/workers/WorkerAppium.js +123 -0
- package/workers/WorkerExtensionSingleBrowser.js +4 -4
- package/workers/WorkerSelenium.js +5 -2
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
const path = require('path');
|
|
3
6
|
const moment = require('moment');
|
|
4
7
|
const pRetry = require('p-retry');
|
|
5
|
-
const _ = require('lodash');
|
|
6
8
|
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
|
-
const path = require('path');
|
|
11
|
-
const httpRequest = require('./commons/httpRequest');
|
|
12
9
|
const decompress = require('decompress');
|
|
13
|
-
const
|
|
10
|
+
const fs = Promise.promisifyAll(require('fs'));
|
|
11
|
+
const stringUtils = require('./stringUtils');
|
|
12
|
+
const promiseUtils = require('./promiseUtils');
|
|
13
|
+
const httpRequest = require('../commons/httpRequest');
|
|
14
|
+
const { W3C_ELEMENT_ID } = require('../player/constants');
|
|
15
|
+
const { sessionType, testStatus: testStatusConst } = require('../commons/constants');
|
|
14
16
|
|
|
15
17
|
const HOMEDIR = os.homedir();
|
|
16
18
|
const TESTIM_BROWSER_DIR = path.join(HOMEDIR, '.testim-browser-profile');
|
|
@@ -43,6 +45,7 @@ const BROWSERS = [
|
|
|
43
45
|
{ browserName: 'Firefox', bs: { browser: 'Firefox', browser_version: '89' }, sl: { browserName: 'firefox', version: '89.0' }, browserValue: 'firefox' },
|
|
44
46
|
{ browserName: 'Safari', bs: { browser: 'Safari' }, sl: { browserName: 'safari' }, browserValue: 'safari' },
|
|
45
47
|
{ browserName: 'Edge', bs: { browser: 'Edge', browser_version: '18' }, sl: { browserName: 'MicrosoftEdge', version: '18.17763' }, browserValue: 'edge' },
|
|
48
|
+
// eslint-disable-next-line max-len
|
|
46
49
|
{ browserName: 'Edge Chromium', bs: { browser: 'Edge', browser_version: '94' }, sl: { browserName: 'MicrosoftEdge', version: '94' }, synonyms: ['edge-chromium'], browserValue: 'edge-chromium', seleniumName: 'MicrosoftEdge' },
|
|
47
50
|
{ browserName: 'Internet Explorer 11', bs: { browser: 'IE', browser_version: '11' }, sl: { browserName: 'internet explorer', version: '11.0' }, synonyms: ['ie11'], browserValue: 'ie11' },
|
|
48
51
|
{ browserName: 'Browser', bs: {}, sl: { browserName: 'Browser' }, browserValue: 'browser' },
|
|
@@ -53,7 +56,7 @@ const BROWSERS = [
|
|
|
53
56
|
|
|
54
57
|
function getRunConfigByBrowserName(browser, saucelabs, browserstack) {
|
|
55
58
|
browser = browser.toLowerCase();
|
|
56
|
-
const selectedBrowser = BROWSERS.find(b => b.browserName.toLowerCase() === browser || browser.
|
|
59
|
+
const selectedBrowser = BROWSERS.find(b => b.browserName.toLowerCase() === browser || browser.includes(b.synonyms)) || BROWSERS[0];
|
|
57
60
|
|
|
58
61
|
// BS and SL do not support Linux for newer browser, so use Windows instead.
|
|
59
62
|
let selectedOS = OSS.find(x => x.osName === 'Windows 10');
|
|
@@ -75,22 +78,6 @@ function getRunConfigByBrowserName(browser, saucelabs, browserstack) {
|
|
|
75
78
|
return _.merge(selectedBrowser, selectedOS);
|
|
76
79
|
}
|
|
77
80
|
|
|
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
81
|
function getDuration(ms) {
|
|
95
82
|
const duration = moment.duration(ms);
|
|
96
83
|
return `${duration.hours()}:${duration.minutes()}:${duration.seconds()}.${duration.milliseconds()}`;
|
|
@@ -102,7 +89,8 @@ function getDurationSec(ms) {
|
|
|
102
89
|
|
|
103
90
|
function getRunnerVersion() {
|
|
104
91
|
try {
|
|
105
|
-
|
|
92
|
+
/** @type {import('../../package.json')} */
|
|
93
|
+
const pack = require(`${__dirname}/../package.json`); // eslint-disable-line import/no-dynamic-require
|
|
106
94
|
return pack.version;
|
|
107
95
|
} catch (err) {
|
|
108
96
|
return '';
|
|
@@ -111,7 +99,8 @@ function getRunnerVersion() {
|
|
|
111
99
|
|
|
112
100
|
function getEnginesVersion() {
|
|
113
101
|
try {
|
|
114
|
-
|
|
102
|
+
/** @type {import('../../package.json')} */
|
|
103
|
+
const pack = require(`${__dirname}/../package.json`); // eslint-disable-line import/no-dynamic-require
|
|
115
104
|
return pack.engines.node;
|
|
116
105
|
} catch (err) {
|
|
117
106
|
return '';
|
|
@@ -120,7 +109,8 @@ function getEnginesVersion() {
|
|
|
120
109
|
|
|
121
110
|
async function getEnginesVersionAsync() {
|
|
122
111
|
try {
|
|
123
|
-
|
|
112
|
+
/** @type {import('../../package.json')} */
|
|
113
|
+
const pack = JSON.parse(await fs.readFileAsync(`${__dirname}/../package.json`));
|
|
124
114
|
return pack.engines.node;
|
|
125
115
|
} catch (err) {
|
|
126
116
|
return '';
|
|
@@ -138,19 +128,6 @@ function getUniqBrowsers(options, testList) {
|
|
|
138
128
|
return [options.browser.toLowerCase()];
|
|
139
129
|
}
|
|
140
130
|
|
|
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
131
|
function removePropertyFromObject(obj, propName, cmpFunction) {
|
|
155
132
|
for (const prop in obj) {
|
|
156
133
|
if (obj.hasOwnProperty(prop)) {
|
|
@@ -169,72 +146,18 @@ function getCliLocation() {
|
|
|
169
146
|
return process.cwd(); // fall back on the current working directory
|
|
170
147
|
}
|
|
171
148
|
if (require.main.filename.includes('/src') || require.main.filename.includes('\\src') || process.env.IS_UNIT_TEST) {
|
|
172
|
-
cliLocation = path.resolve(__dirname, '
|
|
149
|
+
cliLocation = path.resolve(__dirname, '../../');
|
|
173
150
|
} else {
|
|
174
|
-
cliLocation = __dirname;
|
|
151
|
+
cliLocation = path.resolve(__dirname, '../');
|
|
175
152
|
}
|
|
176
153
|
|
|
177
154
|
return cliLocation;
|
|
178
155
|
}
|
|
179
156
|
|
|
180
|
-
function buildBasicHeader(userName, password) {
|
|
181
|
-
const userAndPasswordBase64 = Buffer.from(`${userName}:${password}`).toString('base64');
|
|
182
|
-
return `Basic ${userAndPasswordBase64}`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
157
|
function extractElementId(element) {
|
|
186
158
|
return element.ELEMENT || element[W3C_ELEMENT_ID];
|
|
187
159
|
}
|
|
188
160
|
|
|
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
161
|
const DOWNLOAD_RETRY = 3;
|
|
239
162
|
const download = async (url) => pRetry(() => httpRequest.download(url), { retries: DOWNLOAD_RETRY });
|
|
240
163
|
|
|
@@ -255,7 +178,7 @@ const copy = async (readFile, destFile) => new Promise((resolve, reject) => {
|
|
|
255
178
|
}
|
|
256
179
|
});
|
|
257
180
|
function getSourcePath(location, fileName) {
|
|
258
|
-
if (isURL(location)) {
|
|
181
|
+
if (stringUtils.isURL(location)) {
|
|
259
182
|
return fileName || path.join(process.cwd(), location.replace(/^.*[\\\/]/, ''));
|
|
260
183
|
}
|
|
261
184
|
|
|
@@ -264,7 +187,7 @@ function getSourcePath(location, fileName) {
|
|
|
264
187
|
|
|
265
188
|
const getSource = async (location, fileName) => {
|
|
266
189
|
const destFile = getSourcePath(location, fileName);
|
|
267
|
-
if (isURL(location)) {
|
|
190
|
+
if (stringUtils.isURL(location)) {
|
|
268
191
|
return downloadAndSave(location, destFile);
|
|
269
192
|
}
|
|
270
193
|
|
|
@@ -272,7 +195,7 @@ const getSource = async (location, fileName) => {
|
|
|
272
195
|
};
|
|
273
196
|
|
|
274
197
|
const getSourceAsBuffer = async (location) => {
|
|
275
|
-
if (isURL(location)) {
|
|
198
|
+
if (stringUtils.isURL(location)) {
|
|
276
199
|
return download(location);
|
|
277
200
|
}
|
|
278
201
|
return fs.readFileAsync(location);
|
|
@@ -304,16 +227,6 @@ function getPlanType(plan) {
|
|
|
304
227
|
return 'free';
|
|
305
228
|
}
|
|
306
229
|
|
|
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
230
|
const calcPercentile = (arr, percentile) => {
|
|
318
231
|
if (arr.length === 0) return 0;
|
|
319
232
|
if (typeof percentile !== 'number') throw new TypeError('p must be a number');
|
|
@@ -327,7 +240,7 @@ const calcPercentile = (arr, percentile) => {
|
|
|
327
240
|
return arr[index];
|
|
328
241
|
};
|
|
329
242
|
|
|
330
|
-
const hasTestPlanFlag = (options) => (options.testPlan
|
|
243
|
+
const hasTestPlanFlag = (options) => Boolean(options.testPlan?.length || options.testPlanIds?.length);
|
|
331
244
|
|
|
332
245
|
const isRemoteRun = (options) => options.resultId && options.source === 'remote-run';
|
|
333
246
|
|
|
@@ -396,22 +309,16 @@ function getArgsOnRemoteRunFailure() {
|
|
|
396
309
|
module.exports = {
|
|
397
310
|
TESTIM_BROWSER_DIR,
|
|
398
311
|
removePropertyFromObject,
|
|
399
|
-
getTestUrl,
|
|
400
312
|
getDuration,
|
|
401
313
|
getDurationSec,
|
|
402
314
|
getRunnerVersion,
|
|
403
315
|
getEnginesVersion,
|
|
404
316
|
getEnginesVersionAsync,
|
|
405
|
-
isPromise,
|
|
406
317
|
getEnvironmentGitBranch,
|
|
407
318
|
getUniqBrowsers,
|
|
408
|
-
guid: (n = 16) => randomString(n),
|
|
409
319
|
getRunConfigByBrowserName,
|
|
410
|
-
blueBirdify,
|
|
411
|
-
buildBasicHeader,
|
|
412
320
|
extractElementId,
|
|
413
321
|
getCliLocation,
|
|
414
|
-
isURL,
|
|
415
322
|
download,
|
|
416
323
|
downloadAndSave,
|
|
417
324
|
copy,
|
|
@@ -427,7 +334,8 @@ module.exports = {
|
|
|
427
334
|
isQuarantineAndNotRemoteRun,
|
|
428
335
|
groupTestsByRetries,
|
|
429
336
|
getPlanType,
|
|
430
|
-
delay,
|
|
431
337
|
getCdpAddressForHost,
|
|
432
338
|
getArgsOnRemoteRunFailure,
|
|
339
|
+
...promiseUtils,
|
|
340
|
+
...stringUtils,
|
|
433
341
|
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
const Bluebird = require('bluebird');
|
|
4
|
+
const { TimeoutError } = require('../errors');
|
|
5
|
+
|
|
6
|
+
function isPromise(obj) {
|
|
7
|
+
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' && typeof obj.catch === 'function';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated This is horrifying. Please stop what you are doing and think about what you just considered!
|
|
12
|
+
* @template T
|
|
13
|
+
* @param {() => Promise<T>} fn
|
|
14
|
+
* @returns {Bluebird<T>}
|
|
15
|
+
*/
|
|
16
|
+
function blueBirdify(fn) {
|
|
17
|
+
return new Bluebird((resolve, reject) =>
|
|
18
|
+
fn()
|
|
19
|
+
.then(x => resolve(x))
|
|
20
|
+
.catch(y => reject(y)));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param time {number} in ms
|
|
25
|
+
* @returns {Promise}
|
|
26
|
+
*/
|
|
27
|
+
function delay(time) {
|
|
28
|
+
return new Promise(((resolve) => {
|
|
29
|
+
setTimeout(resolve, time);
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @template T
|
|
35
|
+
* @param {Promise<T> | undefined} promise
|
|
36
|
+
* @param {number} timeout
|
|
37
|
+
* @param {string=} errMsg
|
|
38
|
+
*/
|
|
39
|
+
function promiseTimeout(promise, timeout, errMsg = 'Timeout Error') {
|
|
40
|
+
return Promise.race([
|
|
41
|
+
promise,
|
|
42
|
+
delay(timeout).then(() => { throw new TimeoutError(errMsg); }),
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @template T, U
|
|
48
|
+
* @param {ArrayLike<T> | Iterable<T>} arr
|
|
49
|
+
* @param {(item: T, index: number) => PromiseLike<U>} handler
|
|
50
|
+
*/
|
|
51
|
+
function promiseMap(arr, handler) {
|
|
52
|
+
return Promise.all(Array.from(arr, handler));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @template ErrT, T
|
|
57
|
+
* @param {(callback: (err: ErrT, result: T) => void) => void} resolver
|
|
58
|
+
* @returns {Promise<T>}
|
|
59
|
+
*/
|
|
60
|
+
function promiseFromCallback(resolver) {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
resolver((err, result) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
return reject(err);
|
|
65
|
+
}
|
|
66
|
+
return resolve(result);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
isPromise,
|
|
73
|
+
blueBirdify,
|
|
74
|
+
delay,
|
|
75
|
+
promiseTimeout,
|
|
76
|
+
promiseMap,
|
|
77
|
+
promiseFromCallback,
|
|
78
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {number} length
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
function randomString(length) {
|
|
8
|
+
const a = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890';
|
|
9
|
+
const index = (Math.random() * (a.length - 1)).toFixed(0);
|
|
10
|
+
return length > 0 ? a[index] + randomString(length - 1) : '';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} editorUrl
|
|
15
|
+
* @param {string} projectId
|
|
16
|
+
* @param {string} testId
|
|
17
|
+
* @param {string | undefined} resultId
|
|
18
|
+
* @param {string} branch
|
|
19
|
+
*/
|
|
20
|
+
function getTestUrl(editorUrl, projectId, testId, resultId, branch) {
|
|
21
|
+
let testUrl = '';
|
|
22
|
+
branch = branch ? encodeURIComponent(branch) : 'master';
|
|
23
|
+
if (projectId && testId) {
|
|
24
|
+
testUrl = `${editorUrl}/#/project/${projectId}/branch/${branch}/test/${testId}`;
|
|
25
|
+
if (resultId) {
|
|
26
|
+
testUrl += `?result-id=${resultId}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return testUrl;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} userName
|
|
34
|
+
* @param {string} password
|
|
35
|
+
*/
|
|
36
|
+
function buildBasicHeader(userName, password) {
|
|
37
|
+
const userAndPasswordBase64 = Buffer.from(`${userName}:${password}`).toString('base64');
|
|
38
|
+
return `Basic ${userAndPasswordBase64}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @param {string} path */
|
|
42
|
+
function isURL(path) {
|
|
43
|
+
const legacyPattern = /^(https?:\/\/)/i;
|
|
44
|
+
|
|
45
|
+
// https://gist.github.com/dperini/729294 (validator.js based on).
|
|
46
|
+
const pattern = new RegExp(
|
|
47
|
+
'^' +
|
|
48
|
+
// protocol identifier (optional)
|
|
49
|
+
// short syntax // still required
|
|
50
|
+
'(?:(?:(?:https?|ftp):)?\\/\\/)' +
|
|
51
|
+
// user:pass BasicAuth (optional)
|
|
52
|
+
'(?:\\S+(?::\\S*)?@)?' +
|
|
53
|
+
'(?:' +
|
|
54
|
+
// IP address exclusion
|
|
55
|
+
// private & local networks
|
|
56
|
+
'(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
|
|
57
|
+
'(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
|
|
58
|
+
'(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
|
|
59
|
+
// IP address dotted notation octets
|
|
60
|
+
// excludes loopback network 0.0.0.0
|
|
61
|
+
// excludes reserved space >= 224.0.0.0
|
|
62
|
+
// excludes network & broadcast addresses
|
|
63
|
+
// (first & last IP address of each class)
|
|
64
|
+
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
|
|
65
|
+
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
|
|
66
|
+
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
|
|
67
|
+
'|' +
|
|
68
|
+
// host & domain names, may end with dot
|
|
69
|
+
// can be replaced by a shortest alternative
|
|
70
|
+
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
|
|
71
|
+
'(?:' +
|
|
72
|
+
'(?:' +
|
|
73
|
+
'[a-z0-9\\u00a1-\\uffff]' +
|
|
74
|
+
'[a-z0-9\\u00a1-\\uffff_-]{0,62}' +
|
|
75
|
+
')?' +
|
|
76
|
+
'[a-z0-9\\u00a1-\\uffff]\\.' +
|
|
77
|
+
')+' +
|
|
78
|
+
// TLD identifier name, may end with dot
|
|
79
|
+
'(?:[a-z\\u00a1-\\uffff]{2,}\\.?)' +
|
|
80
|
+
')' +
|
|
81
|
+
// port number (optional)
|
|
82
|
+
'(?::\\d{2,5})?' +
|
|
83
|
+
// resource path (optional)
|
|
84
|
+
'(?:[/?#]\\S*)?' +
|
|
85
|
+
'$', 'i'
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return legacyPattern.test(path) || pattern.test(path);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
isURL,
|
|
93
|
+
getTestUrl,
|
|
94
|
+
buildBasicHeader,
|
|
95
|
+
guid: (n = 16) => randomString(n),
|
|
96
|
+
};
|
package/workers/BaseWorker.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const Bluebird = require('bluebird');
|
|
4
3
|
const moment = require('moment');
|
|
5
4
|
const pRetry = require('p-retry');
|
|
6
5
|
const ms = require('ms');
|
|
7
6
|
|
|
8
|
-
const { timeoutMessages, testRunStatus, stepResult, runnerTestStatus } = require('../commons/constants');
|
|
7
|
+
const { timeoutMessages, testRunStatus, stepResult, runnerTestStatus, CLI_MODE } = require('../commons/constants');
|
|
9
8
|
const logger = require('../commons/logger').getLogger('base-worker');
|
|
10
9
|
const testResultService = require('../commons/socket/testResultService');
|
|
11
10
|
const remoteStepService = require('../commons/socket/remoteStepService');
|
|
@@ -110,8 +109,15 @@ class BaseWorker {
|
|
|
110
109
|
return undefined;
|
|
111
110
|
}
|
|
112
111
|
|
|
112
|
+
setSessionTimeout() {
|
|
113
|
+
if (this.options.mode === CLI_MODE.APPIUM) {
|
|
114
|
+
return Math.max(ms('180s'), this.options.getSessionTimeout);
|
|
115
|
+
}
|
|
116
|
+
return Math.max(this.lambdatestService.getSessionTimeout, this.options.getSessionTimeout);
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
async getTestPlayer(testRunHandler, customExtensionLocalLocation) {
|
|
114
|
-
const projectId = this.userData
|
|
120
|
+
const projectId = this.userData?.projectId;
|
|
115
121
|
let testPlayer;
|
|
116
122
|
|
|
117
123
|
try {
|
|
@@ -121,8 +127,7 @@ class BaseWorker {
|
|
|
121
127
|
let gridInfo = await pRetry(async () => {
|
|
122
128
|
const startTime = Date.now();
|
|
123
129
|
try {
|
|
124
|
-
return await
|
|
125
|
-
.timeout(this.options.getBrowserTimeout, timeoutMessages.GET_BROWSER_TIMEOUT_MSG);
|
|
130
|
+
return await utils.promiseTimeout(this.getSlotOnce(testRunHandler), this.options.getBrowserTimeout, timeoutMessages.GET_BROWSER_TIMEOUT_MSG);
|
|
126
131
|
} catch (error) {
|
|
127
132
|
logger.error('error getting grid slot', { error, testId: this.testId, testResultId: this.testResultId, executionId: this.executionId });
|
|
128
133
|
failedGetSlotAttempts++;
|
|
@@ -148,12 +153,14 @@ class BaseWorker {
|
|
|
148
153
|
this.options.gridData.provider = gridInfo.provider;
|
|
149
154
|
this.options.gridData.host = gridInfo.host;
|
|
150
155
|
this.options.gridData.failedGetBrowserAttempts = failedGetBrowserAttempts;
|
|
151
|
-
const getSessionTimeout =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
const getSessionTimeout = this.setSessionTimeout();
|
|
157
|
+
perf.log('before getBrowserOnce');
|
|
158
|
+
const getBrowserRes = await utils.promiseTimeout(
|
|
159
|
+
this.getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo),
|
|
160
|
+
getSessionTimeout,
|
|
161
|
+
timeoutMessages.GET_BROWSER_TIMEOUT_MSG,
|
|
162
|
+
);
|
|
163
|
+
perf.log('after getBrowserOnce');
|
|
157
164
|
reporter.onGetBrowserSuccess(this.id, projectId);
|
|
158
165
|
return player || getBrowserRes;
|
|
159
166
|
} catch (error) {
|
|
@@ -186,7 +193,7 @@ class BaseWorker {
|
|
|
186
193
|
|
|
187
194
|
async runTest(testRunHandler, customExtensionLocalLocation, shouldRerun) {
|
|
188
195
|
perf.log('inside runTest');
|
|
189
|
-
const projectId = this.userData
|
|
196
|
+
const projectId = this.userData?.projectId;
|
|
190
197
|
const quarantineResult = this.handleQuarantine(testRunHandler);
|
|
191
198
|
if (quarantineResult) {
|
|
192
199
|
return quarantineResult;
|
|
@@ -236,7 +243,7 @@ class BaseWorker {
|
|
|
236
243
|
const testRetryKey = testRunHandler.getRetryKey();
|
|
237
244
|
testResult.testRetryKey = testRetryKey;
|
|
238
245
|
await this.onTestCompleted(this.id, this.testId, testResult, sessionId, shouldRerun);
|
|
239
|
-
if (this.executionQueue.hasMoreTests() && !
|
|
246
|
+
if (this.executionQueue.hasMoreTests() && !this.options.lightweightMode?.general) {
|
|
240
247
|
await utils.delay(DELAY_BETWEEN_TESTS);
|
|
241
248
|
}
|
|
242
249
|
await this.runTestCleanup();
|
|
@@ -257,11 +264,11 @@ class BaseWorker {
|
|
|
257
264
|
return await runTestAndCalcResult(testRunHandler, shouldRerun);
|
|
258
265
|
}
|
|
259
266
|
return await runNextTest();
|
|
260
|
-
} catch (
|
|
261
|
-
if (
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof StopRunOnError) {
|
|
262
269
|
return undefined;
|
|
263
270
|
}
|
|
264
|
-
logger.error('failed to process test result', {
|
|
271
|
+
logger.error('failed to process test result', { error });
|
|
265
272
|
runNextTest();
|
|
266
273
|
return undefined;
|
|
267
274
|
}
|
|
@@ -335,7 +342,7 @@ class BaseWorker {
|
|
|
335
342
|
}
|
|
336
343
|
logger.warn('error on run', { err });
|
|
337
344
|
|
|
338
|
-
const projectId = this.userData
|
|
345
|
+
const projectId = this.userData?.projectId;
|
|
339
346
|
const { errorType, reason } = buildError(err, wasNetworkHealthy);
|
|
340
347
|
testimServicesApi.updateTestResult(projectId, this.testResultId, this.testId, {
|
|
341
348
|
status: testRunStatus.COMPLETED,
|
|
@@ -351,7 +358,7 @@ class BaseWorker {
|
|
|
351
358
|
const recoverTestResults = async (runError, testRunHandler) => {
|
|
352
359
|
const testId = this.testId;
|
|
353
360
|
const resultId = this.testResultId;
|
|
354
|
-
const projectId = this.userData
|
|
361
|
+
const projectId = this.userData?.projectId;
|
|
355
362
|
const branch = this.branch;
|
|
356
363
|
if (!testId || !resultId || !projectId || !branch) {
|
|
357
364
|
// Not enough data to call the API
|
|
@@ -380,8 +387,8 @@ class BaseWorker {
|
|
|
380
387
|
}
|
|
381
388
|
};
|
|
382
389
|
|
|
383
|
-
const disableResults = this.options.disableSockets || (this.options.lightweightMode
|
|
384
|
-
const disableRemoteStep = this.options.disableSockets || (this.options.lightweightMode
|
|
390
|
+
const disableResults = this.options.disableSockets || (this.options.lightweightMode?.disableResults && (this.options.useChromeLauncher || this.options.mode !== 'extension'));
|
|
391
|
+
const disableRemoteStep = this.options.disableSockets || (this.options.lightweightMode?.disableRemoteStep);
|
|
385
392
|
|
|
386
393
|
const runTestAndCalcResult = (testRunHandler, shouldRerun) => Promise.all([
|
|
387
394
|
!disableRemoteStep && remoteStepService.joinToRemoteStep(this.testResultId),
|
|
@@ -402,6 +409,7 @@ class BaseWorker {
|
|
|
402
409
|
});
|
|
403
410
|
|
|
404
411
|
const testRunHandler = this.executionQueue.getNext();
|
|
412
|
+
|
|
405
413
|
if (!testRunHandler) { // no more tests to run
|
|
406
414
|
return this.onQueueCompleted();
|
|
407
415
|
}
|
|
@@ -411,6 +419,7 @@ class BaseWorker {
|
|
|
411
419
|
this.overrideTestConfigId = testRunHandler.getOverrideTestConfigId();
|
|
412
420
|
this.testRunConfig = testRunHandler.getRunConfig();
|
|
413
421
|
this.branch = testRunHandler.getBranch();
|
|
422
|
+
|
|
414
423
|
return runTestAndCalcResult(testRunHandler);
|
|
415
424
|
}
|
|
416
425
|
}
|