UNPKG

9 kBJavaScriptView Raw
1"use strict";
2
3const Promise = require('bluebird');
4const _ = require('lodash');
5const constants = require('../commons/constants');
6const TESTIM_RUN_STATUS = constants.testStatus;
7const {CLI_MODE} = constants;
8const reporter = require("../reports/reporter");
9const RealDataService = require('../commons/socket/realDataService');
10const testimCustomToken = require('../commons/testimCustomToken');
11const BaseRunner = require('./BaseRunner');
12const TestRunStatus = require('../testRunStatus');
13const config = require('../commons/config');
14const utils = require('../utils');
15const guid = utils.guid;
16const {StopRunOnError} = require('../errors');
17const Logger = require('../commons/logger');
18const perf = require('../commons/performance-logger');
19
20class BaseTestPlanRunner extends BaseRunner {
21 constructor(strategy) {
22 super(strategy);
23 this.runTestPlan = Promise.method(this.runTestPlan);
24 }
25 runTestAllPhases(beforeTests, tests, afterTests, branchToUse, tpOptions, executionId, testStatus) {
26 let executionResults = {};
27 const authData = testimCustomToken.getTokenV3UserData();
28
29 const runBeforeTests = (beforeTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
30 const workerCount = 1;
31 const stopOnError = true;
32 return this.strategy.runTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
33 .then(beforeTestsResults => Object.assign(executionResults, beforeTestsResults));
34 };
35
36 const runTestPlanTests = (tests, testStatus, executionId, tpOptions, branchToUse, authData) => {
37 const workerCount = config.TESTIM_CONCURRENT_WORKER_COUNT || tpOptions.parallel;
38 const stopOnError = false;
39 perf.log('right before this.strategy.runTests')
40 return this.strategy.runTests(tests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
41 .log('right after this.strategy.runTests')
42 .then(testsResults => Object.assign(executionResults, testsResults));
43 };
44
45 const runAfterTests = (afterTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
46 const workerCount = 1;
47 const stopOnError = false;
48 return this.strategy.runTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
49 .then(afterTestsResults => Object.assign(executionResults, afterTestsResults));
50 };
51
52 function catchBeforeTestsFailed(executionId) {
53 return testStatus.markAllQueuedTests(executionId, utils.TestStatus.ABORTED, "aborted", false);
54 }
55
56 const sessionType = utils.getSessionType(tpOptions);
57 this.analyticsExecsStart({authData, executionId, projectId: tpOptions.project, sessionType});
58 perf.log('right before runBeforeTests');
59 return runBeforeTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData)
60 .log('right before runTestPlanTests')
61 .then(() => runTestPlanTests(tests, testStatus, executionId, tpOptions, branchToUse, authData))
62 .log('right after runTestPlanTests')
63 .then(() => runAfterTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData))
64 .then(() => executionResults)
65 .catch(StopRunOnError, () => catchBeforeTestsFailed(executionId));
66 }
67
68 prepareForTestResources(tpOptions) {
69 if (tpOptions.mode !== CLI_MODE.APPIUM && !(this.strategy.constructor.name === 'DeviceFarmStrategy')) {
70 return Promise.resolve();
71 }
72 const apkUploaderFactory = require('../commons/apkUploader/apkUploaderFactory');
73 return apkUploaderFactory.uploadApk(tpOptions);
74 }
75
76 async initRealDataService(projectId) {
77 const realDataService = new RealDataService();
78 await realDataService.init(projectId);
79 return realDataService;
80 }
81
82 async listenToTestCreatedInFile(realDataService, projectId, runId, testStatus) {
83 const childTestResults = {};
84 realDataService.joinToTestResultsByRunId(runId, projectId);
85 const promise = new Promise(resolve => {
86
87 realDataService.listenToTestResultsByRunId(runId, testResult => {
88 const resultId = testResult.id;
89 if (!testStatus.getTestResult(resultId)) {
90 const prevTestResult = childTestResults[resultId];
91 const mergedTestResult = _.merge({}, prevTestResult, testResult, {resultId});
92 childTestResults[resultId] = mergedTestResult;
93 if (!prevTestResult || prevTestResult.status !== testResult.status) {
94 const parentTestResult = testStatus.getTestResult(mergedTestResult.parentResultId) || {workerId: 1};
95 const workerId = parentTestResult.workerId;
96 if ([TESTIM_RUN_STATUS.RUNNING].includes(testResult.status)) {
97 reporter.onTestStarted(mergedTestResult, workerId);
98 }
99 if ([TESTIM_RUN_STATUS.COMPLETED].includes(testResult.status)) {
100 mergedTestResult.duration = (mergedTestResult.endTime - mergedTestResult.startTime) || 0;
101 reporter.onTestFinished(mergedTestResult, workerId);
102 }
103 }
104 }
105
106 const allChildTestResultCompleted = !(Object.values(childTestResults)
107 .some(result => ["QUEUED", "RUNNING"].includes(result.runnerStatus)));
108
109 const allParentTestResultCompleted = !(Object.values(testStatus.getAllTestResults())
110 .some(result => ["QUEUED", "RUNNING"].includes(result.status)));
111
112 if(allChildTestResultCompleted && allParentTestResultCompleted) {
113 return resolve(Object.values(childTestResults));
114 }
115
116 if(allParentTestResultCompleted && !allChildTestResultCompleted) {
117 // wait 10 sec to handle race condition when parent test result (file) finished before child test result
118 return Promise.delay(10000)
119 .then(() => {
120 if(promise.isPending()) {
121 // TODO(Benji) we are missing the child test results here.
122 // we are resolving here with partial data - we should consider fetching it
123 // from the server
124 resolve(childTestResults);
125 }
126 })
127 }
128 });
129 });
130
131 return await promise;
132 };
133
134 async runTestPlan(beforeTests, tests, afterTests, tpOptions, testPlanName, branch, isAnonymous) {
135 const executionId = guid();
136 const projectId = tpOptions.project;
137 Logger.setExecutionId(executionId);
138 beforeTests.map(test => test.isBeforeTestPlan = true);
139 afterTests.map(test => test.isAfterTestPlan = true);
140 const testStatus = new TestRunStatus(_.concat(beforeTests, tests, afterTests), tpOptions, branch);
141
142 const configs = _(_.concat(beforeTests, tests, afterTests)).map(test => test.overrideTestConfig && test.overrideTestConfig.name || "").uniq().filter(Boolean).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 = testStatus.executionStart(executionId, projectId, this.startTime, testPlanName);
152 let childTestResults = Promise.resolve();
153 if (isCodeMode) {
154 childTestResults = Promise.try(async () => {
155 const realDataService = await this.initRealDataService(projectId);
156 return this.listenToTestCreatedInFile(realDataService, projectId, executionId, testStatus);
157 });
158 }
159 const testListInfo = await testListInfoPromise;
160 reporter.onTestPlanStarted(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, testPlanName, executionId, isAnonymous, configName, isCodeMode);
161 const results = await this.runTestAllPhases(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, branch, tpOptions, executionId, testStatus);
162 const childResults = await childTestResults;
163 await testStatus.executionEnd(executionId);
164 return {results, executionId, testPlanName, configName, childTestResults: childResults };
165 }
166}
167
168module.exports = BaseTestPlanRunner;