@testim/testim-cli 3.254.0 → 3.256.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/routers/cliJsCode/service.js +11 -8
- package/agent/routers/codim/router.test.js +9 -12
- package/agent/routers/codim/service.js +16 -16
- package/agent/routers/playground/service.js +5 -7
- package/cli.js +6 -6
- package/cliAgentMode.js +11 -10
- package/codim/codim-cli.js +14 -9
- package/commons/featureFlags.js +29 -7
- package/commons/httpRequest.js +5 -1
- package/commons/httpRequestCounters.js +21 -10
- package/commons/initializeUserWithAuth.js +7 -4
- package/commons/lazyRequire.js +4 -3
- package/commons/preloadTests.js +6 -3
- package/commons/prepareRunner.js +7 -5
- package/commons/prepareRunnerAndTestimStartUtils.js +51 -45
- package/commons/runnerFileCache.js +10 -2
- package/commons/testimServicesApi.js +36 -5
- package/commons/testimTunnel.test.js +2 -1
- package/coverage/SummaryToObjectReport.js +0 -1
- package/coverage/jsCoverage.js +12 -10
- package/inputFileUtils.js +11 -9
- package/npm-shrinkwrap.json +214 -471
- package/package.json +4 -3
- package/player/services/tabService.js +15 -1
- package/player/stepActions/apiStepAction.js +49 -43
- package/player/stepActions/baseCliJsStepAction.js +19 -14
- package/player/stepActions/baseJsStepAction.js +9 -8
- package/player/stepActions/dropFileStepAction.js +1 -3
- package/player/stepActions/inputFileStepAction.js +10 -8
- package/player/stepActions/locateStepAction.js +2 -0
- package/player/stepActions/mouseStepAction.js +21 -22
- package/player/stepActions/nodePackageStepAction.js +34 -35
- package/player/stepActions/stepAction.js +1 -0
- package/player/utils/imageCaptureUtils.js +133 -172
- package/player/utils/screenshotUtils.js +16 -13
- package/player/utils/windowUtils.js +20 -8
- package/player/webdriver.js +25 -22
- package/processHandler.js +4 -0
- package/reports/junitReporter.js +6 -7
- package/reports/reporter.js +34 -39
- package/runOptions.d.ts +286 -0
- package/runOptions.js +60 -45
- package/runner.js +64 -24
- package/runners/ParallelWorkerManager.js +12 -12
- package/runners/TestPlanRunner.js +14 -15
- package/runners/buildCodeTests.js +1 -0
- package/runners/runnerUtils.js +11 -2
- package/services/branchService.js +11 -5
- package/services/gridService.js +36 -40
- package/services/localRCASaver.js +4 -0
- package/testRunStatus.js +8 -5
- package/utils/argsUtils.js +86 -0
- package/utils/argsUtils.test.js +32 -0
- package/utils/fsUtils.js +154 -0
- package/utils/index.js +10 -161
- package/utils/promiseUtils.js +13 -2
- package/utils/stringUtils.js +4 -2
- package/utils/stringUtils.test.js +22 -0
- package/utils/timeUtils.js +25 -0
- package/utils/utils.test.js +0 -41
- package/workers/WorkerExtension.js +6 -7
|
@@ -39,12 +39,13 @@ class TestPlanRunner {
|
|
|
39
39
|
* @param {any[]} tests
|
|
40
40
|
* @param {any[]} afterTests
|
|
41
41
|
* @param {string} branchToUse
|
|
42
|
-
* @param {
|
|
42
|
+
* @param {import('../runOptions').RunnerOptions} tpOptions
|
|
43
43
|
* @param {string} executionId
|
|
44
44
|
* @param {string} executionName
|
|
45
45
|
* @param {TestRunStatus} testStatus
|
|
46
46
|
*/
|
|
47
47
|
async runTestAllPhases(beforeTests, tests, afterTests, branchToUse, tpOptions, executionId, executionName, testStatus) {
|
|
48
|
+
/** @type {{ [testResultId: string]: any; }} */
|
|
48
49
|
const executionResults = {};
|
|
49
50
|
const authData = testimCustomToken.getTokenV3UserData();
|
|
50
51
|
|
|
@@ -71,10 +72,6 @@ class TestPlanRunner {
|
|
|
71
72
|
Object.assign(executionResults, afterTestsResults);
|
|
72
73
|
};
|
|
73
74
|
|
|
74
|
-
function catchBeforeTestsFailed() {
|
|
75
|
-
return testStatus.markAllQueuedTests(executionId, constants.runnerTestStatus.ABORTED, 'aborted', false);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
75
|
const sessionType = utils.getSessionType(tpOptions);
|
|
79
76
|
analyticsService.analyticsExecsStart({ authData, executionId, projectId: tpOptions.project, sessionType });
|
|
80
77
|
perf.log('right before runBeforeTests');
|
|
@@ -88,7 +85,7 @@ class TestPlanRunner {
|
|
|
88
85
|
} catch (err) {
|
|
89
86
|
logger.error('error running test plan', { err });
|
|
90
87
|
if (err instanceof StopRunOnError) {
|
|
91
|
-
return
|
|
88
|
+
return testStatus.markAllQueuedTests(executionId, constants.runnerTestStatus.ABORTED, 'aborted', false);
|
|
92
89
|
}
|
|
93
90
|
throw err;
|
|
94
91
|
} finally {
|
|
@@ -149,6 +146,7 @@ class TestPlanRunner {
|
|
|
149
146
|
const childTestResults = {};
|
|
150
147
|
realDataService.joinToTestResultsByRunId(runId, projectId);
|
|
151
148
|
let isPromisePending = true;
|
|
149
|
+
/** @type {Promise<any[]>} */
|
|
152
150
|
const promise = new Promise(_resolve => {
|
|
153
151
|
const resolve = (val) => {
|
|
154
152
|
isPromisePending = false;
|
|
@@ -218,7 +216,7 @@ class TestPlanRunner {
|
|
|
218
216
|
* @param {any[]} beforeTests
|
|
219
217
|
* @param {any[]} tests
|
|
220
218
|
* @param {any[]} afterTests
|
|
221
|
-
* @param {
|
|
219
|
+
* @param {import('../runOptions').RunnerOptions} tpOptions
|
|
222
220
|
* @param {string} testPlanName
|
|
223
221
|
* @param {string | null} testPlanId
|
|
224
222
|
* @param {string} branch
|
|
@@ -230,17 +228,18 @@ class TestPlanRunner {
|
|
|
230
228
|
Logger.setExecutionId(executionId);
|
|
231
229
|
beforeTests.forEach(test => { test.isBeforeTestPlan = true; });
|
|
232
230
|
afterTests.forEach(test => { test.isAfterTestPlan = true; });
|
|
233
|
-
const testStatus = new TestRunStatus(
|
|
231
|
+
const testStatus = new TestRunStatus([].concat(beforeTests, tests, afterTests), tpOptions, testPlanId, branch);
|
|
234
232
|
|
|
235
|
-
const configs = _.chain(
|
|
233
|
+
const configs = _.chain([].concat(beforeTests, tests, afterTests))
|
|
236
234
|
.map(test => test.overrideTestConfig?.name || '')
|
|
237
235
|
.uniq()
|
|
238
236
|
.compact()
|
|
239
237
|
.value();
|
|
238
|
+
/** @type {string | null} */
|
|
240
239
|
const configName = configs?.length === 1 ? configs[0] : null;
|
|
241
240
|
|
|
242
241
|
const isCodeMode = tpOptions.files.length > 0;
|
|
243
|
-
const testNames = tpOptions.lightweightMode?.onlyTestIdsNoSuite ? [] :
|
|
242
|
+
const testNames = tpOptions.lightweightMode?.onlyTestIdsNoSuite ? [] : [].concat(beforeTests, tests, afterTests).map(test => test.name);
|
|
244
243
|
|
|
245
244
|
const testListInfoPromise = tpOptions.lightweightMode?.onlyTestIdsNoSuite ?
|
|
246
245
|
{ beforeTests, tests, afterTests } :
|
|
@@ -276,7 +275,7 @@ class TestPlanRunner {
|
|
|
276
275
|
|
|
277
276
|
/**
|
|
278
277
|
* @private
|
|
279
|
-
* @param {
|
|
278
|
+
* @param {import('../runOptions').RunnerOptions} options
|
|
280
279
|
* @param {string} branchToUse
|
|
281
280
|
*/
|
|
282
281
|
async runTestPlans(options, branchToUse) {
|
|
@@ -347,7 +346,7 @@ class TestPlanRunner {
|
|
|
347
346
|
|
|
348
347
|
/**
|
|
349
348
|
* @private
|
|
350
|
-
* @param {
|
|
349
|
+
* @param {import('../runOptions').RunnerOptions} options
|
|
351
350
|
* @param {string} branchToUse
|
|
352
351
|
*/
|
|
353
352
|
async runAnonymousTestPlan(options, branchToUse) {
|
|
@@ -379,13 +378,13 @@ class TestPlanRunner {
|
|
|
379
378
|
suiteResult.runName = `rerun-${options.rerunFailedByRunId}`;
|
|
380
379
|
}
|
|
381
380
|
}
|
|
382
|
-
const testPlanName = options.overrideExecutionName || suiteResult.runName ||
|
|
381
|
+
const testPlanName = options.overrideExecutionName || suiteResult.runName || [].concat(options.label, options.name, options.suites).join(' ');
|
|
383
382
|
const isAnonymous = true;
|
|
384
383
|
perf.log('Right before validateConfig + runAnonymousTestPlan tests map');
|
|
385
384
|
return await utils.promiseMap(suiteResult.tests, async suiteTests => { // array of results per execution
|
|
386
385
|
//override result id for remote run mode and run only the first test data
|
|
387
386
|
if (options.resultId) {
|
|
388
|
-
const firstTest =
|
|
387
|
+
const firstTest = suiteTests[0];
|
|
389
388
|
firstTest.resultId = options.resultId;
|
|
390
389
|
suiteTests = [firstTest];
|
|
391
390
|
}
|
|
@@ -400,7 +399,7 @@ class TestPlanRunner {
|
|
|
400
399
|
}
|
|
401
400
|
|
|
402
401
|
/**
|
|
403
|
-
* @param {
|
|
402
|
+
* @param {import('../runOptions').RunnerOptions} options
|
|
404
403
|
*/
|
|
405
404
|
async run(options) {
|
|
406
405
|
const branchToUse = branchService.getCurrentBranch();
|
|
@@ -122,6 +122,7 @@ async function buildCodeTests(files, webpackConfig = { mode: 'development' }, ru
|
|
|
122
122
|
throw new ArgError(`Compilation Webpack Error in tests: ${e.message}`);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/** @type {{ code: string; name: string; }[]} */
|
|
125
126
|
const fileResults = files.map((file, i) => ({ code: mfs.readFileSync(path.resolve('./dist', `${fileHashes[i]}.bundle.js`)), name: file })); // read all files
|
|
126
127
|
|
|
127
128
|
suite.tests = [fileResults.map(({ code, name }) => ({
|
package/runners/runnerUtils.js
CHANGED
|
@@ -7,9 +7,14 @@ const analytics = require('../commons/testimAnalytics');
|
|
|
7
7
|
const { ArgError } = require('../errors');
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @param {import('../runOptions').RunnerOptions} options
|
|
12
|
+
* @param {string} branchToUse
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
10
15
|
async function getSuite(options, branchToUse) {
|
|
11
16
|
if (options.lightweightMode?.onlyTestIdsNoSuite && options.testId) {
|
|
12
|
-
return { tests: [options.testId.map(testId => ({ testId, testConfig: {
|
|
17
|
+
return { tests: [options.testId.map(testId => ({ testId, testConfig: {}, resultId: utils.guid() }))] };
|
|
13
18
|
}
|
|
14
19
|
// local code test
|
|
15
20
|
if (options.files.length > 0) {
|
|
@@ -47,7 +52,11 @@ function calcTestResultStatus(tests) {
|
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
/**
|
|
56
|
+
* @template T
|
|
57
|
+
* @param {import('../runOptions').RunnerOptions} options
|
|
58
|
+
* @param {T[]} testList
|
|
59
|
+
*/
|
|
51
60
|
async function validateConfig(options, testList) {
|
|
52
61
|
const supportedBrowsers = options.mode === 'extension' ? [
|
|
53
62
|
'edge-chromium', 'chrome',
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
|
+
/** @type {string | undefined} */
|
|
3
4
|
let currentBranch;
|
|
4
5
|
|
|
5
6
|
function getCurrentBranch() {
|
|
6
|
-
return currentBranch ||
|
|
7
|
+
return currentBranch || 'master';
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {Awaited<ReturnType<import('../commons/initializeUserWithAuth')['initializeUserWithAuth']>>['branchName']} branchData
|
|
12
|
+
* @param {boolean | string} acknowledgeAutoDetect
|
|
13
|
+
*/
|
|
14
|
+
function setCurrentBranch(branchData = 'master', acknowledgeAutoDetect = 'false') {
|
|
15
|
+
if (branchData?.branch && branchData.branch === 'master') {
|
|
10
16
|
currentBranch = 'master';
|
|
11
17
|
return;
|
|
12
18
|
}
|
|
@@ -19,5 +25,5 @@ function setCurrentBranch(branchData = "master", acknowledgeAutoDetect = "false"
|
|
|
19
25
|
|
|
20
26
|
module.exports = {
|
|
21
27
|
getCurrentBranch,
|
|
22
|
-
setCurrentBranch
|
|
28
|
+
setCurrentBranch,
|
|
23
29
|
};
|
package/services/gridService.js
CHANGED
|
@@ -4,7 +4,7 @@ const _ = require('lodash');
|
|
|
4
4
|
const Promise = require('bluebird');
|
|
5
5
|
|
|
6
6
|
const { GridError, ArgError } = require('../errors');
|
|
7
|
-
const { hasTestPlanFlag } = require('../utils');
|
|
7
|
+
const { hasTestPlanFlag, promiseMap } = require('../utils');
|
|
8
8
|
const { gridMessages, gridTypes } = require('../commons/constants');
|
|
9
9
|
const logger = require('../commons/logger').getLogger('grid-service');
|
|
10
10
|
const servicesApi = require('../commons/testimServicesApi');
|
|
@@ -37,22 +37,22 @@ function extractHost(hostUrl) {
|
|
|
37
37
|
|
|
38
38
|
function getSerializableObject(grid) {
|
|
39
39
|
const host = grid && extractHost(grid.host);
|
|
40
|
-
const port = grid
|
|
41
|
-
const path = grid
|
|
40
|
+
const port = grid?.port;
|
|
41
|
+
const path = grid?.path;
|
|
42
42
|
const protocol = grid && extractProtocol(grid);
|
|
43
|
-
const accessToken = grid
|
|
44
|
-
const slotId = grid
|
|
45
|
-
const tunnel = grid
|
|
46
|
-
const user = grid
|
|
47
|
-
const key = grid
|
|
48
|
-
const type = grid
|
|
43
|
+
const accessToken = grid?.token;
|
|
44
|
+
const slotId = grid?.slotId;
|
|
45
|
+
const tunnel = grid?.hybrid?.tunnel;
|
|
46
|
+
const user = grid?.external?.user;
|
|
47
|
+
const key = grid?.external?.key;
|
|
48
|
+
const type = grid?.type;
|
|
49
49
|
const tunnelUser = type === gridTypes.HYBRID ?
|
|
50
50
|
(tunnel && grid.hybrid.external && grid.hybrid.external[grid.hybrid.tunnel] && grid.hybrid.external[grid.hybrid.tunnel].user) : user;
|
|
51
51
|
const tunnelKey = type === gridTypes.HYBRID ?
|
|
52
52
|
(tunnel && grid.hybrid.external && grid.hybrid.external[grid.hybrid.tunnel] && grid.hybrid.external[grid.hybrid.tunnel].key) : key;
|
|
53
|
-
const name = grid
|
|
53
|
+
const name = grid?.name;
|
|
54
54
|
const gridId = grid && (grid._id || grid.gridId);
|
|
55
|
-
const provider = grid
|
|
55
|
+
const provider = grid?.provider;
|
|
56
56
|
|
|
57
57
|
return {
|
|
58
58
|
host, port, path, protocol, accessToken, slotId, gridId, tunnel, user, key, type, name, provider, tunnelUser, tunnelKey,
|
|
@@ -97,14 +97,16 @@ function addItemToGridCache(workerId, companyId, gridId, slotId, browser) {
|
|
|
97
97
|
gridCache[workerId] = { gridId, companyId, slotId, browser };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function getHostAndPortById(workerId, companyId, projectId, gridId, browser, executionId
|
|
101
|
-
return handleGetGridResponse(projectId, companyId, workerId, browser, () =>
|
|
100
|
+
function getHostAndPortById(workerId, companyId, projectId, gridId, browser, executionId) {
|
|
101
|
+
return handleGetGridResponse(projectId, companyId, workerId, browser, () =>
|
|
102
|
+
servicesApi.getGridById(companyId, projectId, gridId, browser, executionId),
|
|
103
|
+
);
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
function getHostAndPortByName(workerId, companyId, projectId, gridName, browser, executionId, options) {
|
|
105
107
|
const get = () => {
|
|
106
|
-
const grid = options.allGrids
|
|
107
|
-
if (grid
|
|
108
|
+
const grid = options.allGrids?.find(g => (g.name || '').toLowerCase() === gridName.toLowerCase());
|
|
109
|
+
if (grid?._id) {
|
|
108
110
|
return servicesApi.getGridById(companyId, projectId, grid._id, browser, executionId);
|
|
109
111
|
}
|
|
110
112
|
return servicesApi.getGridByName(companyId, projectId, gridName, browser, executionId);
|
|
@@ -116,26 +118,22 @@ function getAllGrids(companyId) {
|
|
|
116
118
|
return servicesApi.getAllGrids(companyId);
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
function getGridDataByGridId(companyId, gridId, allGrids) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return getSerializableObject(grid);
|
|
127
|
-
});
|
|
121
|
+
async function getGridDataByGridId(companyId, gridId, allGrids) {
|
|
122
|
+
const grids = await Promise.resolve(allGrids || getAllGrids(companyId));
|
|
123
|
+
const grid = grids.find(g => g._id === gridId);
|
|
124
|
+
if (!grid) {
|
|
125
|
+
throw new ArgError(`Failed to find grid id: ${gridId}`);
|
|
126
|
+
}
|
|
127
|
+
return getSerializableObject(grid);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
function getGridDataByGridName(companyId, gridName, allGrids) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return getSerializableObject(grid);
|
|
138
|
-
});
|
|
130
|
+
async function getGridDataByGridName(companyId, gridName, allGrids) {
|
|
131
|
+
const grids = await Promise.resolve(allGrids || getAllGrids(companyId));
|
|
132
|
+
const grid = grids.find(g => (g.name || '').toLowerCase() === gridName.toLowerCase());
|
|
133
|
+
if (!grid) {
|
|
134
|
+
throw new ArgError(`Failed to find grid name: ${gridName}`);
|
|
135
|
+
}
|
|
136
|
+
return getSerializableObject(grid);
|
|
139
137
|
}
|
|
140
138
|
|
|
141
139
|
function releaseGridSlot(workerId, projectId) {
|
|
@@ -184,7 +182,7 @@ function releaseAllSlots(projectId) {
|
|
|
184
182
|
}
|
|
185
183
|
|
|
186
184
|
logger.warn('not all slots released before end runner flow', { projectId });
|
|
187
|
-
return
|
|
185
|
+
return promiseMap(workerIds, workerId => releaseGridSlot(workerId, projectId))
|
|
188
186
|
.catch(err => logger.error('failed to release all slots', { err, projectId }));
|
|
189
187
|
}
|
|
190
188
|
|
|
@@ -266,20 +264,18 @@ async function getGridData(options) {
|
|
|
266
264
|
throw new GridError('Missing host or grid configuration');
|
|
267
265
|
}
|
|
268
266
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
async function _getGridSlot(browser, executionId, testResultId, onGridSlot, options, workerId) {
|
|
272
|
-
const getGridDataFromServer = () => {
|
|
267
|
+
async function getGridSlot(browser, executionId, testResultId, onGridSlot, options, workerId) {
|
|
268
|
+
const getGridDataFromServer = async () => {
|
|
273
269
|
const { host, project, grid, gridId, useLocalChromeDriver, useChromeLauncher, company = {} } = options;
|
|
274
270
|
const companyId = company.companyId;
|
|
275
271
|
if (useLocalChromeDriver || useChromeLauncher) {
|
|
276
272
|
return { mode: 'local' };
|
|
277
273
|
}
|
|
278
274
|
if (host) {
|
|
279
|
-
return
|
|
275
|
+
return getOptionGrid(options);
|
|
280
276
|
}
|
|
281
277
|
if (gridId) {
|
|
282
|
-
return getHostAndPortById(workerId, companyId, project, gridId, browser, executionId
|
|
278
|
+
return getHostAndPortById(workerId, companyId, project, gridId, browser, executionId);
|
|
283
279
|
}
|
|
284
280
|
if (grid) {
|
|
285
281
|
return getHostAndPortByName(workerId, companyId, project, grid, browser, executionId, options);
|
|
@@ -37,6 +37,10 @@ function mapFilesToLocalDrive(test, logger) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @param {Partial<Pick<import('../runOptions').AgentModeOptions, 'agentPort' | 'agentBind'> & Pick<import('../runOptions').RunnerOptions, 'saveRCALocally'>>} param0
|
|
42
|
+
* @returns {Promise<import('net').AddressInfo>}
|
|
43
|
+
*/
|
|
40
44
|
async function initServer({ agentPort, agentBind, saveRCALocally }) {
|
|
41
45
|
const multer = await lazyRequire('multer');
|
|
42
46
|
saveRCALocally = typeof saveRCALocally === 'string' ? saveRCALocally : DEFUALT_PATH;
|
package/testRunStatus.js
CHANGED
|
@@ -17,6 +17,7 @@ const { calculateCoverage } = require('./coverage/jsCoverage');
|
|
|
17
17
|
const featureAvailabilityService = require('./commons/featureAvailabilityService');
|
|
18
18
|
const featureFlags = require('./commons/featureFlags');
|
|
19
19
|
const { mapFilesToLocalDrive } = require('./services/localRCASaver');
|
|
20
|
+
const { promiseMap } = require('./utils');
|
|
20
21
|
|
|
21
22
|
const gitBranch = utils.getEnvironmentGitBranch();
|
|
22
23
|
const gitCommit = process.env.GIT_COMMIT || process.env.CIRCLE_SHA1 || process.env.TRAVIS_COMMIT;
|
|
@@ -400,7 +401,7 @@ class RunStatus {
|
|
|
400
401
|
|
|
401
402
|
const reportExecutionStarted = () => {
|
|
402
403
|
const testResults = _.cloneDeep(this.testRunStatus);
|
|
403
|
-
return
|
|
404
|
+
return promiseMap(Object.keys(testResults), testResultId => {
|
|
404
405
|
const test = testResults[testResultId];
|
|
405
406
|
const testData = test.config?.testData;
|
|
406
407
|
const testId = test.testId;
|
|
@@ -498,10 +499,10 @@ class RunStatus {
|
|
|
498
499
|
});
|
|
499
500
|
}
|
|
500
501
|
|
|
501
|
-
markAllQueuedTests(executionId, status, failureReason, success) {
|
|
502
|
+
async markAllQueuedTests(executionId, status, failureReason, success) {
|
|
502
503
|
const queuedResultIds = Object.keys(this.testRunStatus).filter(resultId => this.getTestResult(resultId).status === 'QUEUED');
|
|
503
504
|
|
|
504
|
-
|
|
505
|
+
await servicesApi.updateExecutionTests(
|
|
505
506
|
executionId,
|
|
506
507
|
['QUEUED'],
|
|
507
508
|
status,
|
|
@@ -510,12 +511,14 @@ class RunStatus {
|
|
|
510
511
|
this.startTime,
|
|
511
512
|
null,
|
|
512
513
|
this.options.project
|
|
513
|
-
)
|
|
514
|
+
);
|
|
515
|
+
for (const resultId of queuedResultIds) {
|
|
514
516
|
const test = this.getTestResult(resultId);
|
|
515
517
|
test.status = status;
|
|
516
518
|
test.failureReason = failureReason;
|
|
517
519
|
test.success = success;
|
|
518
|
-
}
|
|
520
|
+
}
|
|
521
|
+
return this.testRunStatus;
|
|
519
522
|
}
|
|
520
523
|
}
|
|
521
524
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { sessionType, testStatus: testStatusConst } = require('../commons/constants');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('../runOptions').Options} Options
|
|
10
|
+
* @typedef {import('../runOptions').InitModeOptions} InitModeOptions
|
|
11
|
+
* @typedef {import('../runOptions').LoginModeOptions} LoginModeOptions
|
|
12
|
+
* @typedef {import('../runOptions').TunnelOptions} TunnelOptions
|
|
13
|
+
* @typedef {import('../runOptions').NgrokTunnelOptions} NgrokTunnelOptions
|
|
14
|
+
* @typedef {import('../runOptions').TunnelDaemonOptions} TunnelDaemonOptions
|
|
15
|
+
* @typedef {import('../runOptions').RunnerOptions} RunnerOptions
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** @param {RunnerOptions} options */
|
|
19
|
+
function getSessionType(options) {
|
|
20
|
+
return options.files.length > 0 ? sessionType.CODEFUL : sessionType.CODELESS;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {RunnerOptions} options
|
|
25
|
+
* @param {{ runConfig: { browserValue: string } }[]} testList
|
|
26
|
+
*/
|
|
27
|
+
function getUniqBrowsers(options, testList) {
|
|
28
|
+
if ((options.testConfigNames?.length || options.testConfigIds?.length || options.testPlan.length || options.testPlanIds.length) && !options.browser) {
|
|
29
|
+
return [...new Set(testList.map(t => t.runConfig.browserValue))];
|
|
30
|
+
}
|
|
31
|
+
return [options.browser?.toLowerCase()];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** @param {RunnerOptions} options */
|
|
35
|
+
const hasTestPlanFlag = (options) => Boolean(options.testPlan?.length || options.testPlanIds?.length);
|
|
36
|
+
|
|
37
|
+
/** @param {RunnerOptions} options */
|
|
38
|
+
const isRemoteRun = (options) => options.resultId && options.source === 'remote-run';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {{ testStatus?: string }} test
|
|
42
|
+
* @param {RunnerOptions} options
|
|
43
|
+
*/
|
|
44
|
+
const isQuarantineAndNotRemoteRun = (test, options) => test.testStatus === testStatusConst.QUARANTINE && !isRemoteRun(options) && !options.runQuarantinedTests;
|
|
45
|
+
|
|
46
|
+
function getArgsOnRemoteRunFailure() {
|
|
47
|
+
const { argv: args } = process;
|
|
48
|
+
if (!args.includes('--remoteRunId')) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
remoteRunId: args[args.indexOf('--remoteRunId') + 1],
|
|
53
|
+
projectId: args[args.indexOf('--project') + 1],
|
|
54
|
+
token: args[args.indexOf('--token') + 1],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/** @type {(options: Options) => options is InitModeOptions} options */
|
|
60
|
+
// @ts-ignore should be `as any` etc
|
|
61
|
+
const isInitCodimMode = (options) => Boolean(options.initCodimMode);
|
|
62
|
+
|
|
63
|
+
/** @type {(options: Options) => options is LoginModeOptions} options */
|
|
64
|
+
// @ts-ignore should be `as any` etc
|
|
65
|
+
const isLoginMode = (options) => Boolean(options.loginMode);
|
|
66
|
+
|
|
67
|
+
/** @type {(options: Options) => options is TunnelOptions} options */
|
|
68
|
+
// @ts-ignore should be `as any` etc
|
|
69
|
+
const isTunnelOnlyMode = (options) => Boolean(options.tunnelOnlyMode);
|
|
70
|
+
|
|
71
|
+
/** @type {(options: Options) => options is RunnerOptions & { createPrefechedData: true; }} options */
|
|
72
|
+
// @ts-ignore should be `as any` etc
|
|
73
|
+
const isCreatePrefetchedDataMode = (options) => Boolean(options.createPrefechedData);
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
getSessionType,
|
|
77
|
+
getUniqBrowsers,
|
|
78
|
+
hasTestPlanFlag,
|
|
79
|
+
isRemoteRun,
|
|
80
|
+
isQuarantineAndNotRemoteRun,
|
|
81
|
+
getArgsOnRemoteRunFailure,
|
|
82
|
+
isInitCodimMode,
|
|
83
|
+
isLoginMode,
|
|
84
|
+
isTunnelOnlyMode,
|
|
85
|
+
isCreatePrefetchedDataMode,
|
|
86
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const chai = require('chai'); // eslint-disable-line import/no-extraneous-dependencies
|
|
2
|
+
const argsUtils = require('./argsUtils');
|
|
3
|
+
|
|
4
|
+
const expect = chai.expect;
|
|
5
|
+
|
|
6
|
+
describe('argsUtils', () => {
|
|
7
|
+
describe('getArgsOnRemoteRunFailure', () => {
|
|
8
|
+
let originalArgv;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
originalArgv = process.argv;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
process.argv = originalArgv;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return undefined if no remote run is current', () => {
|
|
19
|
+
process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id'];
|
|
20
|
+
expect(argsUtils.getArgsOnRemoteRunFailure()).to.be.undefined;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return details if remote run is current', () => {
|
|
24
|
+
process.argv = ['node', 'file.js', '--token', 'token', '--project', 'project-id', '--remoteRunId', 'remote-run-id'];
|
|
25
|
+
expect(argsUtils.getArgsOnRemoteRunFailure()).to.eql({
|
|
26
|
+
remoteRunId: 'remote-run-id',
|
|
27
|
+
projectId: 'project-id',
|
|
28
|
+
token: 'token',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
package/utils/fsUtils.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const pRetry = require('p-retry');
|
|
7
|
+
const decompress = require('decompress');
|
|
8
|
+
const stringUtils = require('./stringUtils');
|
|
9
|
+
const httpRequest = require('../commons/httpRequest');
|
|
10
|
+
const { promises: fsPromises, createReadStream, createWriteStream, statSync } = require('fs');
|
|
11
|
+
|
|
12
|
+
const DOWNLOAD_RETRY = 3;
|
|
13
|
+
|
|
14
|
+
function getCliLocation() {
|
|
15
|
+
let cliLocation;
|
|
16
|
+
if (!require.main) { // we're in a REPL
|
|
17
|
+
return process.cwd(); // fall back on the current working directory
|
|
18
|
+
}
|
|
19
|
+
if (require.main.filename.includes('/src') || require.main.filename.includes('\\src') || process.env.IS_UNIT_TEST) {
|
|
20
|
+
cliLocation = path.resolve(__dirname, '../../');
|
|
21
|
+
} else {
|
|
22
|
+
cliLocation = path.resolve(__dirname, '../');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return cliLocation;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} url
|
|
30
|
+
*/
|
|
31
|
+
const download = async (url) => pRetry(() => httpRequest.download(url), { retries: DOWNLOAD_RETRY });
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} url
|
|
35
|
+
* @param {import('fs').PathLike} saveToLocation
|
|
36
|
+
*/
|
|
37
|
+
const downloadAndSave = async (url, saveToLocation) => {
|
|
38
|
+
const res = await download(url);
|
|
39
|
+
return fsPromises.writeFile(saveToLocation, res.body);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {import('fs').PathLike} readFile
|
|
44
|
+
* @param {import('fs').PathLike} destFile
|
|
45
|
+
* @return {Promise<void>}
|
|
46
|
+
*/
|
|
47
|
+
const copy = async (readFile, destFile) => new Promise((resolve, reject) => {
|
|
48
|
+
try {
|
|
49
|
+
// TODO: can this use fsPromises.copyFile?
|
|
50
|
+
const file = createWriteStream(destFile);
|
|
51
|
+
createReadStream(readFile).pipe(file);
|
|
52
|
+
file.on('finish', () => {
|
|
53
|
+
file.close(() => resolve());
|
|
54
|
+
});
|
|
55
|
+
} catch (err) {
|
|
56
|
+
reject(err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} location
|
|
62
|
+
* @param {string=} fileName
|
|
63
|
+
*/
|
|
64
|
+
function getSourcePath(location, fileName) {
|
|
65
|
+
if (stringUtils.isURL(location)) {
|
|
66
|
+
return fileName || path.join(process.cwd(), location.replace(/^.*[\\\/]/, ''));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return fileName || path.basename(location);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} location
|
|
74
|
+
* @param {string} fileName
|
|
75
|
+
*/
|
|
76
|
+
const getSource = async (location, fileName) => {
|
|
77
|
+
const destFile = getSourcePath(location, fileName);
|
|
78
|
+
if (stringUtils.isURL(location)) {
|
|
79
|
+
return downloadAndSave(location, destFile);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return copy(location, destFile);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} location
|
|
87
|
+
*/
|
|
88
|
+
const getSourceAsBuffer = async (location) => {
|
|
89
|
+
if (stringUtils.isURL(location)) {
|
|
90
|
+
return download(location);
|
|
91
|
+
}
|
|
92
|
+
return fsPromises.readFile(location);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} srcZipFile
|
|
97
|
+
* @param {string} destZipPath
|
|
98
|
+
*/
|
|
99
|
+
const unzipFile = async (srcZipFile, destZipPath) => await decompress(srcZipFile, destZipPath);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {import('fs').PathLike} fileLocation
|
|
103
|
+
*/
|
|
104
|
+
const getLocalFileSizeInMB = (fileLocation) => {
|
|
105
|
+
const stats = statSync(fileLocation);
|
|
106
|
+
const fileSizeInBytes = stats.size;
|
|
107
|
+
return fileSizeInBytes / 1000000;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
function getRunnerVersion() {
|
|
112
|
+
try {
|
|
113
|
+
/** @type {import('../../package.json')} */
|
|
114
|
+
const pack = require(`${__dirname}/../package.json`); // eslint-disable-line import/no-dynamic-require
|
|
115
|
+
return pack.version;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return '';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getEnginesVersion() {
|
|
122
|
+
try {
|
|
123
|
+
/** @type {import('../../package.json')} */
|
|
124
|
+
const pack = require(`${__dirname}/../package.json`); // eslint-disable-line import/no-dynamic-require
|
|
125
|
+
return pack.engines.node;
|
|
126
|
+
} catch (err) {
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function getEnginesVersionAsync() {
|
|
132
|
+
try {
|
|
133
|
+
/** @type {import('../../package.json')} */
|
|
134
|
+
const pack = JSON.parse(await fsPromises.readFile(`${__dirname}/../package.json`, 'utf8'));
|
|
135
|
+
return pack.engines.node;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
getCliLocation,
|
|
143
|
+
download,
|
|
144
|
+
downloadAndSave,
|
|
145
|
+
copy,
|
|
146
|
+
getSourcePath,
|
|
147
|
+
getSource,
|
|
148
|
+
getSourceAsBuffer,
|
|
149
|
+
unzipFile,
|
|
150
|
+
getLocalFileSizeInMB,
|
|
151
|
+
getRunnerVersion,
|
|
152
|
+
getEnginesVersion,
|
|
153
|
+
getEnginesVersionAsync,
|
|
154
|
+
};
|