appium-android-driver 13.3.1 → 13.3.3
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/CHANGELOG.md +12 -0
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +13 -12
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/bidi/models.d.ts.map +1 -1
- package/build/lib/commands/bidi/models.js +1 -5
- package/build/lib/commands/bidi/models.js.map +1 -1
- package/build/lib/commands/bluetooth.d.ts.map +1 -1
- package/build/lib/commands/bluetooth.js +1 -5
- package/build/lib/commands/bluetooth.js.map +1 -1
- package/build/lib/commands/context/exports.d.ts.map +1 -1
- package/build/lib/commands/context/exports.js +11 -15
- package/build/lib/commands/context/exports.js.map +1 -1
- package/build/lib/commands/context/helpers.d.ts.map +1 -1
- package/build/lib/commands/context/helpers.js +43 -22
- package/build/lib/commands/context/helpers.js.map +1 -1
- package/build/lib/commands/device/common.d.ts.map +1 -1
- package/build/lib/commands/device/common.js +9 -8
- package/build/lib/commands/device/common.js.map +1 -1
- package/build/lib/commands/device/utils.d.ts.map +1 -1
- package/build/lib/commands/device/utils.js +2 -6
- package/build/lib/commands/device/utils.js.map +1 -1
- package/build/lib/commands/deviceidle.d.ts.map +1 -1
- package/build/lib/commands/deviceidle.js +2 -6
- package/build/lib/commands/deviceidle.js.map +1 -1
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +2 -6
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/file-actions.d.ts.map +1 -1
- package/build/lib/commands/file-actions.js +2 -3
- package/build/lib/commands/file-actions.js.map +1 -1
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js +4 -7
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/geolocation.d.ts.map +1 -1
- package/build/lib/commands/geolocation.js +3 -4
- package/build/lib/commands/geolocation.js.map +1 -1
- package/build/lib/commands/image-injection.d.ts.map +1 -1
- package/build/lib/commands/image-injection.js +1 -2
- package/build/lib/commands/image-injection.js.map +1 -1
- package/build/lib/commands/intent.d.ts.map +1 -1
- package/build/lib/commands/intent.js +14 -18
- package/build/lib/commands/intent.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +6 -6
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/lock/exports.d.ts.map +1 -1
- package/build/lib/commands/lock/exports.js +1 -5
- package/build/lib/commands/lock/exports.js.map +1 -1
- package/build/lib/commands/lock/helpers.d.ts.map +1 -1
- package/build/lib/commands/lock/helpers.js +5 -9
- package/build/lib/commands/lock/helpers.js.map +1 -1
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +9 -9
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/media-projection.d.ts.map +1 -1
- package/build/lib/commands/media-projection.js +5 -6
- package/build/lib/commands/media-projection.js.map +1 -1
- package/build/lib/commands/network.d.ts.map +1 -1
- package/build/lib/commands/network.js +12 -16
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/nfc.d.ts.map +1 -1
- package/build/lib/commands/nfc.js +1 -5
- package/build/lib/commands/nfc.js.map +1 -1
- package/build/lib/commands/performance.d.ts.map +1 -1
- package/build/lib/commands/performance.js +7 -12
- package/build/lib/commands/performance.js.map +1 -1
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +21 -23
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +10 -11
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/resources.d.ts.map +1 -1
- package/build/lib/commands/resources.js +6 -10
- package/build/lib/commands/resources.js.map +1 -1
- package/build/lib/commands/shell.d.ts.map +1 -1
- package/build/lib/commands/shell.js +7 -6
- package/build/lib/commands/shell.js.map +1 -1
- package/build/lib/commands/streamscreen.d.ts.map +1 -1
- package/build/lib/commands/streamscreen.js +14 -15
- package/build/lib/commands/streamscreen.js.map +1 -1
- package/build/lib/commands/system-bars.d.ts.map +1 -1
- package/build/lib/commands/system-bars.js +19 -21
- package/build/lib/commands/system-bars.js.map +1 -1
- package/build/lib/commands/time.d.ts.map +1 -1
- package/build/lib/commands/time.js +23 -8
- package/build/lib/commands/time.js.map +1 -1
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +2 -6
- package/build/lib/driver.js.map +1 -1
- package/build/lib/logger.d.ts +2 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +9 -12
- package/build/lib/utils.js.map +1 -1
- package/lib/commands/app-management.ts +19 -9
- package/lib/commands/bidi/models.ts +1 -2
- package/lib/commands/bluetooth.ts +1 -2
- package/lib/commands/context/exports.ts +12 -14
- package/lib/commands/context/helpers.ts +47 -22
- package/lib/commands/device/common.ts +10 -8
- package/lib/commands/device/utils.ts +2 -3
- package/lib/commands/deviceidle.ts +2 -3
- package/lib/commands/execute.ts +2 -3
- package/lib/commands/file-actions.ts +2 -3
- package/lib/commands/find.ts +4 -5
- package/lib/commands/geolocation.ts +3 -4
- package/lib/commands/image-injection.ts +1 -2
- package/lib/commands/intent.ts +14 -15
- package/lib/commands/keyboard.ts +6 -3
- package/lib/commands/lock/exports.ts +1 -2
- package/lib/commands/lock/helpers.ts +5 -6
- package/lib/commands/log.ts +9 -9
- package/lib/commands/media-projection.ts +5 -6
- package/lib/commands/network.ts +12 -13
- package/lib/commands/nfc.ts +1 -2
- package/lib/commands/performance.ts +7 -11
- package/lib/commands/permissions.ts +23 -20
- package/lib/commands/recordscreen.ts +10 -11
- package/lib/commands/resources.ts +7 -8
- package/lib/commands/shell.ts +7 -3
- package/lib/commands/streamscreen.ts +14 -16
- package/lib/commands/system-bars.ts +20 -20
- package/lib/commands/time.ts +26 -8
- package/lib/driver.ts +2 -4
- package/lib/logger.ts +2 -1
- package/lib/utils.ts +9 -9
- package/package.json +2 -5
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
toCredentialType,
|
|
16
16
|
verifyUnlock,
|
|
17
17
|
} from './helpers';
|
|
18
|
-
import _ from 'lodash';
|
|
19
18
|
import type {AndroidDriver, AndroidDriverCaps} from '../../driver';
|
|
20
19
|
import type {UnlockType} from '../types';
|
|
21
20
|
|
|
@@ -119,7 +118,7 @@ export async function unlockWithOptions(
|
|
|
119
118
|
if (
|
|
120
119
|
unlockKey &&
|
|
121
120
|
unlockType !== FINGERPRINT_UNLOCK &&
|
|
122
|
-
(
|
|
121
|
+
(unlockStrategy == null || String(unlockStrategy)?.toLowerCase() === 'locksettings') &&
|
|
123
122
|
(await this.adb.isLockManagementSupported())
|
|
124
123
|
) {
|
|
125
124
|
await fastUnlock.bind(this)({
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {util} from '@appium/support';
|
|
2
2
|
import {sleep, waitForCondition} from 'asyncbox';
|
|
3
|
-
import _ from 'lodash';
|
|
4
3
|
import type {ADB} from 'appium-adb';
|
|
5
4
|
import type {Position, StringRecord} from '@appium/types';
|
|
6
5
|
import type {AndroidDriver, AndroidDriverCaps} from '../../driver';
|
|
@@ -58,16 +57,16 @@ export function validateUnlockCapabilities<T extends AndroidDriverCaps>(caps: T)
|
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
if ([PIN_UNLOCK, PIN_UNLOCK_KEY_EVENT, FINGERPRINT_UNLOCK].includes(unlockType)) {
|
|
61
|
-
if (!/^[0-9]+$/.test(
|
|
60
|
+
if (!/^[0-9]+$/.test(String(unlockKey)?.trim())) {
|
|
62
61
|
throw new Error(`Unlock key value '${unlockKey}' must only consist of digits`);
|
|
63
62
|
}
|
|
64
63
|
} else if (unlockType === PATTERN_UNLOCK) {
|
|
65
|
-
if (!/^[1-9]{2,9}$/.test(
|
|
64
|
+
if (!/^[1-9]{2,9}$/.test(String(unlockKey)?.trim())) {
|
|
66
65
|
throw new Error(
|
|
67
66
|
`Unlock key value '${unlockKey}' must only include from two to nine digits in range 1..9`,
|
|
68
67
|
);
|
|
69
68
|
}
|
|
70
|
-
if (/([1-9]).*?\1/.test(
|
|
69
|
+
if (/([1-9]).*?\1/.test(String(unlockKey)?.trim())) {
|
|
71
70
|
throw new Error(
|
|
72
71
|
`Unlock key value '${unlockKey}' must define a valid pattern where repeats are not allowed`,
|
|
73
72
|
);
|
|
@@ -161,7 +160,7 @@ export async function pinUnlock(
|
|
|
161
160
|
await this.adb.dismissKeyguard();
|
|
162
161
|
const keys = stringKeyToArr(String(capabilities.unlockKey));
|
|
163
162
|
const els = await this.findElOrEls('id', 'com.android.systemui:id/digit_text', true);
|
|
164
|
-
if (
|
|
163
|
+
if (util.isEmpty(els)) {
|
|
165
164
|
// fallback to pin with key event
|
|
166
165
|
return await pinUnlockWithKeyEvent.bind(this)(capabilities);
|
|
167
166
|
}
|
|
@@ -267,7 +266,7 @@ export function getPatternActions(
|
|
|
267
266
|
): StringRecord[] {
|
|
268
267
|
// https://www.w3.org/TR/webdriver2/#actions
|
|
269
268
|
const pointerActions: StringRecord[] = [];
|
|
270
|
-
const intKeys = keys.map((key) => (
|
|
269
|
+
const intKeys = keys.map((key) => (typeof key === 'string' ? parseInt(key, 10) : key));
|
|
271
270
|
let lastPos: Position | undefined;
|
|
272
271
|
for (const key of intKeys) {
|
|
273
272
|
const keyPos = getPatternKeyPosition(key, initPos, piece);
|
package/lib/commands/log.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {DEFAULT_WS_PATHNAME_PREFIX, BaseDriver} from 'appium/driver';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import os from 'node:os';
|
|
4
3
|
import WebSocket from 'ws';
|
|
5
4
|
import type {AppiumServer, WSServer} from '@appium/types';
|
|
@@ -16,6 +15,7 @@ import {NATIVE_WIN} from './context/helpers';
|
|
|
16
15
|
import {BIDI_EVENT_NAME} from './bidi/constants';
|
|
17
16
|
import {makeLogEntryAddedEvent} from './bidi/models';
|
|
18
17
|
import type {AndroidDriver} from '../driver';
|
|
18
|
+
import {util} from '@appium/support';
|
|
19
19
|
|
|
20
20
|
export const supportedLogTypes = {
|
|
21
21
|
logcat: {
|
|
@@ -52,7 +52,7 @@ export const supportedLogTypes = {
|
|
|
52
52
|
export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<void> {
|
|
53
53
|
const server = this.server as AppiumServer;
|
|
54
54
|
const pathname = WEBSOCKET_ENDPOINT(this.sessionId as string);
|
|
55
|
-
if (!
|
|
55
|
+
if (!util.isEmpty(await server.getWebSocketHandlers(pathname))) {
|
|
56
56
|
this.log.debug(`The logcat broadcasting web socket server is already listening at ${pathname}`);
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
@@ -67,7 +67,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
|
|
|
67
67
|
});
|
|
68
68
|
wss.on('connection', (ws, req) => {
|
|
69
69
|
if (req) {
|
|
70
|
-
const remoteIp =
|
|
70
|
+
const remoteIp = util.isEmpty(req.headers['x-forwarded-for'])
|
|
71
71
|
? req.socket.remoteAddress
|
|
72
72
|
: req.headers['x-forwarded-for'];
|
|
73
73
|
this.log.debug(`Established a new logcat listener web socket connection from ${remoteIp}`);
|
|
@@ -75,7 +75,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
|
|
|
75
75
|
this.log.debug('Established a new logcat listener web socket connection');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
if (
|
|
78
|
+
if (!this._logcatWebsocketListener) {
|
|
79
79
|
this._logcatWebsocketListener = (logRecord: LogEntry) => {
|
|
80
80
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
81
81
|
ws.send(logRecord.message);
|
|
@@ -85,7 +85,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
|
|
|
85
85
|
this.adb.setLogcatListener(this._logcatWebsocketListener);
|
|
86
86
|
|
|
87
87
|
ws.on('close', (code, reason) => {
|
|
88
|
-
if (
|
|
88
|
+
if (this._logcatWebsocketListener) {
|
|
89
89
|
try {
|
|
90
90
|
this.adb.removeLogcatListener(this._logcatWebsocketListener);
|
|
91
91
|
} catch {}
|
|
@@ -93,10 +93,10 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
let closeMsg = 'Logcat listener web socket is closed.';
|
|
96
|
-
if (!
|
|
96
|
+
if (!util.isEmpty(code)) {
|
|
97
97
|
closeMsg += ` Code: ${code}.`;
|
|
98
98
|
}
|
|
99
|
-
if (!
|
|
99
|
+
if (!util.isEmpty(reason)) {
|
|
100
100
|
closeMsg += ` Reason: ${reason.toString()}.`;
|
|
101
101
|
}
|
|
102
102
|
this.log.debug(closeMsg);
|
|
@@ -114,7 +114,7 @@ export async function mobileStartLogsBroadcast(this: AndroidDriver): Promise<voi
|
|
|
114
114
|
export async function mobileStopLogsBroadcast(this: AndroidDriver): Promise<void> {
|
|
115
115
|
const pathname = WEBSOCKET_ENDPOINT(this.sessionId as string);
|
|
116
116
|
const server = this.server as AppiumServer;
|
|
117
|
-
if (
|
|
117
|
+
if (util.isEmpty(await server.getWebSocketHandlers(pathname))) {
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -174,7 +174,7 @@ export function assignBiDiLogListener<EE extends EventEmitter>(
|
|
|
174
174
|
* @returns Promise that resolves to the logs for the specified type.
|
|
175
175
|
*/
|
|
176
176
|
export async function getLog(this: AndroidDriver, logType: string): Promise<any> {
|
|
177
|
-
if (this.isWebContext() && !
|
|
177
|
+
if (this.isWebContext() && !Object.keys(this.supportedLogTypes).includes(logType)) {
|
|
178
178
|
return await (this.chromedriver as Chromedriver).jwproxy.command('/log', 'POST', {
|
|
179
179
|
type: logType,
|
|
180
180
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {fs, net, util} from '@appium/support';
|
|
2
2
|
import type {NetOptions, HttpUploadOptions} from '@appium/support';
|
|
3
|
-
import
|
|
4
|
-
import moment from 'moment';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import type {HTTPMethod, StringRecord} from '@appium/types';
|
|
7
6
|
import type {AndroidDriver} from '../driver';
|
|
@@ -51,7 +50,7 @@ export async function mobileStartMediaProjectionRecording(
|
|
|
51
50
|
await verifyMediaProjectionRecordingIsSupported(this.adb);
|
|
52
51
|
|
|
53
52
|
const recorder = this.settingsApp.makeMediaProjectionRecorder();
|
|
54
|
-
const fname = adjustMediaExtension(filename ||
|
|
53
|
+
const fname = adjustMediaExtension(filename || dayjs().format(DEFAULT_FILENAME_FORMAT));
|
|
55
54
|
const didStart = await recorder.start({
|
|
56
55
|
resolution,
|
|
57
56
|
priority,
|
|
@@ -128,7 +127,7 @@ export async function mobileStopMediaProjectionRecording(
|
|
|
128
127
|
throw new Error(`No recent media projection recording have been found. Did you start any?`);
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
if (
|
|
130
|
+
if (util.isEmpty(remotePath)) {
|
|
132
131
|
const {size} = await fs.stat(recentRecordingPath);
|
|
133
132
|
this.log.debug(
|
|
134
133
|
`The size of the resulting media projection recording is ${util.toReadableSizeString(size)}`,
|
|
@@ -154,7 +153,7 @@ async function uploadRecordedMedia(
|
|
|
154
153
|
remotePath?: string,
|
|
155
154
|
uploadOptions: UploadOptions = {},
|
|
156
155
|
): Promise<string> {
|
|
157
|
-
if (
|
|
156
|
+
if (util.isEmpty(remotePath)) {
|
|
158
157
|
return (await util.toInMemoryBase64(localFile)).toString();
|
|
159
158
|
}
|
|
160
159
|
|
|
@@ -182,7 +181,7 @@ async function uploadRecordedMedia(
|
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
function adjustMediaExtension(name: string): string {
|
|
185
|
-
return
|
|
184
|
+
return name?.toLowerCase()?.endsWith(DEFAULT_EXT) ? name : `${name}${DEFAULT_EXT}`;
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
async function verifyMediaProjectionRecordingIsSupported(adb: ADB): Promise<void> {
|
package/lib/commands/network.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {errors} from 'appium/driver';
|
|
3
2
|
import {util} from '@appium/support';
|
|
4
3
|
import type {AndroidDriver} from '../driver';
|
|
@@ -69,7 +68,7 @@ export async function mobileSetConnectivity(
|
|
|
69
68
|
data?: boolean,
|
|
70
69
|
airplaneMode?: boolean,
|
|
71
70
|
): Promise<void> {
|
|
72
|
-
if (
|
|
71
|
+
if (wifi === undefined && data === undefined && airplaneMode === undefined) {
|
|
73
72
|
throw new errors.InvalidArgumentError(
|
|
74
73
|
`Either one of ${JSON.stringify(SUPPORTED_SERVICE_NAMES)} options must be provided`,
|
|
75
74
|
);
|
|
@@ -81,20 +80,20 @@ export async function mobileSetConnectivity(
|
|
|
81
80
|
[airplaneMode, AIRPLANE_MODE_KEY_NAME],
|
|
82
81
|
];
|
|
83
82
|
const services = connectivityPairs.reduce<ServiceType[]>((acc, [value, key]) => {
|
|
84
|
-
if (
|
|
83
|
+
if (value !== undefined) {
|
|
85
84
|
acc.push(key);
|
|
86
85
|
}
|
|
87
86
|
return acc;
|
|
88
87
|
}, []);
|
|
89
88
|
const currentState = await this.mobileGetConnectivity(services);
|
|
90
89
|
const setters: Promise<any>[] = [];
|
|
91
|
-
if (
|
|
90
|
+
if (wifi !== undefined && currentState.wifi !== Boolean(wifi)) {
|
|
92
91
|
setters.push(this.setWifiState(wifi));
|
|
93
92
|
}
|
|
94
|
-
if (
|
|
93
|
+
if (data !== undefined && currentState.data !== Boolean(data)) {
|
|
95
94
|
setters.push(this.setDataState(data));
|
|
96
95
|
}
|
|
97
|
-
if (
|
|
96
|
+
if (airplaneMode !== undefined && currentState.airplaneMode !== Boolean(airplaneMode)) {
|
|
98
97
|
setters.push(
|
|
99
98
|
(async () => {
|
|
100
99
|
await this.adb.setAirplaneMode(airplaneMode);
|
|
@@ -104,7 +103,7 @@ export async function mobileSetConnectivity(
|
|
|
104
103
|
})(),
|
|
105
104
|
);
|
|
106
105
|
}
|
|
107
|
-
if (
|
|
106
|
+
if (setters.length > 0) {
|
|
108
107
|
await Promise.all(setters);
|
|
109
108
|
}
|
|
110
109
|
}
|
|
@@ -120,9 +119,9 @@ export async function mobileGetConnectivity(
|
|
|
120
119
|
this: AndroidDriver,
|
|
121
120
|
services: ServiceType[] | ServiceType = SUPPORTED_SERVICE_NAMES,
|
|
122
121
|
): Promise<GetConnectivityResult> {
|
|
123
|
-
const svcs =
|
|
124
|
-
const unsupportedServices =
|
|
125
|
-
if (
|
|
122
|
+
const svcs = Array.isArray(services) ? services : [services];
|
|
123
|
+
const unsupportedServices = svcs.filter((svc) => !SUPPORTED_SERVICE_NAMES.includes(svc));
|
|
124
|
+
if (unsupportedServices.length > 0) {
|
|
126
125
|
throw new errors.InvalidArgumentError(
|
|
127
126
|
`${util.pluralize(
|
|
128
127
|
'Service name',
|
|
@@ -144,13 +143,13 @@ export async function mobileGetConnectivity(
|
|
|
144
143
|
: Promise.resolve(undefined),
|
|
145
144
|
]);
|
|
146
145
|
const result: GetConnectivityResult = {};
|
|
147
|
-
if (
|
|
146
|
+
if (wifi !== undefined) {
|
|
148
147
|
result.wifi = Boolean(wifi);
|
|
149
148
|
}
|
|
150
|
-
if (
|
|
149
|
+
if (data !== undefined) {
|
|
151
150
|
result.data = Boolean(data);
|
|
152
151
|
}
|
|
153
|
-
if (
|
|
152
|
+
if (airplaneMode !== undefined) {
|
|
154
153
|
result.airplaneMode = Boolean(airplaneMode);
|
|
155
154
|
}
|
|
156
155
|
return result;
|
package/lib/commands/nfc.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {errors} from 'appium/driver';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import type {AndroidDriver} from '../driver';
|
|
4
3
|
import type {NfcAction} from './types';
|
|
5
4
|
|
|
@@ -27,7 +26,7 @@ export async function mobileNfc(this: AndroidDriver, action: NfcAction): Promise
|
|
|
27
26
|
break;
|
|
28
27
|
default:
|
|
29
28
|
throw new errors.InvalidArgumentError(
|
|
30
|
-
`You must provide a valid 'action' argument. Supported actions are: ${
|
|
29
|
+
`You must provide a valid 'action' argument. Supported actions are: ${Object.values(SUPPORTED_ACTIONS)}`,
|
|
31
30
|
);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {retryInterval} from 'asyncbox';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import type {ExecError} from 'teen_process';
|
|
4
3
|
import type {AndroidDriver} from '../driver';
|
|
5
4
|
import type {PerformanceDataType} from './types';
|
|
5
|
+
import {util} from '@appium/support';
|
|
6
6
|
|
|
7
7
|
export const NETWORK_KEYS = [
|
|
8
8
|
[
|
|
@@ -68,7 +68,7 @@ const RETRY_PAUSE_MS = 1000;
|
|
|
68
68
|
* The possible values are: 'cpuinfo', 'memoryinfo', 'batteryinfo', 'networkinfo'.
|
|
69
69
|
*/
|
|
70
70
|
export async function getPerformanceDataTypes(this: AndroidDriver): Promise<PerformanceDataType[]> {
|
|
71
|
-
return
|
|
71
|
+
return Object.keys(SUPPORTED_PERFORMANCE_DATA_TYPES) as PerformanceDataType[];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -89,7 +89,7 @@ export async function getPerformanceData(
|
|
|
89
89
|
dataType: string,
|
|
90
90
|
retries: number = 2,
|
|
91
91
|
): Promise<any[][]> {
|
|
92
|
-
switch (
|
|
92
|
+
switch (dataType?.toLowerCase()) {
|
|
93
93
|
case 'batteryinfo':
|
|
94
94
|
return await getBatteryInfo.call(this, retries);
|
|
95
95
|
case 'cpuinfo':
|
|
@@ -187,7 +187,7 @@ export async function getMemoryInfo(
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
if (valDict.totalPrivateDirty && valDict.totalPrivateDirty !== 'nodex') {
|
|
190
|
-
const headers =
|
|
190
|
+
const headers = structuredClone(MEMORY_KEYS);
|
|
191
191
|
const values = headers.map((header) => valDict[header]);
|
|
192
192
|
return [headers, values];
|
|
193
193
|
}
|
|
@@ -371,11 +371,7 @@ export async function getNetworkTrafficInfo(
|
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
-
if (
|
|
375
|
-
!_.isEqual(pendingBytes, '') &&
|
|
376
|
-
!_.isUndefined(pendingBytes) &&
|
|
377
|
-
!_.isEqual(pendingBytes, 'nodex')
|
|
378
|
-
) {
|
|
374
|
+
if (pendingBytes !== '' && pendingBytes !== undefined && pendingBytes !== 'nodex') {
|
|
379
375
|
return returnValue;
|
|
380
376
|
} else {
|
|
381
377
|
throw new Error(`Unable to parse network traffic data: '${data}'`);
|
|
@@ -427,7 +423,7 @@ export async function getCPUInfo(
|
|
|
427
423
|
// `output` will be something like
|
|
428
424
|
// +0% 2209/io.appium.android.apis: 0.1% user + 0.2% kernel / faults: 70 minor
|
|
429
425
|
const usagesPattern = new RegExp(
|
|
430
|
-
`^.+\\/${
|
|
426
|
+
`^.+\\/${util.escapeRegExp(packageName)}:\\D+([\\d.]+)%\\s+user\\s+\\+\\s+([\\d.]+)%\\s+kernel`,
|
|
431
427
|
'm',
|
|
432
428
|
);
|
|
433
429
|
const match = usagesPattern.exec(output);
|
|
@@ -462,7 +458,7 @@ export async function getBatteryInfo(this: AndroidDriver, retries: number = 2):
|
|
|
462
458
|
const power = parseInt((data.split(':')[1] || '').trim(), 10);
|
|
463
459
|
|
|
464
460
|
if (!Number.isNaN(power)) {
|
|
465
|
-
return [
|
|
461
|
+
return [structuredClone(BATTERY_KEYS), [power.toString()]];
|
|
466
462
|
} else {
|
|
467
463
|
throw new Error(`Unable to parse battery data: '${data}'`);
|
|
468
464
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {errors} from 'appium/driver';
|
|
2
|
-
import _ from 'lodash';
|
|
3
2
|
import {ADB_SHELL_FEATURE} from '../utils';
|
|
4
3
|
import type {AndroidDriver} from '../driver';
|
|
4
|
+
import {util} from '@appium/support';
|
|
5
5
|
|
|
6
6
|
const ALL_PERMISSIONS_MAGIC = 'all';
|
|
7
7
|
const PM_ACTION = Object.freeze({
|
|
@@ -67,31 +67,32 @@ export async function mobileChangePermissions(
|
|
|
67
67
|
if (!appPackage) {
|
|
68
68
|
throw new errors.InvalidArgumentError(`A valid appPackage must be provided`);
|
|
69
69
|
}
|
|
70
|
-
action ??=
|
|
71
|
-
|
|
70
|
+
action ??=
|
|
71
|
+
target?.toLowerCase() === PERMISSION_TARGET.APPOPS ? APPOPS_ACTION.ALLOW : PM_ACTION.GRANT;
|
|
72
|
+
if (permissions == null) {
|
|
72
73
|
throw new errors.InvalidArgumentError(`'permissions' argument is required`);
|
|
73
74
|
}
|
|
74
|
-
if (
|
|
75
|
+
if (util.isEmpty(permissions)) {
|
|
75
76
|
throw new errors.InvalidArgumentError(`'permissions' argument must not be empty`);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
switch (
|
|
79
|
+
switch (target?.toLowerCase()) {
|
|
79
80
|
case PERMISSION_TARGET.PM:
|
|
80
81
|
return await changePermissionsViaPm.bind(this)(
|
|
81
82
|
permissions,
|
|
82
83
|
appPackage,
|
|
83
|
-
|
|
84
|
+
action?.toLowerCase() as PMAction,
|
|
84
85
|
);
|
|
85
86
|
case PERMISSION_TARGET.APPOPS:
|
|
86
87
|
this.assertFeatureEnabled(ADB_SHELL_FEATURE);
|
|
87
88
|
return await changePermissionsViaAppops.bind(this)(
|
|
88
89
|
permissions,
|
|
89
90
|
appPackage,
|
|
90
|
-
|
|
91
|
+
action?.toLowerCase() as AppOpsAction,
|
|
91
92
|
);
|
|
92
93
|
default:
|
|
93
94
|
throw new errors.InvalidArgumentError(
|
|
94
|
-
`'target' argument must be one of: ${
|
|
95
|
+
`'target' argument must be one of: ${Object.values(PERMISSION_TARGET)}`,
|
|
95
96
|
);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
@@ -111,7 +112,7 @@ export async function mobileGetPermissions(
|
|
|
111
112
|
): Promise<string[]> {
|
|
112
113
|
appPackage ??= this.opts.appPackage;
|
|
113
114
|
let actionFunc: (pkg: string) => Promise<string[]>;
|
|
114
|
-
switch (
|
|
115
|
+
switch (type?.toLowerCase()) {
|
|
115
116
|
case PERMISSIONS_TYPE.REQUESTED:
|
|
116
117
|
actionFunc = (pkg) => this.adb.getReqPermissions(pkg);
|
|
117
118
|
break;
|
|
@@ -124,7 +125,7 @@ export async function mobileGetPermissions(
|
|
|
124
125
|
default:
|
|
125
126
|
throw new errors.InvalidArgumentError(
|
|
126
127
|
`Unknown permissions type '${type}'. ` +
|
|
127
|
-
`Only ${JSON.stringify(
|
|
128
|
+
`Only ${JSON.stringify(Object.values(PERMISSIONS_TYPE))} types are supported`,
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
return await actionFunc(appPackage as string);
|
|
@@ -135,24 +136,26 @@ async function changePermissionsViaPm(
|
|
|
135
136
|
appPackage: string,
|
|
136
137
|
action: PMAction,
|
|
137
138
|
): Promise<void> {
|
|
138
|
-
if (!
|
|
139
|
+
if (!Object.values(PM_ACTION).includes(action)) {
|
|
139
140
|
throw new errors.InvalidArgumentError(
|
|
140
141
|
`Unknown action '${action}'. ` +
|
|
141
|
-
`Only ${JSON.stringify(
|
|
142
|
+
`Only ${JSON.stringify(Object.values(PM_ACTION))} actions are supported`,
|
|
142
143
|
);
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
let affectedPermissions =
|
|
146
|
-
if (
|
|
146
|
+
let affectedPermissions = Array.isArray(permissions) ? permissions : [permissions];
|
|
147
|
+
if (typeof permissions === 'string' && permissions.toLowerCase() === ALL_PERMISSIONS_MAGIC) {
|
|
147
148
|
const dumpsys = await this.adb.shell(['dumpsys', 'package', appPackage]);
|
|
148
149
|
const grantedPermissions = await this.adb.getGrantedPermissions(appPackage, dumpsys);
|
|
149
150
|
if (action === PM_ACTION.GRANT) {
|
|
150
151
|
const reqPermissons = await this.adb.getReqPermissions(appPackage, dumpsys);
|
|
151
|
-
affectedPermissions =
|
|
152
|
+
affectedPermissions = reqPermissons.filter(
|
|
153
|
+
(permission) => !grantedPermissions.includes(permission),
|
|
154
|
+
);
|
|
152
155
|
} else {
|
|
153
156
|
affectedPermissions = grantedPermissions;
|
|
154
157
|
}
|
|
155
|
-
if (
|
|
158
|
+
if (util.isEmpty(affectedPermissions)) {
|
|
156
159
|
this.log.info(`'${appPackage}' contains no permissions to ${action}`);
|
|
157
160
|
return;
|
|
158
161
|
}
|
|
@@ -172,13 +175,13 @@ async function changePermissionsViaAppops(
|
|
|
172
175
|
appPackage: string,
|
|
173
176
|
action: AppOpsAction,
|
|
174
177
|
): Promise<void> {
|
|
175
|
-
if (!
|
|
178
|
+
if (!Object.values(APPOPS_ACTION).includes(action)) {
|
|
176
179
|
throw new errors.InvalidArgumentError(
|
|
177
180
|
`Unknown action '${action}'. ` +
|
|
178
|
-
`Only ${JSON.stringify(
|
|
181
|
+
`Only ${JSON.stringify(Object.values(APPOPS_ACTION))} actions are supported`,
|
|
179
182
|
);
|
|
180
183
|
}
|
|
181
|
-
if (
|
|
184
|
+
if (typeof permissions === 'string' && permissions.toLowerCase() === ALL_PERMISSIONS_MAGIC) {
|
|
182
185
|
throw new errors.InvalidArgumentError(
|
|
183
186
|
`'${ALL_PERMISSIONS_MAGIC}' permission is only supported for ` +
|
|
184
187
|
`'${PERMISSION_TARGET.PM}' target. ` +
|
|
@@ -186,7 +189,7 @@ async function changePermissionsViaAppops(
|
|
|
186
189
|
);
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
const promises = (
|
|
192
|
+
const promises = (Array.isArray(permissions) ? permissions : [permissions]).map((permission) =>
|
|
190
193
|
this.adb.shell(['appops', 'set', appPackage, permission, action]),
|
|
191
194
|
);
|
|
192
195
|
await Promise.all(promises);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {fs, net, system, tempDir, timing, util} from '@appium/support';
|
|
2
2
|
import type {NetOptions, HttpUploadOptions} from '@appium/support';
|
|
3
3
|
import {waitForCondition} from 'asyncbox';
|
|
4
|
-
import _ from 'lodash';
|
|
5
4
|
import path from 'node:path';
|
|
6
5
|
import {exec} from 'teen_process';
|
|
7
6
|
import type {AndroidDriver} from '../driver';
|
|
@@ -63,7 +62,7 @@ export async function startRecordingScreen(
|
|
|
63
62
|
);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
if (
|
|
65
|
+
if (this._screenRecordingProperties) {
|
|
67
66
|
// XXX: this doesn't need to be done in serial, does it?
|
|
68
67
|
const props = this._screenRecordingProperties;
|
|
69
68
|
for (const record of props.records || []) {
|
|
@@ -117,7 +116,7 @@ export async function stopRecordingScreen(
|
|
|
117
116
|
await verifyScreenRecordIsSupported(this.adb, this.isEmulator());
|
|
118
117
|
|
|
119
118
|
const props = this._screenRecordingProperties;
|
|
120
|
-
if (
|
|
119
|
+
if (props) {
|
|
121
120
|
props.stopped = true;
|
|
122
121
|
}
|
|
123
122
|
|
|
@@ -125,12 +124,12 @@ export async function stopRecordingScreen(
|
|
|
125
124
|
await terminateBackgroundScreenRecording(this.adb, false);
|
|
126
125
|
} catch (err) {
|
|
127
126
|
this.log.warn((err as Error).message);
|
|
128
|
-
if (
|
|
127
|
+
if (props) {
|
|
129
128
|
this.log.warn('The resulting video might be corrupted');
|
|
130
129
|
}
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
if (
|
|
132
|
+
if (!props) {
|
|
134
133
|
this.log.info(
|
|
135
134
|
`Screen recording has not been previously started by Appium. There is nothing to stop`,
|
|
136
135
|
);
|
|
@@ -148,7 +147,7 @@ export async function stopRecordingScreen(
|
|
|
148
147
|
props.recordingProcess = null;
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
if (
|
|
150
|
+
if (util.isEmpty(props.records)) {
|
|
152
151
|
throw this.log.errorWithException(
|
|
153
152
|
`No screen recordings have been stored on the device so far. ` +
|
|
154
153
|
`Are you sure the ${SCREENRECORD_BINARY} utility works as expected?`,
|
|
@@ -164,7 +163,7 @@ export async function stopRecordingScreen(
|
|
|
164
163
|
await this.adb.pull(pathOnDevice, relativePath, {timeout: ADB_PULL_TIMEOUT});
|
|
165
164
|
await this.adb.rimraf(pathOnDevice);
|
|
166
165
|
}
|
|
167
|
-
let resultFilePath =
|
|
166
|
+
let resultFilePath = localRecords.at(-1) as string;
|
|
168
167
|
if (localRecords.length > 1) {
|
|
169
168
|
this.log.info(`Got ${localRecords.length} screen recordings. Trying to merge them`);
|
|
170
169
|
try {
|
|
@@ -176,7 +175,7 @@ export async function stopRecordingScreen(
|
|
|
176
175
|
);
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
|
-
if (
|
|
178
|
+
if (util.isEmpty(options.remotePath)) {
|
|
180
179
|
const {size} = await fs.stat(resultFilePath);
|
|
181
180
|
this.log.debug(
|
|
182
181
|
`The size of the resulting screen recording is ${util.toReadableSizeString(size)}`,
|
|
@@ -196,7 +195,7 @@ async function uploadRecordedMedia(
|
|
|
196
195
|
remotePath?: string,
|
|
197
196
|
uploadOptions: StopScreenRecordingOpts = {},
|
|
198
197
|
): Promise<string> {
|
|
199
|
-
if (
|
|
198
|
+
if (util.isEmpty(remotePath)) {
|
|
200
199
|
return (await util.toInMemoryBase64(localFile)).toString();
|
|
201
200
|
}
|
|
202
201
|
|
|
@@ -322,14 +321,14 @@ async function mergeScreenRecords(this: AndroidDriver, mediaFiles: string[]): Pr
|
|
|
322
321
|
|
|
323
322
|
async function terminateBackgroundScreenRecording(adb: ADB, force = true): Promise<boolean> {
|
|
324
323
|
const screenrecordPids = await adb.getProcessIdsByName(SCREENRECORD_BINARY);
|
|
325
|
-
if (
|
|
324
|
+
if (util.isEmpty(screenrecordPids)) {
|
|
326
325
|
return false;
|
|
327
326
|
}
|
|
328
327
|
|
|
329
328
|
try {
|
|
330
329
|
await adb.shell(['kill', force ? '-15' : '-2', ...screenrecordPids.map(String)]);
|
|
331
330
|
await waitForCondition(
|
|
332
|
-
async () =>
|
|
331
|
+
async () => util.isEmpty(await adb.getProcessIdsByName(SCREENRECORD_BINARY)),
|
|
333
332
|
{
|
|
334
333
|
waitMs: PROCESS_SHUTDOWN_TIMEOUT,
|
|
335
334
|
intervalMs: 500,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {fs, tempDir} from '@appium/support';
|
|
1
|
+
import {fs, tempDir, util} from '@appium/support';
|
|
3
2
|
import type {StringRecord} from '@appium/types';
|
|
4
3
|
import type {AndroidDriver, AndroidDriverOpts} from '../driver';
|
|
5
4
|
import type {Locale} from './types';
|
|
@@ -24,8 +23,8 @@ export async function getStrings(
|
|
|
24
23
|
// and values of type string
|
|
25
24
|
const preprocessStringsMap = (mapping: StringRecord): StringRecord => {
|
|
26
25
|
const result: StringRecord = {};
|
|
27
|
-
for (const [key, value] of
|
|
28
|
-
result[key] =
|
|
26
|
+
for (const [key, value] of Object.entries(mapping)) {
|
|
27
|
+
result[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
29
28
|
}
|
|
30
29
|
return result;
|
|
31
30
|
};
|
|
@@ -63,7 +62,7 @@ export async function ensureDeviceLocale(
|
|
|
63
62
|
} catch (e1) {
|
|
64
63
|
this.log.debug((e1 as Error).stack);
|
|
65
64
|
}
|
|
66
|
-
if (
|
|
65
|
+
if (suggestions.length > 0) {
|
|
67
66
|
errMsg += ` You may want to apply one of the following locales instead: ${suggestions}`;
|
|
68
67
|
}
|
|
69
68
|
throw new Error(errMsg, {cause: e});
|
|
@@ -114,10 +113,10 @@ async function fetchLocaleSuggestions(
|
|
|
114
113
|
const supportedLocales = await this.settingsApp.listSupportedLocales();
|
|
115
114
|
const suggestedLocales = supportedLocales.filter(
|
|
116
115
|
(locale) =>
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
language?.toLowerCase() === locale.language?.toLowerCase() ||
|
|
117
|
+
country?.toLowerCase() === locale.country?.toLowerCase(),
|
|
119
118
|
);
|
|
120
|
-
return
|
|
119
|
+
return util.isEmpty(suggestedLocales) ? supportedLocales : suggestedLocales;
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
function toLocaleAbbr({language, country, script}: Locale): string {
|
package/lib/commands/shell.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {util} from '@appium/support';
|
|
2
2
|
import {errors} from 'appium/driver';
|
|
3
|
-
import _ from 'lodash';
|
|
4
3
|
import {exec} from 'teen_process';
|
|
5
4
|
import type {ExecError} from 'teen_process';
|
|
6
5
|
import {ADB_SHELL_FEATURE} from '../utils';
|
|
@@ -36,11 +35,16 @@ export async function mobileShell<T extends boolean>(
|
|
|
36
35
|
): Promise<T extends true ? {stdout: string; stderr: string} : string> {
|
|
37
36
|
this.assertFeatureEnabled(ADB_SHELL_FEATURE);
|
|
38
37
|
|
|
39
|
-
if (
|
|
38
|
+
if (typeof command !== 'string') {
|
|
40
39
|
throw new errors.InvalidArgumentError(`The 'command' argument is mandatory`);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
const adbArgs = [
|
|
42
|
+
const adbArgs = [
|
|
43
|
+
...this.adb.executable.defaultArgs,
|
|
44
|
+
'shell',
|
|
45
|
+
command,
|
|
46
|
+
...(Array.isArray(args) ? args : [args]),
|
|
47
|
+
];
|
|
44
48
|
this.log.debug(`Running '${this.adb.executable.path} ${util.quote(adbArgs)}'`);
|
|
45
49
|
try {
|
|
46
50
|
const {stdout, stderr} = await exec(this.adb.executable.path, adbArgs, {timeout});
|