@ahnseungchan/device-farm 0.1.1 → 0.1.2
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/CHANGELOG.md +12 -0
- package/README.md +139 -150
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
`@ahnseungchan/device-farm`의 바뀐 점을 적는다.
|
|
4
4
|
|
|
5
|
+
## [0.1.2] - 2026-11-11
|
|
6
|
+
|
|
7
|
+
### Docs
|
|
8
|
+
|
|
9
|
+
- README를 영어로 다시 쓰고 webview-kit README 형식(배지·한 줄 소개·데모 GIF·Live demo·Features·Install·FAQ·Contributing)으로 맞춤. Why 표는 빼고 기능·사용법만 둠. npm 패키지 설명을 영어로 바꿈. 한국어 문서는 `docs/README.ko.md`로 옮김
|
|
10
|
+
- `docs/assets/demo.gif` 추가: 데모 화면을 Playwright로 녹화해 ffmpeg로 만든다(`tools/record-demo-gif.ts`)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- 데모 모드(`apps/web/demo/`): 서버·기기 없이 가상 앱 「Sample Shop」 가짜 데이터로 화면을 띄운다. 바꾸는 버튼은 「Demo mode: read-only」 토스트만 띄운다. `npm run demo:dev`·`demo:build`·`demo:preview`(127.0.0.1:5499). npm 패키지에는 들어가지 않는다
|
|
15
|
+
- GitHub Actions: CI(typecheck·test·build·데모 빌드), 데모를 GitHub Pages(https://ahnseungc.github.io/device-farm/)에 배포
|
|
16
|
+
|
|
5
17
|
## [0.1.1] - 2026-11-11
|
|
6
18
|
|
|
7
19
|
### Docs
|
package/README.md
CHANGED
|
@@ -1,95 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="https://raw.githubusercontent.com/Ahnseungc/device-farm/main/docs/assets/logo.svg?sanitize=true" width="96" alt="device-farm 로고: 초록 바탕의 폰에서 새싹이 자란다" />
|
|
3
|
-
</p>
|
|
1
|
+
# device-farm
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<b>WebView로 만든 모바일 앱을 실기기에서 QA 자동화하는 디바이스팜.</b><br />
|
|
9
|
-
내 프로젝트에 설치하고 <code>farm.config.ts</code> 하나로 점검할 앱을 정한다.
|
|
10
|
-
</p>
|
|
3
|
+
[](https://www.npmjs.com/package/@ahnseungchan/device-farm)
|
|
4
|
+
[](https://github.com/Ahnseungc/device-farm/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/Ahnseungc/device-farm/blob/main/LICENSE)
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
<a href="https://www.npmjs.com/package/@ahnseungchan/device-farm"><img src="https://img.shields.io/npm/v/@ahnseungchan/device-farm" alt="npm" /></a>
|
|
14
|
-
<a href="https://github.com/Ahnseungc/device-farm/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="license" /></a>
|
|
15
|
-
<a href="https://nodejs.org/"><img src="https://img.shields.io/node/v/@ahnseungchan/device-farm" alt="node" /></a>
|
|
16
|
-
</p>
|
|
7
|
+
**A device farm that runs QA on real phones for mobile apps built with WebViews. Install it in your project and describe your app in one `farm.config.ts`.**
|
|
17
8
|
|
|
18
9
|
<p align="center">
|
|
19
|
-
<a href="
|
|
20
|
-
<a href="#quick-start">Quick start</a> ·
|
|
21
|
-
<a href="#cli">CLI</a> ·
|
|
22
|
-
<a href="#claude-code-mcp">Claude Code</a> ·
|
|
23
|
-
<a href="#안전">안전</a> ·
|
|
24
|
-
<a href="https://github.com/Ahnseungc/device-farm/blob/main/CHANGELOG.md">Changelog</a>
|
|
10
|
+
<a href="https://ahnseungc.github.io/device-farm/"><img src="https://raw.githubusercontent.com/Ahnseungc/device-farm/main/docs/assets/demo.gif" width="800" alt="device-farm demo: device list, a failed screen compared with its baseline, the screen map, chaos candidate weights, and extracted policy rules" /></a>
|
|
25
11
|
</p>
|
|
26
12
|
|
|
27
13
|
<p align="center">
|
|
28
|
-
<
|
|
14
|
+
<b><a href="https://ahnseungc.github.io/device-farm/">Live demo →</a></b> (fixture data for a fictional "Sample Shop" app, no server or device needed, read-only)
|
|
29
15
|
</p>
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
손으로 하는 QA는 기기 수와 화면 수에 비례해 늘어납니다. device-farm은 그 반복을 실기기 위에서 자동으로 돌립니다.
|
|
17
|
+
The app under test is not in the code. Package ids, web origins, bottom tabs, forbidden words, and scenarios are all **injected** through `farm.config.ts`.
|
|
18
|
+
Android is driven with adb + Chrome DevTools Protocol (Playwright), iOS with Appium (XCUITest, WebDriverAgent), both inside the app's WebView.
|
|
34
19
|
|
|
35
|
-
|
|
36
|
-
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
37
|
-
| 새 빌드가 나오면 기기마다 깔고 화면을 하나씩 열어 본다 | Firebase App Distribution·TestFlight 새 빌드를 받아 연결된 기기에 깔고, 고정 주소 화면을 딥링크로 열어 기준 화면과 비교 |
|
|
38
|
-
| 버튼을 다 눌러 봤는지 기억에 의존한다 | 화면의 버튼을 하나씩 눌러 반응(이동·팝업·반응 없음·오류)을 남기고, 결과가 **화면 지도**가 된다 |
|
|
39
|
-
| 시나리오 테스트를 코드로 짜고 고친다 | 사람이 기기를 움직이면 녹화해 플로우 후보로 올리고, 확정한 것만 돈다 |
|
|
40
|
-
| 뒤로·연타·백그라운드 같은 엣지 케이스는 운에 맡긴다 | 시드 고정 카오스 탐색으로 재현 가능한 무작위 조작 |
|
|
41
|
-
| 정책 문서·피그마 문구와 화면을 눈으로 대조한다 | Confluence 정책·피그마 문구를 화면 글자와 맞춰 본다 |
|
|
42
|
-
| 탐색 자동화가 실제 결제·탈퇴를 누를까 무섭다 | 쓰기 요청 가드와 금지 버튼 목록. 가드를 못 걸면 시작하지 않는다 |
|
|
43
|
-
|
|
44
|
-
점검할 앱은 코드에 없습니다. 패키지 id·웹 주소·하단 탭·금지어·시나리오처럼 앱마다 다른 값은 전부 `farm.config.ts`로 **주입**합니다.
|
|
45
|
-
Android는 adb + Chrome DevTools Protocol(Playwright), iOS는 Appium(XCUITest·WebDriverAgent)으로 앱 안 WebView를 직접 조작합니다.
|
|
20
|
+
[한국어 문서](https://github.com/Ahnseungc/device-farm/blob/main/docs/README.ko.md)
|
|
46
21
|
|
|
47
22
|
## Features
|
|
48
23
|
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- **Claude Code MCP**: Claude
|
|
24
|
+
- **Build checks**: watch for new builds → install → check fixed screens. Fails on app error text, login bounce, blank screens, or a pixel diff against the baseline.
|
|
25
|
+
- **Button check and screen map**: records how every button reacts and stitches screens together into a map. Build flows by chaining map blocks.
|
|
26
|
+
- **Flows**: scenarios that start at home and move only by tapping buttons and links. Record them, assemble them from the map, or generate candidates from a PRD with an LLM (optional).
|
|
27
|
+
- **Data QA**: re-calls the GET APIs a screen uses and checks totals and displayed values with plain rules (no LLM).
|
|
28
|
+
- **Exploration and chaos**: breadth-first exploration, plus seeded chaos (back, double tap, back while loading, background, restart).
|
|
29
|
+
- **Policy and Figma checks**: detects Confluence policy changes, extracts rules, and compares Figma copy.
|
|
30
|
+
- **Remote control**: watch the device screen in the browser (Android H.264, iOS MJPEG) and tap it yourself. A device you control is locked for others.
|
|
31
|
+
- **Claude Code (MCP)**: Claude explores the device through MCP tools and turns the paths it finds into flow candidates.
|
|
57
32
|
|
|
58
|
-
##
|
|
33
|
+
## Install
|
|
59
34
|
|
|
60
35
|
```bash
|
|
61
36
|
npm install -D @ahnseungchan/device-farm
|
|
62
37
|
```
|
|
63
38
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
| macOS, Node.js 22
|
|
67
|
-
| `adb`(Android platform-tools)
|
|
68
|
-
|
|
|
69
|
-
| Xcode, Appium + XCUITest
|
|
70
|
-
| Firebase
|
|
71
|
-
| Confluence API
|
|
39
|
+
| Requirement | When |
|
|
40
|
+
| --------------------------------------------------------------------------------- | ---------------------------- |
|
|
41
|
+
| macOS, Node.js 22+ | Always |
|
|
42
|
+
| `adb` (Android platform-tools) and a device with USB debugging on | Android |
|
|
43
|
+
| A dev build with WebView debugging: `WebView.setWebContentsDebuggingEnabled(true)` | Android |
|
|
44
|
+
| Xcode, Appium + XCUITest driver (WebDriverAgent), a build with `isInspectable = true` | iOS |
|
|
45
|
+
| Firebase service account, App Store Connect API key | Auto-installing new builds (optional) |
|
|
46
|
+
| Confluence API token, Figma token | Policy and Figma checks (optional) |
|
|
72
47
|
|
|
73
|
-
|
|
48
|
+
Host Mac and device setup: [docs/operations.md](https://github.com/Ahnseungc/device-farm/blob/main/docs/operations.md) (Korean)
|
|
74
49
|
|
|
75
50
|
<details>
|
|
76
|
-
<summary><b
|
|
51
|
+
<summary><b>What the app under test needs</b></summary>
|
|
77
52
|
|
|
78
|
-
1. **WebView
|
|
79
|
-
2.
|
|
80
|
-
3.
|
|
81
|
-
4.
|
|
53
|
+
1. A build with **WebView debugging** on. device-farm connects through that debugging socket.
|
|
54
|
+
2. **Path deep links**: `<scheme>://<web path>` opens that web path in the WebView.
|
|
55
|
+
3. **Bottom tabs are web links** (`a[href]` or `role=link`), and the in-page back button has a name (`aria-label`).
|
|
56
|
+
4. A test account that is already signed in. If a screen bounces to login, it is reported as "login required".
|
|
82
57
|
|
|
83
58
|
</details>
|
|
84
59
|
|
|
85
60
|
## Quick start
|
|
86
61
|
|
|
87
62
|
```bash
|
|
88
|
-
npx device-farm init # farm.config.ts
|
|
89
|
-
npx device-farm dev # http://127.0.0.1:5173 (--port
|
|
63
|
+
npx device-farm init # creates farm.config.ts and .env.example (skips files that exist)
|
|
64
|
+
npx device-farm dev # http://127.0.0.1:5173 (change with --port)
|
|
90
65
|
```
|
|
91
66
|
|
|
92
|
-
|
|
67
|
+
Edit the `farm.config.ts` that `init` created. With only the required fields it looks like this:
|
|
93
68
|
|
|
94
69
|
```ts
|
|
95
70
|
// farm.config.ts
|
|
@@ -100,38 +75,38 @@ export default defineAppConfig({
|
|
|
100
75
|
appIds: { dev: "com.example.app.dev", prod: "com.example.app" },
|
|
101
76
|
web: { dev: "https://dev.app.example.com", prod: "https://app.example.com" },
|
|
102
77
|
homePath: "/home",
|
|
103
|
-
homeTabName: "
|
|
104
|
-
tabPaths: {
|
|
78
|
+
homeTabName: "Home",
|
|
79
|
+
tabPaths: { Search: "/search", My: "/my" },
|
|
105
80
|
deepLink: { scheme: "myapp", homeHost: "home" },
|
|
106
81
|
});
|
|
107
82
|
```
|
|
108
83
|
|
|
109
|
-
|
|
110
|
-
|
|
84
|
+
If you need tokens, copy `.env.example` to `.env` and fill it in. Add `.env` and `test-results/` to `.gitignore`.
|
|
85
|
+
Everything the farm produces (run history, screenshots, baselines, the screen map) goes to `test-results/` in your working directory.
|
|
111
86
|
|
|
112
87
|
<details>
|
|
113
|
-
<summary><b
|
|
88
|
+
<summary><b>Data QA rule example</b></summary>
|
|
114
89
|
|
|
115
90
|
```ts
|
|
116
|
-
import { defineAppConfig,
|
|
91
|
+
import { defineAppConfig, formatWon, numberAt, textHas } from "@ahnseungchan/device-farm";
|
|
117
92
|
|
|
118
93
|
export default defineAppConfig({
|
|
119
|
-
//
|
|
94
|
+
// ...required fields
|
|
120
95
|
apiOrigin: "https://api-dev.app.example.com",
|
|
121
96
|
qa: {
|
|
122
97
|
sessionPath: "/api/auth/session",
|
|
123
98
|
screens: [
|
|
124
99
|
{
|
|
125
100
|
path: "/cart",
|
|
126
|
-
name: "
|
|
101
|
+
name: "Cart",
|
|
127
102
|
apis: { cart: "/v1/users/{userId}/cart" },
|
|
128
103
|
rules: [
|
|
129
104
|
{
|
|
130
105
|
id: "total-shown",
|
|
131
|
-
title: "
|
|
106
|
+
title: "The total from the API is shown on screen",
|
|
132
107
|
check: (context) => {
|
|
133
108
|
const shown = `${formatWon(numberAt(context, "cart", "totalWon"))}원`;
|
|
134
|
-
return textHas(context.text, shown) ? null :
|
|
109
|
+
return textHas(context.text, shown) ? null : `Not on screen: total "${shown}"`;
|
|
135
110
|
},
|
|
136
111
|
},
|
|
137
112
|
],
|
|
@@ -141,157 +116,171 @@ export default defineAppConfig({
|
|
|
141
116
|
});
|
|
142
117
|
```
|
|
143
118
|
|
|
144
|
-
`npx device-farm qa --device <id> [--path /cart]
|
|
119
|
+
Run it with `npx device-farm qa --device <id> [--path /cart]`. The full example is in the `farm.config.ts` that `init` creates.
|
|
145
120
|
|
|
146
121
|
</details>
|
|
147
122
|
|
|
148
|
-
##
|
|
123
|
+
## Configuration
|
|
149
124
|
|
|
150
|
-
`defineAppConfig()
|
|
151
|
-
|
|
125
|
+
`defineAppConfig()` fills in defaults for anything you leave out. The `AppConfigInput` and `AppConfig` types are exported from the package.
|
|
126
|
+
The config file is resolved as `FARM_CONFIG` → `farm.config.ts` in the working directory → `farm.config.example.ts`. Secrets are read from `.env` in the working directory.
|
|
152
127
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
| `name`
|
|
156
|
-
| `appIds`
|
|
157
|
-
| `homePath
|
|
158
|
-
| `web`
|
|
159
|
-
| `apiOrigin`
|
|
160
|
-
| `deepLink`
|
|
161
|
-
| `guard`
|
|
162
|
-
| `scenarios
|
|
128
|
+
| Field | Required | What it is |
|
|
129
|
+
| ----------------------------------- | -------- | ---------------------------------------------------------------------------------- |
|
|
130
|
+
| `name` | ✓ | App name, used in the UI title and prompts |
|
|
131
|
+
| `appIds` | ✓ | `{ dev, prod }` Android package = iOS bundle id. Use `iosBundleIds` if they differ |
|
|
132
|
+
| `homePath`, `homeTabName`, `tabPaths` | ✓ | Home path, home tab name, and bottom tab name → path |
|
|
133
|
+
| `web` | | `{ dev, prod }` WebView origins. Falls back to `FARM_WEB_ORIGIN_DEV` / `FARM_WEB_ORIGIN_PROD` |
|
|
134
|
+
| `apiOrigin` | | API host when it differs from the web origin. The guard treats it as internal, and data QA calls it |
|
|
135
|
+
| `deepLink` | | `{ scheme, homeHost }` for path deep links |
|
|
136
|
+
| `guard` | | Words and API patterns **added** to the built-in deny lists, and `pushTokenRequests` that always pass |
|
|
137
|
+
| `scenarios`, `excludedScenarioPaths` | | Fixed screens to check, and paths to skip |
|
|
163
138
|
|
|
164
139
|
<details>
|
|
165
|
-
<summary><b
|
|
166
|
-
|
|
167
|
-
|
|
|
168
|
-
| --------------------------- |
|
|
169
|
-
| `inPageBackNames` |
|
|
170
|
-
| `loginPaths
|
|
171
|
-
| `android.mainActivity` | WebView
|
|
172
|
-
| `webview` | `stackResetScript`(
|
|
173
|
-
| `guard` | `extraForbiddenButtonWords
|
|
174
|
-
| `qa` | `{ screens, sessionPath }
|
|
175
|
-
| `policy.confluence` | `{ cloudId, spaceKey, titleQuery, siteUrl }
|
|
176
|
-
| `figma` | `{ frontRepo, ref, appDir }
|
|
177
|
-
| `install` | `sources`(
|
|
178
|
-
| `ios` | `{ teamId, wdaBundleId }` WDA
|
|
179
|
-
| `access.allowedDomains` |
|
|
180
|
-
|
|
181
|
-
|
|
140
|
+
<summary><b>All fields</b></summary>
|
|
141
|
+
|
|
142
|
+
| Field | What it is |
|
|
143
|
+
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `inPageBackNames` | Names of the in-page back button. Default `["뒤로가기", "Back"]` |
|
|
145
|
+
| `loginPaths`, `appErrorText` | Login screen paths and the app's error text. Default `["/login"]`, `"Application error"` |
|
|
146
|
+
| `android.mainActivity` | The WebView activity. If another native screen is on top, it is closed with the back key |
|
|
147
|
+
| `webview` | `stackResetScript` (bridge call that closes the WebView stack), `sheetCloseName` (bottom sheet close button) |
|
|
148
|
+
| `guard` | `extraForbiddenButtonWords`, `extraForbiddenClickNames`, `extraForbiddenApiPatterns`, `pushTokenRequests`, and `avoidedNamePattern` / `noPressPathPattern` to never press |
|
|
149
|
+
| `qa` | `{ screens, sessionPath }`: data QA screens and rules, and the signed-in session path |
|
|
150
|
+
| `policy.confluence` | `{ cloudId, spaceKey, titleQuery, siteUrl }`: where the policy docs live |
|
|
151
|
+
| `figma` | `{ frontRepo, ref, appDir }`: the front-end checkout used to map screens to Figma nodes |
|
|
152
|
+
| `install` | `sources` (`firebase` / `testflight` per platform and build), `firebase` (app ids), `testflightDevAppName` |
|
|
153
|
+
| `ios` | `{ teamId, wdaBundleId }` for WDA signing. Falls back to `IOS_TEAM_ID` / `IOS_WDA_BUNDLE_ID` |
|
|
154
|
+
| `access.allowedDomains` | Tailscale account domains allowed to open the UI. Empty means this Mac only |
|
|
155
|
+
|
|
156
|
+
The data QA helpers `equalWon`, `formatWon`, `numberAt`, `textHas`, and `pick` are exported from the package root. Environment variables are listed in `.env.example`.
|
|
182
157
|
|
|
183
158
|
</details>
|
|
184
159
|
|
|
185
160
|
## CLI
|
|
186
161
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
| `device-farm init`
|
|
192
|
-
| `device-farm dev [--port 5173]`
|
|
193
|
-
| `device-farm check --build dev\|prod --version 2.1.5` |
|
|
194
|
-
| `device-farm explore`
|
|
195
|
-
| `device-farm chaos --device <serial>`
|
|
196
|
-
| `device-farm record --device <serial>`
|
|
197
|
-
| `device-farm qa --device <id>`
|
|
198
|
-
| `device-farm policy-watch`
|
|
199
|
-
| `device-farm policy-check --device <id>`
|
|
200
|
-
| `device-farm explore-allowlist`
|
|
201
|
-
| `device-farm mcp`
|
|
162
|
+
Every command reads config and `.env` from the working directory (your project) and writes results there. Run `npx device-farm <command> --help` for options.
|
|
163
|
+
|
|
164
|
+
| Command | What it does |
|
|
165
|
+
| ---------------------------------------------------- | -------------------------------------------------------------------- |
|
|
166
|
+
| `device-farm init` | Create `farm.config.ts` and `.env.example` |
|
|
167
|
+
| `device-farm dev [--port 5173]` | UI + API server on 127.0.0.1. New-build and policy watchers run with it |
|
|
168
|
+
| `device-farm check --build dev\|prod --version 2.1.5` | Ask the running server for a build check and print progress until it ends |
|
|
169
|
+
| `device-farm explore` | Exploration check (needs the server) |
|
|
170
|
+
| `device-farm chaos --device <serial>` | Chaos exploration (Android dev) |
|
|
171
|
+
| `device-farm record --device <serial>` | Record a person's actions → screen map and flow candidates |
|
|
172
|
+
| `device-farm qa --device <id>` | Data QA only |
|
|
173
|
+
| `device-farm policy-watch` | Detect policy changes, extract rules, cache Figma copy |
|
|
174
|
+
| `device-farm policy-check --device <id>` | Policy and Figma comparison on one device |
|
|
175
|
+
| `device-farm explore-allowlist` | Add write APIs from OpenAPI (`FARM_OPENAPI_URL`) to the allowlist as deny |
|
|
176
|
+
| `device-farm mcp` | MCP server for Claude Code (stdio) |
|
|
202
177
|
|
|
203
178
|
<details>
|
|
204
|
-
<summary><b
|
|
179
|
+
<summary><b>Options per command</b></summary>
|
|
205
180
|
|
|
206
181
|
```bash
|
|
207
182
|
device-farm dev [--port 5173]
|
|
208
183
|
device-farm check --build dev|prod --version 2.1.5 [--devices all|id,id] [--port 5173]
|
|
209
184
|
device-farm explore [--devices all|id,id] [--build dev] [--port 5173]
|
|
210
185
|
device-farm chaos --device <Android serial> [--seed 42] [--episodes 3] [--steps 50] [--minutes 60] [--no-guard]
|
|
211
|
-
device-farm record --device <Android serial> [--seconds 300] [--name "
|
|
186
|
+
device-farm record --device <Android serial> [--seconds 300] [--name "flow name"]
|
|
212
187
|
device-farm qa --device <id> [--path /cart]
|
|
213
188
|
device-farm policy-watch [--extract <pageId>] [--figma]
|
|
214
189
|
device-farm policy-check --device <id> [--path /my/orders]
|
|
215
190
|
```
|
|
216
191
|
|
|
217
|
-
`check
|
|
218
|
-
|
|
192
|
+
`check` and `explore` send work to a running `dev` server. If you changed the port, pass the same `--port`.
|
|
193
|
+
Keep the exploration allowlist `explore/allowlist.json`, input test values `explore/form-values.json`, and policy sources `policy/sources.json` in your working directory and commit them. Without an allowlist, every write request is blocked.
|
|
219
194
|
|
|
220
195
|
</details>
|
|
221
196
|
|
|
222
197
|
## Claude Code (MCP)
|
|
223
198
|
|
|
224
199
|
```bash
|
|
225
|
-
#
|
|
200
|
+
# in your project folder
|
|
226
201
|
claude mcp add --scope project device-farm -- npx device-farm mcp
|
|
227
202
|
```
|
|
228
203
|
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
204
|
+
- Tool flow: `device_start` → `device_look` / `device_press` / `device_go_home` / `device_report_issue` → `device_finish`.
|
|
205
|
+
- When it finishes, results are merged into the screen map, and paths that can be replayed from home become flow candidates. The report is written to `test-results/agent/<time>/report.json`.
|
|
206
|
+
- Exploration runs only on Android dev builds with the write-request guard on. It runs on your Claude Code account; no API key is needed.
|
|
232
207
|
|
|
233
|
-
|
|
208
|
+
This repository also works as a Claude Code plugin (`.claude-plugin`, skills `run-check`, `chaos-explore`, `policy-figma-check`). Clone it, run `npm install`, and add it as a plugin.
|
|
234
209
|
|
|
235
|
-
##
|
|
210
|
+
## Safety
|
|
236
211
|
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
- `chaos --no-guard
|
|
241
|
-
-
|
|
212
|
+
- Exploration, button checks, chaos, and MCP exploration run on **Android dev builds** with the write-request guard on. If the guard cannot attach, they do not start (fail closed).
|
|
213
|
+
- Guard order: reads (GET, HEAD, OPTIONS) pass → `pushTokenRequests` pass → forbidden paths are blocked → allowlist `allow` passes → `deny` and unknown are blocked.
|
|
214
|
+
- Built-in forbidden buttons (buy, pay, sell, transfer, withdraw, delete account, order, refund, cancel, delete, log out, and more) are removed from the candidate list and refused if pressed. App config can only add forbidden words, never remove them. Password and PIN screens are never pressed, only left.
|
|
215
|
+
- `chaos --no-guard` does not block write requests. Use it **only against a dev server**. Forbidden buttons are still never pressed.
|
|
216
|
+
- The UI server listens on 127.0.0.1 only. Remote access goes through `tailscale serve`, limited to `access.allowedDomains`.
|
|
242
217
|
|
|
243
218
|
## FAQ
|
|
244
219
|
|
|
245
220
|
<details>
|
|
246
|
-
<summary><b
|
|
221
|
+
<summary><b>Can I open the UI without a device?</b></summary>
|
|
222
|
+
|
|
223
|
+
Yes. `npx device-farm init && npx device-farm dev` opens the UI with the example config. The device list is empty and no checks run.
|
|
224
|
+
To see the UI filled with data, open the [live demo](https://ahnseungc.github.io/device-farm/).
|
|
225
|
+
|
|
226
|
+
</details>
|
|
227
|
+
|
|
228
|
+
<details>
|
|
229
|
+
<summary><b>Does iOS support exploration and chaos?</b></summary>
|
|
247
230
|
|
|
248
|
-
|
|
231
|
+
Build checks and remote control support iOS (Appium). Exploration, chaos, recording, and MCP exploration support Android dev builds only for now.
|
|
249
232
|
|
|
250
233
|
</details>
|
|
251
234
|
|
|
252
235
|
<details>
|
|
253
|
-
<summary><b>
|
|
236
|
+
<summary><b>Why is the UI in Korean?</b></summary>
|
|
254
237
|
|
|
255
|
-
|
|
238
|
+
It was built for a Korean team first. The CLI help and UI text are Korean for now; an English UI PR is welcome.
|
|
256
239
|
|
|
257
240
|
</details>
|
|
258
241
|
|
|
259
242
|
<details>
|
|
260
|
-
<summary><b><code>check</code
|
|
243
|
+
<summary><b><code>check</code> says it cannot reach the server.</b></summary>
|
|
261
244
|
|
|
262
|
-
`check
|
|
245
|
+
`check` and `explore` send work to a running `device-farm dev` server. Start `dev` in another terminal first, and pass the same `--port` if you changed it.
|
|
263
246
|
|
|
264
247
|
</details>
|
|
265
248
|
|
|
266
249
|
<details>
|
|
267
|
-
<summary><b>LLM
|
|
250
|
+
<summary><b>Do I need an LLM API key?</b></summary>
|
|
268
251
|
|
|
269
|
-
|
|
252
|
+
No. Checks, exploration, chaos, and data QA are judged by rules only. An LLM is used only to generate flow candidates from a PRD (`FLOW_LLM_*`) and to judge policy behavior rules.
|
|
270
253
|
|
|
271
254
|
</details>
|
|
272
255
|
|
|
273
|
-
##
|
|
256
|
+
## Docs
|
|
274
257
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
- [
|
|
278
|
-
- [
|
|
279
|
-
- [
|
|
258
|
+
Design notes are in Korean for now.
|
|
259
|
+
|
|
260
|
+
- [Operations: host Mac and device setup](https://github.com/Ahnseungc/device-farm/blob/main/docs/operations.md)
|
|
261
|
+
- [Design: build checks, baselines, flows, screen map](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-device-farm.md)
|
|
262
|
+
- [Design: exploration and the write-request guard](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-explore.md)
|
|
263
|
+
- [Design: chaos exploration](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-chaos.md)
|
|
264
|
+
- [Design: policy and Figma checks](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-policy-figma.md)
|
|
280
265
|
|
|
281
266
|
## Contributing
|
|
282
267
|
|
|
283
|
-
|
|
268
|
+
Issues and PRs are welcome. Bug reports with a repro, docs and translations, and demo improvements all help.
|
|
284
269
|
|
|
285
270
|
```bash
|
|
286
271
|
git clone https://github.com/Ahnseungc/device-farm.git && cd device-farm
|
|
287
272
|
npm install
|
|
288
273
|
npm run typecheck && npm test && npm run build
|
|
289
|
-
npm run dev
|
|
274
|
+
npm run dev # UI development (vite dev), config: farm.config.ts → farm.config.example.ts
|
|
275
|
+
npm run demo:dev # demo UI with fixture data at http://127.0.0.1:5499
|
|
290
276
|
```
|
|
291
277
|
|
|
292
|
-
npm workspaces
|
|
278
|
+
It is an npm workspaces monorepo. Dependencies point one way, `core ← devices ← engine ← server ← apps`, enforced by `tools/boundaries.test.ts`. The published package bundles the workspaces into `dist/` (tsup + vite build).
|
|
279
|
+
|
|
280
|
+
The demo (`apps/web/demo/`) swaps only `./api-client` and `./live-view` for fixture modules and reuses the real screens; it is not part of the package build.
|
|
281
|
+
`npm run demo:build && npx tsx tools/record-demo-gif.ts` regenerates `docs/assets/demo.gif` (needs ffmpeg). Pushing to `main` deploys the demo to GitHub Pages via `.github/workflows/pages.yml`.
|
|
293
282
|
|
|
294
|
-
|
|
283
|
+
Releases: bump `package.json` and `CHANGELOG.md`, run `npm run publish:npm` (`prepublishOnly` runs typecheck, test, and build), then push a `v<version>` tag.
|
|
295
284
|
|
|
296
285
|
## License
|
|
297
286
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahnseungchan/device-farm",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A device farm that runs QA on real phones for WebView-based mobile apps: baseline screen diffs, button crawl and screen map, flow recording, seeded chaos exploration, policy/Figma copy checks, and a Claude Code MCP server. Configure your app in one farm.config.ts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"device-farm": "dist/bin.js"
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"dev": "vite --config apps/web/vite.config.ts --configLoader runner",
|
|
24
|
+
"demo:dev": "vite --config apps/web/demo/vite.config.ts --configLoader runner",
|
|
25
|
+
"demo:build": "vite build --config apps/web/demo/vite.config.ts --configLoader runner",
|
|
26
|
+
"demo:preview": "vite preview --config apps/web/demo/vite.config.ts --configLoader runner",
|
|
24
27
|
"build": "rm -rf dist && tsup && vite build --config apps/web/vite.config.ts --configLoader runner --outDir ../../dist/web --emptyOutDir && tsx tools/build-templates.ts",
|
|
25
28
|
"test": "for f in $(find packages apps tools -name '*.test.ts' -not -path '*/node_modules/*'); do npx tsx \"$f\" || exit 1; done",
|
|
26
29
|
"typecheck": "tsc --noEmit",
|