appium-android-driver 5.0.6 → 5.0.9
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 +0 -1
- package/build/lib/commands/actions.js +16 -20
- package/build/lib/commands/app-management.js +15 -17
- package/build/lib/commands/context.js +46 -66
- package/build/lib/commands/coverage.js +2 -6
- package/build/lib/commands/element.js +3 -9
- package/build/lib/commands/emu-console.js +3 -5
- package/build/lib/commands/execute.js +3 -6
- package/build/lib/commands/file-actions.js +22 -29
- package/build/lib/commands/general.js +18 -30
- package/build/lib/commands/ime.js +8 -21
- package/build/lib/commands/network.js +14 -27
- package/build/lib/commands/performance.js +3 -6
- package/build/lib/commands/recordscreen.js +27 -38
- package/build/lib/commands/shell.js +6 -7
- package/build/lib/commands/streamscreen.js +29 -43
- package/build/lib/commands/system-bars.js +10 -16
- package/build/lib/commands/touch.js +4 -10
- package/build/lib/driver.js +61 -67
- package/build/lib/webview-helpers.js +2 -7
- package/lib/commands/actions.js +18 -19
- package/lib/commands/app-management.js +13 -12
- package/lib/commands/context.js +36 -40
- package/lib/commands/coverage.js +1 -4
- package/lib/commands/element.js +2 -3
- package/lib/commands/emu-console.js +2 -2
- package/lib/commands/execute.js +2 -3
- package/lib/commands/file-actions.js +31 -25
- package/lib/commands/general.js +16 -18
- package/lib/commands/ime.js +7 -8
- package/lib/commands/network.js +15 -16
- package/lib/commands/performance.js +2 -3
- package/lib/commands/recordscreen.js +23 -22
- package/lib/commands/shell.js +6 -6
- package/lib/commands/streamscreen.js +22 -23
- package/lib/commands/system-bars.js +11 -10
- package/lib/commands/touch.js +3 -4
- package/lib/driver.js +45 -46
- package/lib/webview-helpers.js +1 -7
- package/package.json +2 -2
package/lib/commands/ime.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import log from '../logger';
|
|
2
1
|
import { errors } from '@appium/base-driver';
|
|
3
2
|
|
|
4
3
|
let commands = {}, helpers = {}, extensions = {};
|
|
@@ -9,32 +8,32 @@ commands.isIMEActivated = async function isIMEActivated () { // eslint-disable-l
|
|
|
9
8
|
};
|
|
10
9
|
|
|
11
10
|
commands.availableIMEEngines = async function availableIMEEngines () {
|
|
12
|
-
log.debug('Retrieving available IMEs');
|
|
11
|
+
this.log.debug('Retrieving available IMEs');
|
|
13
12
|
let engines = await this.adb.availableIMEs();
|
|
14
|
-
log.debug(`Engines: ${JSON.stringify(engines)}`);
|
|
13
|
+
this.log.debug(`Engines: ${JSON.stringify(engines)}`);
|
|
15
14
|
return engines;
|
|
16
15
|
};
|
|
17
16
|
|
|
18
17
|
commands.getActiveIMEEngine = async function getActiveIMEEngine () {
|
|
19
|
-
log.debug('Retrieving current default IME');
|
|
18
|
+
this.log.debug('Retrieving current default IME');
|
|
20
19
|
return await this.adb.defaultIME();
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
commands.activateIMEEngine = async function activateIMEEngine (imeId) {
|
|
24
|
-
log.debug(`Attempting to activate IME ${imeId}`);
|
|
23
|
+
this.log.debug(`Attempting to activate IME ${imeId}`);
|
|
25
24
|
let availableEngines = await this.adb.availableIMEs();
|
|
26
25
|
if (availableEngines.indexOf(imeId) === -1) {
|
|
27
|
-
log.debug('IME not found, failing');
|
|
26
|
+
this.log.debug('IME not found, failing');
|
|
28
27
|
throw new errors.IMENotAvailableError();
|
|
29
28
|
}
|
|
30
|
-
log.debug('Found installed IME, attempting to activate');
|
|
29
|
+
this.log.debug('Found installed IME, attempting to activate');
|
|
31
30
|
await this.adb.enableIME(imeId);
|
|
32
31
|
await this.adb.setIME(imeId);
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
commands.deactivateIMEEngine = async function deactivateIMEEngine () {
|
|
36
35
|
let currentEngine = await this.getActiveIMEEngine();
|
|
37
|
-
log.debug(`Attempting to deactivate ${currentEngine}`);
|
|
36
|
+
this.log.debug(`Attempting to deactivate ${currentEngine}`);
|
|
38
37
|
await this.adb.disableIME(currentEngine);
|
|
39
38
|
};
|
|
40
39
|
|
package/lib/commands/network.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import log from '../logger';
|
|
2
1
|
import _ from 'lodash';
|
|
3
2
|
import { errors } from '@appium/base-driver';
|
|
4
3
|
import B from 'bluebird';
|
|
@@ -16,7 +15,7 @@ const DATA_MASK = 0b100;
|
|
|
16
15
|
const GEO_EPSILON = Number.MIN_VALUE;
|
|
17
16
|
|
|
18
17
|
commands.getNetworkConnection = async function getNetworkConnection () {
|
|
19
|
-
log.info('Getting network connection');
|
|
18
|
+
this.log.info('Getting network connection');
|
|
20
19
|
let airplaneModeOn = await this.adb.isAirplaneModeOn();
|
|
21
20
|
let connection = airplaneModeOn ? AIRPLANE_MODE_MASK : 0;
|
|
22
21
|
|
|
@@ -39,7 +38,7 @@ commands.isWifiOn = async function isWifiOn () {
|
|
|
39
38
|
};
|
|
40
39
|
|
|
41
40
|
commands.setNetworkConnection = async function setNetworkConnection (type) {
|
|
42
|
-
log.info('Setting network connection');
|
|
41
|
+
this.log.info('Setting network connection');
|
|
43
42
|
// decode the input
|
|
44
43
|
const shouldEnableAirplaneMode = (type & AIRPLANE_MODE_MASK) !== 0;
|
|
45
44
|
const shouldEnableWifi = (type & WIFI_MASK) !== 0;
|
|
@@ -58,12 +57,12 @@ commands.setNetworkConnection = async function setNetworkConnection (type) {
|
|
|
58
57
|
await this.adb.broadcastAirplaneMode(shouldEnableAirplaneMode);
|
|
59
58
|
});
|
|
60
59
|
} else {
|
|
61
|
-
log.info(`Not changing airplane mode, since it is already ` +
|
|
60
|
+
this.log.info(`Not changing airplane mode, since it is already ` +
|
|
62
61
|
`${shouldEnableAirplaneMode ? 'enabled' : 'disabled'}`);
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
if (shouldEnableWifi === isWiFiEnabled && shouldEnableDataConnection === isDataEnabled) {
|
|
66
|
-
log.info('Not changing data connection/Wi-Fi states, since they are already set to expected values');
|
|
65
|
+
this.log.info('Not changing data connection/Wi-Fi states, since they are already set to expected values');
|
|
67
66
|
if (await this.adb.isAirplaneModeOn()) {
|
|
68
67
|
return AIRPLANE_MODE_MASK | currentState;
|
|
69
68
|
}
|
|
@@ -74,15 +73,15 @@ commands.setNetworkConnection = async function setNetworkConnection (type) {
|
|
|
74
73
|
if (shouldEnableWifi !== isWiFiEnabled) {
|
|
75
74
|
await this.setWifiState(shouldEnableWifi);
|
|
76
75
|
} else {
|
|
77
|
-
log.info(`Not changing Wi-Fi state, since it is already ` +
|
|
78
|
-
|
|
76
|
+
this.log.info(`Not changing Wi-Fi state, since it is already ` +
|
|
77
|
+
`${shouldEnableWifi ? 'enabled' : 'disabled'}`);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
if (shouldEnableAirplaneMode) {
|
|
82
|
-
log.info('Not changing data connection state, because airplane mode is enabled');
|
|
81
|
+
this.log.info('Not changing data connection state, because airplane mode is enabled');
|
|
83
82
|
} else if (shouldEnableDataConnection === isDataEnabled) {
|
|
84
|
-
log.info(`Not changing data connection state, since it is already ` +
|
|
85
|
-
|
|
83
|
+
this.log.info(`Not changing data connection state, since it is already ` +
|
|
84
|
+
`${shouldEnableDataConnection ? 'enabled' : 'disabled'}`);
|
|
86
85
|
} else {
|
|
87
86
|
await this.adb.setDataState(shouldEnableDataConnection, this.isEmulator());
|
|
88
87
|
}
|
|
@@ -100,7 +99,7 @@ commands.setWifiState = async function setWifiState (wifi) {
|
|
|
100
99
|
|
|
101
100
|
commands.toggleData = async function toggleData () {
|
|
102
101
|
let data = !(await this.adb.isDataOn());
|
|
103
|
-
log.info(`Turning network data ${data ? 'on' : 'off'}`);
|
|
102
|
+
this.log.info(`Turning network data ${data ? 'on' : 'off'}`);
|
|
104
103
|
await this.wrapBootstrapDisconnect(async () => {
|
|
105
104
|
await this.adb.setWifiAndData({data}, this.isEmulator());
|
|
106
105
|
});
|
|
@@ -108,7 +107,7 @@ commands.toggleData = async function toggleData () {
|
|
|
108
107
|
|
|
109
108
|
commands.toggleWiFi = async function toggleWiFi () {
|
|
110
109
|
let wifi = !(await this.adb.isWifiOn());
|
|
111
|
-
log.info(`Turning WiFi ${wifi ? 'on' : 'off'}`);
|
|
110
|
+
this.log.info(`Turning WiFi ${wifi ? 'on' : 'off'}`);
|
|
112
111
|
await this.wrapBootstrapDisconnect(async () => {
|
|
113
112
|
await this.adb.setWifiAndData({wifi}, this.isEmulator());
|
|
114
113
|
});
|
|
@@ -120,7 +119,7 @@ commands.toggleFlightMode = async function toggleFlightMode () {
|
|
|
120
119
|
* real devices, it should throw a NotYetImplementedError
|
|
121
120
|
*/
|
|
122
121
|
let flightMode = !(await this.adb.isAirplaneModeOn());
|
|
123
|
-
log.info(`Turning flight mode ${flightMode ? 'on' : 'off'}`);
|
|
122
|
+
this.log.info(`Turning flight mode ${flightMode ? 'on' : 'off'}`);
|
|
124
123
|
await this.wrapBootstrapDisconnect(async () => {
|
|
125
124
|
await this.adb.setAirplaneMode(flightMode);
|
|
126
125
|
});
|
|
@@ -134,8 +133,8 @@ commands.setGeoLocation = async function setGeoLocation (location) {
|
|
|
134
133
|
try {
|
|
135
134
|
return await this.getGeoLocation();
|
|
136
135
|
} catch (e) {
|
|
137
|
-
log.warn(`Could not get the current geolocation info: ${e.message}`);
|
|
138
|
-
log.warn(`Returning the default zero'ed values`);
|
|
136
|
+
this.log.warn(`Could not get the current geolocation info: ${e.message}`);
|
|
137
|
+
this.log.warn(`Returning the default zero'ed values`);
|
|
139
138
|
return {
|
|
140
139
|
latitude: GEO_EPSILON,
|
|
141
140
|
longitude: GEO_EPSILON,
|
|
@@ -182,7 +181,7 @@ const KeyCode = {
|
|
|
182
181
|
CENTER: 23
|
|
183
182
|
};
|
|
184
183
|
commands.toggleLocationServices = async function toggleLocationServices () {
|
|
185
|
-
log.info('Toggling location services');
|
|
184
|
+
this.log.info('Toggling location services');
|
|
186
185
|
let api = await this.adb.getApiLevel();
|
|
187
186
|
if (this.isEmulator()) {
|
|
188
187
|
let providers = await this.adb.getLocationProviders();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import { retryInterval } from 'asyncbox';
|
|
3
|
-
import log from '../logger';
|
|
4
3
|
|
|
5
4
|
const commands = {}, helpers = {}, extensions = {};
|
|
6
5
|
|
|
@@ -150,7 +149,7 @@ helpers.getCPUInfo = async function getCPUInfo (packageName, retries = 2) {
|
|
|
150
149
|
output = await this.adb.shell(['dumpsys', 'cpuinfo']);
|
|
151
150
|
} catch (e) {
|
|
152
151
|
if (e.stderr) {
|
|
153
|
-
log.info(e.stderr);
|
|
152
|
+
this.log.info(e.stderr);
|
|
154
153
|
}
|
|
155
154
|
throw e;
|
|
156
155
|
}
|
|
@@ -160,7 +159,7 @@ helpers.getCPUInfo = async function getCPUInfo (packageName, retries = 2) {
|
|
|
160
159
|
new RegExp(`^.+\\/${_.escapeRegExp(packageName)}:\\D+([\\d.]+)%\\s+user\\s+\\+\\s+([\\d.]+)%\\s+kernel`, 'm');
|
|
161
160
|
const match = usagesPattern.exec(output);
|
|
162
161
|
if (!match) {
|
|
163
|
-
log.debug(output);
|
|
162
|
+
this.log.debug(output);
|
|
164
163
|
throw new Error(`Unable to parse cpu usage data for '${packageName}'. Check the server log for more details`);
|
|
165
164
|
}
|
|
166
165
|
return [CPU_KEYS, [match[1], match[2]]];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import { waitForCondition } from 'asyncbox';
|
|
3
3
|
import { util, fs, net, tempDir, system, timing } from '@appium/support';
|
|
4
|
-
import log from '../logger';
|
|
5
4
|
import { exec } from 'teen_process';
|
|
6
5
|
import path from 'path';
|
|
7
6
|
|
|
@@ -21,8 +20,6 @@ const FFMPEG_BINARY = `ffmpeg${system.isWindows() ? '.exe' : ''}`;
|
|
|
21
20
|
|
|
22
21
|
async function uploadRecordedMedia (localFile, remotePath = null, uploadOptions = {}) {
|
|
23
22
|
if (_.isEmpty(remotePath)) {
|
|
24
|
-
const {size} = await fs.stat(localFile);
|
|
25
|
-
log.debug(`The size of the resulting screen recording is ${util.toReadableSizeString(size)}`);
|
|
26
23
|
return (await util.toInMemoryBase64(localFile)).toString();
|
|
27
24
|
}
|
|
28
25
|
|
|
@@ -50,7 +47,7 @@ async function verifyScreenRecordIsSupported (adb, isEmulator) {
|
|
|
50
47
|
}
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
async function scheduleScreenRecord (adb, recordingProperties) {
|
|
50
|
+
async function scheduleScreenRecord (adb, recordingProperties, log = null) {
|
|
54
51
|
if (recordingProperties.stopped) {
|
|
55
52
|
return;
|
|
56
53
|
}
|
|
@@ -83,10 +80,10 @@ async function scheduleScreenRecord (adb, recordingProperties) {
|
|
|
83
80
|
return;
|
|
84
81
|
}
|
|
85
82
|
const currentDuration = timer.getDuration().asSeconds.toFixed(0);
|
|
86
|
-
log
|
|
83
|
+
log?.debug(`The overall screen recording duration is ${currentDuration}s so far`);
|
|
87
84
|
const timeLimitInt = parseInt(timeLimit, 10);
|
|
88
85
|
if (isNaN(timeLimitInt) || currentDuration >= timeLimitInt) {
|
|
89
|
-
log
|
|
86
|
+
log?.debug('There is no need to start the next recording chunk');
|
|
90
87
|
return;
|
|
91
88
|
}
|
|
92
89
|
|
|
@@ -94,11 +91,11 @@ async function scheduleScreenRecord (adb, recordingProperties) {
|
|
|
94
91
|
const chunkDuration = recordingProperties.currentTimeLimit < MAX_RECORDING_TIME_SEC
|
|
95
92
|
? recordingProperties.currentTimeLimit
|
|
96
93
|
: MAX_RECORDING_TIME_SEC;
|
|
97
|
-
log
|
|
94
|
+
log?.debug(`Starting the next ${chunkDuration}s-chunk ` +
|
|
98
95
|
`of screen recording in order to achieve ${timeLimitInt}s total duration`);
|
|
99
|
-
scheduleScreenRecord(adb, recordingProperties)
|
|
96
|
+
scheduleScreenRecord(adb, recordingProperties, log)
|
|
100
97
|
.catch((e) => {
|
|
101
|
-
log
|
|
98
|
+
log?.error(e.stack);
|
|
102
99
|
recordingProperties.stopped = true;
|
|
103
100
|
});
|
|
104
101
|
});
|
|
@@ -116,7 +113,7 @@ async function scheduleScreenRecord (adb, recordingProperties) {
|
|
|
116
113
|
recordingProperties.recordingProcess = recordingProc;
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
async function mergeScreenRecords (mediaFiles) {
|
|
116
|
+
async function mergeScreenRecords (mediaFiles, log = null) {
|
|
120
117
|
try {
|
|
121
118
|
await fs.which(FFMPEG_BINARY);
|
|
122
119
|
} catch (e) {
|
|
@@ -127,10 +124,10 @@ async function mergeScreenRecords (mediaFiles) {
|
|
|
127
124
|
.join('\n');
|
|
128
125
|
const configFile = path.resolve(path.dirname(mediaFiles[0]), 'config.txt');
|
|
129
126
|
await fs.writeFile(configFile, configContent, 'utf8');
|
|
130
|
-
log
|
|
127
|
+
log?.debug(`Generated ffmpeg merging config '${configFile}' with items:\n${configContent}`);
|
|
131
128
|
const result = path.resolve(path.dirname(mediaFiles[0]), `merge_${Math.floor(new Date())}${DEFAULT_EXT}`);
|
|
132
129
|
const args = ['-safe', '0', '-f', 'concat', '-i', configFile, '-c', 'copy', result];
|
|
133
|
-
log
|
|
130
|
+
log?.info(`Initiating screen records merging using the command '${FFMPEG_BINARY} ${args.join(' ')}'`);
|
|
134
131
|
await exec(FFMPEG_BINARY, args);
|
|
135
132
|
return result;
|
|
136
133
|
}
|
|
@@ -217,7 +214,7 @@ commands.startRecordingScreen = async function startRecordingScreen (options = {
|
|
|
217
214
|
}
|
|
218
215
|
|
|
219
216
|
if (await terminateBackgroundScreenRecording(this.adb, true)) {
|
|
220
|
-
log.warn(`There were some ${SCREENRECORD_BINARY} process leftovers running ` +
|
|
217
|
+
this.log.warn(`There were some ${SCREENRECORD_BINARY} process leftovers running ` +
|
|
221
218
|
`in the background. Make sure you stop screen recording each time after it is started, ` +
|
|
222
219
|
`otherwise the recorded media might quickly exceed all the free space on the device under test.`);
|
|
223
220
|
}
|
|
@@ -246,7 +243,7 @@ commands.startRecordingScreen = async function startRecordingScreen (options = {
|
|
|
246
243
|
recordingProcess: null,
|
|
247
244
|
stopped: false,
|
|
248
245
|
};
|
|
249
|
-
await scheduleScreenRecord(this.adb, this._screenRecordingProperties);
|
|
246
|
+
await scheduleScreenRecord(this.adb, this._screenRecordingProperties, this.log);
|
|
250
247
|
return result;
|
|
251
248
|
};
|
|
252
249
|
|
|
@@ -289,14 +286,14 @@ commands.stopRecordingScreen = async function stopRecordingScreen (options = {})
|
|
|
289
286
|
try {
|
|
290
287
|
await terminateBackgroundScreenRecording(this.adb, false);
|
|
291
288
|
} catch (err) {
|
|
292
|
-
log.warn(err.message);
|
|
289
|
+
this.log.warn(err.message);
|
|
293
290
|
if (!_.isEmpty(this._screenRecordingProperties)) {
|
|
294
|
-
log.warn('The resulting video might be corrupted');
|
|
291
|
+
this.log.warn('The resulting video might be corrupted');
|
|
295
292
|
}
|
|
296
293
|
}
|
|
297
294
|
|
|
298
295
|
if (_.isEmpty(this._screenRecordingProperties)) {
|
|
299
|
-
log.info(`Screen recording has not been previously started by Appium. There is nothing to stop`);
|
|
296
|
+
this.log.info(`Screen recording has not been previously started by Appium. There is nothing to stop`);
|
|
300
297
|
return '';
|
|
301
298
|
}
|
|
302
299
|
|
|
@@ -304,13 +301,13 @@ commands.stopRecordingScreen = async function stopRecordingScreen (options = {})
|
|
|
304
301
|
try {
|
|
305
302
|
await this._screenRecordingProperties.recordingProcess.stop('SIGINT', PROCESS_SHUTDOWN_TIMEOUT);
|
|
306
303
|
} catch (e) {
|
|
307
|
-
log.errorAndThrow(`Unable to stop screen recording within ${PROCESS_SHUTDOWN_TIMEOUT}ms`);
|
|
304
|
+
this.log.errorAndThrow(`Unable to stop screen recording within ${PROCESS_SHUTDOWN_TIMEOUT}ms`);
|
|
308
305
|
}
|
|
309
306
|
this._screenRecordingProperties.recordingProcess = null;
|
|
310
307
|
}
|
|
311
308
|
|
|
312
309
|
if (_.isEmpty(this._screenRecordingProperties.records)) {
|
|
313
|
-
log.errorAndThrow(`No screen recordings have been stored on the device so far. ` +
|
|
310
|
+
this.log.errorAndThrow(`No screen recordings have been stored on the device so far. ` +
|
|
314
311
|
`Are you sure the ${SCREENRECORD_BINARY} utility works as expected?`);
|
|
315
312
|
}
|
|
316
313
|
|
|
@@ -324,14 +321,18 @@ commands.stopRecordingScreen = async function stopRecordingScreen (options = {})
|
|
|
324
321
|
}
|
|
325
322
|
let resultFilePath = _.last(localRecords);
|
|
326
323
|
if (localRecords.length > 1) {
|
|
327
|
-
log.info(`Got ${localRecords.length} screen recordings. Trying to merge them`);
|
|
324
|
+
this.log.info(`Got ${localRecords.length} screen recordings. Trying to merge them`);
|
|
328
325
|
try {
|
|
329
|
-
resultFilePath = await mergeScreenRecords(localRecords);
|
|
326
|
+
resultFilePath = await mergeScreenRecords(localRecords, this.log);
|
|
330
327
|
} catch (e) {
|
|
331
|
-
log.warn(`Cannot merge the recorded files. The most recent screen recording is going to be returned as the result. ` +
|
|
328
|
+
this.log.warn(`Cannot merge the recorded files. The most recent screen recording is going to be returned as the result. ` +
|
|
332
329
|
`Original error: ${e.message}`);
|
|
333
330
|
}
|
|
334
331
|
}
|
|
332
|
+
if (_.isEmpty(options.remotePath)) {
|
|
333
|
+
const {size} = await fs.stat(resultFilePath);
|
|
334
|
+
this.log.debug(`The size of the resulting screen recording is ${util.toReadableSizeString(size)}`);
|
|
335
|
+
}
|
|
335
336
|
return await uploadRecordedMedia(resultFilePath, options.remotePath, options);
|
|
336
337
|
} finally {
|
|
337
338
|
await fs.rimraf(tmpRoot);
|
package/lib/commands/shell.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import log from '../logger';
|
|
2
1
|
import _ from 'lodash';
|
|
3
2
|
import { exec } from 'teen_process';
|
|
4
3
|
import { util } from '@appium/support';
|
|
4
|
+
import { errors } from '@appium/base-driver';
|
|
5
5
|
|
|
6
6
|
const ADB_SHELL_FEATURE = 'adb_shell';
|
|
7
7
|
|
|
@@ -18,7 +18,7 @@ commands.mobileShell = async function mobileShell (opts = {}) {
|
|
|
18
18
|
} = opts;
|
|
19
19
|
|
|
20
20
|
if (!_.isString(command)) {
|
|
21
|
-
|
|
21
|
+
throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const adbArgs = [
|
|
@@ -27,7 +27,7 @@ commands.mobileShell = async function mobileShell (opts = {}) {
|
|
|
27
27
|
command,
|
|
28
28
|
...(_.isArray(args) ? args : [args])
|
|
29
29
|
];
|
|
30
|
-
log.debug(`Running '${this.adb.executable.path} ${util.quote(adbArgs)}'`);
|
|
30
|
+
this.log.debug(`Running '${this.adb.executable.path} ${util.quote(adbArgs)}'`);
|
|
31
31
|
try {
|
|
32
32
|
const {stdout, stderr} = await exec(this.adb.executable.path, adbArgs, {timeout});
|
|
33
33
|
if (includeStderr) {
|
|
@@ -38,9 +38,9 @@ commands.mobileShell = async function mobileShell (opts = {}) {
|
|
|
38
38
|
}
|
|
39
39
|
return stdout;
|
|
40
40
|
} catch (err) {
|
|
41
|
-
log.errorAndThrow(`Cannot execute the '${command}' shell command. ` +
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
this.log.errorAndThrow(`Cannot execute the '${command}' shell command. ` +
|
|
42
|
+
`Original error: ${err.message}. ` +
|
|
43
|
+
`StdOut: ${err.stdout}. StdErr: ${err.stderr}`);
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import { fs, system, logger, util } from '@appium/support';
|
|
3
|
-
import log from '../logger';
|
|
4
3
|
import { exec, SubProcess } from 'teen_process';
|
|
5
4
|
import { checkPortStatus } from 'portscanner';
|
|
6
5
|
import http from 'http';
|
|
@@ -74,7 +73,7 @@ async function verifyStreamingRequirements (adb) {
|
|
|
74
73
|
await B.all(moduleCheckPromises);
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
async function getDeviceInfo (adb) {
|
|
76
|
+
async function getDeviceInfo (adb, log = null) {
|
|
78
77
|
const output = await adb.shell(['dumpsys', 'display']);
|
|
79
78
|
const result = {};
|
|
80
79
|
for (const [key, pattern] of [
|
|
@@ -84,7 +83,7 @@ async function getDeviceInfo (adb) {
|
|
|
84
83
|
]) {
|
|
85
84
|
const match = pattern.exec(output);
|
|
86
85
|
if (!match) {
|
|
87
|
-
log
|
|
86
|
+
log?.debug(output);
|
|
88
87
|
throw new Error(`Cannot parse the device ${key} from the adb command output. ` +
|
|
89
88
|
`Check the server log for more details.`);
|
|
90
89
|
}
|
|
@@ -94,7 +93,7 @@ async function getDeviceInfo (adb) {
|
|
|
94
93
|
return result;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
async function initDeviceStreamingProc (adb, deviceInfo, opts = {}) {
|
|
96
|
+
async function initDeviceStreamingProc (adb, log, deviceInfo, opts = {}) {
|
|
98
97
|
const {
|
|
99
98
|
width,
|
|
100
99
|
height,
|
|
@@ -158,7 +157,7 @@ async function initDeviceStreamingProc (adb, deviceInfo, opts = {}) {
|
|
|
158
157
|
return deviceStreaming;
|
|
159
158
|
}
|
|
160
159
|
|
|
161
|
-
async function initGstreamerPipeline (deviceStreamingProc, deviceInfo, opts = {}) {
|
|
160
|
+
async function initGstreamerPipeline (deviceStreamingProc, deviceInfo, log, opts = {}) {
|
|
162
161
|
const {
|
|
163
162
|
width,
|
|
164
163
|
height,
|
|
@@ -291,30 +290,30 @@ commands.mobileStartScreenStreaming = async function mobileStartScreenStreaming
|
|
|
291
290
|
await verifyStreamingRequirements(this.adb);
|
|
292
291
|
}
|
|
293
292
|
if (!_.isEmpty(this._screenStreamingProps)) {
|
|
294
|
-
log.info(`The screen streaming session is already running. ` +
|
|
293
|
+
this.log.info(`The screen streaming session is already running. ` +
|
|
295
294
|
`Stop it first in order to start a new one.`);
|
|
296
295
|
return;
|
|
297
296
|
}
|
|
298
297
|
if ((await checkPortStatus(port, host)) === 'open') {
|
|
299
|
-
log.info(`The port #${port} at ${host} is busy. ` +
|
|
298
|
+
this.log.info(`The port #${port} at ${host} is busy. ` +
|
|
300
299
|
`Assuming the screen streaming is already running`);
|
|
301
300
|
return;
|
|
302
301
|
}
|
|
303
302
|
if ((await checkPortStatus(tcpPort, TCP_HOST)) === 'open') {
|
|
304
|
-
log.errorAndThrow(`The port #${tcpPort} at ${TCP_HOST} is busy. ` +
|
|
303
|
+
this.log.errorAndThrow(`The port #${tcpPort} at ${TCP_HOST} is busy. ` +
|
|
305
304
|
`Make sure there are no leftovers from previous sessions.`);
|
|
306
305
|
}
|
|
307
306
|
this._screenStreamingProps = null;
|
|
308
307
|
|
|
309
|
-
const deviceInfo = await getDeviceInfo(this.adb);
|
|
310
|
-
const deviceStreamingProc = await initDeviceStreamingProc(this.adb, deviceInfo, {
|
|
308
|
+
const deviceInfo = await getDeviceInfo(this.adb, this.log);
|
|
309
|
+
const deviceStreamingProc = await initDeviceStreamingProc(this.adb, this.log, deviceInfo, {
|
|
311
310
|
width,
|
|
312
311
|
height,
|
|
313
312
|
bitRate,
|
|
314
313
|
});
|
|
315
314
|
let gstreamerPipeline;
|
|
316
315
|
try {
|
|
317
|
-
gstreamerPipeline = await initGstreamerPipeline(deviceStreamingProc, deviceInfo, {
|
|
316
|
+
gstreamerPipeline = await initGstreamerPipeline(deviceStreamingProc, deviceInfo, this.log, {
|
|
318
317
|
width,
|
|
319
318
|
height,
|
|
320
319
|
quality,
|
|
@@ -334,15 +333,15 @@ commands.mobileStartScreenStreaming = async function mobileStartScreenStreaming
|
|
|
334
333
|
try {
|
|
335
334
|
await new B((resolve, reject) => {
|
|
336
335
|
mjpegSocket = net.createConnection(tcpPort, TCP_HOST, () => {
|
|
337
|
-
log.info(`Successfully connected to MJPEG stream at tcp://${TCP_HOST}:${tcpPort}`);
|
|
336
|
+
this.log.info(`Successfully connected to MJPEG stream at tcp://${TCP_HOST}:${tcpPort}`);
|
|
338
337
|
mjpegServer = http.createServer((req, res) => {
|
|
339
338
|
const remoteAddress = extractRemoteAddress(req);
|
|
340
339
|
const currentPathname = url.parse(req.url).pathname;
|
|
341
|
-
log.info(`Got an incoming screen bradcasting request from ${remoteAddress} ` +
|
|
340
|
+
this.log.info(`Got an incoming screen bradcasting request from ${remoteAddress} ` +
|
|
342
341
|
`(${req.headers['user-agent'] || 'User Agent unknown'}) at ${currentPathname}`);
|
|
343
342
|
|
|
344
343
|
if (pathname && currentPathname !== pathname) {
|
|
345
|
-
log.info('Rejecting the broadcast request since it does not match the given pathname');
|
|
344
|
+
this.log.info('Rejecting the broadcast request since it does not match the given pathname');
|
|
346
345
|
res.writeHead(404, {
|
|
347
346
|
Connection: 'close',
|
|
348
347
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
@@ -352,7 +351,7 @@ commands.mobileStartScreenStreaming = async function mobileStartScreenStreaming
|
|
|
352
351
|
return;
|
|
353
352
|
}
|
|
354
353
|
|
|
355
|
-
log.info('Starting MJPEG broadcast');
|
|
354
|
+
this.log.info('Starting MJPEG broadcast');
|
|
356
355
|
res.writeHead(200, {
|
|
357
356
|
'Cache-Control': 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
|
|
358
357
|
Pragma: 'no-cache',
|
|
@@ -363,20 +362,20 @@ commands.mobileStartScreenStreaming = async function mobileStartScreenStreaming
|
|
|
363
362
|
mjpegSocket.pipe(res);
|
|
364
363
|
});
|
|
365
364
|
mjpegServer.on('error', (e) => {
|
|
366
|
-
log.warn(e);
|
|
365
|
+
this.log.warn(e);
|
|
367
366
|
reject(e);
|
|
368
367
|
});
|
|
369
368
|
mjpegServer.on('close', () => {
|
|
370
|
-
log.debug(`MJPEG server at http://${host}:${port} has been closed`);
|
|
369
|
+
this.log.debug(`MJPEG server at http://${host}:${port} has been closed`);
|
|
371
370
|
});
|
|
372
371
|
mjpegServer.on('listening', () => {
|
|
373
|
-
log.info(`Successfully started MJPEG server at http://${host}:${port}`);
|
|
372
|
+
this.log.info(`Successfully started MJPEG server at http://${host}:${port}`);
|
|
374
373
|
resolve();
|
|
375
374
|
});
|
|
376
375
|
mjpegServer.listen(port, host);
|
|
377
376
|
});
|
|
378
377
|
mjpegSocket.on('error', (e) => {
|
|
379
|
-
log.error(e);
|
|
378
|
+
this.log.error(e);
|
|
380
379
|
reject(e);
|
|
381
380
|
});
|
|
382
381
|
}).timeout(STREAMING_STARTUP_TIMEOUT_MS,
|
|
@@ -412,7 +411,7 @@ commands.mobileStartScreenStreaming = async function mobileStartScreenStreaming
|
|
|
412
411
|
commands.mobileStopScreenStreaming = async function mobileStopScreenStreaming (/* options = {} */) {
|
|
413
412
|
if (_.isEmpty(this._screenStreamingProps)) {
|
|
414
413
|
if (!_.isUndefined(this._screenStreamingProps)) {
|
|
415
|
-
log.debug(`Screen streaming is not running. There is nothing to stop`);
|
|
414
|
+
this.log.debug(`Screen streaming is not running. There is nothing to stop`);
|
|
416
415
|
}
|
|
417
416
|
return;
|
|
418
417
|
}
|
|
@@ -436,15 +435,15 @@ commands.mobileStopScreenStreaming = async function mobileStopScreenStreaming (/
|
|
|
436
435
|
try {
|
|
437
436
|
await gstreamerPipeline.stop('SIGINT');
|
|
438
437
|
} catch (e) {
|
|
439
|
-
log.warn(e);
|
|
438
|
+
this.log.warn(e);
|
|
440
439
|
try {
|
|
441
440
|
await gstreamerPipeline.stop('SIGKILL');
|
|
442
441
|
} catch (e1) {
|
|
443
|
-
log.error(e1);
|
|
442
|
+
this.log.error(e1);
|
|
444
443
|
}
|
|
445
444
|
}
|
|
446
445
|
}
|
|
447
|
-
log.info(`Successfully terminated the screen streaming MJPEG server`);
|
|
446
|
+
this.log.info(`Successfully terminated the screen streaming MJPEG server`);
|
|
448
447
|
} finally {
|
|
449
448
|
this._screenStreamingProps = null;
|
|
450
449
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import log from '../logger';
|
|
3
2
|
|
|
4
3
|
const WINDOW_TITLE_PATTERN = /^\s+Window\s#\d+\sWindow\{[0-9a-f]+\s\w+\s([\w-]+)\}:$/;
|
|
5
4
|
const FRAME_PATTERN = /^\s+mFrame=\[([0-9.-]+),([0-9.-]+)\]\[([0-9.-]+),([0-9.-]+)\]/m;
|
|
@@ -28,15 +27,16 @@ const commands = {};
|
|
|
28
27
|
* @param {string} name The name of the window whose properties are being parsed
|
|
29
28
|
* @param {Array<string>} props The list of particular window property lines.
|
|
30
29
|
* Check the corresponding unit tests for more details on the input format.
|
|
30
|
+
* @param {Object?} log Logger instance
|
|
31
31
|
* @returns {WindowProperties} Parsed properties object
|
|
32
32
|
* @throws {Error} If there was an issue while parsing the properties string
|
|
33
33
|
*/
|
|
34
|
-
function parseWindowProperties (name, props) {
|
|
34
|
+
function parseWindowProperties (name, props, log = null) {
|
|
35
35
|
const result = _.cloneDeep(DEFAULT_WINDOW_PROPERTIES);
|
|
36
36
|
const propLines = props.join('\n');
|
|
37
37
|
const frameMatch = FRAME_PATTERN.exec(propLines);
|
|
38
38
|
if (!frameMatch) {
|
|
39
|
-
log
|
|
39
|
+
log?.debug(propLines);
|
|
40
40
|
throw new Error(`Cannot parse the frame size from '${name}' window properties`);
|
|
41
41
|
}
|
|
42
42
|
result.x = parseFloat(frameMatch[1]);
|
|
@@ -47,7 +47,7 @@ function parseWindowProperties (name, props) {
|
|
|
47
47
|
result.height = top - result.y;
|
|
48
48
|
const visibilityMatch = SURFACE_PATTERN.exec(propLines);
|
|
49
49
|
if (!visibilityMatch) {
|
|
50
|
-
log
|
|
50
|
+
log?.debug(propLines);
|
|
51
51
|
throw new Error(`Cannot parse the visibility value from '${name}' window properties`);
|
|
52
52
|
}
|
|
53
53
|
result.visible = visibilityMatch[1] === 'true';
|
|
@@ -59,11 +59,12 @@ function parseWindowProperties (name, props) {
|
|
|
59
59
|
*
|
|
60
60
|
* @param {Array<string>} lines Output from dumpsys command.
|
|
61
61
|
* Check the corresponding unit tests for more details on the input format.
|
|
62
|
+
* @param {Object?} log Logger instance
|
|
62
63
|
* @return {Object} An object containing two items where keys are statusBar and navigationBar,
|
|
63
64
|
* and values are corresponding WindowProperties objects
|
|
64
65
|
* @throws {Error} If no window properties could be parsed
|
|
65
66
|
*/
|
|
66
|
-
function parseWindows (lines) {
|
|
67
|
+
function parseWindows (lines, log = null) {
|
|
67
68
|
const windows = {};
|
|
68
69
|
let currentWindowName = null;
|
|
69
70
|
let windowNameRowIndent = null;
|
|
@@ -91,16 +92,16 @@ function parseWindows (lines) {
|
|
|
91
92
|
windows[currentWindowName].push(line);
|
|
92
93
|
}
|
|
93
94
|
if (_.isEmpty(windows)) {
|
|
94
|
-
log
|
|
95
|
+
log?.debug(lines.join('\n'));
|
|
95
96
|
throw new Error('Cannot parse any window information from the dumpsys output');
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
const result = {statusBar: null, navigationBar: null};
|
|
99
100
|
for (const [name, props] of _.toPairs(windows)) {
|
|
100
101
|
if (name.startsWith(STATUS_BAR_WINDOW_NAME_PREFIX)) {
|
|
101
|
-
result.statusBar = parseWindowProperties(name, props);
|
|
102
|
+
result.statusBar = parseWindowProperties(name, props, log);
|
|
102
103
|
} else if (name.startsWith(NAVIGATION_BAR_WINDOW_NAME_PREFIX)) {
|
|
103
|
-
result.navigationBar = parseWindowProperties(name, props);
|
|
104
|
+
result.navigationBar = parseWindowProperties(name, props, log);
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
const unmatchedWindows = [
|
|
@@ -108,7 +109,7 @@ function parseWindows (lines) {
|
|
|
108
109
|
['navigationBar', NAVIGATION_BAR_WINDOW_NAME_PREFIX]
|
|
109
110
|
].filter(([name]) => _.isNil(result[name]));
|
|
110
111
|
for (const [window, namePrefix] of unmatchedWindows) {
|
|
111
|
-
log
|
|
112
|
+
log?.info(`No windows have been found whose title matches to ` +
|
|
112
113
|
`'${namePrefix}'. Assuming it is invisible. ` +
|
|
113
114
|
`Only the following windows are available: ${_.keys(windows)}`);
|
|
114
115
|
result[window] = _.cloneDeep(DEFAULT_WINDOW_PROPERTIES);
|
|
@@ -123,7 +124,7 @@ commands.getSystemBars = async function getSystemBars () {
|
|
|
123
124
|
} catch (e) {
|
|
124
125
|
throw new Error(`Cannot retrieve system bars details. Original error: ${e.message}`);
|
|
125
126
|
}
|
|
126
|
-
return parseWindows(stdout);
|
|
127
|
+
return parseWindows(stdout, this.log);
|
|
127
128
|
};
|
|
128
129
|
|
|
129
130
|
// for unit tests
|
package/lib/commands/touch.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import log from '../logger';
|
|
2
1
|
import _ from 'lodash';
|
|
3
2
|
import androidHelpers from '../android-helpers';
|
|
4
3
|
import B from 'bluebird';
|
|
@@ -49,10 +48,10 @@ commands.doTouchAction = async function doTouchAction (action, opts = {}) {
|
|
|
49
48
|
return await this.touchLongClick(null, x, y, duration || 1000);
|
|
50
49
|
case 'cancel':
|
|
51
50
|
// TODO: clarify behavior of 'cancel' action and fix this
|
|
52
|
-
log.warn('Cancel action currently has no effect');
|
|
51
|
+
this.log.warn('Cancel action currently has no effect');
|
|
53
52
|
break;
|
|
54
53
|
default:
|
|
55
|
-
log.errorAndThrow(`unknown action ${action}`);
|
|
54
|
+
this.log.errorAndThrow(`unknown action ${action}`);
|
|
56
55
|
}
|
|
57
56
|
};
|
|
58
57
|
|
|
@@ -146,7 +145,7 @@ helpers.performGesture = async function performGesture (gesture) {
|
|
|
146
145
|
if (isErrorType(e, errors.NoSuchElementError) && gesture.action === 'release' &&
|
|
147
146
|
gesture.options.element) {
|
|
148
147
|
delete gesture.options.element;
|
|
149
|
-
log.debug(`retrying release without element opts: ${gesture.options}.`);
|
|
148
|
+
this.log.debug(`retrying release without element opts: ${gesture.options}.`);
|
|
150
149
|
return await this.doTouchAction(gesture.action, gesture.options || {});
|
|
151
150
|
}
|
|
152
151
|
throw e;
|