@testim/testim-cli 3.196.0 → 3.197.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 (66) 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/socket/testResultService.js +4 -14
  7. package/commons/testimAnalytics.js +0 -1
  8. package/commons/testimDesiredCapabilitiesBuilder.js +0 -94
  9. package/commons/testimServicesApi.js +9 -79
  10. package/executionQueue.js +7 -4
  11. package/npm-shrinkwrap.json +948 -512
  12. package/package.json +3 -1
  13. package/player/stepActions/baseJsStepAction.js +5 -1
  14. package/player/stepActions/pixelValidationStepAction.js +28 -0
  15. package/player/stepActions/salesforceAutoLoginStepAction.js +5 -3
  16. package/player/stepActions/stepActionRegistrar.js +3 -48
  17. package/player/utils/eyeSdkService.js +230 -0
  18. package/reports/consoleReporter.js +0 -20
  19. package/reports/reporter.js +0 -21
  20. package/runOptions.js +13 -89
  21. package/runner.js +3 -44
  22. package/runners/{strategies/LocalStrategy.js → ParallelWorkerManager.js} +59 -68
  23. package/runners/TestPlanRunner.js +286 -67
  24. package/runners/runnerUtils.js +73 -0
  25. package/services/analyticsService.js +94 -0
  26. package/services/gridService.js +24 -16
  27. package/services/gridService.test.js +21 -21
  28. package/testRunHandler.js +1 -1
  29. package/testRunStatus.js +13 -9
  30. package/utils.js +5 -5
  31. package/workers/BaseWorker.js +38 -39
  32. package/workers/BaseWorker.test.js +1 -1
  33. package/workers/WorkerExtensionSingleBrowser.js +6 -3
  34. package/commons/apkUploader/apkUploader.js +0 -46
  35. package/commons/apkUploader/apkUploaderFactory.js +0 -68
  36. package/commons/apkUploader/deviceFarmApkUploader.js +0 -41
  37. package/commons/apkUploader/saucelabsApkUploader.js +0 -36
  38. package/commons/apkUploader/testObjectApkUploader.js +0 -34
  39. package/player/mobile/mobileTestPlayer.js +0 -80
  40. package/player/mobile/mobileWebDriver.js +0 -155
  41. package/player/mobile/services/frameLocatorMock.js +0 -18
  42. package/player/mobile/services/mobilePortSelector.js +0 -22
  43. package/player/mobile/services/mobileTabService.js +0 -241
  44. package/player/mobile/utils/mobileScreenshotUtils.js +0 -46
  45. package/player/mobile/utils/mobileWindowUtils.js +0 -84
  46. package/player/stepActions/mobile/android/androidLocateStepAction.js +0 -122
  47. package/player/stepActions/mobile/android/androidLongClickStepAction.js +0 -12
  48. package/player/stepActions/mobile/android/androidScrollStepAction.js +0 -134
  49. package/player/stepActions/mobile/android/androidSpecialKeyStepAction.js +0 -22
  50. package/player/stepActions/mobile/android/androidSwipeStepAction.js +0 -32
  51. package/player/stepActions/mobile/androidGlobalActionStepAction.js +0 -12
  52. package/player/stepActions/mobile/androidTapStepAction.js +0 -19
  53. package/player/stepActions/mobile/androidTextChangeStepAction.js +0 -23
  54. package/player/stepActions/mobile/ios/iosLocateStepAction.js +0 -124
  55. package/player/stepActions/mobile/ios/iosScrollStepAction.js +0 -76
  56. package/runners/AnonymousTestPlanRunner.js +0 -106
  57. package/runners/BaseRunner.js +0 -42
  58. package/runners/BaseTestPlanRunner.js +0 -194
  59. package/runners/DeviceFarmRemoteRunner.js +0 -50
  60. package/runners/SchedulerRemoteRunner.js +0 -47
  61. package/runners/strategies/BaseStrategy.js +0 -86
  62. package/runners/strategies/DeviceFarmStrategy.js +0 -195
  63. package/runners/strategies/LocalDeviceFarmStrategy.js +0 -12
  64. package/runners/strategies/LocalTestStrategy.js +0 -14
  65. package/runners/strategies/Strategy.js +0 -17
  66. package/workers/WorkerAppium.js +0 -70
@@ -1,24 +1,190 @@
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(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
- 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
- }
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
- 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
- });
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
+ };
@@ -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
  };