@ph-qa/midscene-android 1.6.1

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.
@@ -0,0 +1 @@
1
+ export { }
@@ -0,0 +1,454 @@
1
+ import { AbstractInterface } from '@midscene/core/device';
2
+ import type { ActionParam } from '@midscene/core';
3
+ import type { ActionReturn } from '@midscene/core';
4
+ import { ADB } from 'appium-adb';
5
+ import type { Adb } from '@yume-chan/adb';
6
+ import { Agent } from '@midscene/core/agent';
7
+ import { AgentOpt } from '@midscene/core/agent';
8
+ import { AndroidDeviceInputOpt } from '@midscene/core/device';
9
+ import { AndroidDeviceOpt } from '@midscene/core/device';
10
+ import { BaseMidsceneTools } from '@midscene/shared/mcp';
11
+ import { Device } from 'appium-adb';
12
+ import { DeviceAction } from '@midscene/core';
13
+ import type { ElementInfo } from '@midscene/shared/extractor';
14
+ import { InterfaceType } from '@midscene/core';
15
+ import { overrideAIConfig } from '@midscene/shared/env';
16
+ import { Point } from '@midscene/core';
17
+ import { Size } from '@midscene/core';
18
+ import { ToolDefinition } from '@midscene/shared/mcp';
19
+
20
+ declare type ActionArgs<T extends DeviceAction> = [ActionParam<T>] extends [undefined] ? [] : [ActionParam<T>];
21
+
22
+ export declare function agentFromAdbDevice(deviceId?: string, opts?: AndroidAgentOpt & AndroidDeviceOpt): Promise<AndroidAgent>;
23
+
24
+ export declare class AndroidAgent extends Agent<AndroidDevice> {
25
+ /**
26
+ * Trigger the system back operation on Android devices
27
+ */
28
+ back: WrappedAction<DeviceActionAndroidBackButton>;
29
+ /**
30
+ * Trigger the system home operation on Android devices
31
+ */
32
+ home: WrappedAction<DeviceActionAndroidHomeButton>;
33
+ /**
34
+ * Trigger the system recent apps operation on Android devices
35
+ */
36
+ recentApps: WrappedAction<DeviceActionAndroidRecentAppsButton>;
37
+ /**
38
+ * User-provided app name to package name mapping
39
+ */
40
+ private appNameMapping;
41
+ constructor(device: AndroidDevice, opts?: AndroidAgentOpt);
42
+ /**
43
+ * Launch an Android app or URL
44
+ * @param uri - App package name, URL, or app name to launch
45
+ */
46
+ launch(uri: string): Promise<void>;
47
+ /**
48
+ * Execute ADB shell command on Android device
49
+ * @param command - ADB shell command to execute
50
+ */
51
+ runAdbShell(command: string): Promise<string>;
52
+ private createActionWrapper;
53
+ }
54
+
55
+ export declare type AndroidAgentOpt = AgentOpt & {
56
+ /**
57
+ * Custom mapping of app names to package names
58
+ * User-provided mappings will take precedence over default mappings
59
+ */
60
+ appNameMapping?: Record<string, string>;
61
+ };
62
+
63
+ export declare interface AndroidConnectedDevice extends Device {
64
+ model?: string;
65
+ brand?: string;
66
+ resolution?: string;
67
+ density?: number;
68
+ }
69
+
70
+ export declare class AndroidDevice implements AbstractInterface {
71
+ private deviceId;
72
+ private yadbPushed;
73
+ private devicePixelRatio;
74
+ private devicePixelRatioInitialized;
75
+ private adb;
76
+ private connectingAdb;
77
+ private destroyed;
78
+ private description;
79
+ private customActions?;
80
+ private cachedScreenSize;
81
+ private cachedOrientation;
82
+ private cachedPhysicalDisplayId;
83
+ private scrcpyAdapter;
84
+ private appNameMapping;
85
+ private cachedAdjustScale;
86
+ private takeScreenshotFailCount;
87
+ private static readonly TAKE_SCREENSHOT_FAIL_THRESHOLD;
88
+ private uiautomatorProcess;
89
+ private uiautomatorConfirmedUnavailable;
90
+ private uiautomatorKnownHealthy;
91
+ interfaceType: InterfaceType;
92
+ uri: string | undefined;
93
+ options?: AndroidDeviceOpt;
94
+ actionSpace(): DeviceAction<any>[];
95
+ constructor(deviceId: string, options?: AndroidDeviceOpt);
96
+ describe(): string;
97
+ connect(): Promise<ADB>;
98
+ getAdb(): Promise<ADB>;
99
+ private createAdbProxy;
100
+ /**
101
+ * Get or create the scrcpy adapter (lazy initialization)
102
+ */
103
+ private getScrcpyAdapter;
104
+ /**
105
+ * Get device physical info needed by scrcpy adapter
106
+ */
107
+ private getDevicePhysicalInfo;
108
+ /**
109
+ * Set the app name to package name mapping
110
+ */
111
+ setAppNameMapping(mapping: Record<string, string>): void;
112
+ /**
113
+ * Resolve app name to package name using the mapping
114
+ * Comparison is case-insensitive and ignores spaces, dashes, and underscores.
115
+ * Keys in appNameMapping are pre-normalized, so we only need to normalize the input.
116
+ * @param appName The app name to resolve
117
+ */
118
+ private resolvePackageName;
119
+ launch(uri: string): Promise<AndroidDevice>;
120
+ execYadb(keyboardContent: string): Promise<void>;
121
+ getElementsInfo(): Promise<ElementInfo[]>;
122
+ getElementsNodeTree(): Promise<any>;
123
+ getScreenSize(): Promise<{
124
+ override: string;
125
+ physical: string;
126
+ orientation: number;
127
+ isCurrentOrientation?: boolean;
128
+ }>;
129
+ private initializeDevicePixelRatio;
130
+ getDisplayDensity(): Promise<number>;
131
+ getDisplayOrientation(): Promise<number>;
132
+ /**
133
+ * Get physical screen dimensions adjusted for current orientation.
134
+ * Swaps width/height when the device is in landscape and the reported
135
+ * dimensions do not already reflect the current orientation.
136
+ */
137
+ private getOrientedPhysicalSize;
138
+ size(): Promise<Size>;
139
+ /**
140
+ * Compute and cache the coordinate adjustment scale by comparing
141
+ * physical dimensions with logical dimensions from size().
142
+ * Cached after first call; invalidated on destroy().
143
+ */
144
+ private getAdjustScale;
145
+ /**
146
+ * Convert logical coordinates (from AI) back to physical coordinates (for ADB).
147
+ * The ratio is derived from size(), so overriding size() alone is sufficient.
148
+ */
149
+ private adjustCoordinates;
150
+ /**
151
+ * Calculate the end point for scroll operations based on start point, scroll delta, and screen boundaries.
152
+ * This method ensures that scroll operations stay within screen bounds and maintain a minimum scroll distance
153
+ * for effective scrolling gestures on Android devices.
154
+ *
155
+ * @param start - The starting point of the scroll gesture
156
+ * @param deltaX - The horizontal scroll distance (positive = scroll right, negative = scroll left)
157
+ * @param deltaY - The vertical scroll distance (positive = scroll down, negative = scroll up)
158
+ * @param maxWidth - The maximum width boundary (screen width)
159
+ * @param maxHeight - The maximum height boundary (screen height)
160
+ * @returns The calculated end point for the scroll gesture
161
+ */
162
+ private calculateScrollEndPoint;
163
+ screenshotBase64(): Promise<string>;
164
+ clearInput(element?: ElementInfo): Promise<void>;
165
+ forceScreenshot(path: string): Promise<void>;
166
+ url(): Promise<string>;
167
+ scrollUntilTop(startPoint?: Point): Promise<void>;
168
+ scrollUntilBottom(startPoint?: Point): Promise<void>;
169
+ scrollUntilLeft(startPoint?: Point): Promise<void>;
170
+ scrollUntilRight(startPoint?: Point): Promise<void>;
171
+ scrollUp(distance?: number, startPoint?: Point): Promise<void>;
172
+ scrollDown(distance?: number, startPoint?: Point): Promise<void>;
173
+ scrollLeft(distance?: number, startPoint?: Point): Promise<void>;
174
+ scrollRight(distance?: number, startPoint?: Point): Promise<void>;
175
+ ensureYadb(): Promise<void>;
176
+ /**
177
+ * Check if text contains characters that may cause issues with ADB inputText.
178
+ * appium-adb's inputText has known bugs with certain characters:
179
+ * - Backslash causes broken shell quoting
180
+ * - Backtick is not escaped at all
181
+ * - Text containing both " and ' throws an error
182
+ * - Dollar sign can cause variable expansion issues
183
+ *
184
+ * For these characters, we route through yadb which handles them correctly
185
+ * via escapeForShell + double-quoted shell context.
186
+ */
187
+ private shouldUseYadbForText;
188
+ keyboardType(text: string, options?: AndroidDeviceInputOpt): Promise<void>;
189
+ private normalizeKeyName;
190
+ keyboardPress(key: string): Promise<void>;
191
+ /**
192
+ * Start the UIAutomator2 server process on the device.
193
+ */
194
+ private startUIAutomatorProcess;
195
+ /**
196
+ * Ensure the UIAutomator2 HTTP server is alive and ready.
197
+ * Returns true if UIAutomator2 is ready to accept requests, false otherwise.
198
+ */
199
+ private ensureUIAutomatorReady;
200
+ mouseClick(x: number, y: number): Promise<void>;
201
+ mouseDoubleClick(x: number, y: number): Promise<void>;
202
+ mouseMove(): Promise<void>;
203
+ mouseDrag(from: {
204
+ x: number;
205
+ y: number;
206
+ }, to: {
207
+ x: number;
208
+ y: number;
209
+ }, duration?: number): Promise<void>;
210
+ scroll(deltaX: number, deltaY: number, duration?: number): Promise<void>;
211
+ destroy(): Promise<void>;
212
+ /**
213
+ * Get the current time from the Android device.
214
+ * Returns the device's current timestamp in milliseconds.
215
+ * This is useful when the system time and device time are not synchronized.
216
+ */
217
+ getTimestamp(): Promise<number>;
218
+ back(): Promise<void>;
219
+ home(): Promise<void>;
220
+ recentApps(): Promise<void>;
221
+ longPress(x: number, y: number, duration?: number): Promise<void>;
222
+ pullDown(startPoint?: Point, distance?: number, duration?: number): Promise<void>;
223
+ pullDrag(from: {
224
+ x: number;
225
+ y: number;
226
+ }, to: {
227
+ x: number;
228
+ y: number;
229
+ }, duration: number): Promise<void>;
230
+ pullUp(startPoint?: Point, distance?: number, duration?: number): Promise<void>;
231
+ private getDisplayArg;
232
+ getPhysicalDisplayId(): Promise<string | null>;
233
+ hideKeyboard(options?: AndroidDeviceInputOpt, timeoutMs?: number): Promise<boolean>;
234
+ }
235
+
236
+ /**
237
+ * Android-specific tools manager
238
+ * Extends BaseMidsceneTools to provide Android ADB device connection tools
239
+ */
240
+ export declare class AndroidMidsceneTools extends BaseMidsceneTools<AndroidAgent> {
241
+ protected createTemporaryDevice(): AndroidDevice;
242
+ protected ensureAgent(deviceId?: string): Promise<AndroidAgent>;
243
+ /**
244
+ * Provide Android-specific platform tools
245
+ */
246
+ protected preparePlatformTools(): ToolDefinition[];
247
+ }
248
+
249
+ declare type DeviceActionAndroidBackButton = DeviceAction<undefined, void>;
250
+
251
+ declare type DeviceActionAndroidHomeButton = DeviceAction<undefined, void>;
252
+
253
+ declare type DeviceActionAndroidRecentAppsButton = DeviceAction<undefined, void>;
254
+
255
+ declare interface DevicePhysicalInfo {
256
+ physicalWidth: number;
257
+ physicalHeight: number;
258
+ dpr: number;
259
+ orientation: number;
260
+ isCurrentOrientation?: boolean;
261
+ }
262
+
263
+ export declare function getConnectedDevices(): Promise<Device[]>;
264
+
265
+ export declare function getConnectedDevicesWithDetails(): Promise<AndroidConnectedDevice[]>;
266
+
267
+ export { overrideAIConfig }
268
+
269
+ declare interface ResolvedScrcpyConfig {
270
+ enabled: boolean;
271
+ maxSize: number;
272
+ videoBitRate: number;
273
+ idleTimeoutMs: number;
274
+ }
275
+
276
+ declare interface ScrcpyConfig {
277
+ enabled?: boolean;
278
+ maxSize?: number;
279
+ videoBitRate?: number;
280
+ idleTimeoutMs?: number;
281
+ }
282
+
283
+ /**
284
+ * Adapter that encapsulates all scrcpy-related logic for AndroidDevice.
285
+ * Handles config normalization, manager lifecycle, screenshot, and resolution.
286
+ */
287
+ export declare class ScrcpyDeviceAdapter {
288
+ private deviceId;
289
+ private scrcpyConfig;
290
+ private manager;
291
+ private resolvedConfig;
292
+ private initFailed;
293
+ constructor(deviceId: string, scrcpyConfig: ScrcpyConfig | undefined);
294
+ isEnabled(): boolean;
295
+ /**
296
+ * Initialize scrcpy connection. Called once during device.connect().
297
+ * If initialization fails, marks scrcpy as permanently disabled (no further retries).
298
+ */
299
+ initialize(deviceInfo: DevicePhysicalInfo): Promise<void>;
300
+ /**
301
+ * Resolve scrcpy config.
302
+ * maxSize defaults to 0 (no scaling, full physical resolution) so the Agent layer
303
+ * receives the highest quality image for AI processing.
304
+ * videoBitRate is auto-scaled based on physical pixel count to ensure
305
+ * sufficient quality for all-I-frame H.264 encoding.
306
+ */
307
+ resolveConfig(deviceInfo: DevicePhysicalInfo): ResolvedScrcpyConfig;
308
+ /**
309
+ * Get or create the ScrcpyScreenshotManager.
310
+ * Uses dynamic import for @yume-chan packages (ESM-only, must use await import in CJS builds).
311
+ */
312
+ ensureManager(deviceInfo: DevicePhysicalInfo): Promise<ScrcpyScreenshotManager>;
313
+ /**
314
+ * Take a screenshot via scrcpy, returns base64 string.
315
+ * Throws on failure (caller should fallback to ADB).
316
+ */
317
+ screenshotBase64(deviceInfo: DevicePhysicalInfo): Promise<string>;
318
+ /**
319
+ * Get scrcpy's actual video resolution.
320
+ * Returns null if scrcpy is not connected yet.
321
+ */
322
+ getResolution(): {
323
+ width: number;
324
+ height: number;
325
+ } | null;
326
+ /**
327
+ * Compute size from scrcpy resolution.
328
+ * Returns null if scrcpy is not connected.
329
+ */
330
+ getSize(deviceInfo: DevicePhysicalInfo): Size | null;
331
+ /**
332
+ * Calculate the scaling ratio from physical to scrcpy resolution.
333
+ */
334
+ getScalingRatio(physicalWidth: number): number | null;
335
+ disconnect(): Promise<void>;
336
+ }
337
+
338
+ declare class ScrcpyScreenshotManager {
339
+ private adb;
340
+ private scrcpyClient;
341
+ private videoStream;
342
+ private spsHeader;
343
+ private idleTimer;
344
+ private isConnecting;
345
+ private isInitialized;
346
+ private options;
347
+ private ffmpegAvailable;
348
+ private keyframeResolvers;
349
+ private lastRawKeyframe;
350
+ private videoResolution;
351
+ private streamReader;
352
+ constructor(adb: Adb, options?: ScrcpyScreenshotOptions);
353
+ /**
354
+ * Validate environment prerequisites (ffmpeg, scrcpy-server, etc.)
355
+ * Must be called once after construction, before any screenshot operations.
356
+ * Throws if prerequisites are not met.
357
+ */
358
+ validateEnvironment(): Promise<void>;
359
+ /**
360
+ * Ensure scrcpy connection is active
361
+ */
362
+ ensureConnected(): Promise<void>;
363
+ /**
364
+ * Resolve path to scrcpy server binary
365
+ */
366
+ private resolveServerBinPath;
367
+ /**
368
+ * Get ffmpeg executable path
369
+ * Priority: @ffmpeg-installer/ffmpeg > system ffmpeg
370
+ */
371
+ private getFfmpegPath;
372
+ /**
373
+ * Consume video frames and keep latest frame
374
+ */
375
+ private startFrameConsumer;
376
+ /**
377
+ * Main frame consumption loop
378
+ * Includes busy-loop detection: if reader.read() resolves too fast
379
+ * (e.g. broken stream returning immediately), we throttle to prevent 100% CPU.
380
+ */
381
+ private consumeFramesLoop;
382
+ /**
383
+ * Process a single video packet from the scrcpy stream.
384
+ * With sendFrameMeta: true, the stream emits properly framed packets:
385
+ * - "configuration" packets contain SPS/PPS header data
386
+ * - "data" packets contain complete video frames with correct boundaries
387
+ * This avoids the frame-splitting issue that occurs with sendFrameMeta: false
388
+ * at high resolutions where raw chunks may not align with frame boundaries.
389
+ */
390
+ private processFrame;
391
+ /**
392
+ * Get screenshot as JPEG.
393
+ * Tries to get a fresh frame within a short timeout. If the screen is static
394
+ * (no new frames arrive), falls back to the latest cached keyframe.
395
+ */
396
+ getScreenshotJpeg(): Promise<Buffer>;
397
+ /**
398
+ * Get the actual video stream resolution
399
+ * Returns null if scrcpy is not connected yet
400
+ */
401
+ getResolution(): {
402
+ width: number;
403
+ height: number;
404
+ } | null;
405
+ /**
406
+ * Notify all pending keyframe waiters
407
+ */
408
+ private notifyKeyframeWaiters;
409
+ /**
410
+ * Wait for the next keyframe to arrive
411
+ */
412
+ private waitForNextKeyframe;
413
+ /**
414
+ * Ensure ffmpeg is available for PNG conversion
415
+ */
416
+ private ensureFfmpegAvailable;
417
+ /**
418
+ * Wait for first keyframe with SPS/PPS header
419
+ */
420
+ private waitForKeyframe;
421
+ /**
422
+ * Check if ffmpeg is available in the system
423
+ */
424
+ private checkFfmpegAvailable;
425
+ /**
426
+ * Decode H.264 data to JPEG using ffmpeg
427
+ */
428
+ private decodeH264ToJpeg;
429
+ /**
430
+ * Reset idle timeout timer
431
+ */
432
+ private resetIdleTimer;
433
+ /**
434
+ * Disconnect scrcpy
435
+ */
436
+ disconnect(): Promise<void>;
437
+ /**
438
+ * Check if scrcpy is initialized and connected
439
+ */
440
+ isConnected(): boolean;
441
+ }
442
+
443
+ declare interface ScrcpyScreenshotOptions {
444
+ maxSize?: number;
445
+ videoBitRate?: number;
446
+ idleTimeoutMs?: number;
447
+ }
448
+
449
+ /**
450
+ * Helper type to convert DeviceAction to wrapped method signature
451
+ */
452
+ declare type WrappedAction<T extends DeviceAction> = (...args: ActionArgs<T>) => Promise<ActionReturn<T>>;
453
+
454
+ export { }