@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.
- 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 +962 -526
- 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/stepActionRegistrar.js +6 -48
- package/player/utils/eyeSdkService.js +230 -0
- package/reports/consoleReporter.js +29 -44
- 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 +292 -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 +23 -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,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const StepAction = require('../stepAction');
|
|
4
|
-
class AndroidGlobalActionStepAction extends StepAction {
|
|
5
|
-
|
|
6
|
-
performAction() {
|
|
7
|
-
return this.driver.pressKeycode(this.step.keyCode);
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
module.exports = AndroidGlobalActionStepAction;
|
|
12
|
-
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const StepAction = require('../stepAction');
|
|
4
|
-
|
|
5
|
-
class AndroidTapStepAction extends StepAction {
|
|
6
|
-
|
|
7
|
-
performAction() {
|
|
8
|
-
const {appiumSelector, rect} = this.getTarget();
|
|
9
|
-
if (this.step.isTouch) {
|
|
10
|
-
const positionX = parseInt(rect.left + (rect.width * this.step.relativeTouch.percentX));
|
|
11
|
-
const positionY = parseInt(rect.top + (rect.height * this.step.relativeTouch.percentY));
|
|
12
|
-
return this.driver.touch(positionX, positionY);
|
|
13
|
-
}
|
|
14
|
-
return this.driver.tap(appiumSelector);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = AndroidTapStepAction;
|
|
19
|
-
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const StepAction = require('../stepAction');
|
|
4
|
-
const sessionPlayer = require('../../../commons/getSessionPlayerRequire');
|
|
5
|
-
const paramEvaluator = sessionPlayer.stepParamExpressionEvaluator;
|
|
6
|
-
|
|
7
|
-
class AndroidTextChangeStepAction extends StepAction {
|
|
8
|
-
|
|
9
|
-
computeExpression(stepActionFactory) {
|
|
10
|
-
if (paramEvaluator) {
|
|
11
|
-
return paramEvaluator.computeExpressionPromise(this.step.expression, context, this.exportsGlobal, this.exportsTest);
|
|
12
|
-
}
|
|
13
|
-
return stepActionFactory.executeStep(this.step.expression, this.context, this.frameHandler, this.exportsGlobal, null, this.exportsTest)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
performAction(stepActionFactory) {
|
|
17
|
-
return this.computeExpression(stepActionFactory)
|
|
18
|
-
.then(res => this.driver.setText(this.getTarget().seleniumElement, res.evaluatedText));
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
module.exports = AndroidTextChangeStepAction;
|
|
23
|
-
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const StepAction = require('../../stepAction');
|
|
4
|
-
const sessionPlayer = require('../../../../commons/getSessionPlayerRequire');
|
|
5
|
-
const mobileUtils = sessionPlayer.mobileUtils;
|
|
6
|
-
const { getElementAttributesMap, getXPathSelector } = require('appium-dom-utils');
|
|
7
|
-
const logger = require('../../../../commons/logger').getLogger("ios-locate-step-action");
|
|
8
|
-
|
|
9
|
-
const { JSDOM, VirtualConsole } = require('jsdom');
|
|
10
|
-
|
|
11
|
-
function createUtils(driver) {
|
|
12
|
-
return class {
|
|
13
|
-
static getFrameIdByTestimFrameId() {}
|
|
14
|
-
|
|
15
|
-
static setElementResultDataOnContext(target, result) {
|
|
16
|
-
const { dom, element, success } = result;
|
|
17
|
-
if (!success) {
|
|
18
|
-
return Promise.resolve();
|
|
19
|
-
}
|
|
20
|
-
const selector = getXPathSelector(element);
|
|
21
|
-
if (!selector) {
|
|
22
|
-
return Promise.reject(new Error('Could not build iOS selector'));
|
|
23
|
-
}
|
|
24
|
-
target.appiumSelector = selector.replace('/AppiumAUT', '');
|
|
25
|
-
|
|
26
|
-
const attributes = getElementAttributesMap(element);
|
|
27
|
-
delete attributes["testim_dom_element_id"];
|
|
28
|
-
target.attributes = attributes;
|
|
29
|
-
|
|
30
|
-
return driver.getElementBySelector(target.appiumSelector)
|
|
31
|
-
.then(seleniumResponse => {
|
|
32
|
-
return target.seleniumElement = seleniumResponse.value
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
static getElementRectangle(target) {
|
|
37
|
-
if (mobileUtils) {
|
|
38
|
-
const {attributes} = target;
|
|
39
|
-
const {x, y, width, height} = mobileUtils.getIosRect(attributes);
|
|
40
|
-
return Promise.resolve({left: x, top: y, width, height});
|
|
41
|
-
}
|
|
42
|
-
return driver.getElementRect(target);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
static getOffsets() {
|
|
46
|
-
return Promise.resolve([]);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
static htmlStringToDom(htmlString, url) {
|
|
50
|
-
|
|
51
|
-
const virtualConsole = new VirtualConsole();
|
|
52
|
-
const jsdom = new JSDOM(htmlString, {
|
|
53
|
-
virtualConsole,
|
|
54
|
-
features: {
|
|
55
|
-
FetchExternalResources: ["script", "frame", "iframe", "link", "img"]
|
|
56
|
-
},
|
|
57
|
-
contentType: "text/xml"
|
|
58
|
-
}) || {};
|
|
59
|
-
|
|
60
|
-
const {document} = jsdom.window;
|
|
61
|
-
process.nextTick(() => {
|
|
62
|
-
jsdom.window.close();
|
|
63
|
-
});
|
|
64
|
-
document.TESTIM_URL = url;
|
|
65
|
-
var elements = Array.from(document.querySelectorAll("*"));
|
|
66
|
-
elements.forEach(function (element, index) {
|
|
67
|
-
element.setAttribute("testim_dom_element_id", index);
|
|
68
|
-
});
|
|
69
|
-
return document;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
static isVisible(target) {
|
|
73
|
-
if (target.seleniumElement) {
|
|
74
|
-
return Promise.resolve(true);
|
|
75
|
-
}
|
|
76
|
-
return Promise.resolve(false);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class IosLocateStepAction extends StepAction {
|
|
83
|
-
execute() {
|
|
84
|
-
return this.driver.getSource()
|
|
85
|
-
.then(res => Promise.resolve({html: res.value}));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
static getUtils(driver) {
|
|
89
|
-
return createUtils(driver);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
static getFrameIdByTestimFrameId(...args) {
|
|
94
|
-
logger.warn('Unplanned access to getFrameIdByTestimFrameId() #Android');
|
|
95
|
-
throw new Error('Use .getUtils() instead');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
static setElementResultDataOnContext(...args) {
|
|
99
|
-
logger.warn('Unplanned access to setElementResultDataOnContext() #Android');
|
|
100
|
-
throw new Error('Use .getUtils() instead');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
static getElementRectangle(...args) {
|
|
104
|
-
logger.warn('Unplanned access to getElementRectangle() #Android');
|
|
105
|
-
throw new Error('Use .getUtils() instead');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
static getOffsets(...args) {
|
|
109
|
-
logger.warn('Unplanned access to getOffsets() #Android');
|
|
110
|
-
throw new Error('Use .getUtils() instead');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
static htmlStringToDom(...args) {
|
|
114
|
-
logger.warn('Unplanned access to htmlStringToDom() #Android');
|
|
115
|
-
throw new Error('Use .getUtils() instead');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
static isVisible(...args) {
|
|
119
|
-
logger.warn('Unplanned access to isVisible() #Android');
|
|
120
|
-
throw new Error('Use .getUtils() instead');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
module.exports = IosLocateStepAction;
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
const StepAction = require('../../stepAction');
|
|
2
|
-
const Directions = { Up: "scroll_up", Down: "scroll_down", Left: "scroll_left", Right: "scroll_right", };
|
|
3
|
-
const logger = require('../../../../commons/logger').getLogger('ios-scroll-step-action');
|
|
4
|
-
const sessionPlayer = require('../../../../commons/getSessionPlayerRequire');
|
|
5
|
-
const constants = sessionPlayer.commonConstants.stepResult;
|
|
6
|
-
|
|
7
|
-
class IosScrollStepAction extends StepAction {
|
|
8
|
-
performAction() {
|
|
9
|
-
const {step} = this;
|
|
10
|
-
const target = this.getTarget();
|
|
11
|
-
|
|
12
|
-
if (!step.isScrollToElement) {
|
|
13
|
-
throw new Error('absolute scroll not supported on iOS');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
step.scrollToElement.targetId = "scroll-to-element";
|
|
17
|
-
const scrollDirection = step.direction;
|
|
18
|
-
return this.scrollToElement(step, target, scrollDirection);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
scrollToElement(step, scrolledOnElementTarget, scrollDirection) {
|
|
22
|
-
const runTimeout = this.context.data.timeToPlayStep;
|
|
23
|
-
const runMaxTime = Date.now() + runTimeout;
|
|
24
|
-
return this.locateElementPlayer.findElements([this.step.scrollToElement], step, this.frameHandler)
|
|
25
|
-
.catch((err) => {
|
|
26
|
-
const { start, end } = this.getScrollCoordinatesFromPageScroll(scrolledOnElementTarget, scrollDirection);
|
|
27
|
-
if (Date.now() > runMaxTime) {
|
|
28
|
-
logger.log("Scroll to element reached timeout")
|
|
29
|
-
throw Object.assign(new Error("Scroll timeout reached:" + err.message), {
|
|
30
|
-
id: this.step.id,
|
|
31
|
-
success: false,
|
|
32
|
-
shouldRetry: true,
|
|
33
|
-
isTimeout: true,
|
|
34
|
-
errorType: constants.ACTION_TIMEOUT
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
return this.driver.scroll(start, end)
|
|
38
|
-
.then(() => this.scrollToElement(step, scrolledOnElementTarget, scrollDirection));
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getScrollCoordinatesFromPageScroll(target, direction, offsetFromStart = 0.75) {
|
|
43
|
-
const {rect} = target;
|
|
44
|
-
const smallOffset = 5;
|
|
45
|
-
let startX, startY, endX, endY;
|
|
46
|
-
switch (direction) {
|
|
47
|
-
case Directions.Down:
|
|
48
|
-
startX = 0;
|
|
49
|
-
startY = rect.top + (rect.height * offsetFromStart);
|
|
50
|
-
endX = 0;
|
|
51
|
-
endY = rect.top + smallOffset;
|
|
52
|
-
break;
|
|
53
|
-
case Directions.Up:
|
|
54
|
-
startX = 0;
|
|
55
|
-
startY = rect.top + (rect.height * (1 - offsetFromStart));
|
|
56
|
-
endX = 0;
|
|
57
|
-
endY = rect.top + rect.height - smallOffset;
|
|
58
|
-
break;
|
|
59
|
-
case Directions.Right:
|
|
60
|
-
startX = rect.left + (rect.width * offsetFromStart);
|
|
61
|
-
startY = 0;
|
|
62
|
-
endX = rect.left + smallOffset;
|
|
63
|
-
endY = 0;
|
|
64
|
-
break;
|
|
65
|
-
case Directions.Left:
|
|
66
|
-
startX = rect.left + (rect.width * (1 - offsetFromStart));
|
|
67
|
-
startY = 0;
|
|
68
|
-
endX = rect.left + rect.width - smallOffset;
|
|
69
|
-
endY = 0;
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
return {start: {x: startX, y: startY}, end: {x: endX, y: endY}};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
module.exports = IosScrollStepAction;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const Promise = require('bluebird');
|
|
5
|
-
|
|
6
|
-
const _ = require('lodash');
|
|
7
|
-
|
|
8
|
-
const reporter = require('../reports/reporter');
|
|
9
|
-
const logger = require('../commons/logger').getLogger('suite-runner');
|
|
10
|
-
const utils = require('../utils.js');
|
|
11
|
-
const { ArgError } = require('../errors');
|
|
12
|
-
const perf = require('../commons/performance-logger');
|
|
13
|
-
|
|
14
|
-
const BaseTestPlanRunner = require('./BaseTestPlanRunner');
|
|
15
|
-
|
|
16
|
-
class AnonymousTestPlanRunner extends BaseTestPlanRunner {
|
|
17
|
-
runAnonymousTestPlan(options, branchToUse) {
|
|
18
|
-
logger.info('start to run anonymous', {
|
|
19
|
-
options: Object.assign({}, options, { token: undefined }),
|
|
20
|
-
branchToUse,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return this.prepareForTestResources(options)
|
|
24
|
-
.log('after prepareForTestResoutces')
|
|
25
|
-
.then(() => getSuite(options, branchToUse))
|
|
26
|
-
.log('after getSuite')
|
|
27
|
-
.then((suiteResult) => {
|
|
28
|
-
if (!suiteResult.tests[0] || suiteResult.tests[0].length === 0) {
|
|
29
|
-
if (options.rerunFailedByRunId) {
|
|
30
|
-
throw new ArgError('No failed tests found in the provided run');
|
|
31
|
-
}
|
|
32
|
-
if (options.passZeroTests) {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
throw new ArgError('No tests to run');
|
|
36
|
-
}
|
|
37
|
-
branchToUse = suiteResult.branch || branchToUse;
|
|
38
|
-
if (options.rerunFailedByRunId && !suiteResult.runName) {
|
|
39
|
-
if (!suiteResult.runExists) {
|
|
40
|
-
throw new ArgError('Invalid run ID - no such run.');
|
|
41
|
-
}
|
|
42
|
-
const isAnonymouslyNamedRun = suiteResult.runName === '';
|
|
43
|
-
if (isAnonymouslyNamedRun) {
|
|
44
|
-
suiteResult.runName = `rerun-${options.rerunFailedByRunId}`;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const testPlanName = options.overrideExecutionName || suiteResult.runName || _.concat(options.label, options.name, options.suites).join(' ');
|
|
48
|
-
const isAnonymous = true;
|
|
49
|
-
perf.log('Right before validateConfig + runTestPlan Promise.map');
|
|
50
|
-
return Promise.map(suiteResult.tests, suiteTests => {
|
|
51
|
-
//override result id for remote run mode and run only the first test data
|
|
52
|
-
if (options.resultId) {
|
|
53
|
-
const firstTest = _.first(suiteTests);
|
|
54
|
-
firstTest.resultId = options.resultId;
|
|
55
|
-
suiteTests = [firstTest];
|
|
56
|
-
}
|
|
57
|
-
return this.validateConfig(options, suiteTests)
|
|
58
|
-
.log('right before runTestPlan')
|
|
59
|
-
.then(() => this.runTestPlan([], suiteTests, [], options, testPlanName, null, branchToUse, isAnonymous))
|
|
60
|
-
.log('right after runTestPlan')
|
|
61
|
-
.tap(async res => {
|
|
62
|
-
const isCodeMode = options.files.length > 0;
|
|
63
|
-
await reporter.onTestPlanFinished(res.results, testPlanName, this.startTime, res.executionId, isAnonymous, isCodeMode, res.childTestResults);
|
|
64
|
-
});
|
|
65
|
-
}).then(async results => { // array of results per execution
|
|
66
|
-
const flattenResults = _(results).flattenDeep().value();
|
|
67
|
-
perf.log('right before onAllTestPlansFinished');
|
|
68
|
-
await reporter.onAllTestPlansFinished(flattenResults);
|
|
69
|
-
perf.log('right after onAllTestPlansFinished');
|
|
70
|
-
return flattenResults.map(res => res.results).reduce((total, cur) => Object.assign(total, cur), {});
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function getSuite(options, branchToUse) {
|
|
77
|
-
if (options.lightweightMode && options.lightweightMode.onlyTestIdsNoSuite && options.testId) {
|
|
78
|
-
return { tests: [options.testId.map(testId => ({ testId, testConfig: { }, resultId: utils.guid() }))] };
|
|
79
|
-
}
|
|
80
|
-
// local code test
|
|
81
|
-
if (options.files.length > 0) {
|
|
82
|
-
const { buildCodeTests } = require('./buildCodeTests');
|
|
83
|
-
let webpackConfig = {};
|
|
84
|
-
if (options.webpackConfig) {
|
|
85
|
-
const webpackConfigPath = path.join(process.cwd(), options.webpackConfig);
|
|
86
|
-
webpackConfig = require(webpackConfigPath);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return buildCodeTests(options.files, webpackConfig, { baseUrl: options.baseUrl });
|
|
90
|
-
}
|
|
91
|
-
// regular test
|
|
92
|
-
const servicesApi = require('../commons/testimServicesApi');
|
|
93
|
-
return await servicesApi.getSuiteTestList({
|
|
94
|
-
projectId: options.project,
|
|
95
|
-
labels: options.label,
|
|
96
|
-
testIds: options.testId,
|
|
97
|
-
testNames: options.name,
|
|
98
|
-
testConfigNames: options.testConfigNames,
|
|
99
|
-
suiteNames: options.suites,
|
|
100
|
-
suiteIds: options.suiteIds,
|
|
101
|
-
branch: branchToUse,
|
|
102
|
-
rerunFailedByRunId: options.rerunFailedByRunId,
|
|
103
|
-
testConfigIds: options.testConfigIds,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
module.exports = AnonymousTestPlanRunner;
|
package/runners/BaseRunner.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
const Promise = require('bluebird');
|
|
2
|
-
const _ = require('lodash');
|
|
3
|
-
const utils = require('../utils');
|
|
4
|
-
const testimCustomToken = require('../commons/testimCustomToken');
|
|
5
|
-
const analytics = require('../commons/testimAnalytics');
|
|
6
|
-
const {ArgError} = require('../errors');
|
|
7
|
-
|
|
8
|
-
class BaseRunner {
|
|
9
|
-
constructor(strategy) {
|
|
10
|
-
this.strategy = strategy;
|
|
11
|
-
this.startTime = Date.now();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
analyticsExecsStart({ executionId, projectId, sessionType }) {
|
|
15
|
-
analytics.trackWithCIUser('batch-run-ci', {
|
|
16
|
-
executionId,
|
|
17
|
-
projectId,
|
|
18
|
-
sessionType,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
validateConfig(options, testList) {
|
|
23
|
-
let supportedBrowsers = options.mode === 'extension' ? [
|
|
24
|
-
'edge-chromium', 'chrome',
|
|
25
|
-
] : [
|
|
26
|
-
'ie11', 'firefox', 'chrome', 'edge', 'edge-chromium', 'safari', 'safari technology preview', 'browser', 'android', 'ios', 'iphone', 'ipad',
|
|
27
|
-
];
|
|
28
|
-
let diff = _.difference(utils.getUniqBrowsers(options, testList), supportedBrowsers);
|
|
29
|
-
|
|
30
|
-
if (diff.length > 0) {
|
|
31
|
-
analytics.trackWithCIUser('invalid-config-run', {
|
|
32
|
-
browser: diff.join(", "),
|
|
33
|
-
mode: 'runner'
|
|
34
|
-
});
|
|
35
|
-
return Promise.reject(new ArgError(`browser type <${diff}> is not supported in ${options.mode} mode.`));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return Promise.resolve(testList);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
module.exports = BaseRunner;
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const Promise = require('bluebird');
|
|
4
|
-
const _ = require('lodash');
|
|
5
|
-
const constants = require('../commons/constants');
|
|
6
|
-
|
|
7
|
-
const TESTIM_RUN_STATUS = constants.testRunStatus;
|
|
8
|
-
const { CLI_MODE } = constants;
|
|
9
|
-
const reporter = require('../reports/reporter');
|
|
10
|
-
const RealDataService = require('../commons/socket/realDataService');
|
|
11
|
-
const testimServicesApi = require('../commons/testimServicesApi');
|
|
12
|
-
const testimCustomToken = require('../commons/testimCustomToken');
|
|
13
|
-
const BaseRunner = require('./BaseRunner');
|
|
14
|
-
const TestRunStatus = require('../testRunStatus');
|
|
15
|
-
const config = require('../commons/config');
|
|
16
|
-
const utils = require('../utils');
|
|
17
|
-
const { StopRunOnError } = require('../errors');
|
|
18
|
-
const Logger = require('../commons/logger');
|
|
19
|
-
const perf = require('../commons/performance-logger');
|
|
20
|
-
|
|
21
|
-
const guid = utils.guid;
|
|
22
|
-
const _logger = Logger.getLogger('base-test-plan-runner');
|
|
23
|
-
const TDK_CHILD_RESULTS_TIMEOUT = 1000 * 60 * 5;
|
|
24
|
-
|
|
25
|
-
class BaseTestPlanRunner extends BaseRunner {
|
|
26
|
-
constructor(strategy) {
|
|
27
|
-
super(strategy);
|
|
28
|
-
this.runTestPlan = Promise.method(this.runTestPlan);
|
|
29
|
-
}
|
|
30
|
-
runTestAllPhases(beforeTests, tests, afterTests, branchToUse, tpOptions, executionId, testStatus) {
|
|
31
|
-
const executionResults = {};
|
|
32
|
-
const authData = testimCustomToken.getTokenV3UserData();
|
|
33
|
-
|
|
34
|
-
const runBeforeTests = (beforeTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
35
|
-
const workerCount = 1;
|
|
36
|
-
const stopOnError = true;
|
|
37
|
-
return this.strategy.runTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
38
|
-
.then(beforeTestsResults => Object.assign(executionResults, beforeTestsResults));
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const runTestPlanTests = (tests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
42
|
-
const workerCount = config.TESTIM_CONCURRENT_WORKER_COUNT || tpOptions.parallel;
|
|
43
|
-
const stopOnError = false;
|
|
44
|
-
perf.log('right before this.strategy.runTests');
|
|
45
|
-
return this.strategy.runTests(tests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
46
|
-
.log('right after this.strategy.runTests')
|
|
47
|
-
.then(testsResults => Object.assign(executionResults, testsResults));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const runAfterTests = (afterTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
51
|
-
const workerCount = 1;
|
|
52
|
-
const stopOnError = false;
|
|
53
|
-
return this.strategy.runTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
54
|
-
.then(afterTestsResults => Object.assign(executionResults, afterTestsResults));
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
function catchBeforeTestsFailed(executionId) {
|
|
58
|
-
return testStatus.markAllQueuedTests(executionId, constants.runnerTestStatus.ABORTED, 'aborted', false);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const sessionType = utils.getSessionType(tpOptions);
|
|
62
|
-
this.analyticsExecsStart({ authData, executionId, projectId: tpOptions.project, sessionType });
|
|
63
|
-
perf.log('right before runBeforeTests');
|
|
64
|
-
return runBeforeTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData)
|
|
65
|
-
.log('right before runTestPlanTests')
|
|
66
|
-
.then(() => runTestPlanTests(tests, testStatus, executionId, tpOptions, branchToUse, authData))
|
|
67
|
-
.log('right after runTestPlanTests')
|
|
68
|
-
.then(() => runAfterTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData))
|
|
69
|
-
.then(() => executionResults)
|
|
70
|
-
.catch(StopRunOnError, () => catchBeforeTestsFailed(executionId));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
prepareForTestResources(tpOptions) {
|
|
74
|
-
if (tpOptions.mode !== CLI_MODE.APPIUM && !(this.strategy.constructor.name === 'DeviceFarmStrategy')) {
|
|
75
|
-
return Promise.resolve();
|
|
76
|
-
}
|
|
77
|
-
const apkUploaderFactory = require('../commons/apkUploader/apkUploaderFactory');
|
|
78
|
-
return apkUploaderFactory.uploadApk(tpOptions);
|
|
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 Promise.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 = Promise.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 Promise.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
|
-
|
|
194
|
-
module.exports = BaseTestPlanRunner;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const utils = require('../utils');
|
|
4
|
-
const config = require('../commons/config');
|
|
5
|
-
const testimCustomToken = require('../commons/testimCustomToken');
|
|
6
|
-
const BaseRunner = require('./BaseRunner');
|
|
7
|
-
const TestRunStatus = require('../testRunStatus');
|
|
8
|
-
const reporter = require("../reports/reporter");
|
|
9
|
-
const Logger = require('../commons/logger');
|
|
10
|
-
const gridService = require('../services/gridService');
|
|
11
|
-
const logger = Logger.getLogger('remote-test-runner');
|
|
12
|
-
|
|
13
|
-
class DeviceFarmRemoteRunner extends BaseRunner {
|
|
14
|
-
runExecution(testList, testStatus, executionId, options, branchToUse) {
|
|
15
|
-
const projectId = options.project;
|
|
16
|
-
const sessionType = utils.getSessionType(options);
|
|
17
|
-
// report exec start analytics
|
|
18
|
-
const authData = testimCustomToken.getTokenV3UserData();
|
|
19
|
-
this.analyticsExecsStart({authData, executionId, projectId, sessionType});
|
|
20
|
-
const workerCount = config.TESTIM_CONCURRENT_WORKER_COUNT || options.parallel;
|
|
21
|
-
|
|
22
|
-
return this.strategy.runTests(testList, testStatus, executionId, options, branchToUse, authData, workerCount);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
runTest(executionId, testList, gridInfo, branchToUse, options) {
|
|
26
|
-
Logger.setExecutionId(executionId);
|
|
27
|
-
|
|
28
|
-
logger.info("start to remote runner", {
|
|
29
|
-
executionId: executionId,
|
|
30
|
-
options: Object.assign({}, options, {token: undefined}),
|
|
31
|
-
branchToUse: branchToUse,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const testPlanName = "remote run";
|
|
35
|
-
const testStatus = new TestRunStatus(testList, options, null, branchToUse);
|
|
36
|
-
reporter.onTestPlanStarted([], testList, [], testPlanName, executionId);
|
|
37
|
-
|
|
38
|
-
const workerId = 1;
|
|
39
|
-
const browserType = options.projectData.type; //android or ios
|
|
40
|
-
const {gridId, slotId} = gridInfo;
|
|
41
|
-
gridService.addItemToGridCache(workerId, gridId, slotId, browserType);
|
|
42
|
-
options.disableWindowAnimation = options.remoteRunObject.echoedOptions.disableWindowAnimation
|
|
43
|
-
options.baseUrl = options.remoteRunObject.echoedOptions.baseUrl
|
|
44
|
-
return this.runExecution(testList, testStatus, executionId, options, branchToUse)
|
|
45
|
-
.tap(results => reporter.onTestPlanFinished(results, testPlanName, this.startTime, executionId))
|
|
46
|
-
.then(combinedTestResults => combinedTestResults);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
module.exports = DeviceFarmRemoteRunner;
|