@panicgit/android-test-pilot 0.1.0

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.
Files changed (65) hide show
  1. package/.claude/skills/atp/analyze-app/SKILL.md +86 -0
  2. package/.claude/skills/atp/app-map/SKILL.md +36 -0
  3. package/.claude/skills/atp/check-logs/SKILL.md +77 -0
  4. package/.claude/skills/atp/run-test/SKILL.md +92 -0
  5. package/README.ko.md +241 -0
  6. package/README.md +241 -0
  7. package/lib/android.d.ts +96 -0
  8. package/lib/android.js +740 -0
  9. package/lib/android.js.map +1 -0
  10. package/lib/image-utils.d.ts +28 -0
  11. package/lib/image-utils.js +156 -0
  12. package/lib/image-utils.js.map +1 -0
  13. package/lib/index.d.ts +2 -0
  14. package/lib/index.js +90 -0
  15. package/lib/index.js.map +1 -0
  16. package/lib/ios.d.ts +54 -0
  17. package/lib/ios.js +241 -0
  18. package/lib/ios.js.map +1 -0
  19. package/lib/iphone-simulator.d.ts +34 -0
  20. package/lib/iphone-simulator.js +227 -0
  21. package/lib/iphone-simulator.js.map +1 -0
  22. package/lib/logger.d.ts +2 -0
  23. package/lib/logger.js +23 -0
  24. package/lib/logger.js.map +1 -0
  25. package/lib/mobile-device.d.ts +25 -0
  26. package/lib/mobile-device.js +141 -0
  27. package/lib/mobile-device.js.map +1 -0
  28. package/lib/mobilecli.d.ts +32 -0
  29. package/lib/mobilecli.js +113 -0
  30. package/lib/mobilecli.js.map +1 -0
  31. package/lib/png.d.ts +9 -0
  32. package/lib/png.js +20 -0
  33. package/lib/png.js.map +1 -0
  34. package/lib/robot.d.ts +116 -0
  35. package/lib/robot.js +10 -0
  36. package/lib/robot.js.map +1 -0
  37. package/lib/server.d.ts +3 -0
  38. package/lib/server.js +692 -0
  39. package/lib/server.js.map +1 -0
  40. package/lib/tiers/abstract-tier.d.ts +48 -0
  41. package/lib/tiers/abstract-tier.js +35 -0
  42. package/lib/tiers/abstract-tier.js.map +1 -0
  43. package/lib/tiers/screenshot-tier.d.ts +19 -0
  44. package/lib/tiers/screenshot-tier.js +53 -0
  45. package/lib/tiers/screenshot-tier.js.map +1 -0
  46. package/lib/tiers/text-tier.d.ts +20 -0
  47. package/lib/tiers/text-tier.js +138 -0
  48. package/lib/tiers/text-tier.js.map +1 -0
  49. package/lib/tiers/tier-runner.d.ts +27 -0
  50. package/lib/tiers/tier-runner.js +91 -0
  51. package/lib/tiers/tier-runner.js.map +1 -0
  52. package/lib/tiers/types.d.ts +100 -0
  53. package/lib/tiers/types.js +12 -0
  54. package/lib/tiers/types.js.map +1 -0
  55. package/lib/tiers/uiautomator-tier.d.ts +16 -0
  56. package/lib/tiers/uiautomator-tier.js +91 -0
  57. package/lib/tiers/uiautomator-tier.js.map +1 -0
  58. package/lib/utils.d.ts +4 -0
  59. package/lib/utils.js +81 -0
  60. package/lib/utils.js.map +1 -0
  61. package/lib/webdriver-agent.d.ts +45 -0
  62. package/lib/webdriver-agent.js +400 -0
  63. package/lib/webdriver-agent.js.map +1 -0
  64. package/package.json +50 -0
  65. package/templates/scenario.md +49 -0
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # android-test-pilot
2
+
3
+ [한국어](README.ko.md)
4
+
5
+ Automated Android app testing tool. Integrates with Claude Code to automate everything from static source code analysis to real device test execution.
6
+
7
+ ## Why This Exists
8
+
9
+ Testing apps with mobile-mcp means repeating a **screenshot → LLM image analysis → next action** loop. This approach is:
10
+
11
+ - **Expensive.** Every step sends a screenshot image to the LLM, consuming image tokens.
12
+ - **Slow.** Screenshot capture + image transfer + analysis adds latency at every step.
13
+
14
+ android-test-pilot solves this by using **text-based ADB commands as the primary information source**.
15
+
16
+ ```
17
+ Conventional approach (mobile-mcp):
18
+ screenshot → LLM image analysis → next action → screenshot → ...
19
+ (image tokens every step, slow)
20
+
21
+ android-test-pilot:
22
+ dumpsys + logcat text → instant parsing → next action → ...
23
+ (text-based, fast and cheap)
24
+ ↘ falls back to uiautomator → screenshot only when needed
25
+ ```
26
+
27
+ Tier 1 combines `dumpsys activity` (current Activity), `dumpsys window` (focused window), and logcat (API responses, View state) to determine app state. All text — minimal token usage and instant parsing.
28
+ Screenshots are only used in Tier 3 as a last resort (image rendering verification, unexpected popups).
29
+
30
+ ## How It Works
31
+
32
+ ```
33
+ Step 0 — Static Analysis (build app map)
34
+ Step 1 — Log Coverage Check & Augmentation
35
+
36
+ Prerequisites for Step 2
37
+
38
+ Step 2 — Device Test Execution
39
+ Tier 1: text-based (dumpsys + logcat) → Tier 2: uiautomator → Tier 3: screenshot
40
+ ```
41
+
42
+ ### Step 0: Static Analysis
43
+
44
+ Analyzes source code to map the app's structure.
45
+
46
+ | Analysis | Output |
47
+ |----------|--------|
48
+ | Screen navigation flow | `navigation_map.mermaid` |
49
+ | API connections & response scenarios | `api_scenarios.json` |
50
+ | View state mapping | `view_state_map.json` |
51
+
52
+ ### Step 1: Log Coverage Check & Augmentation
53
+
54
+ Based on Step 0 results, checks if the source code has the logcat logs needed for testing and adds missing ones.
55
+
56
+ | Log Tag | Purpose | Example |
57
+ |---------|---------|---------|
58
+ | `ATP_SCREEN` | Screen entry/transition | `enter: LoginActivity` |
59
+ | `ATP_RENDER` | View state change | `renderState: screen=Login, btnVisible=true` |
60
+ | `ATP_API` | API response | `apiResponse: endpoint=GET /api/users, status=200` |
61
+
62
+ ### Step 2: Device Test Execution
63
+
64
+ Reads a markdown scenario file and runs tests using a 3-tier strategy.
65
+
66
+ | Tier | Tools | When Used | Detectable Info |
67
+ |------|-------|-----------|-----------------|
68
+ | Tier 1 | dumpsys + logcat (text) | Always tried first | Current Activity, focused window, View state, API response data |
69
+ | Tier 2 | uiautomator + accessibility tree | When Tier 1 can't determine | Rendered View hierarchy, resource-id, bounds |
70
+ | Tier 3 | Screenshot | Last resort | Image rendering, unexpected popup detection |
71
+
72
+ ## Installation
73
+
74
+ ### Requirements
75
+
76
+ - Node.js >= 18
77
+ - ADB (Android SDK Platform-Tools)
78
+ - Claude Code
79
+ - Android device or emulator (USB debugging enabled)
80
+
81
+ ### Setup
82
+
83
+ ```bash
84
+ git clone https://github.com/panicgit/android-test-pilot
85
+ cd android-test-pilot
86
+ npm install
87
+ npm run build
88
+ ```
89
+
90
+ ### Register MCP Server
91
+
92
+ In your Android project directory:
93
+
94
+ ```bash
95
+ # Via CLI (recommended)
96
+ claude mcp add --transport stdio --scope project android-test-pilot \
97
+ -- node /path/to/android-test-pilot/lib/index.js
98
+
99
+ # Or create .mcp.json directly
100
+ cat > .mcp.json << 'EOF'
101
+ {
102
+ "mcpServers": {
103
+ "android-test-pilot": {
104
+ "command": "node",
105
+ "args": ["/path/to/android-test-pilot/lib/index.js"],
106
+ "env": {
107
+ "MAX_MCP_OUTPUT_TOKENS": "50000"
108
+ }
109
+ }
110
+ }
111
+ }
112
+ EOF
113
+ ```
114
+
115
+ ### Install Slash Commands
116
+
117
+ ```bash
118
+ cp -r /path/to/android-test-pilot/.claude/skills/atp \
119
+ /path/to/my-android-app/.claude/skills/atp
120
+ ```
121
+
122
+ ## Usage
123
+
124
+ Run via slash commands in Claude Code.
125
+
126
+ ```bash
127
+ # 1. Static analysis (Step 0)
128
+ /atp:analyze-app
129
+
130
+ # 2. Log coverage check (Step 1)
131
+ /atp:check-logs
132
+
133
+ # 3. Write a scenario
134
+ cp /path/to/android-test-pilot/templates/scenario.md scenarios/login.md
135
+ # Edit the scenario...
136
+
137
+ # 4. Run test (Step 2)
138
+ /atp:run-test scenarios/login.md
139
+
140
+ # View analysis summary
141
+ /atp:app-map
142
+ ```
143
+
144
+ ## Writing Scenarios
145
+
146
+ Write test scenarios in natural-language markdown. See `templates/scenario.md` for the template.
147
+
148
+ ```markdown
149
+ # Test Scenario: Login
150
+
151
+ ## Test Steps
152
+
153
+ ### Step 1: Launch App
154
+ - **Action**: Launch app and navigate to login screen
155
+ - **Expected logcat**:
156
+ - `ATP_SCREEN` → `enter: LoginActivity`
157
+ - **Verify**: Login screen loaded successfully
158
+
159
+ ### Step 2: Attempt Login
160
+ - **Action**: Enter email and password, tap login button
161
+ - **Tap target**: `resource-id: btn_login`
162
+ - **Expected logcat**:
163
+ - `ATP_API` → `apiResponse: endpoint=POST /api/login, status=200`
164
+ - **Verify**: Navigated to home screen
165
+ ```
166
+
167
+ ## Project Structure
168
+
169
+ ```
170
+ android-test-pilot/
171
+ ├── .claude/skills/atp/ # Claude Code slash commands
172
+ │ ├── analyze-app/SKILL.md # /atp:analyze-app (Step 0)
173
+ │ ├── check-logs/SKILL.md # /atp:check-logs (Step 1)
174
+ │ ├── run-test/SKILL.md # /atp:run-test (Step 2)
175
+ │ └── app-map/SKILL.md # /atp:app-map
176
+ ├── src/
177
+ │ ├── index.ts # MCP server entry point
178
+ │ ├── server.ts # MCP tool registration
179
+ │ ├── android.ts # AndroidRobot (ADB wrapper)
180
+ │ ├── robot.ts # Robot interface
181
+ │ └── tiers/ # Tier plugin system
182
+ │ ├── types.ts # TierContext, TierResult types
183
+ │ ├── abstract-tier.ts # AbstractTier base class
184
+ │ ├── tier-runner.ts # TierRunner chain executor
185
+ │ ├── text-tier.ts # Tier 1: text-based (dumpsys + logcat)
186
+ │ ├── uiautomator-tier.ts # Tier 2: UI hierarchy
187
+ │ └── screenshot-tier.ts # Tier 3: screenshot
188
+ ├── templates/
189
+ │ └── scenario.md # Scenario template
190
+ └── package.json
191
+ ```
192
+
193
+ ## MCP Tools
194
+
195
+ android-test-pilot exposes 5 MCP tools for device interaction:
196
+
197
+ | Tool | Description |
198
+ |------|-------------|
199
+ | `atp_run_step` | Execute a single test step with automatic 3-tier fallback (text → uiautomator → screenshot) |
200
+ | `atp_dumpsys` | Query current Activity or focused Window (text-based) |
201
+ | `atp_logcat_start` | Start logcat streaming session with ATP tag filtering |
202
+ | `atp_logcat_read` | Read collected log lines from active session (supports incremental reads) |
203
+ | `atp_logcat_stop` | Stop logcat session and return stats |
204
+
205
+ All existing [mobile-mcp](https://github.com/mobile-next/mobile-mcp) tools (`mobile_take_screenshot`, `mobile_list_elements_on_screen`, `mobile_click_on_screen_at_coordinates`, etc.) are also available.
206
+
207
+ ## Extending with Custom Tiers
208
+
209
+ Add custom Tiers to extend the testing strategy.
210
+
211
+ ```typescript
212
+ import { AbstractTier } from "./tiers/abstract-tier";
213
+ import { TierContext, TierResult } from "./tiers/types";
214
+
215
+ class MyCustomTier extends AbstractTier {
216
+ readonly name = "custom-monitor";
217
+ readonly priority = 1.5; // Insert between Tier 1 and 2
218
+
219
+ async canHandle(context: TierContext): Promise<boolean> {
220
+ // Check if this Tier can handle the current step
221
+ }
222
+
223
+ async execute(context: TierContext): Promise<TierResult> {
224
+ // Test execution logic
225
+ }
226
+ }
227
+ ```
228
+
229
+ ## Built On
230
+
231
+ Forked from [mobile-mcp](https://github.com/mobile-next/mobile-mcp) (Apache-2.0), specialized for Android test automation.
232
+
233
+ | Component | Role |
234
+ |-----------|------|
235
+ | Claude Code slash commands | User interface, workflow orchestrator |
236
+ | Claude Code native features | Source file reading, bash execution, file writing |
237
+ | mobile-mcp (fork) | Screenshots, accessibility tree, logcat streaming |
238
+
239
+ ## License
240
+
241
+ Apache-2.0
@@ -0,0 +1,96 @@
1
+ import { ChildProcess } from "node:child_process";
2
+ import { Button, InstalledApp, Robot, ScreenElement, ScreenSize, SwipeDirection, Orientation } from "./robot";
3
+ export interface LogcatSession {
4
+ id: string;
5
+ deviceId: string;
6
+ process: ChildProcess;
7
+ buffer: string[];
8
+ startTime: number;
9
+ maxDuration: number;
10
+ tags: string[];
11
+ timer: NodeJS.Timeout;
12
+ }
13
+ export interface AndroidDevice {
14
+ deviceId: string;
15
+ deviceType: "tv" | "mobile";
16
+ }
17
+ export declare class AndroidRobot implements Robot {
18
+ private deviceId;
19
+ constructor(deviceId: string);
20
+ getDeviceId(): string;
21
+ adb(...args: string[]): Buffer;
22
+ silentAdb(...args: string[]): Buffer;
23
+ getSystemFeatures(): string[];
24
+ getScreenSize(): Promise<ScreenSize>;
25
+ listApps(): Promise<InstalledApp[]>;
26
+ private listPackages;
27
+ launchApp(packageName: string, locale?: string): Promise<void>;
28
+ listRunningProcesses(): Promise<string[]>;
29
+ swipe(direction: SwipeDirection): Promise<void>;
30
+ swipeFromCoordinate(x: number, y: number, direction: SwipeDirection, distance?: number): Promise<void>;
31
+ private getDisplayCount;
32
+ private getFirstDisplayId;
33
+ getScreenshot(): Promise<Buffer>;
34
+ private collectElements;
35
+ getElementsOnScreen(): Promise<ScreenElement[]>;
36
+ terminateApp(packageName: string): Promise<void>;
37
+ installApp(path: string): Promise<void>;
38
+ uninstallApp(bundleId: string): Promise<void>;
39
+ openUrl(url: string): Promise<void>;
40
+ private isAscii;
41
+ private escapeShellText;
42
+ private isDeviceKitInstalled;
43
+ sendKeys(text: string): Promise<void>;
44
+ pressButton(button: Button): Promise<void>;
45
+ tap(x: number, y: number): Promise<void>;
46
+ longPress(x: number, y: number, duration: number): Promise<void>;
47
+ doubleTap(x: number, y: number): Promise<void>;
48
+ setOrientation(orientation: Orientation): Promise<void>;
49
+ getOrientation(): Promise<Orientation>;
50
+ private getUiAutomatorDump;
51
+ private getUiAutomatorXml;
52
+ private getScreenElementRect;
53
+ /**
54
+ * Get the current foreground Activity via dumpsys.
55
+ * Returns parsed activity info as text.
56
+ */
57
+ getDumpsysActivity(): string;
58
+ /**
59
+ * Get the current focused window via dumpsys.
60
+ */
61
+ getDumpsysWindow(): string;
62
+ /**
63
+ * Start a logcat streaming session.
64
+ * Spawns `adb logcat` as a background process and buffers output.
65
+ */
66
+ startLogcat(tags: string[], durationSeconds: number): LogcatSession;
67
+ /**
68
+ * Read collected log lines from an active session.
69
+ * @param since - If provided, only return lines after this index (for incremental reads)
70
+ */
71
+ readLogcat(sessionId: string, since?: number): {
72
+ lines: string[];
73
+ lineCount: number;
74
+ };
75
+ /**
76
+ * Stop a logcat streaming session and return stats.
77
+ */
78
+ stopLogcat(sessionId: string): {
79
+ totalLines: number;
80
+ durationMs: number;
81
+ };
82
+ /** Get an active logcat session by ID (for server.ts to check existence) */
83
+ static getSession(sessionId: string): LogcatSession | undefined;
84
+ /** Get the most recent active logcat session for a device (for TextTier) */
85
+ static getSessionByDevice(deviceId: string): LogcatSession | undefined;
86
+ }
87
+ export declare class AndroidDeviceManager {
88
+ private getDeviceType;
89
+ private getDeviceVersion;
90
+ private getDeviceName;
91
+ getConnectedDevices(): AndroidDevice[];
92
+ getConnectedDevicesWithDetails(): Array<AndroidDevice & {
93
+ version: string;
94
+ name: string;
95
+ }>;
96
+ }