@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.
- package/.claude/skills/atp/analyze-app/SKILL.md +86 -0
- package/.claude/skills/atp/app-map/SKILL.md +36 -0
- package/.claude/skills/atp/check-logs/SKILL.md +77 -0
- package/.claude/skills/atp/run-test/SKILL.md +92 -0
- package/README.ko.md +241 -0
- package/README.md +241 -0
- package/lib/android.d.ts +96 -0
- package/lib/android.js +740 -0
- package/lib/android.js.map +1 -0
- package/lib/image-utils.d.ts +28 -0
- package/lib/image-utils.js +156 -0
- package/lib/image-utils.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +90 -0
- package/lib/index.js.map +1 -0
- package/lib/ios.d.ts +54 -0
- package/lib/ios.js +241 -0
- package/lib/ios.js.map +1 -0
- package/lib/iphone-simulator.d.ts +34 -0
- package/lib/iphone-simulator.js +227 -0
- package/lib/iphone-simulator.js.map +1 -0
- package/lib/logger.d.ts +2 -0
- package/lib/logger.js +23 -0
- package/lib/logger.js.map +1 -0
- package/lib/mobile-device.d.ts +25 -0
- package/lib/mobile-device.js +141 -0
- package/lib/mobile-device.js.map +1 -0
- package/lib/mobilecli.d.ts +32 -0
- package/lib/mobilecli.js +113 -0
- package/lib/mobilecli.js.map +1 -0
- package/lib/png.d.ts +9 -0
- package/lib/png.js +20 -0
- package/lib/png.js.map +1 -0
- package/lib/robot.d.ts +116 -0
- package/lib/robot.js +10 -0
- package/lib/robot.js.map +1 -0
- package/lib/server.d.ts +3 -0
- package/lib/server.js +692 -0
- package/lib/server.js.map +1 -0
- package/lib/tiers/abstract-tier.d.ts +48 -0
- package/lib/tiers/abstract-tier.js +35 -0
- package/lib/tiers/abstract-tier.js.map +1 -0
- package/lib/tiers/screenshot-tier.d.ts +19 -0
- package/lib/tiers/screenshot-tier.js +53 -0
- package/lib/tiers/screenshot-tier.js.map +1 -0
- package/lib/tiers/text-tier.d.ts +20 -0
- package/lib/tiers/text-tier.js +138 -0
- package/lib/tiers/text-tier.js.map +1 -0
- package/lib/tiers/tier-runner.d.ts +27 -0
- package/lib/tiers/tier-runner.js +91 -0
- package/lib/tiers/tier-runner.js.map +1 -0
- package/lib/tiers/types.d.ts +100 -0
- package/lib/tiers/types.js +12 -0
- package/lib/tiers/types.js.map +1 -0
- package/lib/tiers/uiautomator-tier.d.ts +16 -0
- package/lib/tiers/uiautomator-tier.js +91 -0
- package/lib/tiers/uiautomator-tier.js.map +1 -0
- package/lib/utils.d.ts +4 -0
- package/lib/utils.js +81 -0
- package/lib/utils.js.map +1 -0
- package/lib/webdriver-agent.d.ts +45 -0
- package/lib/webdriver-agent.js +400 -0
- package/lib/webdriver-agent.js.map +1 -0
- package/package.json +50 -0
- 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
|
package/lib/android.d.ts
ADDED
|
@@ -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
|
+
}
|