@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +139 -150
  3. 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
- <p align="center">
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
- <h1 align="center">device-farm</h1>
6
-
7
- <p align="center">
8
- <b>WebView로 만든 모바일 앱을 실기기에서 QA 자동화하는 디바이스팜.</b><br />
9
- 내 프로젝트에 설치하고 <code>farm.config.ts</code> 하나로 점검할 앱을 정한다.
10
- </p>
3
+ [![npm](https://img.shields.io/npm/v/@ahnseungchan/device-farm)](https://www.npmjs.com/package/@ahnseungchan/device-farm)
4
+ [![CI](https://github.com/Ahnseungc/device-farm/actions/workflows/ci.yml/badge.svg)](https://github.com/Ahnseungc/device-farm/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Ahnseungc/device-farm/blob/main/LICENSE)
11
6
 
12
- <p align="center">
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="#설치">설치</a> ·
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
- <img src="https://raw.githubusercontent.com/Ahnseungc/device-farm/main/docs/assets/architecture.svg?sanitize=true" width="760" alt="device-farm 구조: farm.config.ts를 읽은 웹 화면·CLI·MCP가 Android(adb+CDP)·iOS(Appium) 기기를 조작하고 결과를 test-results에 남긴다" />
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
- ## Why
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
- | 손으로 하는 QA | device-farm |
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
- - **플로우**: 홈에서 버튼·링크만 눌러 가는 시나리오. 녹화, 화면 지도 조립, PRD로 LLM 후보 생성(선택).
52
- - **데이터 QA**: 화면이 부르는 GET API를 다시 불러 합계·표시 값을 규칙으로 맞춰 본다(LLM 없음).
53
- - **탐색·카오스 탐색**: 너비 우선 탐색과 시드 고정 카오스(뒤로·연타·로딩 중 뒤로·백그라운드·재시작).
54
- - **정책·피그마 대조**: Confluence 정책 바뀜 감지, 규칙 뽑기, 피그마 문구 대조.
55
- - **원격 조작**: 웹 화면에서 기기 화면을 보며(Android H.264, iOS MJPEG) 직접 누른다. 조작 중인 기기는 잠근다.
56
- - **Claude Code MCP**: Claude가 MCP 도구로 기기를 탐색하고, 찾은 경로를 플로우 후보로 올린다.
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), USB 디버깅을 켠 기기 | Android |
68
- | WebView 디버깅을 켠 dev 빌드: `WebView.setWebContentsDebuggingEnabled(true)` | Android |
69
- | Xcode, Appium + XCUITest 드라이버(WebDriverAgent), `isInspectable = true`인 빌드 | iOS |
70
- | Firebase 서비스 계정, App Store Connect API 키 | 새 빌드 자동 받기(선택) |
71
- | Confluence API 토큰, Figma 토큰 | 정책·피그마 대조(선택) |
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
- 호스트 맥·기기 준비: [docs/operations.md](https://github.com/Ahnseungc/device-farm/blob/main/docs/operations.md)
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>점검할 앱이 맞춰야 할 것</b></summary>
51
+ <summary><b>What the app under test needs</b></summary>
77
52
 
78
- 1. **WebView 디버깅**이 켜진 빌드. 디바이스팜은 이 디버깅 소켓으로 들어간다.
79
- 2. **경로형 딥링크**: `<scheme>://<웹 경로>`를 받으면 그 웹 경로를 WebView로 연다.
80
- 3. **하단 탭이 웹 링크**(`a[href]` 또는 `role=link`)이고, 화면 상단 뒤로 버튼에 이름(`aria-label`)이 있다.
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, .env.example 생성(이미 있으면 건너뜀)
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
- `init`이 만든 `farm.config.ts`를 내 앱 값으로 고칩니다. 필수 값만 남기면 이 정도입니다.
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: { 검색: "/search", 마이: "/my" },
78
+ homeTabName: "Home",
79
+ tabPaths: { Search: "/search", My: "/my" },
105
80
  deepLink: { scheme: "myapp", homeHost: "home" },
106
81
  });
107
82
  ```
108
83
 
109
- 토큰이 필요하면 `.env.example`을 `.env`로 복사해 채우고, `.gitignore`에 `.env`와 `test-results/`를 넣어 두세요.
110
- 결과(실행 기록·스크린샷·기준 화면·화면 지도)는 전부 작업 폴더의 `test-results/`에 쌓입니다.
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>데이터 QA 규칙 예시</b></summary>
88
+ <summary><b>Data QA rule example</b></summary>
114
89
 
115
90
  ```ts
116
- import { defineAppConfig, equalWon, formatWon, numberAt, textHas } from "@ahnseungchan/device-farm";
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 : `화면에 없음: 합계 「${shown}」`;
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]`로 돌립니다. 전체 예시는 `init`이 만든 `farm.config.ts`에 있습니다.
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()`가 빠진 값을 기본값으로 채웁니다. 타입은 `AppConfigInput`·`AppConfig`로 패키지에서 가져올 수 있습니다.
151
- 설정 파일은 `FARM_CONFIG` → 작업 폴더 `farm.config.ts` → `farm.config.example.ts` 순으로 찾고, 비밀 값은 작업 폴더 `.env`에서 읽습니다.
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` | ✓ | `{ dev, prod }` Android 패키지 = iOS 번들 id. 다르면 `iosBundleIds` |
157
- | `homePath`·`homeTabName`·`tabPaths` | ✓ | 홈 경로, 홈 탭 이름, 하단 탭 이름 → 경로 |
158
- | `web` | | `{ dev, prod }` WebView 웹 주소. 비면 `FARM_WEB_ORIGIN_DEV`·`FARM_WEB_ORIGIN_PROD` |
159
- | `apiOrigin` | | API 호스트(웹과 다를 때). 가드가 내부로 보고 데이터 QA가 부른다 |
160
- | `deepLink` | | `{ scheme, homeHost }` 경로형 딥링크 |
161
- | `guard` | | 기본 금지어·금지 API에 **더할** 값, 늘 통과시킬 `pushTokenRequests` |
162
- | `scenarios`·`excludedScenarioPaths` | | 고정 주소 화면 점검 목록과 뺄 경로 |
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>전체 필드</b></summary>
166
-
167
- | 필드 | 설명 |
168
- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
169
- | `inPageBackNames` | 화면 상단 뒤로 버튼 이름. 기본 `["뒤로가기", "Back"]` |
170
- | `loginPaths`·`appErrorText` | 로그인 화면 경로, 앱 오류 문구. 기본 `["/login"]`, `"Application error"` |
171
- | `android.mainActivity` | WebView 액티비티. 다른 네이티브 화면이 위에 있으면 뒤로 키로 닫는다 |
172
- | `webview` | `stackResetScript`(WebView 스택 닫기 브리지), `sheetCloseName`(시트 닫기 버튼) |
173
- | `guard` | `extraForbiddenButtonWords`·`extraForbiddenClickNames`·`extraForbiddenApiPatterns`, `pushTokenRequests`, 누르지 않을 `avoidedNamePattern`·`noPressPathPattern` |
174
- | `qa` | `{ screens, sessionPath }` 데이터 QA 화면·규칙, 로그인 세션 경로 |
175
- | `policy.confluence` | `{ cloudId, spaceKey, titleQuery, siteUrl }` 정책 정본 |
176
- | `figma` | `{ frontRepo, ref, appDir }` 화면 → 피그마 노드를 뽑을 프론트 체크아웃 |
177
- | `install` | `sources`(플랫폼·빌드별 `firebase`/`testflight`), `firebase`(앱 id), `testflightDevAppName` |
178
- | `ios` | `{ teamId, wdaBundleId }` WDA 서명. 비면 `IOS_TEAM_ID`·`IOS_WDA_BUNDLE_ID` |
179
- | `access.allowedDomains` | 화면에 들어올 수 있는 Tailscale 계정 도메인. 비면 이 맥에서만 |
180
-
181
- 데이터 QA 도우미 `equalWon`·`formatWon`·`numberAt`·`textHas`·`pick`도 패키지 루트에서 내보냅니다. 환경 변수 목록은 `.env.example`에 있습니다.
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
- 모든 명령은 작업 폴더(내 프로젝트) 기준으로 설정·`.env`를 읽고 결과를 씁니다. `npx device-farm <명령> --help`로 옵션을 볼 수 있습니다.
188
-
189
- | 명령 | 하는 일 |
190
- | ----------------------------------------- | --------------------------------------------------------------------- |
191
- | `device-farm init` | `farm.config.ts`·`.env.example` 생성 |
192
- | `device-farm dev [--port 5173]` | 화면 + API 서버(127.0.0.1). 새 빌드 감시·정책 감시가 함께 돈다 |
193
- | `device-farm check --build dev\|prod --version 2.1.5` | 떠 있는 서버에 빌드 점검을 시키고 끝날 때까지 진행을 출력 |
194
- | `device-farm explore` | 탐색 점검(서버 필요) |
195
- | `device-farm chaos --device <serial>` | 카오스 탐색(Android dev) |
196
- | `device-farm record --device <serial>` | 사람 조작 녹화 → 화면 지도·플로우 후보 |
197
- | `device-farm qa --device <id>` | 데이터 QA만 |
198
- | `device-farm policy-watch` | 정책 바뀜 감지·규칙 뽑기·피그마 문구 |
199
- | `device-farm policy-check --device <id>` | 정책·피그마 대조를 기기 한 대에서 |
200
- | `device-farm explore-allowlist` | OpenAPI(`FARM_OPENAPI_URL`)의 쓰기 API를 허용 목록에 deny로 추가 |
201
- | `device-farm mcp` | Claude Code용 MCP 서버(stdio) |
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>명령별 옵션</b></summary>
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`·`explore`는 떠 있는 `dev` 서버에 작업을 시킵니다. 포트를 바꿨다면 같은 `--port`를 넘기세요.
218
- 탐색 허용 목록 `explore/allowlist.json`, 입력칸 테스트 값 `explore/form-values.json`, 정책 등록 `policy/sources.json`은 작업 폴더에 두고 커밋합니다. 허용 목록이 없으면 모든 쓰기 요청을 막습니다.
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
- - 도구 흐름: `device_start` → `device_look` / `device_press` / `device_go_home` / `device_report_issue` → `device_finish`.
230
- - 끝나면 화면 지도에 합치고, 홈부터 다시 돌 수 있는 경로면 플로우 후보로 올린다. 보고서는 `test-results/agent/<시각>/report.json`.
231
- - 탐색은 Android dev 빌드에서 쓰기 요청 가드를 켠 채로만 돈다. 사용자의 Claude Code 계정으로 돌고 API 키는 필요 없다.
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
- 이 저장소는 Claude Code 플러그인(`.claude-plugin`, `skills/run-check`·`chaos-explore`·`policy-figma-check`)으로도 쓸 수 있습니다. 저장소를 받아 `npm install`한 뒤 플러그인으로 추가하세요.
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
- - 탐색·버튼 눌러보기·카오스·MCP 탐색은 **Android dev 빌드**에서, 쓰기 요청 가드를 켠 채로 돈다. 가드를 못 걸면 시작하지 않는다(fail closed).
238
- - 가드 판단 순서: 읽기(GET·HEAD·OPTIONS) 통과 → `pushTokenRequests` 통과 → 금지 경로 차단 → 허용 목록 allow 통과 → deny·모름 차단.
239
- - 기본 금지 버튼(구매·결제·매도·매수·송금·출금·탈퇴·주문·환불·해지·삭제·로그아웃 등)은 목록에서 빠지고, 눌러도 거절한다. 앱 설정은 금지어를 더할 뿐 빼지 못한다. 비밀번호·PIN 화면은 누르지 않고 나가기만 한다.
240
- - `chaos --no-guard`는 쓰기 요청을 막지 않는다. **dev 서버에서만** 쓴다. 금지 버튼은 그대로 누르지 않는다.
241
- - 화면 서버는 127.0.0.1에만 열린다. 밖에서는 `tailscale serve`로만 들어오고, `access.allowedDomains`로 계정 도메인을 제한한다.
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>기기 없이 화면만 열어 볼 수 있나요?</b></summary>
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
- 네. `npx device-farm init && npx device-farm dev`면 예시 설정으로 화면이 열립니다. 기기 목록은 비어 있고 점검은 돌지 않습니다.
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>iOS도 탐색·카오스가 되나요?</b></summary>
236
+ <summary><b>Why is the UI in Korean?</b></summary>
254
237
 
255
- 빌드 점검·원격 조작은 iOS(Appium)를 지원합니다. 탐색·카오스·녹화·MCP 탐색은 지금 Android dev 빌드만 지원합니다.
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>가 서버에 연결할 수 없다고 합니다.</b></summary>
243
+ <summary><b><code>check</code> says it cannot reach the server.</b></summary>
261
244
 
262
- `check`·`explore`는 떠 있는 `device-farm dev` 서버에 작업을 시킵니다. 다른 터미널에서 `dev`를 먼저 켜고, 포트를 바꿨다면 같은 `--port`를 넘기세요.
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 키가 꼭 필요한가요?</b></summary>
250
+ <summary><b>Do I need an LLM API key?</b></summary>
268
251
 
269
- 아니요. 점검·탐색·카오스·데이터 QA는 규칙으로만 판정합니다. PRD로 플로우 후보를 만들 때(`FLOW_LLM_*`)와 정책 판정에만 씁니다.
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
- - [운영: 호스트 맥과 기기 준비](https://github.com/Ahnseungc/device-farm/blob/main/docs/operations.md)
276
- - [설계: 빌드 점검·기준 화면·플로우·화면 지도](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-device-farm.md)
277
- - [설계: 탐색 점검과 쓰기 요청 가드](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-explore.md)
278
- - [설계: 카오스 탐색](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-chaos.md)
279
- - [설계: 정책·피그마 대조](https://github.com/Ahnseungc/device-farm/blob/main/docs/design-policy-figma.md)
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
- 이슈와 PR 환영합니다. 재현 방법이 있는 버그 리포트, 문서 보완 모두 도움이 됩니다.
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 # 화면 개발(vite dev). 저장소 루트 farm.config.ts → farm.config.example.ts
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 모노레포입니다. 의존 방향은 `core ← devices ← engine ← server ← apps`이고 `tools/boundaries.test.ts`가 지킵니다. 배포 패키지는 워크스페이스를 `dist/`에 묶어 냅니다(tsup + vite build).
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
- 릴리스: `package.json`·`CHANGELOG.md`의 버전을 올리고 `npm run publish:npm`(`prepublishOnly`가 typecheck·test·build를 돈다), `v<version>` 태그를 push합니다.
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.1",
4
- "description": "실기기 WebView 앱 QA 자동화 디바이스팜. Android(adb+CDP)·iOS(Appium) 기기에서 기준 화면 비교, 버튼 눌러보기·화면 지도, 플로우 녹화, 카오스 탐색, 정책·피그마 대조를 돌리고 Claude Code MCP로 연결한다.",
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",