@testim/testim-cli 3.253.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 +7 -7
- package/agent/routers/playground/router.js +11 -10
- 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/detectDebugger.js +4 -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 +3 -2
- package/commons/testimNgrok.js +2 -2
- package/commons/testimNgrok.test.js +1 -1
- package/commons/testimServicesApi.js +27 -20
- package/commons/xhr2.js +97 -100
- package/errors.js +5 -0
- package/fixLocalBuild.js +2 -0
- package/npm-shrinkwrap.json +2515 -1256
- package/package.json +6 -6
- package/player/appiumTestPlayer.js +1 -1
- package/player/chromeLauncherTestPlayer.js +0 -1
- package/player/services/tabServiceMock.js +166 -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/windowUtils.js +4 -3
- package/player/webdriver.js +1 -1
- package/processHandler.js +3 -3
- package/processHandler.test.js +1 -1
- package/reports/consoleReporter.js +3 -2
- package/reports/junitReporter.js +1 -2
- package/runOptions.js +6 -6
- package/runner.js +13 -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 +1 -1
- 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 +16 -14
- package/workers/WorkerAppium.js +1 -1
- package/workers/WorkerExtensionSingleBrowser.js +4 -4
- package/workers/WorkerSelenium.js +5 -2
package/testRunHandler.js
CHANGED
|
@@ -111,6 +111,10 @@ class TestRun {
|
|
|
111
111
|
return this._branch;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
getSfdcCredential() {
|
|
115
|
+
return this._options.sfdcCredential;
|
|
116
|
+
}
|
|
117
|
+
|
|
114
118
|
getRemoteRunId() {
|
|
115
119
|
return this._remoteRunId;
|
|
116
120
|
}
|
|
@@ -203,6 +207,10 @@ class TestRun {
|
|
|
203
207
|
runRequestParams.localRCASaver = this._options.localRCASaver;
|
|
204
208
|
}
|
|
205
209
|
|
|
210
|
+
if (this.getSfdcCredential()) {
|
|
211
|
+
runRequestParams.sfdcCredential = this.getSfdcCredential();
|
|
212
|
+
}
|
|
213
|
+
|
|
206
214
|
return runRequestParams;
|
|
207
215
|
}
|
|
208
216
|
|
package/testRunStatus.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const constants = require('./commons/constants');
|
|
4
4
|
const { TESTIM_CONCURRENT_WORKER_COUNT } = require('./commons/config');
|
|
5
|
-
const utils = require('./utils
|
|
5
|
+
const utils = require('./utils');
|
|
6
6
|
const reporter = require('./reports/reporter.js');
|
|
7
7
|
const servicesApi = require('./commons/testimServicesApi.js');
|
|
8
8
|
const gridService = require('./services/gridService');
|
|
@@ -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,6 +1,5 @@
|
|
|
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');
|
|
@@ -128,8 +127,7 @@ class BaseWorker {
|
|
|
128
127
|
let gridInfo = await pRetry(async () => {
|
|
129
128
|
const startTime = Date.now();
|
|
130
129
|
try {
|
|
131
|
-
return await
|
|
132
|
-
.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);
|
|
133
131
|
} catch (error) {
|
|
134
132
|
logger.error('error getting grid slot', { error, testId: this.testId, testResultId: this.testResultId, executionId: this.executionId });
|
|
135
133
|
failedGetSlotAttempts++;
|
|
@@ -156,11 +154,13 @@ class BaseWorker {
|
|
|
156
154
|
this.options.gridData.host = gridInfo.host;
|
|
157
155
|
this.options.gridData.failedGetBrowserAttempts = failedGetBrowserAttempts;
|
|
158
156
|
const getSessionTimeout = this.setSessionTimeout();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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');
|
|
164
164
|
reporter.onGetBrowserSuccess(this.id, projectId);
|
|
165
165
|
return player || getBrowserRes;
|
|
166
166
|
} catch (error) {
|
|
@@ -243,7 +243,7 @@ class BaseWorker {
|
|
|
243
243
|
const testRetryKey = testRunHandler.getRetryKey();
|
|
244
244
|
testResult.testRetryKey = testRetryKey;
|
|
245
245
|
await this.onTestCompleted(this.id, this.testId, testResult, sessionId, shouldRerun);
|
|
246
|
-
if (this.executionQueue.hasMoreTests() && !
|
|
246
|
+
if (this.executionQueue.hasMoreTests() && !this.options.lightweightMode?.general) {
|
|
247
247
|
await utils.delay(DELAY_BETWEEN_TESTS);
|
|
248
248
|
}
|
|
249
249
|
await this.runTestCleanup();
|
|
@@ -264,11 +264,11 @@ class BaseWorker {
|
|
|
264
264
|
return await runTestAndCalcResult(testRunHandler, shouldRerun);
|
|
265
265
|
}
|
|
266
266
|
return await runNextTest();
|
|
267
|
-
} catch (
|
|
268
|
-
if (
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof StopRunOnError) {
|
|
269
269
|
return undefined;
|
|
270
270
|
}
|
|
271
|
-
logger.error('failed to process test result', {
|
|
271
|
+
logger.error('failed to process test result', { error });
|
|
272
272
|
runNextTest();
|
|
273
273
|
return undefined;
|
|
274
274
|
}
|
|
@@ -342,7 +342,7 @@ class BaseWorker {
|
|
|
342
342
|
}
|
|
343
343
|
logger.warn('error on run', { err });
|
|
344
344
|
|
|
345
|
-
const projectId = this.userData
|
|
345
|
+
const projectId = this.userData?.projectId;
|
|
346
346
|
const { errorType, reason } = buildError(err, wasNetworkHealthy);
|
|
347
347
|
testimServicesApi.updateTestResult(projectId, this.testResultId, this.testId, {
|
|
348
348
|
status: testRunStatus.COMPLETED,
|
|
@@ -358,7 +358,7 @@ class BaseWorker {
|
|
|
358
358
|
const recoverTestResults = async (runError, testRunHandler) => {
|
|
359
359
|
const testId = this.testId;
|
|
360
360
|
const resultId = this.testResultId;
|
|
361
|
-
const projectId = this.userData
|
|
361
|
+
const projectId = this.userData?.projectId;
|
|
362
362
|
const branch = this.branch;
|
|
363
363
|
if (!testId || !resultId || !projectId || !branch) {
|
|
364
364
|
// Not enough data to call the API
|
|
@@ -409,6 +409,7 @@ class BaseWorker {
|
|
|
409
409
|
});
|
|
410
410
|
|
|
411
411
|
const testRunHandler = this.executionQueue.getNext();
|
|
412
|
+
|
|
412
413
|
if (!testRunHandler) { // no more tests to run
|
|
413
414
|
return this.onQueueCompleted();
|
|
414
415
|
}
|
|
@@ -418,6 +419,7 @@ class BaseWorker {
|
|
|
418
419
|
this.overrideTestConfigId = testRunHandler.getOverrideTestConfigId();
|
|
419
420
|
this.testRunConfig = testRunHandler.getRunConfig();
|
|
420
421
|
this.branch = testRunHandler.getBranch();
|
|
422
|
+
|
|
421
423
|
return runTestAndCalcResult(testRunHandler);
|
|
422
424
|
}
|
|
423
425
|
}
|
package/workers/WorkerAppium.js
CHANGED
|
@@ -113,7 +113,7 @@ class WorkerAppium extends BaseWorker {
|
|
|
113
113
|
}
|
|
114
114
|
try {
|
|
115
115
|
await super.runTestOnce(testRunHandler, appiumTestPlayer);
|
|
116
|
-
await runAppiumTest.call(this);
|
|
116
|
+
return await runAppiumTest.call(this);
|
|
117
117
|
} catch (err) {
|
|
118
118
|
logger.error('failed to run test once', { err });
|
|
119
119
|
throw err;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const { delay } = require('../utils');
|
|
4
4
|
const { releasePlayer } = require('./workerUtils');
|
|
5
5
|
const WorkerExtension = require('./WorkerExtension');
|
|
6
6
|
const perf = require('../commons/performance-logger');
|
|
@@ -14,7 +14,7 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
|
|
|
14
14
|
if (!this.testPlayer) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
await releasePlayer(this.id, this.releaseSlotOnTestFinished, this.userData
|
|
17
|
+
await releasePlayer(this.id, this.releaseSlotOnTestFinished, this.userData?.projectId, this.testPlayer);
|
|
18
18
|
this.testPlayer = null;
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -58,8 +58,8 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
|
|
|
58
58
|
await this.onQueueCompleted();
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
if (this.options.lightweightMode
|
|
62
|
-
await
|
|
61
|
+
if (this.options.lightweightMode?.general) {
|
|
62
|
+
await delay(DELAY_BETWEEN_TESTS);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -92,12 +92,15 @@ class WorkerSelenium extends BaseWorker {
|
|
|
92
92
|
sessionPlayer.playbackManager.executionName = testRunHandler.getExecutionName();
|
|
93
93
|
|
|
94
94
|
sessionPlayer.setLightweightMode(this.options.lightweightMode);
|
|
95
|
+
if (testRunHandler.getSfdcCredential()) {
|
|
96
|
+
sessionPlayer.setSfdcCredential(testRunHandler.getSfdcCredential());
|
|
97
|
+
}
|
|
95
98
|
if (sessionPlayerInit.localAssetService) {
|
|
96
99
|
sessionPlayerInit.localAssetService.initialize({ serverUrl: this.options.localRCASaver });
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
let preloadedTest = null;
|
|
100
|
-
if (this.options.lightweightMode
|
|
103
|
+
if (this.options.lightweightMode?.preloadTests) {
|
|
101
104
|
const preloadedTests = await preloadTests(this.options);
|
|
102
105
|
preloadedTest = preloadedTests[this.testId];
|
|
103
106
|
}
|
|
@@ -208,7 +211,7 @@ class WorkerSelenium extends BaseWorker {
|
|
|
208
211
|
function setupCliPerformanceMonitoring(sessionPlayer) {
|
|
209
212
|
const { playback } = sessionPlayerInit.commonConstants;
|
|
210
213
|
function monitorEvent(event) {
|
|
211
|
-
sessionPlayer.playbackManager.on(event, (
|
|
214
|
+
sessionPlayer.playbackManager.on(event, () => {
|
|
212
215
|
perf.log(`Got event ${event}`);
|
|
213
216
|
});
|
|
214
217
|
}
|