@testim/testim-cli 3.194.0 → 3.198.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/README.md +1 -1
- package/cli/onExit.js +12 -1
- package/cli.js +5 -1
- package/commons/constants.js +0 -25
- package/commons/featureFlags.js +2 -0
- package/commons/npmWrapper.js +46 -14
- package/commons/npmWrapper.test.js +182 -6
- package/commons/socket/testResultService.js +4 -14
- package/commons/testimAnalytics.js +0 -1
- package/commons/testimDesiredCapabilitiesBuilder.js +0 -94
- package/commons/testimServicesApi.js +9 -79
- package/executionQueue.js +7 -4
- package/npm-shrinkwrap.json +959 -523
- package/package.json +3 -1
- package/player/stepActions/baseJsStepAction.js +5 -1
- package/player/stepActions/pixelValidationStepAction.js +28 -0
- package/player/stepActions/salesforceApexActionStepAction.js +9 -0
- package/player/stepActions/salesforceAutoLoginStepAction.js +5 -3
- package/player/stepActions/scripts/focusElement.js +5 -3
- package/player/stepActions/stepActionRegistrar.js +6 -48
- package/player/utils/eyeSdkService.js +230 -0
- package/reports/consoleReporter.js +0 -20
- package/reports/reporter.js +0 -21
- package/runOptions.js +13 -89
- package/runner.js +3 -44
- package/runners/{strategies/LocalStrategy.js → ParallelWorkerManager.js} +59 -68
- package/runners/TestPlanRunner.js +286 -67
- package/runners/runnerUtils.js +73 -0
- package/services/analyticsService.js +94 -0
- package/services/gridService.js +24 -16
- package/services/gridService.test.js +21 -21
- package/services/lambdatestService.js +1 -1
- package/testRunHandler.js +16 -2
- package/testRunStatus.js +17 -13
- package/utils.js +5 -5
- package/workers/BaseWorker.js +39 -39
- package/workers/BaseWorker.test.js +1 -1
- package/workers/WorkerExtensionSingleBrowser.js +6 -3
- package/commons/apkUploader/apkUploader.js +0 -46
- package/commons/apkUploader/apkUploaderFactory.js +0 -68
- package/commons/apkUploader/deviceFarmApkUploader.js +0 -41
- package/commons/apkUploader/saucelabsApkUploader.js +0 -36
- package/commons/apkUploader/testObjectApkUploader.js +0 -34
- package/player/mobile/mobileTestPlayer.js +0 -80
- package/player/mobile/mobileWebDriver.js +0 -155
- package/player/mobile/services/frameLocatorMock.js +0 -18
- package/player/mobile/services/mobilePortSelector.js +0 -22
- package/player/mobile/services/mobileTabService.js +0 -241
- package/player/mobile/utils/mobileScreenshotUtils.js +0 -46
- package/player/mobile/utils/mobileWindowUtils.js +0 -84
- package/player/stepActions/mobile/android/androidLocateStepAction.js +0 -122
- package/player/stepActions/mobile/android/androidLongClickStepAction.js +0 -12
- package/player/stepActions/mobile/android/androidScrollStepAction.js +0 -134
- package/player/stepActions/mobile/android/androidSpecialKeyStepAction.js +0 -22
- package/player/stepActions/mobile/android/androidSwipeStepAction.js +0 -32
- package/player/stepActions/mobile/androidGlobalActionStepAction.js +0 -12
- package/player/stepActions/mobile/androidTapStepAction.js +0 -19
- package/player/stepActions/mobile/androidTextChangeStepAction.js +0 -23
- package/player/stepActions/mobile/ios/iosLocateStepAction.js +0 -124
- package/player/stepActions/mobile/ios/iosScrollStepAction.js +0 -76
- package/runners/AnonymousTestPlanRunner.js +0 -106
- package/runners/BaseRunner.js +0 -42
- package/runners/BaseTestPlanRunner.js +0 -194
- package/runners/DeviceFarmRemoteRunner.js +0 -50
- package/runners/SchedulerRemoteRunner.js +0 -47
- package/runners/strategies/BaseStrategy.js +0 -86
- package/runners/strategies/DeviceFarmStrategy.js +0 -195
- package/runners/strategies/LocalDeviceFarmStrategy.js +0 -12
- package/runners/strategies/LocalTestStrategy.js +0 -14
- package/runners/strategies/Strategy.js +0 -17
- package/workers/WorkerAppium.js +0 -70
|
@@ -1,24 +1,190 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const Bluebird = require('bluebird');
|
|
4
4
|
const _ = require('lodash');
|
|
5
|
+
const constants = require('../commons/constants');
|
|
5
6
|
|
|
7
|
+
const TESTIM_RUN_STATUS = constants.testRunStatus;
|
|
6
8
|
const reporter = require('../reports/reporter');
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
9
|
+
const RealDataService = require('../commons/socket/realDataService');
|
|
10
|
+
const testimServicesApi = require('../commons/testimServicesApi');
|
|
11
|
+
const testimCustomToken = require('../commons/testimCustomToken');
|
|
12
|
+
const TestRunStatus = require('../testRunStatus');
|
|
13
|
+
const analyticsService = require('../services/analyticsService');
|
|
14
|
+
const branchService = require('../services/branchService');
|
|
15
|
+
const config = require('../commons/config');
|
|
16
|
+
const ParallelWorkerManager = require('./ParallelWorkerManager');
|
|
17
|
+
const utils = require('../utils');
|
|
18
|
+
const { getSuite, calcTestResultStatus, validateConfig } = require('./runnerUtils');
|
|
19
|
+
const { StopRunOnError, ArgError } = require('../errors');
|
|
10
20
|
const Logger = require('../commons/logger');
|
|
21
|
+
const perf = require('../commons/performance-logger');
|
|
11
22
|
|
|
23
|
+
const guid = utils.guid;
|
|
12
24
|
const logger = Logger.getLogger('test-plan-runner');
|
|
25
|
+
const TDK_CHILD_RESULTS_TIMEOUT = 1000 * 60 * 5;
|
|
13
26
|
|
|
14
|
-
class TestPlanRunner
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return total === passed;
|
|
27
|
+
class TestPlanRunner {
|
|
28
|
+
constructor(customExtensionLocalLocation) {
|
|
29
|
+
this.workerManager = new ParallelWorkerManager(customExtensionLocalLocation);
|
|
30
|
+
this.startTime = Date.now();
|
|
19
31
|
}
|
|
32
|
+
runTestAllPhases(beforeTests, tests, afterTests, branchToUse, tpOptions, executionId, testStatus) {
|
|
33
|
+
const executionResults = {};
|
|
34
|
+
const authData = testimCustomToken.getTokenV3UserData();
|
|
20
35
|
|
|
21
|
-
|
|
36
|
+
const runBeforeTests = (beforeTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
37
|
+
const workerCount = 1;
|
|
38
|
+
const stopOnError = true;
|
|
39
|
+
return this.workerManager.runTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
40
|
+
.then(beforeTestsResults => Object.assign(executionResults, beforeTestsResults));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const runTestPlanTests = (tests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
44
|
+
const workerCount = config.TESTIM_CONCURRENT_WORKER_COUNT || tpOptions.parallel;
|
|
45
|
+
const stopOnError = false;
|
|
46
|
+
perf.log('right before this.workerManager.runTests');
|
|
47
|
+
return this.workerManager.runTests(tests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
48
|
+
.log('right after this.workerManager.runTests')
|
|
49
|
+
.then(testsResults => Object.assign(executionResults, testsResults));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const runAfterTests = (afterTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
53
|
+
const workerCount = 1;
|
|
54
|
+
const stopOnError = false;
|
|
55
|
+
return this.workerManager.runTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
56
|
+
.then(afterTestsResults => Object.assign(executionResults, afterTestsResults));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function catchBeforeTestsFailed(executionId) {
|
|
60
|
+
return testStatus.markAllQueuedTests(executionId, constants.runnerTestStatus.ABORTED, 'aborted', false);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sessionType = utils.getSessionType(tpOptions);
|
|
64
|
+
analyticsService.analyticsExecsStart({ authData, executionId, projectId: tpOptions.project, sessionType });
|
|
65
|
+
perf.log('right before runBeforeTests');
|
|
66
|
+
return runBeforeTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData)
|
|
67
|
+
.log('right before runTestPlanTests')
|
|
68
|
+
.then(() => runTestPlanTests(tests, testStatus, executionId, tpOptions, branchToUse, authData))
|
|
69
|
+
.log('right after runTestPlanTests')
|
|
70
|
+
.then(() => runAfterTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData))
|
|
71
|
+
.then(() => executionResults)
|
|
72
|
+
.catch(StopRunOnError, () => catchBeforeTestsFailed(executionId));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async initRealDataService(projectId) {
|
|
76
|
+
const realDataService = new RealDataService();
|
|
77
|
+
await realDataService.init(projectId);
|
|
78
|
+
return realDataService;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async listenToTestCreatedInFile(realDataService, projectId, runId, testStatus) {
|
|
82
|
+
const childTestResults = {};
|
|
83
|
+
realDataService.joinToTestResultsByRunId(runId, projectId);
|
|
84
|
+
const promise = new Promise(resolve => {
|
|
85
|
+
realDataService.listenToTestResultsByRunId(runId, testResult => {
|
|
86
|
+
const resultId = testResult.id;
|
|
87
|
+
if (!testStatus.getTestResult(resultId)) {
|
|
88
|
+
const prevTestResult = childTestResults[resultId];
|
|
89
|
+
const mergedTestResult = _.merge({}, prevTestResult, testResult, { resultId });
|
|
90
|
+
childTestResults[resultId] = mergedTestResult;
|
|
91
|
+
if (!prevTestResult || prevTestResult.status !== testResult.status) {
|
|
92
|
+
const parentTestResult = testStatus.getTestResult(mergedTestResult.parentResultId) || { workerId: 1 };
|
|
93
|
+
const workerId = parentTestResult.workerId;
|
|
94
|
+
if ([TESTIM_RUN_STATUS.RUNNING].includes(testResult.status)) {
|
|
95
|
+
reporter.onTestStarted(mergedTestResult, workerId);
|
|
96
|
+
}
|
|
97
|
+
if ([TESTIM_RUN_STATUS.COMPLETED].includes(testResult.status)) {
|
|
98
|
+
mergedTestResult.duration = (mergedTestResult.endTime - mergedTestResult.startTime) || 0;
|
|
99
|
+
reporter.onTestFinished(mergedTestResult, workerId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const allChildTestResultCompleted = !(Object.values(childTestResults)
|
|
105
|
+
.some(result => ['QUEUED', 'RUNNING'].includes(result.runnerStatus)));
|
|
106
|
+
|
|
107
|
+
const allParentTestResultCompleted = !(Object.values(testStatus.getAllTestResults())
|
|
108
|
+
.some(result => ['QUEUED', 'RUNNING'].includes(result.status)));
|
|
109
|
+
|
|
110
|
+
if (allChildTestResultCompleted && allParentTestResultCompleted) {
|
|
111
|
+
return resolve(Object.values(childTestResults));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (allParentTestResultCompleted && !allChildTestResultCompleted) {
|
|
115
|
+
// wait 10 sec to handle race condition when parent test result (file) finished before child test result
|
|
116
|
+
return Bluebird.delay(10000)
|
|
117
|
+
.then(() => {
|
|
118
|
+
if (promise.isPending()) {
|
|
119
|
+
// TODO(Benji) we are missing the child test results here.
|
|
120
|
+
// we are resolving here with partial data - we should consider fetching it
|
|
121
|
+
// from the server
|
|
122
|
+
resolve(childTestResults);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return await promise;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async runTestPlan(beforeTests, tests, afterTests, tpOptions, testPlanName, testPlanId, branch, isAnonymous) {
|
|
134
|
+
const executionId = guid();
|
|
135
|
+
const projectId = tpOptions.project;
|
|
136
|
+
Logger.setExecutionId(executionId);
|
|
137
|
+
beforeTests.forEach(test => { test.isBeforeTestPlan = true; });
|
|
138
|
+
afterTests.forEach(test => { test.isAfterTestPlan = true; });
|
|
139
|
+
const testStatus = new TestRunStatus(_.concat(beforeTests, tests, afterTests), tpOptions, testPlanId, branch);
|
|
140
|
+
|
|
141
|
+
const configs = _(_.concat(beforeTests, tests, afterTests)).map(test => (test.overrideTestConfig && test.overrideTestConfig.name) || '').uniq().filter(Boolean)
|
|
142
|
+
.value();
|
|
143
|
+
const configName = configs && configs.length === 1 ? configs[0] : null;
|
|
144
|
+
|
|
145
|
+
const isCodeMode = tpOptions.files.length > 0;
|
|
146
|
+
|
|
147
|
+
if (isCodeMode && tpOptions.mode === constants.CLI_MODE.SELENIUM) {
|
|
148
|
+
// in selenium mode we don't need to wait for the runner and clickim to sync, so we don't need to wait for reports.
|
|
149
|
+
testStatus.setAsyncReporting(true);
|
|
150
|
+
}
|
|
151
|
+
const testListInfoPromise = tpOptions.lightweightMode && tpOptions.lightweightMode.onlyTestIdsNoSuite ?
|
|
152
|
+
{ beforeTests, tests, afterTests } :
|
|
153
|
+
testStatus.executionStart(executionId, projectId, this.startTime, testPlanName);
|
|
154
|
+
let childTestResults;
|
|
155
|
+
if (isCodeMode) {
|
|
156
|
+
childTestResults = Bluebird.try(async () => {
|
|
157
|
+
const realDataService = await this.initRealDataService(projectId);
|
|
158
|
+
return this.listenToTestCreatedInFile(realDataService, projectId, executionId, testStatus);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
perf.log('before testListInfoPromise');
|
|
162
|
+
const testListInfo = await testListInfoPromise;
|
|
163
|
+
if (!(tpOptions.lightweightMode && tpOptions.lightweightMode.onlyTestIdsNoSuite)) {
|
|
164
|
+
reporter.onTestPlanStarted(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, testPlanName, executionId, isAnonymous, configName, isCodeMode);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
perf.log('before runTestAllPhases');
|
|
168
|
+
const results = await this.runTestAllPhases(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, branch, tpOptions, executionId, testStatus);
|
|
169
|
+
const childResults = await Bluebird.resolve(childTestResults)
|
|
170
|
+
.timeout(TDK_CHILD_RESULTS_TIMEOUT)
|
|
171
|
+
.catch(async () => {
|
|
172
|
+
logger.warn('timed out waiting for child resutls on websocket. using REST fallback', { projectId, executionId });
|
|
173
|
+
const testResults = await testimServicesApi.getRealData(projectId, 'testResult', `runId=${executionId}&sort=runOrder`);
|
|
174
|
+
return _.chain((testResults && testResults.data && testResults.data.docs) || [])
|
|
175
|
+
.groupBy('parentResultId')
|
|
176
|
+
.omit('undefined')
|
|
177
|
+
.values()
|
|
178
|
+
.flatten()
|
|
179
|
+
.value();
|
|
180
|
+
});
|
|
181
|
+
perf.log('before executionEnd');
|
|
182
|
+
await testStatus.executionEnd(executionId);
|
|
183
|
+
perf.log('after executionEnd');
|
|
184
|
+
return { results, executionId, testPlanName, configName, childTestResults: childResults };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async runTestPlans(options, branchToUse) {
|
|
22
188
|
logger.info('start to run test plan', {
|
|
23
189
|
options: Object.assign({}, options, { token: undefined, userParamsData: undefined }),
|
|
24
190
|
branchToUse,
|
|
@@ -32,64 +198,117 @@ class TestPlanRunner extends BaseTestPlanRunner {
|
|
|
32
198
|
const testPlansTests = {};
|
|
33
199
|
const projectId = options.project;
|
|
34
200
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const id = testPlan.testPlanId;
|
|
52
|
-
testPlansResults[id] = {};
|
|
53
|
-
|
|
54
|
-
const tpOptions = Object.assign({}, options);
|
|
55
|
-
tpOptions.baseUrl = options.baseUrl || testPlan.startUrl;
|
|
56
|
-
tpOptions.host = options.host;
|
|
57
|
-
tpOptions.port = options.port;
|
|
58
|
-
tpOptions.gridId = options.gridId || testPlan.gridId;
|
|
59
|
-
|
|
60
|
-
//if user pass --grid with test plan we want to use grid option instead of host and port
|
|
61
|
-
if (options.grid) {
|
|
62
|
-
delete tpOptions.gridId;
|
|
63
|
-
}
|
|
201
|
+
const data = await testimServicesApi.getTestPlanTestList(projectId, options.testPlan, options.testPlanIds, branchToUse);
|
|
202
|
+
const testPlans = data.testPlans;
|
|
203
|
+
const testPlansData = data.testPlansData;
|
|
204
|
+
if (!testPlans || testPlans.length === 0) {
|
|
205
|
+
throw new ArgError(`no test plan to run ${options.testPlan}`);
|
|
206
|
+
}
|
|
207
|
+
if (!testPlansData || Object.keys(testPlansData).length === 0) {
|
|
208
|
+
if (options.passZeroTests) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
throw new ArgError(`no test to run in test plan ${options.testPlan}`);
|
|
212
|
+
}
|
|
213
|
+
await validateConfig(options, flattenTestListData(testPlansData));
|
|
214
|
+
return await Promise.all(testPlans.map(async testPlan => {
|
|
215
|
+
const id = testPlan.testPlanId;
|
|
216
|
+
testPlansResults[id] = {};
|
|
64
217
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
218
|
+
const tpOptions = Object.assign({}, options);
|
|
219
|
+
tpOptions.baseUrl = options.baseUrl || testPlan.startUrl;
|
|
220
|
+
tpOptions.host = options.host;
|
|
221
|
+
tpOptions.port = options.port;
|
|
222
|
+
tpOptions.gridId = options.gridId || testPlan.gridId;
|
|
223
|
+
|
|
224
|
+
//if user pass --grid with test plan we want to use grid option instead of host and port
|
|
225
|
+
if (options.grid) {
|
|
226
|
+
delete tpOptions.gridId;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const testPlanName = tpOptions.overrideExecutionName || testPlan.name;
|
|
230
|
+
return await Promise.all(testPlansData[id].map(async testPlanTests => {
|
|
231
|
+
const res = await this.runTestPlan(testPlanTests.beforeTests, testPlanTests.tests, testPlanTests.afterTests, tpOptions, testPlanName, id, branchToUse);
|
|
232
|
+
const isCodeMode = options.files.length > 0;
|
|
233
|
+
reporter.onTestPlanFinished(res.results, testPlan.name, this.startTime, res.executionId, false, isCodeMode, res.childTestResults);
|
|
234
|
+
testPlansResults[id][res.executionId] = res.results;
|
|
235
|
+
|
|
236
|
+
const executions = Object.keys(testPlansResults[id]).map(exeId => ({
|
|
237
|
+
executionId: exeId,
|
|
238
|
+
status: calcTestResultStatus(testPlansResults[id][exeId]),
|
|
239
|
+
}));
|
|
240
|
+
const tests = Object.keys(testPlansResults[id]).map(exeId => testPlansResults[id][exeId]).reduce((testResult, test) => Object.assign(testResult, test), {});
|
|
241
|
+
const success = calcTestResultStatus(tests);
|
|
242
|
+
Object.assign(testPlansTests, tests);
|
|
243
|
+
const executionId = success ? executions[0].executionId : executions.find(exec => !exec.success).executionId;
|
|
244
|
+
await testimServicesApi.saveTestPlanResult(projectId, id, { success, executions, executionId });
|
|
245
|
+
return res;
|
|
246
|
+
}));
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async runAnonymousTestPlan(options, branchToUse) {
|
|
251
|
+
logger.info('start to run anonymous', {
|
|
252
|
+
options: Object.assign({}, options, { token: undefined }),
|
|
253
|
+
branchToUse,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
perf.log('before getSuite');
|
|
257
|
+
const suiteResult = await getSuite(options, branchToUse);
|
|
258
|
+
perf.log('after getSuite');
|
|
259
|
+
|
|
260
|
+
if (!suiteResult.tests[0] || suiteResult.tests[0].length === 0) {
|
|
261
|
+
if (options.rerunFailedByRunId) {
|
|
262
|
+
throw new ArgError('No failed tests found in the provided run');
|
|
263
|
+
}
|
|
264
|
+
if (options.passZeroTests) {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
throw new ArgError('No tests to run');
|
|
268
|
+
}
|
|
269
|
+
branchToUse = suiteResult.branch || branchToUse;
|
|
270
|
+
if (options.rerunFailedByRunId && !suiteResult.runName) {
|
|
271
|
+
if (!suiteResult.runExists) {
|
|
272
|
+
throw new ArgError('Invalid run ID - no such run.');
|
|
273
|
+
}
|
|
274
|
+
const isAnonymouslyNamedRun = suiteResult.runName === '';
|
|
275
|
+
if (isAnonymouslyNamedRun) {
|
|
276
|
+
suiteResult.runName = `rerun-${options.rerunFailedByRunId}`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const testPlanName = options.overrideExecutionName || suiteResult.runName || _.concat(options.label, options.name, options.suites).join(' ');
|
|
280
|
+
const isAnonymous = true;
|
|
281
|
+
perf.log('Right before validateConfig + runAnonymousTestPlan tests map');
|
|
282
|
+
return await Promise.all(suiteResult.tests.map(async suiteTests => { // array of results per execution
|
|
283
|
+
//override result id for remote run mode and run only the first test data
|
|
284
|
+
if (options.resultId) {
|
|
285
|
+
const firstTest = _.first(suiteTests);
|
|
286
|
+
firstTest.resultId = options.resultId;
|
|
287
|
+
suiteTests = [firstTest];
|
|
288
|
+
}
|
|
289
|
+
await validateConfig(options, suiteTests);
|
|
290
|
+
perf.log('right before runTestPlan');
|
|
291
|
+
const res = await this.runTestPlan([], suiteTests, [], options, testPlanName, null, branchToUse, isAnonymous);
|
|
292
|
+
perf.log('right after runTestPlan');
|
|
293
|
+
const isCodeMode = options.files.length > 0;
|
|
294
|
+
await reporter.onTestPlanFinished(res.results, testPlanName, this.startTime, res.executionId, isAnonymous, isCodeMode, res.childTestResults);
|
|
295
|
+
return res;
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async run(options) {
|
|
300
|
+
const branchToUse = branchService.getCurrentBranch();
|
|
301
|
+
let results = [];
|
|
302
|
+
if (utils.hasTestPlanFlag(options)) {
|
|
303
|
+
results = await this.runTestPlans(options, branchToUse);
|
|
304
|
+
} else {
|
|
305
|
+
results = await this.runAnonymousTestPlan(options, branchToUse);
|
|
306
|
+
}
|
|
307
|
+
const flattenResults = _.flattenDeep(results);
|
|
308
|
+
perf.log('right before onAllTestPlansFinished');
|
|
309
|
+
await reporter.onAllTestPlansFinished(flattenResults);
|
|
310
|
+
perf.log('right after onAllTestPlansFinished');
|
|
311
|
+
return flattenResults.map(res => res.results).reduce((total, cur) => Object.assign(total, cur), {});
|
|
93
312
|
}
|
|
94
313
|
}
|
|
95
314
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const utils = require('../utils.js');
|
|
6
|
+
const analytics = require('../commons/testimAnalytics');
|
|
7
|
+
const { ArgError } = require('../errors');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async function getSuite(options, branchToUse) {
|
|
11
|
+
if (options.lightweightMode && options.lightweightMode.onlyTestIdsNoSuite && options.testId) {
|
|
12
|
+
return { tests: [options.testId.map(testId => ({ testId, testConfig: { }, resultId: utils.guid() }))] };
|
|
13
|
+
}
|
|
14
|
+
// local code test
|
|
15
|
+
if (options.files.length > 0) {
|
|
16
|
+
const { buildCodeTests } = require('./buildCodeTests');
|
|
17
|
+
let webpackConfig = {};
|
|
18
|
+
if (options.webpackConfig) {
|
|
19
|
+
const webpackConfigPath = path.join(process.cwd(), options.webpackConfig);
|
|
20
|
+
webpackConfig = require(webpackConfigPath);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return buildCodeTests(options.files, webpackConfig, { baseUrl: options.baseUrl });
|
|
24
|
+
}
|
|
25
|
+
// regular test
|
|
26
|
+
const servicesApi = require('../commons/testimServicesApi');
|
|
27
|
+
return await servicesApi.getSuiteTestList({
|
|
28
|
+
projectId: options.project,
|
|
29
|
+
labels: options.label,
|
|
30
|
+
testIds: options.testId,
|
|
31
|
+
testNames: options.name,
|
|
32
|
+
testConfigNames: options.testConfigNames,
|
|
33
|
+
suiteNames: options.suites,
|
|
34
|
+
suiteIds: options.suiteIds,
|
|
35
|
+
branch: branchToUse,
|
|
36
|
+
rerunFailedByRunId: options.rerunFailedByRunId,
|
|
37
|
+
testConfigIds: options.testConfigIds,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
function calcTestResultStatus(tests) {
|
|
43
|
+
const total = Object.keys(tests).length;
|
|
44
|
+
const passed = Object.keys(tests).reduce((count, resultId) => count + (tests[resultId].success === true ? 1 : 0), 0);
|
|
45
|
+
return total === passed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async function validateConfig(options, testList) {
|
|
51
|
+
const supportedBrowsers = options.mode === 'extension' ? [
|
|
52
|
+
'edge-chromium', 'chrome',
|
|
53
|
+
] : [
|
|
54
|
+
'ie11', 'firefox', 'chrome', 'edge', 'edge-chromium', 'safari', 'safari technology preview', 'browser', 'android', 'ios', 'iphone', 'ipad',
|
|
55
|
+
];
|
|
56
|
+
const diff = _.difference(utils.getUniqBrowsers(options, testList), supportedBrowsers);
|
|
57
|
+
|
|
58
|
+
if (diff.length > 0) {
|
|
59
|
+
analytics.trackWithCIUser('invalid-config-run', {
|
|
60
|
+
browser: diff.join(', '),
|
|
61
|
+
mode: 'runner',
|
|
62
|
+
});
|
|
63
|
+
throw new ArgError(`browser type <${diff}> is not supported in ${options.mode} mode.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return testList;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
getSuite,
|
|
71
|
+
calcTestResultStatus,
|
|
72
|
+
validateConfig,
|
|
73
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const analytics = require('../commons/testimAnalytics');
|
|
4
|
+
const { isCi } = require('../cli/isCiRun');
|
|
5
|
+
|
|
6
|
+
const calcSource = (source, user) => {
|
|
7
|
+
if (source !== 'cli' && source !== 'cli-local') {
|
|
8
|
+
return source;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (isCi && user) {
|
|
12
|
+
return 'ci-with-user';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (isCi) {
|
|
16
|
+
return 'ci';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (user) {
|
|
20
|
+
return 'cli-with-user';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return source;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function setLightweightAnalytics(properties, lightweightMode) {
|
|
27
|
+
if (lightweightMode && lightweightMode.type) {
|
|
28
|
+
properties[`${lightweightMode.type}Mode`] = true;
|
|
29
|
+
}
|
|
30
|
+
return properties;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function analyticsTestStart({
|
|
34
|
+
executionId, projectId, testId, resultId, companyId, companyName, projectName, companyPlan, sessionType, source, user, lightweightMode,
|
|
35
|
+
}) {
|
|
36
|
+
const properties = setLightweightAnalytics({
|
|
37
|
+
executionId,
|
|
38
|
+
projectId,
|
|
39
|
+
testId,
|
|
40
|
+
resultId,
|
|
41
|
+
companyId,
|
|
42
|
+
companyName,
|
|
43
|
+
projectName,
|
|
44
|
+
companyPlan,
|
|
45
|
+
sessionType,
|
|
46
|
+
source: calcSource(source, user),
|
|
47
|
+
}, lightweightMode);
|
|
48
|
+
analytics.trackWithCIUser('test-run-ci', properties);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function analyticsTestEnd({
|
|
52
|
+
executionId, projectId, testId, resultId, result, companyId, companyName, projectName, companyPlan, sessionType, source, user, lightweightMode,
|
|
53
|
+
logger,
|
|
54
|
+
}) {
|
|
55
|
+
try {
|
|
56
|
+
const properties = setLightweightAnalytics({
|
|
57
|
+
executionId,
|
|
58
|
+
projectId,
|
|
59
|
+
testId,
|
|
60
|
+
resultId,
|
|
61
|
+
companyId,
|
|
62
|
+
companyName,
|
|
63
|
+
projectName,
|
|
64
|
+
companyPlan,
|
|
65
|
+
sessionType,
|
|
66
|
+
mockNetworkEnabled: result.wasMockNetworkActivated,
|
|
67
|
+
source: calcSource(source, user),
|
|
68
|
+
}, lightweightMode);
|
|
69
|
+
|
|
70
|
+
if (result.success) {
|
|
71
|
+
analytics.trackWithCIUser('test-run-ci-success', properties);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
analytics.trackWithCIUser('test-run-ci-fail', Object.assign({}, properties, { failureReason: result.failureReason }));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
logger.error('failed to update test end analytics', { err });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
function analyticsExecsStart({ executionId, projectId, sessionType }) {
|
|
83
|
+
analytics.trackWithCIUser('batch-run-ci', {
|
|
84
|
+
executionId,
|
|
85
|
+
projectId,
|
|
86
|
+
sessionType,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
analyticsTestStart,
|
|
92
|
+
analyticsTestEnd,
|
|
93
|
+
analyticsExecsStart,
|
|
94
|
+
};
|
package/services/gridService.js
CHANGED
|
@@ -60,14 +60,14 @@ function getSerializableObject(grid) {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function handleGetGridResponse(projectId, workerId, browser,
|
|
63
|
+
function handleGetGridResponse(projectId, companyId, workerId, browser, getFun) {
|
|
64
64
|
return getFun()
|
|
65
65
|
.catch(err => {
|
|
66
|
-
logger.error('failed to get grid', { projectId, err });
|
|
66
|
+
logger.error('failed to get grid', { projectId, companyId, err });
|
|
67
67
|
throw new Error(gridMessages.UNKNOWN);
|
|
68
68
|
})
|
|
69
69
|
.then(async (res) => {
|
|
70
|
-
logger.info('get grid info', Object.assign({}, res, { projectId }));
|
|
70
|
+
logger.info('get grid info', Object.assign({}, res, { projectId, companyId }));
|
|
71
71
|
const isSuccess = () => res.status === 'success';
|
|
72
72
|
const isError = () => res.status === 'error' && res.code;
|
|
73
73
|
if (!res || (!isError() && !isSuccess())) {
|
|
@@ -77,7 +77,7 @@ function handleGetGridResponse(projectId, workerId, browser, options, getFun) {
|
|
|
77
77
|
|
|
78
78
|
if (isSuccess()) {
|
|
79
79
|
const serGrid = getSerializableObject(res.grid);
|
|
80
|
-
module.exports.addItemToGridCache(workerId, serGrid.gridId, serGrid.slotId, browser);
|
|
80
|
+
module.exports.addItemToGridCache(workerId, companyId, serGrid.gridId, serGrid.slotId, browser);
|
|
81
81
|
return serGrid;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -94,16 +94,23 @@ function handleGetGridResponse(projectId, workerId, browser, options, getFun) {
|
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
function addItemToGridCache(workerId, gridId, slotId, browser) {
|
|
98
|
-
gridCache[workerId] = { gridId, slotId, browser };
|
|
97
|
+
function addItemToGridCache(workerId, companyId, gridId, slotId, browser) {
|
|
98
|
+
gridCache[workerId] = { gridId, companyId, slotId, browser };
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function getHostAndPortById(workerId, projectId, gridId, browser, executionId, options) {
|
|
102
|
-
return handleGetGridResponse(projectId, workerId, browser,
|
|
101
|
+
function getHostAndPortById(workerId, companyId, projectId, gridId, browser, executionId, options) {
|
|
102
|
+
return handleGetGridResponse(projectId, companyId, workerId, browser, () => servicesApi.getGridById(companyId, projectId, gridId, browser, executionId));
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
function getHostAndPortByName(workerId, projectId, gridName, browser, executionId, options) {
|
|
106
|
-
|
|
105
|
+
function getHostAndPortByName(workerId, companyId, projectId, gridName, browser, executionId, options) {
|
|
106
|
+
const get = () => {
|
|
107
|
+
const grid = options.allGrids && options.allGrids.find(grid => (grid.name || '').toLowerCase() === gridName.toLowerCase());
|
|
108
|
+
if (grid && grid._id) {
|
|
109
|
+
return servicesApi.getGridById(companyId, projectId, grid._id, browser, executionId);
|
|
110
|
+
}
|
|
111
|
+
return servicesApi.getGridByName(companyId, projectId, gridName, browser, executionId);
|
|
112
|
+
};
|
|
113
|
+
return handleGetGridResponse(projectId, companyId, workerId, browser, get);
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
function getAllGrids(companyId) {
|
|
@@ -138,15 +145,15 @@ function releaseGridSlot(workerId, projectId) {
|
|
|
138
145
|
return Promise.resolve();
|
|
139
146
|
}
|
|
140
147
|
|
|
141
|
-
const { slotId, gridId, browser } = gridData;
|
|
148
|
+
const { slotId, gridId, browser, companyId } = gridData;
|
|
142
149
|
delete gridCache[workerId];
|
|
143
150
|
if (!slotId) {
|
|
144
151
|
logger.warn('failed to find grid slot id', { projectId });
|
|
145
152
|
return Promise.resolve();
|
|
146
153
|
}
|
|
147
154
|
|
|
148
|
-
logger.info('release slot id', { projectId, slotId, gridId, browser });
|
|
149
|
-
return servicesApi.releaseGridSlot(projectId, slotId, gridId, browser)
|
|
155
|
+
logger.info('release slot id', { projectId, companyId, slotId, gridId, browser });
|
|
156
|
+
return servicesApi.releaseGridSlot(companyId, projectId, slotId, gridId, browser)
|
|
150
157
|
.catch(err => logger.error('failed to release slot', { projectId, err }));
|
|
151
158
|
}
|
|
152
159
|
|
|
@@ -274,7 +281,8 @@ const getGridSlot = Promise.method(_getGridSlot);
|
|
|
274
281
|
|
|
275
282
|
async function _getGridSlot(browser, executionId, testResultId, onGridSlot, options, workerId) {
|
|
276
283
|
const getGridDataFromServer = () => {
|
|
277
|
-
const { host, project, grid, gridId, useLocalChromeDriver, useChromeLauncher } = options;
|
|
284
|
+
const { host, project, grid, gridId, useLocalChromeDriver, useChromeLauncher, company = {} } = options;
|
|
285
|
+
const companyId = company.companyId;
|
|
278
286
|
if (useLocalChromeDriver || useChromeLauncher) {
|
|
279
287
|
return { mode: 'local' };
|
|
280
288
|
}
|
|
@@ -282,10 +290,10 @@ async function _getGridSlot(browser, executionId, testResultId, onGridSlot, opti
|
|
|
282
290
|
return Promise.resolve(getOptionGrid(options));
|
|
283
291
|
}
|
|
284
292
|
if (gridId) {
|
|
285
|
-
return getHostAndPortById(workerId, project, gridId, browser, executionId, options);
|
|
293
|
+
return getHostAndPortById(workerId, companyId, project, gridId, browser, executionId, options);
|
|
286
294
|
}
|
|
287
295
|
if (grid) {
|
|
288
|
-
return getHostAndPortByName(workerId, project, grid, browser, executionId, options);
|
|
296
|
+
return getHostAndPortByName(workerId, companyId, project, grid, browser, executionId, options);
|
|
289
297
|
}
|
|
290
298
|
throw new GridError('Missing host or grid configuration');
|
|
291
299
|
};
|