react-native-ai-debugger 1.0.7 → 1.0.8
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 +84 -152
- package/build/core/android.d.ts +75 -0
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +239 -0
- package/build/core/android.js.map +1 -1
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +69 -29
- package/build/core/executor.js.map +1 -1
- package/build/core/index.d.ts +4 -4
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +6 -2
- package/build/core/index.js.map +1 -1
- package/build/core/ios.d.ts +69 -0
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +213 -0
- package/build/core/ios.js.map +1 -1
- package/build/index.js +281 -0
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ An MCP (Model Context Protocol) server for AI-powered React Native debugging. En
|
|
|
17
17
|
- **Android device control** - screenshots, tap, swipe, text input, key events via ADB
|
|
18
18
|
- **iOS simulator control** - screenshots, app management, URL handling via simctl
|
|
19
19
|
- **iOS UI automation** - tap, swipe, text input, button presses via IDB (optional)
|
|
20
|
-
- **
|
|
20
|
+
- **Element-based UI automation** - find and wait for elements by text/label without screenshots (faster, cheaper)
|
|
21
21
|
|
|
22
22
|
## Requirements
|
|
23
23
|
|
|
@@ -133,55 +133,35 @@ Requires VS Code 1.102+ with Copilot ([docs](https://code.visualstudio.com/docs/
|
|
|
133
133
|
|
|
134
134
|
### Android (ADB)
|
|
135
135
|
|
|
136
|
-
| Tool
|
|
137
|
-
|
|
|
138
|
-
| `list_android_devices`
|
|
139
|
-
| `android_screenshot`
|
|
140
|
-
| `android_install_app`
|
|
141
|
-
| `android_launch_app`
|
|
142
|
-
| `android_list_packages`
|
|
143
|
-
| `android_tap`
|
|
144
|
-
| `android_long_press`
|
|
145
|
-
| `android_swipe`
|
|
146
|
-
| `android_input_text`
|
|
147
|
-
| `android_key_event`
|
|
148
|
-
| `android_get_screen_size`
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
| Tool | Description |
|
|
153
|
-
| ----------------------- | ----------------------------------------------------- |
|
|
154
|
-
| `android_describe_all` | Get full UI hierarchy tree using uiautomator |
|
|
155
|
-
| `android_describe_point`| Get UI element info at specific coordinates |
|
|
156
|
-
| `android_tap_element` | Tap element by text, content-desc, or resource-id |
|
|
136
|
+
| Tool | Description |
|
|
137
|
+
| --------------------------- | ------------------------------------------------------------- |
|
|
138
|
+
| `list_android_devices` | List connected Android devices and emulators via ADB |
|
|
139
|
+
| `android_screenshot` | Take a screenshot from an Android device/emulator |
|
|
140
|
+
| `android_install_app` | Install an APK on an Android device/emulator |
|
|
141
|
+
| `android_launch_app` | Launch an app by package name |
|
|
142
|
+
| `android_list_packages` | List installed packages (with optional filter) |
|
|
143
|
+
| `android_tap` | Tap at specific coordinates on screen |
|
|
144
|
+
| `android_long_press` | Long press at specific coordinates |
|
|
145
|
+
| `android_swipe` | Swipe from one point to another |
|
|
146
|
+
| `android_input_text` | Type text at current focus point |
|
|
147
|
+
| `android_key_event` | Send key events (HOME, BACK, ENTER, etc.) |
|
|
148
|
+
| `android_get_screen_size` | Get device screen resolution |
|
|
149
|
+
| `android_find_element` | Find element by text/contentDesc/resourceId (no screenshot) |
|
|
150
|
+
| `android_wait_for_element` | Wait for element to appear (useful for screen transitions) |
|
|
157
151
|
|
|
158
152
|
### iOS (Simulator)
|
|
159
153
|
|
|
160
|
-
| Tool
|
|
161
|
-
|
|
|
162
|
-
| `list_ios_simulators`
|
|
163
|
-
| `ios_screenshot`
|
|
164
|
-
| `ios_install_app`
|
|
165
|
-
| `ios_launch_app`
|
|
166
|
-
| `ios_open_url`
|
|
167
|
-
| `ios_terminate_app`
|
|
168
|
-
| `ios_boot_simulator`
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
These tools require [Facebook IDB](https://fbidb.io/) to be installed: `brew install idb-companion`
|
|
173
|
-
|
|
174
|
-
| Tool | Description |
|
|
175
|
-
| ------------------- | ----------------------------------------------------- |
|
|
176
|
-
| `ios_tap` | Tap at specific coordinates on screen |
|
|
177
|
-
| `ios_tap_element` | Tap an element by its accessibility label |
|
|
178
|
-
| `ios_swipe` | Swipe from one point to another |
|
|
179
|
-
| `ios_input_text` | Type text into the active input field |
|
|
180
|
-
| `ios_button` | Press hardware buttons (HOME, LOCK, SIRI, etc.) |
|
|
181
|
-
| `ios_key_event` | Send a key event by keycode |
|
|
182
|
-
| `ios_key_sequence` | Send multiple key events in sequence |
|
|
183
|
-
| `ios_describe_all` | Get accessibility tree for entire screen |
|
|
184
|
-
| `ios_describe_point`| Get accessibility info for element at specific point |
|
|
154
|
+
| Tool | Description |
|
|
155
|
+
| ----------------------- | --------------------------------------------------------------- |
|
|
156
|
+
| `list_ios_simulators` | List available iOS simulators |
|
|
157
|
+
| `ios_screenshot` | Take a screenshot from an iOS simulator |
|
|
158
|
+
| `ios_install_app` | Install an app bundle (.app) on a simulator |
|
|
159
|
+
| `ios_launch_app` | Launch an app by bundle ID |
|
|
160
|
+
| `ios_open_url` | Open a URL (deep links or web URLs) |
|
|
161
|
+
| `ios_terminate_app` | Terminate a running app |
|
|
162
|
+
| `ios_boot_simulator` | Boot a simulator by UDID |
|
|
163
|
+
| `ios_find_element` | Find element by label/value (requires IDB, no screenshot) |
|
|
164
|
+
| `ios_wait_for_element` | Wait for element to appear (requires IDB) |
|
|
185
165
|
|
|
186
166
|
## Usage
|
|
187
167
|
|
|
@@ -411,20 +391,6 @@ Set `awaitPromise=false` for synchronous execution only.
|
|
|
411
391
|
|
|
412
392
|
## Device Interaction
|
|
413
393
|
|
|
414
|
-
> **💡 Best Practice: Use Element-Based Tapping**
|
|
415
|
-
>
|
|
416
|
-
> Prefer `android_tap_element` and `ios_tap_element` over coordinate-based tapping (`android_tap`, `ios_tap`).
|
|
417
|
-
>
|
|
418
|
-
> **Why?**
|
|
419
|
-
> - More reliable - elements are found by text/label, not fragile coordinates
|
|
420
|
-
> - Self-documenting - `tap_element(text="Settings")` is clearer than `tap(x=540, y=1200)`
|
|
421
|
-
> - Resolution-independent - works across different screen sizes
|
|
422
|
-
>
|
|
423
|
-
> **Workflow:**
|
|
424
|
-
> 1. Use `android_describe_all` or `ios_describe_all` to see available elements
|
|
425
|
-
> 2. Use `android_tap_element` or `ios_tap_element` to interact by text/label
|
|
426
|
-
> 3. Fall back to coordinate-based tapping only when elements lack accessible text
|
|
427
|
-
|
|
428
394
|
### Android (requires ADB)
|
|
429
395
|
|
|
430
396
|
List connected devices:
|
|
@@ -466,53 +432,6 @@ android_key_event with key="HOME"
|
|
|
466
432
|
android_key_event with key="ENTER"
|
|
467
433
|
```
|
|
468
434
|
|
|
469
|
-
### Android UI Automation (Accessibility)
|
|
470
|
-
|
|
471
|
-
Get the full UI hierarchy:
|
|
472
|
-
|
|
473
|
-
```
|
|
474
|
-
android_describe_all
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
Example output:
|
|
478
|
-
```
|
|
479
|
-
[FrameLayout] frame=(0, 0, 1080x2340) tap=(540, 1170)
|
|
480
|
-
[LinearLayout] frame=(0, 63, 1080x147) tap=(540, 136)
|
|
481
|
-
[TextView] "Settings" frame=(48, 77, 200x63) tap=(148, 108)
|
|
482
|
-
[RecyclerView] frame=(0, 210, 1080x2130) tap=(540, 1275)
|
|
483
|
-
[Button] "Save" frame=(800, 2200, 200x80) tap=(900, 2240)
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
Get element info at coordinates:
|
|
487
|
-
|
|
488
|
-
```
|
|
489
|
-
android_describe_point with x=540 y=1170
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
Tap an element by text:
|
|
493
|
-
|
|
494
|
-
```
|
|
495
|
-
android_tap_element with text="Settings"
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
Tap using partial text match:
|
|
499
|
-
|
|
500
|
-
```
|
|
501
|
-
android_tap_element with textContains="Save"
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
Tap by resource ID:
|
|
505
|
-
|
|
506
|
-
```
|
|
507
|
-
android_tap_element with resourceId="save_button"
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
Tap by content description:
|
|
511
|
-
|
|
512
|
-
```
|
|
513
|
-
android_tap_element with contentDesc="Navigate up"
|
|
514
|
-
```
|
|
515
|
-
|
|
516
435
|
### iOS Simulator (requires Xcode)
|
|
517
436
|
|
|
518
437
|
List available simulators:
|
|
@@ -545,78 +464,91 @@ Open a deep link:
|
|
|
545
464
|
ios_open_url with url="myapp://settings"
|
|
546
465
|
```
|
|
547
466
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
Install IDB first: `brew install idb-companion`
|
|
467
|
+
## Efficient UI Automation (No Screenshots)
|
|
551
468
|
|
|
552
|
-
**
|
|
553
|
-
- iOS IDB uses **points** (logical coordinates), not pixels
|
|
554
|
-
- For 2x Retina displays: 1 point = 2 pixels
|
|
555
|
-
- Example: 1640x2360 pixel screenshot = 820x1180 points
|
|
556
|
-
- Use `ios_describe_all` to get exact element coordinates in points
|
|
469
|
+
For action triggering without layout debugging, use element-based tools instead of screenshots. This is **2-3x faster** and uses fewer tokens.
|
|
557
470
|
|
|
558
|
-
|
|
471
|
+
### Android - Find and Tap by Text
|
|
559
472
|
|
|
560
473
|
```
|
|
561
|
-
|
|
562
|
-
|
|
474
|
+
# Wait for screen to load
|
|
475
|
+
android_wait_for_element with text="Login"
|
|
563
476
|
|
|
564
|
-
|
|
477
|
+
# Find element (returns tap coordinates)
|
|
478
|
+
android_find_element with textContains="submit"
|
|
565
479
|
|
|
566
|
-
|
|
567
|
-
|
|
480
|
+
# Tap the element (use coordinates from find_element)
|
|
481
|
+
android_tap with x=540 y=960
|
|
568
482
|
```
|
|
569
483
|
|
|
570
|
-
|
|
484
|
+
Search options:
|
|
485
|
+
- `text` - exact text match
|
|
486
|
+
- `textContains` - partial text (case-insensitive)
|
|
487
|
+
- `contentDesc` - accessibility content description
|
|
488
|
+
- `contentDescContains` - partial content description
|
|
489
|
+
- `resourceId` - resource ID (e.g., "button" or "com.app:id/button")
|
|
571
490
|
|
|
572
|
-
|
|
573
|
-
ios_swipe with startX=200 startY=600 endX=200 endY=200
|
|
574
|
-
```
|
|
491
|
+
### iOS - Find and Tap by Label (requires IDB)
|
|
575
492
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
ios_tap with x=200 y=300
|
|
580
|
-
ios_input_text with text="hello@example.com"
|
|
493
|
+
```bash
|
|
494
|
+
# Install IDB first
|
|
495
|
+
brew install idb-companion
|
|
581
496
|
```
|
|
582
497
|
|
|
583
|
-
Press hardware buttons:
|
|
584
|
-
|
|
585
498
|
```
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
499
|
+
# Wait for element
|
|
500
|
+
ios_wait_for_element with label="Sign In"
|
|
501
|
+
|
|
502
|
+
# Find element by partial label
|
|
503
|
+
ios_find_element with labelContains="welcome"
|
|
589
504
|
```
|
|
590
505
|
|
|
591
|
-
|
|
506
|
+
Search options:
|
|
507
|
+
- `label` - exact accessibility label
|
|
508
|
+
- `labelContains` - partial label (case-insensitive)
|
|
509
|
+
- `value` - accessibility value
|
|
510
|
+
- `valueContains` - partial value
|
|
511
|
+
- `type` - element type (e.g., "Button", "TextField")
|
|
592
512
|
|
|
593
|
-
|
|
594
|
-
ios_describe_all
|
|
595
|
-
```
|
|
513
|
+
### Wait for Screen Transitions
|
|
596
514
|
|
|
597
|
-
|
|
515
|
+
Both platforms support waiting with timeout:
|
|
598
516
|
|
|
599
517
|
```
|
|
600
|
-
|
|
518
|
+
android_wait_for_element with text="Dashboard" timeoutMs=15000 pollIntervalMs=500
|
|
519
|
+
ios_wait_for_element with label="Home" timeoutMs=10000
|
|
601
520
|
```
|
|
602
521
|
|
|
603
|
-
|
|
522
|
+
### Recommended Workflow (Priority Order)
|
|
604
523
|
|
|
605
|
-
|
|
606
|
-
ios_tap_element with label="Settings"
|
|
607
|
-
```
|
|
524
|
+
**Always try accessibility tools first, fall back to screenshots only when needed:**
|
|
608
525
|
|
|
609
|
-
|
|
526
|
+
1. **Wait for screen** → Use `wait_for_element` with expected text/label
|
|
527
|
+
2. **Find target** → Use `find_element` to get tap coordinates
|
|
528
|
+
3. **Tap** → Use `tap` with coordinates from step 2
|
|
529
|
+
4. **Fallback** → If element not in accessibility tree, use `screenshot`
|
|
610
530
|
|
|
611
531
|
```
|
|
612
|
-
|
|
532
|
+
# Example: Tap "Submit" button after screen loads
|
|
533
|
+
android_wait_for_element with text="Submit" # Step 1: Wait
|
|
534
|
+
android_find_element with text="Submit" # Step 2: Find (returns center coordinates)
|
|
535
|
+
android_tap with x=540 y=1200 # Step 3: Tap (use returned coordinates)
|
|
613
536
|
```
|
|
614
537
|
|
|
615
|
-
|
|
538
|
+
**Why this order?**
|
|
539
|
+
- `find_element`: ~100-200 tokens, <100ms
|
|
540
|
+
- `screenshot`: ~400-500 tokens, 200-500ms
|
|
616
541
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
542
|
+
### When to Use Screenshots vs Element Tools
|
|
543
|
+
|
|
544
|
+
| Use Case | Recommended Tool |
|
|
545
|
+
|----------|------------------|
|
|
546
|
+
| Trigger button taps | `find_element` + `tap` |
|
|
547
|
+
| Wait for screen load | `wait_for_element` |
|
|
548
|
+
| Navigate through flow | `wait_for_element` + `tap` |
|
|
549
|
+
| Debug layout issues | `screenshot` |
|
|
550
|
+
| Verify visual appearance | `screenshot` |
|
|
551
|
+
| Find elements without labels | `screenshot` |
|
|
620
552
|
|
|
621
553
|
## Supported React Native Versions
|
|
622
554
|
|
package/build/core/android.d.ts
CHANGED
|
@@ -96,6 +96,81 @@ export declare function androidInputText(text: string, deviceId?: string): Promi
|
|
|
96
96
|
* Send a key event to an Android device
|
|
97
97
|
*/
|
|
98
98
|
export declare function androidKeyEvent(keyCode: number | keyof typeof ANDROID_KEY_EVENTS, deviceId?: string): Promise<AdbResult>;
|
|
99
|
+
/**
|
|
100
|
+
* UI Element from accessibility tree
|
|
101
|
+
*/
|
|
102
|
+
export interface AndroidUIElement {
|
|
103
|
+
text: string;
|
|
104
|
+
contentDesc: string;
|
|
105
|
+
resourceId: string;
|
|
106
|
+
className: string;
|
|
107
|
+
bounds: {
|
|
108
|
+
left: number;
|
|
109
|
+
top: number;
|
|
110
|
+
right: number;
|
|
111
|
+
bottom: number;
|
|
112
|
+
width: number;
|
|
113
|
+
height: number;
|
|
114
|
+
};
|
|
115
|
+
center: {
|
|
116
|
+
x: number;
|
|
117
|
+
y: number;
|
|
118
|
+
};
|
|
119
|
+
clickable: boolean;
|
|
120
|
+
enabled: boolean;
|
|
121
|
+
focused: boolean;
|
|
122
|
+
scrollable: boolean;
|
|
123
|
+
selected: boolean;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Result of element find operations
|
|
127
|
+
*/
|
|
128
|
+
export interface FindElementResult {
|
|
129
|
+
success: boolean;
|
|
130
|
+
found: boolean;
|
|
131
|
+
element?: AndroidUIElement;
|
|
132
|
+
allMatches?: AndroidUIElement[];
|
|
133
|
+
matchCount?: number;
|
|
134
|
+
error?: string;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Result of wait for element operations
|
|
138
|
+
*/
|
|
139
|
+
export interface WaitForElementResult extends FindElementResult {
|
|
140
|
+
elapsedMs?: number;
|
|
141
|
+
timedOut?: boolean;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Options for finding elements
|
|
145
|
+
*/
|
|
146
|
+
export interface FindElementOptions {
|
|
147
|
+
text?: string;
|
|
148
|
+
textContains?: string;
|
|
149
|
+
contentDesc?: string;
|
|
150
|
+
contentDescContains?: string;
|
|
151
|
+
resourceId?: string;
|
|
152
|
+
index?: number;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get UI accessibility tree from Android device using uiautomator
|
|
156
|
+
*/
|
|
157
|
+
export declare function androidGetUITree(deviceId?: string): Promise<{
|
|
158
|
+
success: boolean;
|
|
159
|
+
elements?: AndroidUIElement[];
|
|
160
|
+
rawXml?: string;
|
|
161
|
+
error?: string;
|
|
162
|
+
}>;
|
|
163
|
+
/**
|
|
164
|
+
* Find element(s) in the UI tree matching the given criteria
|
|
165
|
+
*/
|
|
166
|
+
export declare function androidFindElement(options: FindElementOptions, deviceId?: string): Promise<FindElementResult>;
|
|
167
|
+
/**
|
|
168
|
+
* Wait for element to appear on screen with polling
|
|
169
|
+
*/
|
|
170
|
+
export declare function androidWaitForElement(options: FindElementOptions & {
|
|
171
|
+
timeoutMs?: number;
|
|
172
|
+
pollIntervalMs?: number;
|
|
173
|
+
}, deviceId?: string): Promise<WaitForElementResult>;
|
|
99
174
|
/**
|
|
100
175
|
* Get device screen size
|
|
101
176
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../src/core/android.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../src/core/android.ts"],"names":[],"mappings":"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"}
|
package/build/core/android.js
CHANGED
|
@@ -641,6 +641,245 @@ export async function androidKeyEvent(keyCode, deviceId) {
|
|
|
641
641
|
};
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* Parse bounds string like "[0,0][1080,1920]" to AndroidUIElement bounds
|
|
646
|
+
*/
|
|
647
|
+
function parseBoundsForUIElement(boundsStr) {
|
|
648
|
+
const match = boundsStr.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
|
|
649
|
+
if (!match)
|
|
650
|
+
return null;
|
|
651
|
+
const left = parseInt(match[1], 10);
|
|
652
|
+
const top = parseInt(match[2], 10);
|
|
653
|
+
const right = parseInt(match[3], 10);
|
|
654
|
+
const bottom = parseInt(match[4], 10);
|
|
655
|
+
return {
|
|
656
|
+
left,
|
|
657
|
+
top,
|
|
658
|
+
right,
|
|
659
|
+
bottom,
|
|
660
|
+
width: right - left,
|
|
661
|
+
height: bottom - top
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Parse uiautomator XML dump into element array
|
|
666
|
+
*/
|
|
667
|
+
function parseUIAutomatorXML(xml) {
|
|
668
|
+
const elements = [];
|
|
669
|
+
// Match all node elements with their attributes
|
|
670
|
+
const nodeRegex = /<node\s+([^>]+)\/?>|<node\s+([^>]+)>/g;
|
|
671
|
+
let match;
|
|
672
|
+
while ((match = nodeRegex.exec(xml)) !== null) {
|
|
673
|
+
const attrStr = match[1] || match[2];
|
|
674
|
+
if (!attrStr)
|
|
675
|
+
continue;
|
|
676
|
+
// Extract attributes
|
|
677
|
+
const getAttr = (name) => {
|
|
678
|
+
const attrMatch = attrStr.match(new RegExp(`${name}="([^"]*)"`));
|
|
679
|
+
return attrMatch ? attrMatch[1] : "";
|
|
680
|
+
};
|
|
681
|
+
const boundsStr = getAttr("bounds");
|
|
682
|
+
const bounds = parseBoundsForUIElement(boundsStr);
|
|
683
|
+
if (!bounds)
|
|
684
|
+
continue;
|
|
685
|
+
const element = {
|
|
686
|
+
text: getAttr("text"),
|
|
687
|
+
contentDesc: getAttr("content-desc"),
|
|
688
|
+
resourceId: getAttr("resource-id"),
|
|
689
|
+
className: getAttr("class"),
|
|
690
|
+
bounds,
|
|
691
|
+
center: {
|
|
692
|
+
x: Math.round((bounds.left + bounds.right) / 2),
|
|
693
|
+
y: Math.round((bounds.top + bounds.bottom) / 2)
|
|
694
|
+
},
|
|
695
|
+
clickable: getAttr("clickable") === "true",
|
|
696
|
+
enabled: getAttr("enabled") === "true",
|
|
697
|
+
focused: getAttr("focused") === "true",
|
|
698
|
+
scrollable: getAttr("scrollable") === "true",
|
|
699
|
+
selected: getAttr("selected") === "true"
|
|
700
|
+
};
|
|
701
|
+
elements.push(element);
|
|
702
|
+
}
|
|
703
|
+
return elements;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Match element against find options
|
|
707
|
+
*/
|
|
708
|
+
function matchesElement(element, options) {
|
|
709
|
+
if (options.text !== undefined) {
|
|
710
|
+
if (element.text !== options.text)
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
if (options.textContains !== undefined) {
|
|
714
|
+
if (!element.text.toLowerCase().includes(options.textContains.toLowerCase()))
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
if (options.contentDesc !== undefined) {
|
|
718
|
+
if (element.contentDesc !== options.contentDesc)
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
if (options.contentDescContains !== undefined) {
|
|
722
|
+
if (!element.contentDesc.toLowerCase().includes(options.contentDescContains.toLowerCase()))
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
if (options.resourceId !== undefined) {
|
|
726
|
+
// Support both full "com.app:id/button" and short "button" forms
|
|
727
|
+
const shortId = element.resourceId.split("/").pop() || "";
|
|
728
|
+
if (element.resourceId !== options.resourceId && shortId !== options.resourceId)
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Get UI accessibility tree from Android device using uiautomator
|
|
735
|
+
*/
|
|
736
|
+
export async function androidGetUITree(deviceId) {
|
|
737
|
+
try {
|
|
738
|
+
const adbAvailable = await isAdbAvailable();
|
|
739
|
+
if (!adbAvailable) {
|
|
740
|
+
return {
|
|
741
|
+
success: false,
|
|
742
|
+
error: "ADB is not installed or not in PATH. Install Android SDK Platform Tools."
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
const deviceArg = buildDeviceArg(deviceId);
|
|
746
|
+
const device = deviceId || (await getDefaultAndroidDevice());
|
|
747
|
+
if (!device) {
|
|
748
|
+
return {
|
|
749
|
+
success: false,
|
|
750
|
+
error: "No Android device connected. Connect a device or start an emulator."
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
// Dump UI hierarchy to device
|
|
754
|
+
const remotePath = "/sdcard/ui_dump.xml";
|
|
755
|
+
await execAsync(`adb ${deviceArg} shell uiautomator dump ${remotePath}`, {
|
|
756
|
+
timeout: ADB_TIMEOUT
|
|
757
|
+
});
|
|
758
|
+
// Read the XML content
|
|
759
|
+
const { stdout } = await execAsync(`adb ${deviceArg} shell cat ${remotePath}`, {
|
|
760
|
+
timeout: ADB_TIMEOUT
|
|
761
|
+
});
|
|
762
|
+
// Clean up remote file
|
|
763
|
+
await execAsync(`adb ${deviceArg} shell rm ${remotePath}`, {
|
|
764
|
+
timeout: ADB_TIMEOUT
|
|
765
|
+
}).catch(() => { });
|
|
766
|
+
const elements = parseUIAutomatorXML(stdout);
|
|
767
|
+
return {
|
|
768
|
+
success: true,
|
|
769
|
+
elements,
|
|
770
|
+
rawXml: stdout
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
return {
|
|
775
|
+
success: false,
|
|
776
|
+
error: `Failed to get UI tree: ${error instanceof Error ? error.message : String(error)}`
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Find element(s) in the UI tree matching the given criteria
|
|
782
|
+
*/
|
|
783
|
+
export async function androidFindElement(options, deviceId) {
|
|
784
|
+
try {
|
|
785
|
+
// Validate that at least one search criteria is provided
|
|
786
|
+
if (!options.text && !options.textContains && !options.contentDesc &&
|
|
787
|
+
!options.contentDescContains && !options.resourceId) {
|
|
788
|
+
return {
|
|
789
|
+
success: false,
|
|
790
|
+
found: false,
|
|
791
|
+
error: "At least one search criteria (text, textContains, contentDesc, contentDescContains, or resourceId) must be provided"
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const treeResult = await androidGetUITree(deviceId);
|
|
795
|
+
if (!treeResult.success || !treeResult.elements) {
|
|
796
|
+
return {
|
|
797
|
+
success: false,
|
|
798
|
+
found: false,
|
|
799
|
+
error: treeResult.error
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
// Find matching elements
|
|
803
|
+
const matches = treeResult.elements.filter(el => matchesElement(el, options));
|
|
804
|
+
if (matches.length === 0) {
|
|
805
|
+
return {
|
|
806
|
+
success: true,
|
|
807
|
+
found: false,
|
|
808
|
+
matchCount: 0
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
// Select the element at the specified index (default 0)
|
|
812
|
+
const index = options.index ?? 0;
|
|
813
|
+
const selectedElement = matches[index];
|
|
814
|
+
if (!selectedElement) {
|
|
815
|
+
return {
|
|
816
|
+
success: true,
|
|
817
|
+
found: false,
|
|
818
|
+
matchCount: matches.length,
|
|
819
|
+
error: `Index ${index} out of bounds. Found ${matches.length} matching element(s).`
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
success: true,
|
|
824
|
+
found: true,
|
|
825
|
+
element: selectedElement,
|
|
826
|
+
allMatches: matches,
|
|
827
|
+
matchCount: matches.length
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
catch (error) {
|
|
831
|
+
return {
|
|
832
|
+
success: false,
|
|
833
|
+
found: false,
|
|
834
|
+
error: `Failed to find element: ${error instanceof Error ? error.message : String(error)}`
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Wait for element to appear on screen with polling
|
|
840
|
+
*/
|
|
841
|
+
export async function androidWaitForElement(options, deviceId) {
|
|
842
|
+
const timeoutMs = options.timeoutMs ?? 10000;
|
|
843
|
+
const pollIntervalMs = options.pollIntervalMs ?? 500;
|
|
844
|
+
const startTime = Date.now();
|
|
845
|
+
// Validate that at least one search criteria is provided
|
|
846
|
+
if (!options.text && !options.textContains && !options.contentDesc &&
|
|
847
|
+
!options.contentDescContains && !options.resourceId) {
|
|
848
|
+
return {
|
|
849
|
+
success: false,
|
|
850
|
+
found: false,
|
|
851
|
+
timedOut: false,
|
|
852
|
+
error: "At least one search criteria (text, textContains, contentDesc, contentDescContains, or resourceId) must be provided"
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
856
|
+
const result = await androidFindElement(options, deviceId);
|
|
857
|
+
if (result.found && result.element) {
|
|
858
|
+
return {
|
|
859
|
+
...result,
|
|
860
|
+
elapsedMs: Date.now() - startTime,
|
|
861
|
+
timedOut: false
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
// If there was an error (not just "not found"), return it
|
|
865
|
+
if (!result.success) {
|
|
866
|
+
return {
|
|
867
|
+
...result,
|
|
868
|
+
elapsedMs: Date.now() - startTime,
|
|
869
|
+
timedOut: false
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
// Wait before next poll
|
|
873
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
874
|
+
}
|
|
875
|
+
return {
|
|
876
|
+
success: true,
|
|
877
|
+
found: false,
|
|
878
|
+
elapsedMs: Date.now() - startTime,
|
|
879
|
+
timedOut: true,
|
|
880
|
+
error: `Timed out after ${timeoutMs}ms waiting for element`
|
|
881
|
+
};
|
|
882
|
+
}
|
|
644
883
|
/**
|
|
645
884
|
* Get device screen size
|
|
646
885
|
*/
|