@testim/testim-cli 3.195.0 → 3.199.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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/cli/onExit.js +12 -1
  3. package/cli.js +5 -1
  4. package/commons/constants.js +0 -25
  5. package/commons/featureFlags.js +2 -0
  6. package/commons/npmWrapper.js +46 -14
  7. package/commons/npmWrapper.test.js +182 -6
  8. package/commons/socket/testResultService.js +4 -14
  9. package/commons/testimAnalytics.js +0 -1
  10. package/commons/testimDesiredCapabilitiesBuilder.js +0 -94
  11. package/commons/testimServicesApi.js +9 -79
  12. package/executionQueue.js +7 -4
  13. package/npm-shrinkwrap.json +962 -526
  14. package/package.json +3 -1
  15. package/player/stepActions/baseJsStepAction.js +5 -1
  16. package/player/stepActions/pixelValidationStepAction.js +28 -0
  17. package/player/stepActions/salesforceApexActionStepAction.js +9 -0
  18. package/player/stepActions/salesforceAutoLoginStepAction.js +5 -3
  19. package/player/stepActions/stepActionRegistrar.js +6 -48
  20. package/player/utils/eyeSdkService.js +230 -0
  21. package/reports/consoleReporter.js +29 -44
  22. package/reports/reporter.js +0 -21
  23. package/runOptions.js +13 -89
  24. package/runner.js +3 -44
  25. package/runners/{strategies/LocalStrategy.js → ParallelWorkerManager.js} +59 -68
  26. package/runners/TestPlanRunner.js +292 -67
  27. package/runners/runnerUtils.js +73 -0
  28. package/services/analyticsService.js +94 -0
  29. package/services/gridService.js +24 -16
  30. package/services/gridService.test.js +21 -21
  31. package/services/lambdatestService.js +1 -1
  32. package/testRunHandler.js +23 -2
  33. package/testRunStatus.js +17 -13
  34. package/utils.js +5 -5
  35. package/workers/BaseWorker.js +39 -39
  36. package/workers/BaseWorker.test.js +1 -1
  37. package/workers/WorkerExtensionSingleBrowser.js +6 -3
  38. package/commons/apkUploader/apkUploader.js +0 -46
  39. package/commons/apkUploader/apkUploaderFactory.js +0 -68
  40. package/commons/apkUploader/deviceFarmApkUploader.js +0 -41
  41. package/commons/apkUploader/saucelabsApkUploader.js +0 -36
  42. package/commons/apkUploader/testObjectApkUploader.js +0 -34
  43. package/player/mobile/mobileTestPlayer.js +0 -80
  44. package/player/mobile/mobileWebDriver.js +0 -155
  45. package/player/mobile/services/frameLocatorMock.js +0 -18
  46. package/player/mobile/services/mobilePortSelector.js +0 -22
  47. package/player/mobile/services/mobileTabService.js +0 -241
  48. package/player/mobile/utils/mobileScreenshotUtils.js +0 -46
  49. package/player/mobile/utils/mobileWindowUtils.js +0 -84
  50. package/player/stepActions/mobile/android/androidLocateStepAction.js +0 -122
  51. package/player/stepActions/mobile/android/androidLongClickStepAction.js +0 -12
  52. package/player/stepActions/mobile/android/androidScrollStepAction.js +0 -134
  53. package/player/stepActions/mobile/android/androidSpecialKeyStepAction.js +0 -22
  54. package/player/stepActions/mobile/android/androidSwipeStepAction.js +0 -32
  55. package/player/stepActions/mobile/androidGlobalActionStepAction.js +0 -12
  56. package/player/stepActions/mobile/androidTapStepAction.js +0 -19
  57. package/player/stepActions/mobile/androidTextChangeStepAction.js +0 -23
  58. package/player/stepActions/mobile/ios/iosLocateStepAction.js +0 -124
  59. package/player/stepActions/mobile/ios/iosScrollStepAction.js +0 -76
  60. package/runners/AnonymousTestPlanRunner.js +0 -106
  61. package/runners/BaseRunner.js +0 -42
  62. package/runners/BaseTestPlanRunner.js +0 -194
  63. package/runners/DeviceFarmRemoteRunner.js +0 -50
  64. package/runners/SchedulerRemoteRunner.js +0 -47
  65. package/runners/strategies/BaseStrategy.js +0 -86
  66. package/runners/strategies/DeviceFarmStrategy.js +0 -195
  67. package/runners/strategies/LocalDeviceFarmStrategy.js +0 -12
  68. package/runners/strategies/LocalTestStrategy.js +0 -14
  69. package/runners/strategies/Strategy.js +0 -17
  70. package/workers/WorkerAppium.js +0 -70
@@ -1,24 +1,196 @@
1
1
  'use strict';
2
2
 
3
- const Promise = require('bluebird');
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 servicesApi = require('../commons/testimServicesApi');
8
- const BaseTestPlanRunner = require('./BaseTestPlanRunner');
9
- const { ArgError } = require('../errors');
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 extends BaseTestPlanRunner {
15
- calcTestResultStatus(tests) {
16
- const total = Object.keys(tests).length;
17
- const passed = Object.keys(tests).reduce((count, resultId) => count + (tests[resultId].success === true ? 1 : 0), 0);
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
- runTestPlans(options, branchToUse) {
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(err => {
73
+ logger.error('error running test plan', { err });
74
+ if (err instanceof StopRunOnError) {
75
+ return catchBeforeTestsFailed(executionId);
76
+ }
77
+ throw err;
78
+ });
79
+ }
80
+
81
+ async initRealDataService(projectId) {
82
+ const realDataService = new RealDataService();
83
+ await realDataService.init(projectId);
84
+ return realDataService;
85
+ }
86
+
87
+ async listenToTestCreatedInFile(realDataService, projectId, runId, testStatus) {
88
+ const childTestResults = {};
89
+ realDataService.joinToTestResultsByRunId(runId, projectId);
90
+ const promise = new Promise(resolve => {
91
+ realDataService.listenToTestResultsByRunId(runId, testResult => {
92
+ const resultId = testResult.id;
93
+ if (!testStatus.getTestResult(resultId)) {
94
+ const prevTestResult = childTestResults[resultId];
95
+ const mergedTestResult = _.merge({}, prevTestResult, testResult, { resultId });
96
+ childTestResults[resultId] = mergedTestResult;
97
+ if (!prevTestResult || prevTestResult.status !== testResult.status) {
98
+ const parentTestResult = testStatus.getTestResult(mergedTestResult.parentResultId) || { workerId: 1 };
99
+ const workerId = parentTestResult.workerId;
100
+ if ([TESTIM_RUN_STATUS.RUNNING].includes(testResult.status)) {
101
+ reporter.onTestStarted(mergedTestResult, workerId);
102
+ }
103
+ if ([TESTIM_RUN_STATUS.COMPLETED].includes(testResult.status)) {
104
+ mergedTestResult.duration = (mergedTestResult.endTime - mergedTestResult.startTime) || 0;
105
+ reporter.onTestFinished(mergedTestResult, workerId);
106
+ }
107
+ }
108
+ }
109
+
110
+ const allChildTestResultCompleted = !(Object.values(childTestResults)
111
+ .some(result => ['QUEUED', 'RUNNING'].includes(result.runnerStatus)));
112
+
113
+ const allParentTestResultCompleted = !(Object.values(testStatus.getAllTestResults())
114
+ .some(result => ['QUEUED', 'RUNNING'].includes(result.status)));
115
+
116
+ if (allChildTestResultCompleted && allParentTestResultCompleted) {
117
+ return resolve(Object.values(childTestResults));
118
+ }
119
+
120
+ if (allParentTestResultCompleted && !allChildTestResultCompleted) {
121
+ // wait 10 sec to handle race condition when parent test result (file) finished before child test result
122
+ return Bluebird.delay(10000)
123
+ .then(() => {
124
+ if (promise.isPending()) {
125
+ // TODO(Benji) we are missing the child test results here.
126
+ // we are resolving here with partial data - we should consider fetching it
127
+ // from the server
128
+ resolve(childTestResults);
129
+ }
130
+ });
131
+ }
132
+ return undefined;
133
+ });
134
+ });
135
+
136
+ return await promise;
137
+ }
138
+
139
+ async runTestPlan(beforeTests, tests, afterTests, tpOptions, testPlanName, testPlanId, branch, isAnonymous) {
140
+ const executionId = guid();
141
+ const projectId = tpOptions.project;
142
+ Logger.setExecutionId(executionId);
143
+ beforeTests.forEach(test => { test.isBeforeTestPlan = true; });
144
+ afterTests.forEach(test => { test.isAfterTestPlan = true; });
145
+ const testStatus = new TestRunStatus(_.concat(beforeTests, tests, afterTests), tpOptions, testPlanId, branch);
146
+
147
+ const configs = _(_.concat(beforeTests, tests, afterTests)).map(test => (test.overrideTestConfig && test.overrideTestConfig.name) || '').uniq().filter(Boolean)
148
+ .value();
149
+ const configName = configs && configs.length === 1 ? configs[0] : null;
150
+
151
+ const isCodeMode = tpOptions.files.length > 0;
152
+
153
+ if (isCodeMode && tpOptions.mode === constants.CLI_MODE.SELENIUM) {
154
+ // 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.
155
+ testStatus.setAsyncReporting(true);
156
+ }
157
+ const testListInfoPromise = tpOptions.lightweightMode && tpOptions.lightweightMode.onlyTestIdsNoSuite ?
158
+ { beforeTests, tests, afterTests } :
159
+ testStatus.executionStart(executionId, projectId, this.startTime, testPlanName);
160
+ let childTestResults;
161
+ if (isCodeMode) {
162
+ childTestResults = Bluebird.try(async () => {
163
+ const realDataService = await this.initRealDataService(projectId);
164
+ return this.listenToTestCreatedInFile(realDataService, projectId, executionId, testStatus);
165
+ });
166
+ }
167
+ perf.log('before testListInfoPromise');
168
+ const testListInfo = await testListInfoPromise;
169
+ if (!(tpOptions.lightweightMode && tpOptions.lightweightMode.onlyTestIdsNoSuite)) {
170
+ reporter.onTestPlanStarted(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, testPlanName, executionId, isAnonymous, configName, isCodeMode);
171
+ }
172
+
173
+ perf.log('before runTestAllPhases');
174
+ const results = await this.runTestAllPhases(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, branch, tpOptions, executionId, testStatus);
175
+ const childResults = await Bluebird.resolve(childTestResults)
176
+ .timeout(TDK_CHILD_RESULTS_TIMEOUT)
177
+ .catch(async () => {
178
+ logger.warn('timed out waiting for child resutls on websocket. using REST fallback', { projectId, executionId });
179
+ const testResults = await testimServicesApi.getRealData(projectId, 'testResult', `runId=${executionId}&sort=runOrder`);
180
+ return _.chain((testResults && testResults.data && testResults.data.docs) || [])
181
+ .groupBy('parentResultId')
182
+ .omit('undefined')
183
+ .values()
184
+ .flatten()
185
+ .value();
186
+ });
187
+ perf.log('before executionEnd');
188
+ await testStatus.executionEnd(executionId);
189
+ perf.log('after executionEnd');
190
+ return { results, executionId, testPlanName, configName, childTestResults: childResults };
191
+ }
192
+
193
+ async runTestPlans(options, branchToUse) {
22
194
  logger.info('start to run test plan', {
23
195
  options: Object.assign({}, options, { token: undefined, userParamsData: undefined }),
24
196
  branchToUse,
@@ -32,64 +204,117 @@ class TestPlanRunner extends BaseTestPlanRunner {
32
204
  const testPlansTests = {};
33
205
  const projectId = options.project;
34
206
 
35
- return this.prepareForTestResources(options)
36
- .then(() => servicesApi.getTestPlanTestList(projectId, options.testPlan, options.testPlanIds, branchToUse))
37
- .then(data => {
38
- const testPlans = data.testPlans;
39
- const testPlansData = data.testPlansData;
40
- if (!testPlans || testPlans.length === 0) {
41
- return Promise.reject(new ArgError(`no test plan to run ${options.testPlan}`));
42
- }
43
- if (!testPlansData || Object.keys(testPlansData).length === 0) {
44
- if (options.passZeroTests) {
45
- return [];
46
- }
47
- return Promise.reject(new ArgError(`no test to run in test plan ${options.testPlan}`));
48
- }
49
- return this.validateConfig(options, flattenTestListData(testPlansData))
50
- .then(() => Promise.map(testPlans, testPlan => {
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
- }
207
+ const data = await testimServicesApi.getTestPlanTestList(projectId, options.testPlan, options.testPlanIds, branchToUse);
208
+ const testPlans = data.testPlans;
209
+ const testPlansData = data.testPlansData;
210
+ if (!testPlans || testPlans.length === 0) {
211
+ throw new ArgError(`no test plan to run ${options.testPlan}`);
212
+ }
213
+ if (!testPlansData || Object.keys(testPlansData).length === 0) {
214
+ if (options.passZeroTests) {
215
+ return [];
216
+ }
217
+ throw new ArgError(`no test to run in test plan ${options.testPlan}`);
218
+ }
219
+ await validateConfig(options, flattenTestListData(testPlansData));
220
+ return await Promise.all(testPlans.map(async testPlan => {
221
+ const id = testPlan.testPlanId;
222
+ testPlansResults[id] = {};
64
223
 
65
- const testPlanName = tpOptions.overrideExecutionName || testPlan.name;
66
-
67
- return Promise.map(testPlansData[id], testPlanTests => this.runTestPlan(testPlanTests.beforeTests, testPlanTests.tests, testPlanTests.afterTests, tpOptions, testPlanName, id, branchToUse)
68
- .tap(res => {
69
- const isCodeMode = options.files.length > 0;
70
- reporter.onTestPlanFinished(res.results, testPlan.name, this.startTime, res.executionId, false, isCodeMode, res.childTestResults);
71
- testPlansResults[id][res.executionId] = res.results;
72
- })).tap(() => {
73
- const executions = Object.keys(testPlansResults[id]).map(exeId => ({
74
- executionId: exeId,
75
- status: this.calcTestResultStatus(testPlansResults[id][exeId]),
76
- }));
77
- const tests = Object.keys(testPlansResults[id]).map(exeId => testPlansResults[id][exeId]).reduce((testResult, test) => Object.assign(testResult, test), {});
78
- const success = this.calcTestResultStatus(tests);
79
- Object.assign(testPlansTests, tests);
80
- const executionId = success ? executions[0].executionId : executions.find(exec => !exec.success).executionId;
81
- return servicesApi.saveTestPlanResult(projectId, id, {
82
- success,
83
- executions,
84
- executionId,
85
- });
86
- });
87
- }));
88
- }).then(async results => {
89
- const flattenResults = _(results).flattenDeep().value();
90
- await reporter.onAllTestPlansFinished(flattenResults);
91
- return flattenResults.map(res => res.results).reduce((total, cur) => Object.assign(total, cur), {});
92
- });
224
+ const tpOptions = Object.assign({}, options);
225
+ tpOptions.baseUrl = options.baseUrl || testPlan.startUrl;
226
+ tpOptions.host = options.host;
227
+ tpOptions.port = options.port;
228
+ tpOptions.gridId = options.gridId || testPlan.gridId;
229
+
230
+ //if user pass --grid with test plan we want to use grid option instead of host and port
231
+ if (options.grid) {
232
+ delete tpOptions.gridId;
233
+ }
234
+
235
+ const testPlanName = tpOptions.overrideExecutionName || testPlan.name;
236
+ return await Promise.all(testPlansData[id].map(async testPlanTests => {
237
+ const res = await this.runTestPlan(testPlanTests.beforeTests, testPlanTests.tests, testPlanTests.afterTests, tpOptions, testPlanName, id, branchToUse);
238
+ const isCodeMode = options.files.length > 0;
239
+ reporter.onTestPlanFinished(res.results, testPlan.name, this.startTime, res.executionId, false, isCodeMode, res.childTestResults);
240
+ testPlansResults[id][res.executionId] = res.results;
241
+
242
+ const executions = Object.keys(testPlansResults[id]).map(exeId => ({
243
+ executionId: exeId,
244
+ status: calcTestResultStatus(testPlansResults[id][exeId]),
245
+ }));
246
+ const tests = Object.keys(testPlansResults[id]).map(exeId => testPlansResults[id][exeId]).reduce((testResult, test) => Object.assign(testResult, test), {});
247
+ const success = calcTestResultStatus(tests);
248
+ Object.assign(testPlansTests, tests);
249
+ const executionId = success ? executions[0].executionId : executions.find(exec => !exec.success).executionId;
250
+ await testimServicesApi.saveTestPlanResult(projectId, id, { success, executions, executionId });
251
+ return res;
252
+ }));
253
+ }));
254
+ }
255
+
256
+ async runAnonymousTestPlan(options, branchToUse) {
257
+ logger.info('start to run anonymous', {
258
+ options: Object.assign({}, options, { token: undefined }),
259
+ branchToUse,
260
+ });
261
+
262
+ perf.log('before getSuite');
263
+ const suiteResult = await getSuite(options, branchToUse);
264
+ perf.log('after getSuite');
265
+
266
+ if (!suiteResult.tests[0] || suiteResult.tests[0].length === 0) {
267
+ if (options.rerunFailedByRunId) {
268
+ throw new ArgError('No failed tests found in the provided run');
269
+ }
270
+ if (options.passZeroTests) {
271
+ return [];
272
+ }
273
+ throw new ArgError('No tests to run');
274
+ }
275
+ branchToUse = suiteResult.branch || branchToUse;
276
+ if (options.rerunFailedByRunId && !suiteResult.runName) {
277
+ if (!suiteResult.runExists) {
278
+ throw new ArgError('Invalid run ID - no such run.');
279
+ }
280
+ const isAnonymouslyNamedRun = suiteResult.runName === '';
281
+ if (isAnonymouslyNamedRun) {
282
+ suiteResult.runName = `rerun-${options.rerunFailedByRunId}`;
283
+ }
284
+ }
285
+ const testPlanName = options.overrideExecutionName || suiteResult.runName || _.concat(options.label, options.name, options.suites).join(' ');
286
+ const isAnonymous = true;
287
+ perf.log('Right before validateConfig + runAnonymousTestPlan tests map');
288
+ return await Promise.all(suiteResult.tests.map(async suiteTests => { // array of results per execution
289
+ //override result id for remote run mode and run only the first test data
290
+ if (options.resultId) {
291
+ const firstTest = _.first(suiteTests);
292
+ firstTest.resultId = options.resultId;
293
+ suiteTests = [firstTest];
294
+ }
295
+ await validateConfig(options, suiteTests);
296
+ perf.log('right before runTestPlan');
297
+ const res = await this.runTestPlan([], suiteTests, [], options, testPlanName, null, branchToUse, isAnonymous);
298
+ perf.log('right after runTestPlan');
299
+ const isCodeMode = options.files.length > 0;
300
+ await reporter.onTestPlanFinished(res.results, testPlanName, this.startTime, res.executionId, isAnonymous, isCodeMode, res.childTestResults);
301
+ return res;
302
+ }));
303
+ }
304
+
305
+ async run(options) {
306
+ const branchToUse = branchService.getCurrentBranch();
307
+ let results = [];
308
+ if (utils.hasTestPlanFlag(options)) {
309
+ results = await this.runTestPlans(options, branchToUse);
310
+ } else {
311
+ results = await this.runAnonymousTestPlan(options, branchToUse);
312
+ }
313
+ const flattenResults = _.flattenDeep(results);
314
+ perf.log('right before onAllTestPlansFinished');
315
+ await reporter.onAllTestPlansFinished(flattenResults);
316
+ perf.log('right after onAllTestPlansFinished');
317
+ return flattenResults.map(res => res.results).reduce((total, cur) => Object.assign(total, cur), {});
93
318
  }
94
319
  }
95
320
 
@@ -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
+ };
@@ -60,14 +60,14 @@ function getSerializableObject(grid) {
60
60
  };
61
61
  }
62
62
 
63
- function handleGetGridResponse(projectId, workerId, browser, options, getFun) {
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, options, () => servicesApi.getGridById(projectId, gridId, browser, executionId));
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
- return handleGetGridResponse(projectId, workerId, browser, options, () => servicesApi.getGridByProject(projectId, gridName, browser, executionId));
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
  };