Skip to content

Commit 47e0da9

Browse files
committed
First run at automated integration tests
1 parent f23382d commit 47e0da9

File tree

14 files changed

+5189
-0
lines changed

14 files changed

+5189
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,9 @@ fastlane/Preview.html
6666
fastlane/screenshots
6767
fastlane/test_output
6868
fastlane/readme.md
69+
70+
# Integration tests
71+
integration-tests/node_modules/
72+
integration-tests/dist/
73+
integration-tests/screenshots/
74+
integration-tests/*.apk

integration-tests/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
dist/
3+
screenshots/
4+
*.log

integration-tests/.mocharc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extension": ["ts"],
3+
"node-option": ["import=tsx", "no-warnings"],
4+
"recursive": true,
5+
"timeout": 120000,
6+
"spec": "test/**/*.spec.ts"
7+
}

integration-tests/README.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# HTTP Toolkit Android Integration Tests
2+
3+
Automated integration tests for the HTTP Toolkit Android app using Node.js, Mockttp, and ADB.
4+
5+
## Prerequisites
6+
7+
- Node.js 18+
8+
- Android SDK with ADB in PATH
9+
- A running Android emulator or connected device
10+
- The HTTP Toolkit Android app installed on the device
11+
12+
## Setup
13+
14+
1. **Install dependencies:**
15+
```bash
16+
npm install
17+
```
18+
19+
2. **Build and install the Android app:**
20+
```bash
21+
cd .. # Go to main Android project directory
22+
./gradlew assembleDebug
23+
adb install -t app/build/outputs/apk/debug/app-debug.apk
24+
```
25+
26+
3. **Start an emulator (if not already running):**
27+
```bash
28+
emulator -avd <your-avd-name>
29+
```
30+
31+
## Running Tests
32+
33+
```bash
34+
# Run all tests
35+
npm test
36+
37+
# Run tests in watch mode
38+
npm run test:watch
39+
40+
# Run a specific test by name
41+
npm run test:single -- "should activate VPN"
42+
```
43+
44+
## Test Architecture
45+
46+
```
47+
┌─────────────────────────────────────────────────────────────┐
48+
│ Test Runner (Mocha + TypeScript) │
49+
├─────────────────────────────────────────────────────────────┤
50+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
51+
│ │ Mockttp │ │ ADB │ │ UI Automation │ │
52+
│ │ Proxy │ │ Control │ │ (VPN dialogs) │ │
53+
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
54+
└──────────────────────────┬──────────────────────────────────┘
55+
│ adb reverse / adb shell
56+
57+
┌─────────────────────────────────────────────────────────────┐
58+
│ Android Emulator/Device │
59+
│ ┌─────────────────────────────────────────────────────────┐│
60+
│ │ HTTP Toolkit Android App ││
61+
│ │ - Receives ACTIVATE/DEACTIVATE intents ││
62+
│ │ - Establishes VPN ││
63+
│ │ - Routes traffic to mock proxy ││
64+
│ └─────────────────────────────────────────────────────────┘│
65+
└─────────────────────────────────────────────────────────────┘
66+
```
67+
68+
## Key Components
69+
70+
### `src/adb.ts`
71+
ADB command helpers for device interaction:
72+
- `adbShell()` - Run shell commands on device
73+
- `deviceCurl()` - Make HTTP requests from device
74+
- `deviceNetcat()` - Raw TCP/UDP connections
75+
- `reversePort()` - ADB reverse port forwarding
76+
77+
### `src/ui-automation.ts`
78+
UI interaction via ADB:
79+
- `handleVpnPermissionDialog()` - Auto-accept VPN permission dialogs
80+
- `tapText()` - Tap UI elements by text
81+
- `waitForText()` - Wait for text to appear
82+
83+
### `src/mock-proxy.ts`
84+
Mockttp-based proxy server:
85+
- Serves HTTP Toolkit config endpoints
86+
- Intercepts and records HTTP traffic
87+
- Provides assertions for verifying requests
88+
89+
### `src/httptoolkit-app.ts`
90+
HTTP Toolkit Android app control:
91+
- `buildConnectUrl()` - Create activation URLs
92+
- `activateVpn()` - Start VPN via intent
93+
- `deactivateVpn()` - Stop VPN via intent
94+
- `isVpnActive()` - Check VPN state
95+
96+
## Test Categories
97+
98+
### App Activation
99+
- VPN activation via ACTIVATE intent
100+
- VPN deactivation via DEACTIVATE intent
101+
- Re-activation without restart
102+
103+
### HTTP Traffic Interception
104+
- HTTP request interception
105+
- HTTPS request interception
106+
- POST requests with body
107+
108+
### TCP Traffic Handling
109+
- Raw TCP connections via netcat
110+
111+
### VPN State Management
112+
- Correct state reporting
113+
- Cleanup on force-stop
114+
- Permission handling
115+
116+
### Error Handling
117+
- Unreachable proxy
118+
- Invalid configuration
119+
120+
## Writing New Tests
121+
122+
```typescript
123+
import { expect } from 'chai';
124+
import { createTestContext, cleanupTestContext } from './setup.js';
125+
import { fullActivationFlow, deviceCurl } from '../src/index.js';
126+
127+
describe('My Feature', function() {
128+
this.timeout(60000);
129+
130+
let ctx: TestContext;
131+
132+
beforeEach(async () => {
133+
ctx = await createTestContext();
134+
await fullActivationFlow(ctx.proxy.getProxyInfo());
135+
});
136+
137+
afterEach(async () => {
138+
await cleanupTestContext(ctx);
139+
});
140+
141+
it('should do something', async () => {
142+
// Mock a response
143+
await ctx.proxy.mockUrl('GET', 'http://example.com/', {
144+
status: 200,
145+
body: 'mocked'
146+
});
147+
148+
// Make request from device
149+
const response = await deviceCurl('http://example.com/');
150+
151+
// Assert
152+
expect(response.status).to.equal(200);
153+
});
154+
});
155+
```
156+
157+
## CI Integration
158+
159+
See `.github/workflows/ci.yml` for GitHub Actions configuration. Key points:
160+
- Enable KVM for hardware-accelerated emulator
161+
- Use `reactivecircus/android-emulator-runner` action
162+
- Set up ADB reverse port forwards before tests
163+
164+
## Debugging
165+
166+
### Screenshots
167+
Failed tests automatically capture screenshots to `./screenshots/`.
168+
169+
### Manual screenshot:
170+
```typescript
171+
import { captureDebugScreenshot } from '../src/httptoolkit-app.js';
172+
await captureDebugScreenshot('my-debug');
173+
```
174+
175+
### ADB debugging
176+
```bash
177+
# Check VPN status
178+
adb shell ip addr show tun0
179+
180+
# Check routes
181+
adb shell ip route
182+
183+
# View app logs
184+
adb logcat -s tech.httptoolkit.android
185+
186+
# Dump UI hierarchy
187+
adb shell uiautomator dump /sdcard/ui.xml && adb pull /sdcard/ui.xml
188+
```
189+
190+
## Troubleshooting
191+
192+
### "Device not found"
193+
- Ensure emulator is running: `adb devices`
194+
- Wait for boot: `adb wait-for-device`
195+
196+
### "App not installed"
197+
- Build and install: `./gradlew assembleDebug && adb install -t app/build/outputs/apk/debug/app-debug.apk`
198+
199+
### "VPN permission dialog not handled"
200+
- Different Android versions have different dialog layouts
201+
- Check `ui-automation.ts` for button text patterns
202+
- Add new patterns if needed for your device
203+
204+
### "Proxy connection refused"
205+
- Check ADB reverse forward: `adb reverse --list`
206+
- Verify proxy is running: the test output shows the port
207+
208+
### Tests timing out
209+
- Increase timeout: `this.timeout(120000)`
210+
- Check device responsiveness
211+
- Look for ANRs in logcat

0 commit comments

Comments
 (0)