@testim/testim-cli 3.194.0 → 3.198.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/cli/onExit.js +12 -1
- package/cli.js +5 -1
- package/commons/constants.js +0 -25
- package/commons/featureFlags.js +2 -0
- package/commons/npmWrapper.js +46 -14
- package/commons/npmWrapper.test.js +182 -6
- package/commons/socket/testResultService.js +4 -14
- package/commons/testimAnalytics.js +0 -1
- package/commons/testimDesiredCapabilitiesBuilder.js +0 -94
- package/commons/testimServicesApi.js +9 -79
- package/executionQueue.js +7 -4
- package/npm-shrinkwrap.json +959 -523
- package/package.json +3 -1
- package/player/stepActions/baseJsStepAction.js +5 -1
- package/player/stepActions/pixelValidationStepAction.js +28 -0
- package/player/stepActions/salesforceApexActionStepAction.js +9 -0
- package/player/stepActions/salesforceAutoLoginStepAction.js +5 -3
- package/player/stepActions/scripts/focusElement.js +5 -3
- package/player/stepActions/stepActionRegistrar.js +6 -48
- package/player/utils/eyeSdkService.js +230 -0
- package/reports/consoleReporter.js +0 -20
- package/reports/reporter.js +0 -21
- package/runOptions.js +13 -89
- package/runner.js +3 -44
- package/runners/{strategies/LocalStrategy.js → ParallelWorkerManager.js} +59 -68
- package/runners/TestPlanRunner.js +286 -67
- package/runners/runnerUtils.js +73 -0
- package/services/analyticsService.js +94 -0
- package/services/gridService.js +24 -16
- package/services/gridService.test.js +21 -21
- package/services/lambdatestService.js +1 -1
- package/testRunHandler.js +16 -2
- package/testRunStatus.js +17 -13
- package/utils.js +5 -5
- package/workers/BaseWorker.js +39 -39
- package/workers/BaseWorker.test.js +1 -1
- package/workers/WorkerExtensionSingleBrowser.js +6 -3
- package/commons/apkUploader/apkUploader.js +0 -46
- package/commons/apkUploader/apkUploaderFactory.js +0 -68
- package/commons/apkUploader/deviceFarmApkUploader.js +0 -41
- package/commons/apkUploader/saucelabsApkUploader.js +0 -36
- package/commons/apkUploader/testObjectApkUploader.js +0 -34
- package/player/mobile/mobileTestPlayer.js +0 -80
- package/player/mobile/mobileWebDriver.js +0 -155
- package/player/mobile/services/frameLocatorMock.js +0 -18
- package/player/mobile/services/mobilePortSelector.js +0 -22
- package/player/mobile/services/mobileTabService.js +0 -241
- package/player/mobile/utils/mobileScreenshotUtils.js +0 -46
- package/player/mobile/utils/mobileWindowUtils.js +0 -84
- package/player/stepActions/mobile/android/androidLocateStepAction.js +0 -122
- package/player/stepActions/mobile/android/androidLongClickStepAction.js +0 -12
- package/player/stepActions/mobile/android/androidScrollStepAction.js +0 -134
- package/player/stepActions/mobile/android/androidSpecialKeyStepAction.js +0 -22
- package/player/stepActions/mobile/android/androidSwipeStepAction.js +0 -32
- package/player/stepActions/mobile/androidGlobalActionStepAction.js +0 -12
- package/player/stepActions/mobile/androidTapStepAction.js +0 -19
- package/player/stepActions/mobile/androidTextChangeStepAction.js +0 -23
- package/player/stepActions/mobile/ios/iosLocateStepAction.js +0 -124
- package/player/stepActions/mobile/ios/iosScrollStepAction.js +0 -76
- package/runners/AnonymousTestPlanRunner.js +0 -106
- package/runners/BaseRunner.js +0 -42
- package/runners/BaseTestPlanRunner.js +0 -194
- package/runners/DeviceFarmRemoteRunner.js +0 -50
- package/runners/SchedulerRemoteRunner.js +0 -47
- package/runners/strategies/BaseStrategy.js +0 -86
- package/runners/strategies/DeviceFarmStrategy.js +0 -195
- package/runners/strategies/LocalDeviceFarmStrategy.js +0 -12
- package/runners/strategies/LocalTestStrategy.js +0 -14
- package/runners/strategies/Strategy.js +0 -17
- package/workers/WorkerAppium.js +0 -70
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const crypto = require('crypto');
|
|
3
|
-
const ApkUploader = require('./apkUploader');
|
|
4
|
-
const httpRequest = require('../httpRequest');
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
|
-
const fs = Promise.promisifyAll(require('fs'));
|
|
7
|
-
const testimServicesApi = require('../testimServicesApi');
|
|
8
|
-
|
|
9
|
-
class DeviceFarmApkUploader extends ApkUploader {
|
|
10
|
-
constructor() {
|
|
11
|
-
super();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
uploadFile({arn}, apkFullPath, apkTimeout, projectId) {
|
|
15
|
-
let apkPathObj = {};
|
|
16
|
-
try {
|
|
17
|
-
apkPathObj = path.parse(apkFullPath);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
return Promise.reject(new TypeError('Apk path is not valid.'));
|
|
20
|
-
}
|
|
21
|
-
return fs.readFileAsync(apkFullPath)
|
|
22
|
-
.then(apkBuffer => {
|
|
23
|
-
const apkHash = crypto.createHash('md5').update(apkBuffer).digest("hex");
|
|
24
|
-
const apkName = `${apkHash}${apkPathObj.ext}`;
|
|
25
|
-
return Promise.all([testimServicesApi.getAppUploadUrl(projectId, arn, apkName), apkBuffer]);
|
|
26
|
-
})
|
|
27
|
-
.spread((app, apkBuffer) => {
|
|
28
|
-
if(app.status === 'SUCCEEDED') {
|
|
29
|
-
return app.arn;
|
|
30
|
-
}
|
|
31
|
-
return httpRequest.put(app.url, apkBuffer, {"Content-Type": "application/octet-stream"}, apkTimeout)
|
|
32
|
-
.then(() => app.arn);
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getAppCaps() {
|
|
37
|
-
return {appArn: this._appId};
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
module.exports = new DeviceFarmApkUploader();
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const utils = require('../../utils');
|
|
2
|
-
const ApkUploader = require('./apkUploader');
|
|
3
|
-
const httpRequest = require('../httpRequest');
|
|
4
|
-
const Promise = require('bluebird');
|
|
5
|
-
const fs = Promise.promisifyAll(require('fs'));
|
|
6
|
-
const path = require('path');
|
|
7
|
-
|
|
8
|
-
const SAUCE_LABS_API_URL = 'https://saucelabs.com/rest';
|
|
9
|
-
|
|
10
|
-
class SaucelabsApkUploader extends ApkUploader {
|
|
11
|
-
constructor() {
|
|
12
|
-
super();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
uploadFile({ user, key }, apkFullPath, apkTimeout) {
|
|
16
|
-
const authHeader = utils.buildBasicHeader(user, key);
|
|
17
|
-
const fileName = `${Date.now()}_${path.basename(apkFullPath)}`;
|
|
18
|
-
return fs.readFileAsync(apkFullPath)
|
|
19
|
-
.then(apkBuffer => httpRequest.post({
|
|
20
|
-
url: `${SAUCE_LABS_API_URL}/v1/storage/${user}/${fileName}?overwrite=true`,
|
|
21
|
-
body: apkBuffer,
|
|
22
|
-
headers: {
|
|
23
|
-
Authorization: authHeader,
|
|
24
|
-
'Content-Type': 'application/octet-stream',
|
|
25
|
-
},
|
|
26
|
-
timeout: apkTimeout,
|
|
27
|
-
}))
|
|
28
|
-
.then(res => res.filename);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getAppCaps() {
|
|
32
|
-
return { app: `sauce-storage:${this._appId}` };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
module.exports = new SaucelabsApkUploader();
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const utils = require('../../utils');
|
|
2
|
-
const ApkUploader = require('./apkUploader');
|
|
3
|
-
const httpRequest = require('../httpRequest');
|
|
4
|
-
const Promise = require('bluebird');
|
|
5
|
-
const fs = Promise.promisifyAll(require('fs'));
|
|
6
|
-
|
|
7
|
-
const TEST_OBJECT_API_URL = 'https://app.testobject.com/api';
|
|
8
|
-
|
|
9
|
-
class TestObjectApkUploader extends ApkUploader {
|
|
10
|
-
constructor() {
|
|
11
|
-
super();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
uploadFile({ key }, apkFullPath, apkTimeout) {
|
|
15
|
-
const authHeader = utils.buildBasicHeader('', key);
|
|
16
|
-
return fs.readFileAsync(apkFullPath)
|
|
17
|
-
.then(apkBuffer => httpRequest.post({
|
|
18
|
-
url: `${TEST_OBJECT_API_URL}/storage/upload`,
|
|
19
|
-
body: apkBuffer,
|
|
20
|
-
headers: {
|
|
21
|
-
Authorization: authHeader,
|
|
22
|
-
'Content-Type': 'application/octet-stream',
|
|
23
|
-
'App-Identifier': String(Date.now()),
|
|
24
|
-
},
|
|
25
|
-
timeout: apkTimeout,
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
getAppCaps() {
|
|
30
|
-
return { testobject_app_id: this._appId };
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
module.exports = new TestObjectApkUploader();
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const MobileTabService = require('./services/mobileTabService');
|
|
4
|
-
const MobilePortSelector = require('./services/mobilePortSelector');
|
|
5
|
-
|
|
6
|
-
const windowCreationListener = require('../services/windowCreationListener');
|
|
7
|
-
const CookieUtils = () => {};
|
|
8
|
-
const StepActionUtils = require('../utils/stepActionUtils');
|
|
9
|
-
const downloadsApiUtils = require('../utils/downloadsApiUtils');
|
|
10
|
-
const FrameLocatorMock = require('./services/frameLocatorMock');
|
|
11
|
-
|
|
12
|
-
const sessionPlayer = require('../../commons/getSessionPlayerRequire');
|
|
13
|
-
|
|
14
|
-
const player = sessionPlayer.sessionPlayer;
|
|
15
|
-
// delete after https://github.com/testimio/clickim/pull/3430 release to the store
|
|
16
|
-
const assetService = sessionPlayer.assetService;
|
|
17
|
-
const StepActionFactory = sessionPlayer.stepActionFactory;
|
|
18
|
-
const PlaybackTimeoutCalculator = require('../services/playbackTimeoutCalculator');
|
|
19
|
-
|
|
20
|
-
const MobileWebDriver = require('./mobileWebDriver');
|
|
21
|
-
// delete after https://github.com/testimio/clickim/pull/3430 release to the store
|
|
22
|
-
const CryptoJS = require('crypto-js');
|
|
23
|
-
|
|
24
|
-
class MobileTestPlayer {
|
|
25
|
-
constructor(id, userParamsData, projectType) {
|
|
26
|
-
this.driver = new MobileWebDriver();
|
|
27
|
-
this.id = id;
|
|
28
|
-
const stepActionUtils = new StepActionUtils(this.driver);
|
|
29
|
-
this.stepActionFactory = new StepActionFactory(stepActionUtils);
|
|
30
|
-
require('../stepActions/stepActionRegistrar')(this.driver, this.stepActionFactory, projectType);
|
|
31
|
-
|
|
32
|
-
if (assetService.setMd5) {
|
|
33
|
-
// delete after https://github.com/testimio/clickim/pull/3430 release to the store
|
|
34
|
-
assetService.setMd5(CryptoJS);
|
|
35
|
-
}
|
|
36
|
-
this.mobileTabService = MobileTabService(this.driver);
|
|
37
|
-
this.windowCreationListener = windowCreationListener;
|
|
38
|
-
this.playbackTimeoutCalculator = new PlaybackTimeoutCalculator();
|
|
39
|
-
|
|
40
|
-
this.mobileTabService.createSession(id);
|
|
41
|
-
|
|
42
|
-
this.sessionPlayer = new player(id, this.mobileTabService, CookieUtils(this.driver),
|
|
43
|
-
windowCreationListener, FrameLocatorMock, MobilePortSelector,
|
|
44
|
-
null, downloadsApiUtils, stepActionUtils,
|
|
45
|
-
this.stepActionFactory, this.playbackTimeoutCalculator);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.sessionPlayer.playbackManager.isRemoteSession = true;
|
|
49
|
-
this.sessionPlayer.playbackManager.isLocalRun = false;
|
|
50
|
-
|
|
51
|
-
this.sessionPlayer.playbackManager.userParamsData = userParamsData || {};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
getSessionId() {
|
|
55
|
-
return this.driver.getSessionId();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
onDone() {
|
|
59
|
-
return this.driver.end()
|
|
60
|
-
.finally(() => {
|
|
61
|
-
this.sessionPlayer = null;
|
|
62
|
-
this.mobileTabService = null;
|
|
63
|
-
this.stepActionFactory = null;
|
|
64
|
-
this.driver = null;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
clearSessionTabs() {
|
|
69
|
-
this.mobileTabService.clearAllTabs(this.id);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
addTab(openerStepId) {
|
|
73
|
-
return this.mobileTabService.addNewTab(this.id, 0, openerStepId)
|
|
74
|
-
.then(() => {
|
|
75
|
-
return this.sessionPlayer.addPlaybackFrameHandler(0);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
module.exports = MobileTestPlayer;
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const Promise = require('bluebird');
|
|
4
|
-
const desiredCapabilitiesBuilder = require('../../commons/testimDesiredCapabilitiesBuilder');
|
|
5
|
-
const SeleniumError = require('../../errors').SeleniumError;
|
|
6
|
-
const ArgError = require('../../errors').ArgError;
|
|
7
|
-
const Webdriver = require('../webdriver');
|
|
8
|
-
const { extractElementId } = require('../../utils');
|
|
9
|
-
const logger = require('../../commons/logger').getLogger("mobile-web-driver");
|
|
10
|
-
|
|
11
|
-
class MobileWebDriver extends Webdriver {
|
|
12
|
-
|
|
13
|
-
init(browserOptions, gridInfo, nativeAppData, testName, testResultId) {
|
|
14
|
-
const capabilities = desiredCapabilitiesBuilder.buildAppiumOptions(browserOptions, gridInfo, nativeAppData, testName);
|
|
15
|
-
const packageName = nativeAppData.packageName;
|
|
16
|
-
|
|
17
|
-
return this.initClient(capabilities, testName, testResultId)
|
|
18
|
-
.then(() => this.switchToNativeContext())
|
|
19
|
-
.then(() => this.isAppInstalled(packageName))
|
|
20
|
-
.then(isInstalled => {
|
|
21
|
-
if (!isInstalled) {
|
|
22
|
-
return Promise.reject(new ArgError("Application not installed! " + packageName));
|
|
23
|
-
}
|
|
24
|
-
return this.launch();
|
|
25
|
-
})
|
|
26
|
-
.catch(err => {
|
|
27
|
-
if (err.seleniumStack) {
|
|
28
|
-
return Promise.reject(new SeleniumError(err.seleniumStack));
|
|
29
|
-
}
|
|
30
|
-
if (err instanceof ArgError) {
|
|
31
|
-
return Promise.reject(err);
|
|
32
|
-
}
|
|
33
|
-
return Promise.reject(new Error("failed to init client driver"));
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
//Override methods
|
|
38
|
-
|
|
39
|
-
getElementLocation(target) {
|
|
40
|
-
return this.elementIdLocation(extractElementId(target.seleniumElement));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
setText(el, text) {
|
|
44
|
-
return this.setImmediateValue(extractElementId(el), text);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async getTargetText(target) {
|
|
48
|
-
return target.attributes.text || target.attributes['content-desc'] || "";
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//End override methods
|
|
52
|
-
|
|
53
|
-
switchToNativeContext() {
|
|
54
|
-
return this.context("NATIVE_APP");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
longClick(selector) {
|
|
58
|
-
const attr = [{
|
|
59
|
-
action: 'longPress',
|
|
60
|
-
selector
|
|
61
|
-
}];
|
|
62
|
-
|
|
63
|
-
return this.touchAction(attr);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
tap(selector) {
|
|
67
|
-
let attr = [{
|
|
68
|
-
action: 'tap',
|
|
69
|
-
selector: selector
|
|
70
|
-
}];
|
|
71
|
-
|
|
72
|
-
return this.touchAction(attr);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
touch(x, y) {
|
|
76
|
-
let attr = {
|
|
77
|
-
action: 'tap',
|
|
78
|
-
x: x,
|
|
79
|
-
y: y
|
|
80
|
-
};
|
|
81
|
-
return this.touchAction(attr);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
scroll(start, end, duration = 1000) {
|
|
85
|
-
// https://github.com/appium/appium-android-driver/blob/master/lib/commands/touch.js (doTouchDrag)
|
|
86
|
-
const down = {action: 'longPress', options: {x: parseInt(start.x), y: parseInt(start.y), duration}};
|
|
87
|
-
const move = {action: 'moveTo', options: {x: parseInt(end.x), y: parseInt(end.y)}};
|
|
88
|
-
const up = {action: 'release'};
|
|
89
|
-
return this.touchPerform([down, move, up]);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
swipe(start, end, duration = 250) {
|
|
93
|
-
// https://github.com/appium/appium-android-driver/blob/master/lib/commands/touch.js
|
|
94
|
-
const down = {action: 'press', options: {x: parseInt(start.x), y: parseInt(start.y)}};
|
|
95
|
-
const wait = {action: 'wait', options: {ms: duration}};
|
|
96
|
-
const move = {action: 'moveTo', options: {x: parseInt(end.x), y: parseInt(end.y)}};
|
|
97
|
-
const up = {action: 'release'};
|
|
98
|
-
return this.touchPerform([down, wait, move, up]);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
executeImeAction(action) {
|
|
102
|
-
// Required Appium version: 1.9.2.
|
|
103
|
-
// http://appium.io/docs/en/writing-running-appium/android/android-ime/
|
|
104
|
-
return this.executeJS('mobile: performEditorAction', {action});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
getDeviceInfo() {
|
|
108
|
-
// http://appium.io/docs/en/commands/mobile-command/
|
|
109
|
-
return this.executeJS('mobile:deviceInfo')
|
|
110
|
-
.catch(err => {
|
|
111
|
-
logger.warn(`getDeviceInfo failed`, { err });
|
|
112
|
-
return {};
|
|
113
|
-
})
|
|
114
|
-
.then(deviceInfo => {
|
|
115
|
-
if (deviceInfo.value && deviceInfo.value.model && deviceInfo.value.platformVersion && deviceInfo.value.apiVersion) {
|
|
116
|
-
return {
|
|
117
|
-
'device': deviceInfo.value.model,
|
|
118
|
-
'os': deviceInfo.value.platformVersion + ` (API ${deviceInfo.value.apiVersion})`
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
return {};
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
getPackageInfo(packageName) {
|
|
126
|
-
// http://appium.io/docs/en/commands/mobile-command/
|
|
127
|
-
// Android only. Executes ADB shell commands (appium's "Relaxed Security" must be enabled)
|
|
128
|
-
return this.executeJS('mobile:shell', { 'command': `dumpsys package ${packageName} | grep -E \"versionName|versionCode\"` })
|
|
129
|
-
.catch(err => {
|
|
130
|
-
logger.warn(`getPackageInfo failed at appium command mobile:shell`, { packageName, err });
|
|
131
|
-
return {};
|
|
132
|
-
})
|
|
133
|
-
.then(shellResult => {
|
|
134
|
-
try {
|
|
135
|
-
// Shell result from ADB looks like `versionCode=620 minSdk=24 targetSdk=28\n versionName=0.6.2`
|
|
136
|
-
const packageInfo = shellResult.value.split(/\s/).filter(Boolean).reduce((obj, item) => {
|
|
137
|
-
const [key, value] = item.split('=')
|
|
138
|
-
return { ...obj, [key]: value };
|
|
139
|
-
}, {});
|
|
140
|
-
|
|
141
|
-
if (packageInfo && packageInfo.versionName && packageInfo.versionCode){
|
|
142
|
-
return {
|
|
143
|
-
'autVersion': packageInfo.versionName + ` (${packageInfo.versionCode})`
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
return {};
|
|
147
|
-
} catch (err) {
|
|
148
|
-
logger.warn(`getPackageInfo failed parsing shell result`, { packageName, err });
|
|
149
|
-
return {};
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
module.exports = MobileWebDriver;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const Promise = require('bluebird');
|
|
3
|
-
|
|
4
|
-
class FrameLocatorMock {
|
|
5
|
-
|
|
6
|
-
constructor() {}
|
|
7
|
-
|
|
8
|
-
findFrame() {
|
|
9
|
-
return Promise.resolve({
|
|
10
|
-
frameOffset: {top: 0, left: 0},
|
|
11
|
-
tabId: 0,
|
|
12
|
-
frameId: 0,
|
|
13
|
-
testimFullFrameId: 0
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = FrameLocatorMock;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const Promise = require('bluebird');
|
|
3
|
-
|
|
4
|
-
// Legacy code not supported in selenium mode
|
|
5
|
-
class MobilePortSelector {
|
|
6
|
-
constructor() {
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
select() {
|
|
10
|
-
return Promise.resolve({
|
|
11
|
-
testimFullFrameId : 0
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
prepare() {
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
handleLegacyDataCaching() {
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
module.exports = new MobilePortSelector();
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const _ = require('lodash');
|
|
4
|
-
const Promise = require('bluebird');
|
|
5
|
-
const WindowUtils = require('../utils/mobileWindowUtils');
|
|
6
|
-
const MobileScreenshotUtils = require('../utils/mobileScreenshotUtils');
|
|
7
|
-
const ImageCaptureUtils = require('../../utils/imageCaptureUtils');
|
|
8
|
-
const guid = require('../../../utils').guid;
|
|
9
|
-
|
|
10
|
-
module.exports = function (driver) {
|
|
11
|
-
class MobileTabService {
|
|
12
|
-
constructor() {
|
|
13
|
-
this._utils = {};
|
|
14
|
-
this.sessionTabs = {};
|
|
15
|
-
this.pendingTabs = {};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
on(){}
|
|
19
|
-
|
|
20
|
-
createSesion(sessionId) {
|
|
21
|
-
this.pendingTabs[sessionId] = new Set();
|
|
22
|
-
this.sessionTabs[sessionId] = {
|
|
23
|
-
tabCount: 0,
|
|
24
|
-
tabInfos: {}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getAllOpenTabIds(sessionId) {
|
|
29
|
-
const allTabInfos = this.getAllTabInfos(sessionId);
|
|
30
|
-
return Object.keys(allTabInfos)
|
|
31
|
-
.filter(tabId => !allTabInfos[tabId].isClosed)
|
|
32
|
-
.map(tabId => _.isNaN(Number(tabId)) ? tabId : Number(tabId));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get last tab info for pixel validation
|
|
37
|
-
*
|
|
38
|
-
* @returns last tab info
|
|
39
|
-
*/
|
|
40
|
-
getActiveTabInfo(sessionId){
|
|
41
|
-
return this.sessionTabs[sessionId].lastActiveTabInfo;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
getAllTabIds(sessionId) {
|
|
45
|
-
return Object.keys(this.getAllTabInfos(sessionId)).map(id => id);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
isSessionTab(sessionId, tabId) {
|
|
49
|
-
return this.getAllTabIds(sessionId).includes(tabId);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
createSession(sessionId) {
|
|
53
|
-
this.pendingTabs[sessionId] = new Set();
|
|
54
|
-
this.sessionTabs[sessionId] = {
|
|
55
|
-
tabCount: 0,
|
|
56
|
-
tabInfos: {}
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
getAllTabInfoStrings(sessionId) {
|
|
61
|
-
var allIds = this.getAllTabIds(sessionId);
|
|
62
|
-
return allIds.map(tabId => {
|
|
63
|
-
var tabInfo = this.getTabInfo(sessionId, tabId);
|
|
64
|
-
return `tabId=${tabId}, url=${tabInfo.url}, order=${tabInfo.order}, isMain=${tabInfo.isMain}, openerStepId=${tabInfo.openerStepId}`;
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
getAllTabInfos(sessionId) {
|
|
69
|
-
return this.sessionTabs[sessionId].tabInfos;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
addNewTab(sessionId, tabId, openerStepId) {
|
|
73
|
-
var infos = this.getAllTabInfos(sessionId);
|
|
74
|
-
if(infos && infos[tabId]){
|
|
75
|
-
return Promise.resolve();
|
|
76
|
-
}
|
|
77
|
-
return this.addTab(sessionId, tabId, this.sessionTabs[sessionId].tabCount++, openerStepId);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
addOpenerStepId(sessionId, tabId, openerStepId) {
|
|
81
|
-
this.sessionTabs[sessionId].tabInfos[tabId].openerStepId = openerStepId;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
buildTabInfo(sessionId, tabId, order, openerStepId) {
|
|
85
|
-
return this.getTabDetails(tabId, sessionId)
|
|
86
|
-
.then(tab => {
|
|
87
|
-
var infoId = guid();
|
|
88
|
-
var missingMainTab = !this.getMainTabInfo(sessionId);
|
|
89
|
-
|
|
90
|
-
this.sessionTabs[sessionId].tabInfos[tabId] = {
|
|
91
|
-
infoId : infoId,
|
|
92
|
-
url: tab.url,
|
|
93
|
-
title: tab.title,
|
|
94
|
-
favIconUrl : tab.favIconUrl,
|
|
95
|
-
order: order,
|
|
96
|
-
from: this.getTabInfo(sessionId, tab.openerTabId),
|
|
97
|
-
isMain : missingMainTab,
|
|
98
|
-
openerStepId: openerStepId
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
return Promise.resolve(infoId);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
addTab(sessionId, id, order, openerStepId){
|
|
106
|
-
return new Promise(resolve => {
|
|
107
|
-
return this.buildTabInfo(sessionId, id, order, openerStepId)
|
|
108
|
-
.then(infoId => {
|
|
109
|
-
var _windowUtils = new WindowUtils(id, driver);
|
|
110
|
-
this._utils[infoId] = {
|
|
111
|
-
attachDebugger: () => Promise.resolve(),
|
|
112
|
-
detachDebugger: () => Promise.resolve(),
|
|
113
|
-
onDebuggerDetached: () => {},
|
|
114
|
-
tabId: id,
|
|
115
|
-
domUtils: {getDOM: () => Promise.resolve()},
|
|
116
|
-
windowUtils : _windowUtils,
|
|
117
|
-
imageCaptureUtils : new ImageCaptureUtils(id, _windowUtils, new MobileScreenshotUtils(id, driver))
|
|
118
|
-
};
|
|
119
|
-
resolve();
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
getTabUtilsByTabId(tabId) {
|
|
125
|
-
const infoId = Object.keys(this._utils).find(uId => this._utils[uId].tabId === tabId);
|
|
126
|
-
return this._utils[infoId];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
getTabUtilsByTabIdAndSessionId(sessionId){
|
|
130
|
-
return this.getMainTabUtils(sessionId);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
getTabInfo(sessionId, id){
|
|
134
|
-
return this.sessionTabs[sessionId].tabInfos[id];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
getTabUtils(sessionId, tabInfo){
|
|
138
|
-
if(!tabInfo){
|
|
139
|
-
return this.getMainTabUtils(sessionId);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if(this._utils[tabInfo.infoId]){
|
|
143
|
-
return this._utils[tabInfo.infoId];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if(tabInfo.isMain){
|
|
147
|
-
return this.getMainTabUtils(sessionId);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
var infos = this.getAllTabInfos(sessionId);
|
|
151
|
-
var nonMainTabs = Object.keys(infos)
|
|
152
|
-
.map(tabId => infos[tabId])
|
|
153
|
-
.filter(info => !info.isMain);
|
|
154
|
-
if(nonMainTabs.length === 1){
|
|
155
|
-
return this._utils[nonMainTabs[0].infoId];
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
var sameTabs = Object.keys(sessionId)
|
|
159
|
-
.map(key => sessionId[key])
|
|
160
|
-
.filter(info => this.isSameTab(sessionId, tabInfo, info));
|
|
161
|
-
if(sameTabs.length > 0){
|
|
162
|
-
return this._utils[sameTabs[0].infoId];
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// if nothing else
|
|
166
|
-
return this.getMainTabUtils(sessionId);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
getMainTabInfo(sessionId){
|
|
170
|
-
var infos = this.getAllTabInfos(sessionId);
|
|
171
|
-
return Object.keys(infos)
|
|
172
|
-
.map(id => infos[id])
|
|
173
|
-
.find(tabInfo => tabInfo.isMain);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getMainTabUtils(sessionId){
|
|
177
|
-
var mainTabInfo = this.getMainTabInfo(sessionId);
|
|
178
|
-
if(!mainTabInfo) {
|
|
179
|
-
return {};
|
|
180
|
-
}
|
|
181
|
-
return this.getTabUtils(sessionId, mainTabInfo);
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
removeTabInfo(sessionId, tabId){
|
|
186
|
-
let infos = this.getAllTabInfos(sessionId);
|
|
187
|
-
let info = infos[tabId];
|
|
188
|
-
if (info){
|
|
189
|
-
delete this.sessionTabs[sessionId].tabInfos[tabId];
|
|
190
|
-
delete this._utils[info.infoId];
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
getMainTabId(sessionId){
|
|
195
|
-
var infos = this.getAllTabInfos(sessionId);
|
|
196
|
-
return Object.keys(infos).find(id => infos[id].isMain);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
isMainTabExists(sessionId){
|
|
200
|
-
var mainTabId = this.getMainTabId(sessionId);
|
|
201
|
-
if(!mainTabId){
|
|
202
|
-
return Promise.resolve(false);
|
|
203
|
-
}
|
|
204
|
-
return Promise.resolve(true);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
clearAllTabs(sessionId){
|
|
208
|
-
var infos = this.getAllTabInfos(sessionId);
|
|
209
|
-
|
|
210
|
-
this.sessionTabs[sessionId].tabCount = 0;
|
|
211
|
-
Object.keys(infos)
|
|
212
|
-
.forEach(tabId => this.removeTabInfo(sessionId, tabId));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
clearNonMainTabs(sessionId){
|
|
216
|
-
var infos = this.getAllTabInfos(sessionId);
|
|
217
|
-
|
|
218
|
-
this.sessionTabs[sessionId].tabCount = 1;
|
|
219
|
-
Object.keys(infos)
|
|
220
|
-
.filter(tabId => !infos[tabId].isMain)
|
|
221
|
-
.forEach(tabId => this.removeTabInfo(sessionId, tabId));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
getTabDetails(){
|
|
225
|
-
return Promise.resolve({
|
|
226
|
-
title: "Dummy title",
|
|
227
|
-
url: "Dummy url"
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
isMainTabIncognito() {
|
|
232
|
-
return Promise.resolve(false);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
getTabIdByTabInfo() {
|
|
236
|
-
return Promise.resolve(0);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return new MobileTabService();
|
|
241
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const Promise = require('bluebird');
|
|
4
|
-
const utils = require('../../../utils');
|
|
5
|
-
|
|
6
|
-
class MobileScreenshotUtils {
|
|
7
|
-
constructor(tabId, driver){
|
|
8
|
-
this.tabId = tabId;
|
|
9
|
-
this.driver = driver;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
base64AddPadding(str) {
|
|
13
|
-
return str + Array((4 - str.length % 4) % 4 + 1).join('=');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
takeScreenshot() {
|
|
17
|
-
const MAX_RETRY_COUNT = 3;
|
|
18
|
-
const SCREENSHOT_RETRY_DELAY = 2000;
|
|
19
|
-
const devicePixelRatioPromise = this.currentDevicePixelRatio ? Promise.resolve(this.currentDevicePixelRatio) : this.getDevicePixelRatio();
|
|
20
|
-
const getScreenshot = () => Promise.all([devicePixelRatioPromise, this.driver.takeScreenshot()]);
|
|
21
|
-
return utils.runWithRetries(getScreenshot, MAX_RETRY_COUNT, SCREENSHOT_RETRY_DELAY)
|
|
22
|
-
.spread((devicePixelRatio, image) => {
|
|
23
|
-
const base64 = image ? image.value : '';
|
|
24
|
-
const dataUrl = "data:image/png;base64," + this.base64AddPadding(base64.replace(/[\r\n]/g, ''));
|
|
25
|
-
return Promise.resolve({
|
|
26
|
-
image: dataUrl,
|
|
27
|
-
devicePixelRatio: devicePixelRatio
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
getDevicePixelRatio() {
|
|
33
|
-
return Promise.resolve(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getCurrentDevicePixelRatio() {
|
|
37
|
-
return 1;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
forcePixelRatio(forceRatio = 1){
|
|
41
|
-
this.currentDevicePixelRatio = forceRatio;
|
|
42
|
-
return Promise.resolve();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
module.exports = MobileScreenshotUtils;
|