appium-android-driver 12.4.7 → 12.4.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/CHANGELOG.md +12 -0
- package/build/lib/commands/file-actions.d.ts +37 -19
- package/build/lib/commands/file-actions.d.ts.map +1 -1
- package/build/lib/commands/file-actions.js +44 -58
- package/build/lib/commands/file-actions.js.map +1 -1
- package/build/lib/commands/geolocation.d.ts +44 -36
- package/build/lib/commands/geolocation.d.ts.map +1 -1
- package/build/lib/commands/geolocation.js +38 -32
- package/build/lib/commands/geolocation.js.map +1 -1
- package/build/lib/commands/intent.d.ts +103 -107
- package/build/lib/commands/intent.d.ts.map +1 -1
- package/build/lib/commands/intent.js +103 -97
- package/build/lib/commands/intent.js.map +1 -1
- package/build/lib/commands/log.d.ts +44 -48
- package/build/lib/commands/log.d.ts.map +1 -1
- package/build/lib/commands/log.js +30 -54
- package/build/lib/commands/log.js.map +1 -1
- package/build/lib/commands/network.d.ts +59 -39
- package/build/lib/commands/network.d.ts.map +1 -1
- package/build/lib/commands/network.js +65 -45
- package/build/lib/commands/network.js.map +1 -1
- package/build/lib/commands/recordscreen.d.ts +25 -40
- package/build/lib/commands/recordscreen.d.ts.map +1 -1
- package/build/lib/commands/recordscreen.js +46 -63
- package/build/lib/commands/recordscreen.js.map +1 -1
- package/build/lib/commands/types.d.ts.map +1 -1
- package/build/lib/driver.d.ts +11 -10
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js.map +1 -1
- package/lib/commands/{file-actions.js → file-actions.ts} +88 -74
- package/lib/commands/{geolocation.js → geolocation.ts} +85 -54
- package/lib/commands/intent.ts +422 -0
- package/lib/commands/{log.js → log.ts} +68 -73
- package/lib/commands/{network.js → network.ts} +106 -59
- package/lib/commands/{recordscreen.js → recordscreen.ts} +77 -73
- package/lib/commands/types.ts +17 -0
- package/lib/driver.ts +2 -1
- package/package.json +1 -1
- package/lib/commands/intent.js +0 -409
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import {errors} from 'appium/driver';
|
|
3
|
+
import {util} from '@appium/support';
|
|
4
|
+
import type {AndroidDriver} from '../driver';
|
|
5
|
+
import type {IntentOpts} from './types';
|
|
6
|
+
|
|
7
|
+
const NO_VALUE_ARG_TYPE = 'sn';
|
|
8
|
+
const SUPPORTED_EXTRA_TYPES = [
|
|
9
|
+
's',
|
|
10
|
+
NO_VALUE_ARG_TYPE,
|
|
11
|
+
'z',
|
|
12
|
+
'i',
|
|
13
|
+
'l',
|
|
14
|
+
'f',
|
|
15
|
+
'u',
|
|
16
|
+
'cn',
|
|
17
|
+
'ia',
|
|
18
|
+
'ial',
|
|
19
|
+
'la',
|
|
20
|
+
'lal',
|
|
21
|
+
'fa',
|
|
22
|
+
'fal',
|
|
23
|
+
'sa',
|
|
24
|
+
'sal',
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Starts an Android activity.
|
|
29
|
+
*
|
|
30
|
+
* @deprecated Use {@link mobileStartActivity} instead.
|
|
31
|
+
* @param appPackage The package name of the application to start.
|
|
32
|
+
* @param appActivity The activity name to start.
|
|
33
|
+
* @param appWaitPackage The package name to wait for. Defaults to `appPackage` if not provided.
|
|
34
|
+
* @param appWaitActivity The activity name to wait for. Defaults to `appActivity` if not provided.
|
|
35
|
+
* @param intentAction The intent action to use.
|
|
36
|
+
* @param intentCategory The intent category to use.
|
|
37
|
+
* @param intentFlags The intent flags to use.
|
|
38
|
+
* @param optionalIntentArguments Optional intent arguments.
|
|
39
|
+
* @param dontStopAppOnReset If `true`, does not stop the app on reset. If not provided, uses the capability value.
|
|
40
|
+
* @returns Promise that resolves when the activity is started.
|
|
41
|
+
*/
|
|
42
|
+
export async function startActivity(
|
|
43
|
+
this: AndroidDriver,
|
|
44
|
+
appPackage: string,
|
|
45
|
+
appActivity: string,
|
|
46
|
+
appWaitPackage?: string,
|
|
47
|
+
appWaitActivity?: string,
|
|
48
|
+
intentAction?: string,
|
|
49
|
+
intentCategory?: string,
|
|
50
|
+
intentFlags?: string,
|
|
51
|
+
optionalIntentArguments?: string,
|
|
52
|
+
dontStopAppOnReset?: boolean,
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
this.log.debug(`Starting package '${appPackage}' and activity '${appActivity}'`);
|
|
55
|
+
|
|
56
|
+
// dontStopAppOnReset is both an argument here, and a desired capability
|
|
57
|
+
// if the argument is set, use it, otherwise use the cap
|
|
58
|
+
if (!util.hasValue(dontStopAppOnReset)) {
|
|
59
|
+
dontStopAppOnReset = !!this.opts.dontStopAppOnReset;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const args = {
|
|
63
|
+
pkg: appPackage,
|
|
64
|
+
activity: appActivity,
|
|
65
|
+
waitPkg: appWaitPackage || appPackage,
|
|
66
|
+
waitActivity: appWaitActivity || appActivity,
|
|
67
|
+
action: intentAction,
|
|
68
|
+
category: intentCategory,
|
|
69
|
+
flags: intentFlags,
|
|
70
|
+
optionalIntentArguments,
|
|
71
|
+
stopApp: !dontStopAppOnReset,
|
|
72
|
+
};
|
|
73
|
+
this._cachedActivityArgs = this._cachedActivityArgs || {};
|
|
74
|
+
this._cachedActivityArgs[`${args.waitPkg}/${args.waitActivity}`] = args;
|
|
75
|
+
await this.adb.startApp(args);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Starts an Android activity using the activity manager.
|
|
80
|
+
*
|
|
81
|
+
* @param wait Set it to `true` if you want to block the method call
|
|
82
|
+
* until the activity manager's process returns the control to the system.
|
|
83
|
+
* `false` by default.
|
|
84
|
+
* @param stop Set it to `true` to force stop the target
|
|
85
|
+
* app before starting the activity. `false` by default.
|
|
86
|
+
* @param windowingMode The windowing mode to launch the activity into.
|
|
87
|
+
* Check https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/WindowConfiguration.java
|
|
88
|
+
* for more details on possible windowing modes (constants starting with `WINDOWING_MODE_`).
|
|
89
|
+
* @param activityType The activity type to launch the activity as.
|
|
90
|
+
* Check https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/WindowConfiguration.java
|
|
91
|
+
* for more details on possible activity types (constants starting with `ACTIVITY_TYPE_`).
|
|
92
|
+
* @param display The display identifier to launch the activity into.
|
|
93
|
+
* @param user The user ID for which the activity is started.
|
|
94
|
+
* @param intent The name of the activity intent to start, for example
|
|
95
|
+
* `com.some.package.name/.YourActivitySubClassName`.
|
|
96
|
+
* @param action Action name.
|
|
97
|
+
* @param pkg Package name.
|
|
98
|
+
* @param uri Unified resource identifier.
|
|
99
|
+
* @param mimeType Mime type.
|
|
100
|
+
* @param identifier Optional identifier.
|
|
101
|
+
* @param component Component name.
|
|
102
|
+
* @param categories One or more category names.
|
|
103
|
+
* @param extras Optional intent arguments. Must be represented as array of arrays,
|
|
104
|
+
* where each subarray item contains two or three string items: value type, key name and the value itself.
|
|
105
|
+
* See {@link IntentOpts} for supported value types.
|
|
106
|
+
* @param flags Intent startup-specific flags as a hexadecimal string.
|
|
107
|
+
* @returns Promise that resolves to the command output string.
|
|
108
|
+
*/
|
|
109
|
+
export async function mobileStartActivity(
|
|
110
|
+
this: AndroidDriver,
|
|
111
|
+
wait?: boolean,
|
|
112
|
+
stop?: boolean,
|
|
113
|
+
windowingMode?: string | number,
|
|
114
|
+
activityType?: string | number,
|
|
115
|
+
display?: number | string,
|
|
116
|
+
user?: string,
|
|
117
|
+
intent?: string,
|
|
118
|
+
action?: string,
|
|
119
|
+
pkg?: string,
|
|
120
|
+
uri?: string,
|
|
121
|
+
mimeType?: string,
|
|
122
|
+
identifier?: string,
|
|
123
|
+
component?: string,
|
|
124
|
+
categories?: string | string[],
|
|
125
|
+
extras?: string[][],
|
|
126
|
+
flags?: string,
|
|
127
|
+
): Promise<string> {
|
|
128
|
+
const cmd = [
|
|
129
|
+
'am',
|
|
130
|
+
'start-activity',
|
|
131
|
+
];
|
|
132
|
+
if (!_.isNil(user)) {
|
|
133
|
+
cmd.push('--user', String(user));
|
|
134
|
+
}
|
|
135
|
+
if (wait) {
|
|
136
|
+
cmd.push('-W');
|
|
137
|
+
}
|
|
138
|
+
if (stop) {
|
|
139
|
+
cmd.push('-S');
|
|
140
|
+
}
|
|
141
|
+
if (!_.isNil(windowingMode)) {
|
|
142
|
+
cmd.push('--windowingMode', String(windowingMode));
|
|
143
|
+
}
|
|
144
|
+
if (!_.isNil(activityType)) {
|
|
145
|
+
cmd.push('--activityType', String(activityType));
|
|
146
|
+
}
|
|
147
|
+
if (!_.isNil(display)) {
|
|
148
|
+
cmd.push('--display', String(display));
|
|
149
|
+
}
|
|
150
|
+
cmd.push(...parseIntentSpec({
|
|
151
|
+
intent,
|
|
152
|
+
action,
|
|
153
|
+
package: pkg,
|
|
154
|
+
uri,
|
|
155
|
+
mimeType,
|
|
156
|
+
identifier,
|
|
157
|
+
component,
|
|
158
|
+
categories,
|
|
159
|
+
extras,
|
|
160
|
+
flags,
|
|
161
|
+
}));
|
|
162
|
+
return await this.adb.shell(cmd);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Sends a broadcast intent to the Android system.
|
|
167
|
+
*
|
|
168
|
+
* @param receiverPermission Require receiver to hold the given permission.
|
|
169
|
+
* @param allowBackgroundActivityStarts Whether the receiver may start activities even if in the background.
|
|
170
|
+
* @param user The user ID for which the broadcast is sent.
|
|
171
|
+
* The `current` alias assumes the current user ID. `all` by default.
|
|
172
|
+
* @param intent The name of the activity intent to broadcast, for example
|
|
173
|
+
* `com.some.package.name/.YourServiceSubClassName`.
|
|
174
|
+
* @param action Action name.
|
|
175
|
+
* @param pkg Package name.
|
|
176
|
+
* @param uri Unified resource identifier.
|
|
177
|
+
* @param mimeType Mime type.
|
|
178
|
+
* @param identifier Optional identifier.
|
|
179
|
+
* @param component Component name.
|
|
180
|
+
* @param categories One or more category names.
|
|
181
|
+
* @param extras Optional intent arguments. Must be represented as array of arrays,
|
|
182
|
+
* where each subarray item contains two or three string items: value type, key name and the value itself.
|
|
183
|
+
* See {@link IntentOpts} for supported value types.
|
|
184
|
+
* @param flags Intent startup-specific flags as a hexadecimal string.
|
|
185
|
+
* @returns Promise that resolves to the command output string.
|
|
186
|
+
*/
|
|
187
|
+
export async function mobileBroadcast(
|
|
188
|
+
this: AndroidDriver,
|
|
189
|
+
receiverPermission?: string,
|
|
190
|
+
allowBackgroundActivityStarts?: boolean,
|
|
191
|
+
user?: string | number,
|
|
192
|
+
intent?: string,
|
|
193
|
+
action?: string,
|
|
194
|
+
pkg?: string,
|
|
195
|
+
uri?: string,
|
|
196
|
+
mimeType?: string,
|
|
197
|
+
identifier?: string,
|
|
198
|
+
component?: string,
|
|
199
|
+
categories?: string | string[],
|
|
200
|
+
extras?: string[][],
|
|
201
|
+
flags?: string,
|
|
202
|
+
): Promise<string> {
|
|
203
|
+
const cmd = ['am', 'broadcast'];
|
|
204
|
+
if (!_.isNil(user)) {
|
|
205
|
+
cmd.push('--user', String(user));
|
|
206
|
+
}
|
|
207
|
+
if (receiverPermission) {
|
|
208
|
+
cmd.push('--receiver-permission', receiverPermission);
|
|
209
|
+
}
|
|
210
|
+
if (allowBackgroundActivityStarts) {
|
|
211
|
+
cmd.push('--allow-background-activity-starts');
|
|
212
|
+
}
|
|
213
|
+
cmd.push(...parseIntentSpec({
|
|
214
|
+
intent,
|
|
215
|
+
action,
|
|
216
|
+
package: pkg,
|
|
217
|
+
uri,
|
|
218
|
+
mimeType,
|
|
219
|
+
identifier,
|
|
220
|
+
component,
|
|
221
|
+
categories,
|
|
222
|
+
extras,
|
|
223
|
+
flags,
|
|
224
|
+
}));
|
|
225
|
+
return await this.adb.shell(cmd);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Starts an Android service.
|
|
230
|
+
*
|
|
231
|
+
* @param foreground Set it to `true` if your service must be started as foreground service.
|
|
232
|
+
* This option is ignored if the API level of the device under test is below 26 (Android 8).
|
|
233
|
+
* @param user The user ID for which the service is started.
|
|
234
|
+
* The `current` user id is used by default.
|
|
235
|
+
* @param intent The name of the activity intent to start, for example
|
|
236
|
+
* `com.some.package.name/.YourServiceSubClassName`.
|
|
237
|
+
* @param action Action name.
|
|
238
|
+
* @param pkg Package name.
|
|
239
|
+
* @param uri Unified resource identifier.
|
|
240
|
+
* @param mimeType Mime type.
|
|
241
|
+
* @param identifier Optional identifier.
|
|
242
|
+
* @param component Component name.
|
|
243
|
+
* @param categories One or more category names.
|
|
244
|
+
* @param extras Optional intent arguments. Must be represented as array of arrays,
|
|
245
|
+
* where each subarray item contains two or three string items: value type, key name and the value itself.
|
|
246
|
+
* See {@link IntentOpts} for supported value types.
|
|
247
|
+
* @param flags Intent startup-specific flags as a hexadecimal string.
|
|
248
|
+
* @returns Promise that resolves to the command output string.
|
|
249
|
+
*/
|
|
250
|
+
export async function mobileStartService(
|
|
251
|
+
this: AndroidDriver,
|
|
252
|
+
foreground?: boolean,
|
|
253
|
+
user?: string,
|
|
254
|
+
intent?: string,
|
|
255
|
+
action?: string,
|
|
256
|
+
pkg?: string,
|
|
257
|
+
uri?: string,
|
|
258
|
+
mimeType?: string,
|
|
259
|
+
identifier?: string,
|
|
260
|
+
component?: string,
|
|
261
|
+
categories?: string | string[],
|
|
262
|
+
extras?: string[][],
|
|
263
|
+
flags?: string,
|
|
264
|
+
): Promise<string> {
|
|
265
|
+
const cmd = ['am'];
|
|
266
|
+
cmd.push(foreground ? 'start-foreground-service' : 'start-service');
|
|
267
|
+
if (!_.isNil(user)) {
|
|
268
|
+
cmd.push('--user', String(user));
|
|
269
|
+
}
|
|
270
|
+
cmd.push(...parseIntentSpec({
|
|
271
|
+
intent,
|
|
272
|
+
action,
|
|
273
|
+
package: pkg,
|
|
274
|
+
uri,
|
|
275
|
+
mimeType,
|
|
276
|
+
identifier,
|
|
277
|
+
component,
|
|
278
|
+
categories,
|
|
279
|
+
extras,
|
|
280
|
+
flags,
|
|
281
|
+
}));
|
|
282
|
+
return await this.adb.shell(cmd);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Stops an Android service.
|
|
287
|
+
*
|
|
288
|
+
* @param user The user ID for which the service is stopped.
|
|
289
|
+
* @param intent The name of the activity intent to stop, for example
|
|
290
|
+
* `com.some.package.name/.YourServiceSubClassName`.
|
|
291
|
+
* @param action Action name.
|
|
292
|
+
* @param pkg Package name.
|
|
293
|
+
* @param uri Unified resource identifier.
|
|
294
|
+
* @param mimeType Mime type.
|
|
295
|
+
* @param identifier Optional identifier.
|
|
296
|
+
* @param component Component name.
|
|
297
|
+
* @param categories One or more category names.
|
|
298
|
+
* @param extras Optional intent arguments. Must be represented as array of arrays,
|
|
299
|
+
* where each subarray item contains two or three string items: value type, key name and the value itself.
|
|
300
|
+
* See {@link IntentOpts} for supported value types.
|
|
301
|
+
* @param flags Intent startup-specific flags as a hexadecimal string.
|
|
302
|
+
* @returns Promise that resolves to the command output string.
|
|
303
|
+
* If the service was already stopped, returns the error message.
|
|
304
|
+
*/
|
|
305
|
+
export async function mobileStopService(
|
|
306
|
+
this: AndroidDriver,
|
|
307
|
+
user?: string,
|
|
308
|
+
intent?: string,
|
|
309
|
+
action?: string,
|
|
310
|
+
pkg?: string,
|
|
311
|
+
uri?: string,
|
|
312
|
+
mimeType?: string,
|
|
313
|
+
identifier?: string,
|
|
314
|
+
component?: string,
|
|
315
|
+
categories?: string | string[],
|
|
316
|
+
extras?: string[][],
|
|
317
|
+
flags?: string,
|
|
318
|
+
): Promise<string> {
|
|
319
|
+
const cmd = [
|
|
320
|
+
'am',
|
|
321
|
+
'stop-service',
|
|
322
|
+
];
|
|
323
|
+
if (!_.isNil(user)) {
|
|
324
|
+
cmd.push('--user', String(user));
|
|
325
|
+
}
|
|
326
|
+
cmd.push(...parseIntentSpec({
|
|
327
|
+
intent,
|
|
328
|
+
action,
|
|
329
|
+
package: pkg,
|
|
330
|
+
uri,
|
|
331
|
+
mimeType,
|
|
332
|
+
identifier,
|
|
333
|
+
component,
|
|
334
|
+
categories,
|
|
335
|
+
extras,
|
|
336
|
+
flags,
|
|
337
|
+
}));
|
|
338
|
+
try {
|
|
339
|
+
return await this.adb.shell(cmd);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
// https://github.com/appium/appium-uiautomator2-driver/issues/792
|
|
342
|
+
const err = e as {code?: number; stderr?: string};
|
|
343
|
+
if (err.code === 255 && err.stderr?.includes('Service stopped')) {
|
|
344
|
+
return err.stderr;
|
|
345
|
+
}
|
|
346
|
+
throw e;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// #region Internal helpers
|
|
351
|
+
|
|
352
|
+
function parseIntentSpec(opts: IntentOpts = {}): string[] {
|
|
353
|
+
const {intent, action, uri, mimeType, identifier, categories, component, extras, flags} = opts;
|
|
354
|
+
const resultArgs: string[] = [];
|
|
355
|
+
if (intent) {
|
|
356
|
+
resultArgs.push(intent);
|
|
357
|
+
}
|
|
358
|
+
if (action) {
|
|
359
|
+
resultArgs.push('-a', action);
|
|
360
|
+
}
|
|
361
|
+
if (uri) {
|
|
362
|
+
resultArgs.push('-d', uri);
|
|
363
|
+
}
|
|
364
|
+
if (mimeType) {
|
|
365
|
+
resultArgs.push('-t', mimeType);
|
|
366
|
+
}
|
|
367
|
+
if (!_.isNil(identifier)) {
|
|
368
|
+
resultArgs.push('-i', identifier);
|
|
369
|
+
}
|
|
370
|
+
if (categories) {
|
|
371
|
+
if (_.isArray(categories)) {
|
|
372
|
+
resultArgs.push(..._.flatMap(categories.map((cName) => ['-c', cName])));
|
|
373
|
+
} else {
|
|
374
|
+
resultArgs.push('-c', categories);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (component) {
|
|
378
|
+
resultArgs.push('-n', component);
|
|
379
|
+
}
|
|
380
|
+
if (opts.package) {
|
|
381
|
+
resultArgs.push('-p', opts.package);
|
|
382
|
+
}
|
|
383
|
+
if (extras) {
|
|
384
|
+
if (!_.isArray(extras)) {
|
|
385
|
+
throw new errors.InvalidArgumentError(`'extras' must be an array`);
|
|
386
|
+
}
|
|
387
|
+
for (const item of extras) {
|
|
388
|
+
if (!_.isArray(item)) {
|
|
389
|
+
throw new errors.InvalidArgumentError(`Extra argument '${item}' must be an array`);
|
|
390
|
+
}
|
|
391
|
+
const [type, key, value] = item;
|
|
392
|
+
if (!SUPPORTED_EXTRA_TYPES.includes(type as any)) {
|
|
393
|
+
throw new errors.InvalidArgumentError(
|
|
394
|
+
`Extra argument type '${type}' is not known. ` +
|
|
395
|
+
`Supported intent argument types are: ${SUPPORTED_EXTRA_TYPES.join(', ')}`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
if (_.isEmpty(key) || (_.isString(key) && _.trim(key) === '')) {
|
|
399
|
+
throw new errors.InvalidArgumentError(
|
|
400
|
+
`Extra argument's key in '${JSON.stringify(item)}' must be a valid string identifier`,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
if (type === NO_VALUE_ARG_TYPE) {
|
|
404
|
+
resultArgs.push(`--e${type}`, key);
|
|
405
|
+
} else if (_.isUndefined(value)) {
|
|
406
|
+
throw new errors.InvalidArgumentError(
|
|
407
|
+
`Intent argument type '${type}' in '${JSON.stringify(item)}' requires a ` +
|
|
408
|
+
`valid value to be provided`,
|
|
409
|
+
);
|
|
410
|
+
} else {
|
|
411
|
+
resultArgs.push(`--e${type}`, key, value);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (flags) {
|
|
416
|
+
resultArgs.push('-f', flags);
|
|
417
|
+
}
|
|
418
|
+
return resultArgs;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// #endregion
|
|
422
|
+
|
|
@@ -2,51 +2,42 @@ import {DEFAULT_WS_PATHNAME_PREFIX, BaseDriver} from 'appium/driver';
|
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
|
+
import type {AppiumServer, WSServer} from '@appium/types';
|
|
6
|
+
import type {EventEmitter} from 'node:events';
|
|
7
|
+
import type {ADB} from 'appium-adb';
|
|
8
|
+
import type {Chromedriver} from 'appium-chromedriver';
|
|
5
9
|
import {
|
|
6
10
|
GET_SERVER_LOGS_FEATURE,
|
|
7
11
|
toLogRecord,
|
|
8
12
|
nativeLogEntryToSeleniumEntry,
|
|
13
|
+
type LogEntry,
|
|
9
14
|
} from '../utils';
|
|
10
15
|
import { NATIVE_WIN } from './context/helpers';
|
|
11
16
|
import { BIDI_EVENT_NAME } from './bidi/constants';
|
|
12
17
|
import { makeLogEntryAddedEvent } from './bidi/models';
|
|
18
|
+
import type {AndroidDriver} from '../driver';
|
|
13
19
|
|
|
14
20
|
export const supportedLogTypes = {
|
|
15
21
|
logcat: {
|
|
16
22
|
description: 'Logs for Android applications on real device and emulators via ADB',
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
* @param {import('../driver').AndroidDriver} self
|
|
20
|
-
* @returns
|
|
21
|
-
*/
|
|
22
|
-
getter: (self) => /** @type {ADB} */ (self.adb).getLogcatLogs(),
|
|
23
|
+
getter: (self: AndroidDriver) => (self.adb as ADB).getLogcatLogs(),
|
|
23
24
|
},
|
|
24
25
|
bugreport: {
|
|
25
26
|
description: `'adb bugreport' output for advanced issues diagnostic`,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* @param {import('../driver').AndroidDriver} self
|
|
29
|
-
* @returns
|
|
30
|
-
*/
|
|
31
|
-
getter: async (self) => {
|
|
32
|
-
const output = await /** @type {ADB} */ (self.adb).bugreport();
|
|
27
|
+
getter: async (self: AndroidDriver) => {
|
|
28
|
+
const output = await (self.adb as ADB).bugreport();
|
|
33
29
|
const timestamp = Date.now();
|
|
34
30
|
return output.split(os.EOL).map((x) => toLogRecord(timestamp, x));
|
|
35
31
|
},
|
|
36
32
|
},
|
|
37
33
|
server: {
|
|
38
34
|
description: 'Appium server logs',
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
* @param {import('../driver').AndroidDriver} self
|
|
42
|
-
* @returns
|
|
43
|
-
*/
|
|
44
|
-
getter: (self) => {
|
|
35
|
+
getter: (self: AndroidDriver) => {
|
|
45
36
|
self.assertFeatureEnabled(GET_SERVER_LOGS_FEATURE);
|
|
46
37
|
return self.log.unwrap().record.map(nativeLogEntryToSeleniumEntry);
|
|
47
38
|
},
|
|
48
39
|
},
|
|
49
|
-
};
|
|
40
|
+
} as const;
|
|
50
41
|
|
|
51
42
|
/**
|
|
52
43
|
* Starts Android logcat broadcast websocket on the same host and port
|
|
@@ -56,12 +47,13 @@ export const supportedLogTypes = {
|
|
|
56
47
|
* Each connected websocket listener will receive logcat log lines
|
|
57
48
|
* as soon as they are visible to Appium.
|
|
58
49
|
*
|
|
59
|
-
* @
|
|
60
|
-
* @returns {Promise<void>}
|
|
50
|
+
* @returns Promise that resolves when the logcat broadcasting websocket is started.
|
|
61
51
|
*/
|
|
62
|
-
export async function mobileStartLogsBroadcast(
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
export async function mobileStartLogsBroadcast(
|
|
53
|
+
this: AndroidDriver,
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
const server = this.server as AppiumServer;
|
|
56
|
+
const pathname = WEBSOCKET_ENDPOINT(this.sessionId as string);
|
|
65
57
|
if (!_.isEmpty(await server.getWebSocketHandlers(pathname))) {
|
|
66
58
|
this.log.debug(`The logcat broadcasting web socket server is already listening at ${pathname}`);
|
|
67
59
|
return;
|
|
@@ -78,7 +70,7 @@ export async function mobileStartLogsBroadcast() {
|
|
|
78
70
|
wss.on('connection', (ws, req) => {
|
|
79
71
|
if (req) {
|
|
80
72
|
const remoteIp = _.isEmpty(req.headers['x-forwarded-for'])
|
|
81
|
-
? req.
|
|
73
|
+
? req.socket.remoteAddress
|
|
82
74
|
: req.headers['x-forwarded-for'];
|
|
83
75
|
this.log.debug(`Established a new logcat listener web socket connection from ${remoteIp}`);
|
|
84
76
|
} else {
|
|
@@ -86,7 +78,7 @@ export async function mobileStartLogsBroadcast() {
|
|
|
86
78
|
}
|
|
87
79
|
|
|
88
80
|
if (_.isEmpty(this._logcatWebsocketListener)) {
|
|
89
|
-
this._logcatWebsocketListener = (logRecord) => {
|
|
81
|
+
this._logcatWebsocketListener = (logRecord: LogEntry) => {
|
|
90
82
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
91
83
|
ws.send(logRecord.message);
|
|
92
84
|
}
|
|
@@ -112,19 +104,20 @@ export async function mobileStartLogsBroadcast() {
|
|
|
112
104
|
this.log.debug(closeMsg);
|
|
113
105
|
});
|
|
114
106
|
});
|
|
115
|
-
await server.addWebSocketHandler(pathname,
|
|
107
|
+
await server.addWebSocketHandler(pathname, wss as WSServer);
|
|
116
108
|
}
|
|
117
109
|
|
|
118
110
|
/**
|
|
119
111
|
* Stops the previously started logcat broadcasting wesocket server.
|
|
120
112
|
* This method will return immediately if no server is running.
|
|
121
113
|
*
|
|
122
|
-
* @
|
|
123
|
-
* @returns {Promise<void>}
|
|
114
|
+
* @returns Promise that resolves when the logcat broadcasting websocket is stopped.
|
|
124
115
|
*/
|
|
125
|
-
export async function mobileStopLogsBroadcast(
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
export async function mobileStopLogsBroadcast(
|
|
117
|
+
this: AndroidDriver,
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
const pathname = WEBSOCKET_ENDPOINT(this.sessionId as string);
|
|
120
|
+
const server = this.server as AppiumServer;
|
|
128
121
|
if (_.isEmpty(await server.getWebSocketHandlers(pathname))) {
|
|
129
122
|
return;
|
|
130
123
|
}
|
|
@@ -137,40 +130,53 @@ export async function mobileStopLogsBroadcast() {
|
|
|
137
130
|
}
|
|
138
131
|
|
|
139
132
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
133
|
+
* Gets the list of available log types.
|
|
134
|
+
*
|
|
135
|
+
* @returns Promise that resolves to an array of log type names.
|
|
142
136
|
*/
|
|
143
|
-
export async function getLogTypes(
|
|
137
|
+
export async function getLogTypes(
|
|
138
|
+
this: AndroidDriver,
|
|
139
|
+
): Promise<string[]> {
|
|
144
140
|
// XXX why doesn't `super` work here?
|
|
145
141
|
const nativeLogTypes = await BaseDriver.prototype.getLogTypes.call(this);
|
|
146
142
|
if (this.isWebContext()) {
|
|
147
|
-
const webLogTypes =
|
|
148
|
-
await /** @type {import('appium-chromedriver').Chromedriver} */ (
|
|
149
|
-
this.chromedriver
|
|
150
|
-
).jwproxy.command('/log/types', 'GET')
|
|
151
|
-
);
|
|
143
|
+
const webLogTypes = await (this.chromedriver as Chromedriver).jwproxy.command('/log/types', 'GET') as string[];
|
|
152
144
|
return [...nativeLogTypes, ...webLogTypes];
|
|
153
145
|
}
|
|
154
146
|
return nativeLogTypes;
|
|
155
147
|
}
|
|
156
148
|
|
|
149
|
+
export interface BiDiListenerProperties {
|
|
150
|
+
type: string;
|
|
151
|
+
srcEventName?: string;
|
|
152
|
+
context?: string;
|
|
153
|
+
entryTransformer?: (x: LogEntry) => LogEntry;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type LogListener = (logEntry: LogEntry) => any;
|
|
157
|
+
|
|
157
158
|
/**
|
|
159
|
+
* Assigns a BiDi log listener to an event emitter.
|
|
160
|
+
*
|
|
158
161
|
* https://w3c.github.io/webdriver-bidi/#event-log-entryAdded
|
|
159
162
|
*
|
|
160
|
-
* @template
|
|
161
|
-
* @
|
|
162
|
-
* @param
|
|
163
|
-
* @
|
|
164
|
-
* @returns {[EE, LogListener]}
|
|
163
|
+
* @template EE The event emitter type.
|
|
164
|
+
* @param logEmitter The event emitter to attach the listener to.
|
|
165
|
+
* @param properties The BiDi listener properties.
|
|
166
|
+
* @returns A tuple containing the event emitter and the listener function.
|
|
165
167
|
*/
|
|
166
|
-
export function assignBiDiLogListener (
|
|
168
|
+
export function assignBiDiLogListener<EE extends EventEmitter>(
|
|
169
|
+
this: AndroidDriver,
|
|
170
|
+
logEmitter: EE,
|
|
171
|
+
properties: BiDiListenerProperties,
|
|
172
|
+
): [EE, LogListener] {
|
|
167
173
|
const {
|
|
168
174
|
type,
|
|
169
175
|
context = NATIVE_WIN,
|
|
170
176
|
srcEventName = 'output',
|
|
171
177
|
entryTransformer,
|
|
172
178
|
} = properties;
|
|
173
|
-
const listener = (
|
|
179
|
+
const listener: LogListener = (logEntry: LogEntry) => {
|
|
174
180
|
const finalEntry = entryTransformer ? entryTransformer(logEntry) : logEntry;
|
|
175
181
|
this.eventEmitter.emit(BIDI_EVENT_NAME, makeLogEntryAddedEvent(finalEntry, context, type));
|
|
176
182
|
};
|
|
@@ -179,42 +185,31 @@ export function assignBiDiLogListener (logEmitter, properties) {
|
|
|
179
185
|
}
|
|
180
186
|
|
|
181
187
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* @
|
|
188
|
+
* Gets logs of a specific type.
|
|
189
|
+
*
|
|
190
|
+
* @param logType The type of logs to retrieve.
|
|
191
|
+
* @returns Promise that resolves to the logs for the specified type.
|
|
185
192
|
*/
|
|
186
|
-
export async function getLog(
|
|
193
|
+
export async function getLog(
|
|
194
|
+
this: AndroidDriver,
|
|
195
|
+
logType: string,
|
|
196
|
+
): Promise<any> {
|
|
187
197
|
if (this.isWebContext() && !_.keys(this.supportedLogTypes).includes(logType)) {
|
|
188
|
-
return await
|
|
189
|
-
this.chromedriver
|
|
190
|
-
).jwproxy.command('/log', 'POST', {type: logType});
|
|
198
|
+
return await (this.chromedriver as Chromedriver).jwproxy.command('/log', 'POST', {type: logType});
|
|
191
199
|
}
|
|
192
|
-
// XXX why doesn't `super` work here?
|
|
193
200
|
return await BaseDriver.prototype.getLog.call(this, logType);
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
// #region Internal helpers
|
|
197
204
|
|
|
198
205
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
206
|
+
* Generates the websocket endpoint path for logcat broadcasting.
|
|
207
|
+
*
|
|
208
|
+
* @param sessionId The session ID.
|
|
209
|
+
* @returns The websocket endpoint path.
|
|
201
210
|
*/
|
|
202
|
-
const WEBSOCKET_ENDPOINT = (sessionId) =>
|
|
211
|
+
const WEBSOCKET_ENDPOINT = (sessionId: string): string =>
|
|
203
212
|
`${DEFAULT_WS_PATHNAME_PREFIX}/session/${sessionId}/appium/device/logcat`;
|
|
204
213
|
|
|
205
|
-
|
|
206
214
|
// #endregion
|
|
207
215
|
|
|
208
|
-
/**
|
|
209
|
-
* @typedef {import('appium-adb').ADB} ADB
|
|
210
|
-
*/
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* @typedef {Object} BiDiListenerProperties
|
|
214
|
-
* @property {string} type
|
|
215
|
-
* @property {string} [srcEventName='output']
|
|
216
|
-
* @property {string} [context=NATIVE_WIN]
|
|
217
|
-
* @property {(x: Object) => import('../utils').LogEntry} [entryTransformer]
|
|
218
|
-
*/
|
|
219
|
-
|
|
220
|
-
/** @typedef {(logEntry: import('../utils').LogEntry) => any} LogListener */
|