docs(macos-setup): add macOS migration and real iPhone control guide
Documents moving the repo from Windows/other dev machines to macOS and driving a real iPhone end-to-end via Appium + WebDriverAgent: Xcode/Appium toolchain setup, WDA signing (using the extra_capabilities passthrough already supported by WDADriverConfig), minimal real-device verification, and starting a Runtime API that explicitly registers and connects the device (neither the Console nor REST API currently expose a connect/disconnect endpoint). Also covers multi-device port allocation, common failure modes, and a completion checklist. README gains a pointer to it under a new "Operator Guides" section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
# macOS 迁移与 iPhone 真机控制手册
|
||||
|
||||
本文用于把 Device Agent Runtime 从 Windows 或其他开发机迁移到 macOS,并在
|
||||
macOS 上通过 Appium + WebDriverAgent(WDA)控制真实 iPhone。
|
||||
|
||||
## 1. 当前支持范围
|
||||
|
||||
- 当前仓库只内置了 `wda` Driver,即 iPhone/iPad 的 XCUITest/WDA 控制链路。
|
||||
- Android 只是架构上的未来目标,当前 `driver/registry.py` 没有注册 Android
|
||||
Driver,因此仅安装 Android SDK/ADB 还不能让本项目控制 Android 手机。
|
||||
- iPhone 真机自动化必须在 macOS 上完成,因为 XCUITest、Xcode 和 WDA 签名依赖
|
||||
Apple 工具链。
|
||||
- 当前 Web Console 的设备登记接口不会自动连接设备,REST API 也没有公开的
|
||||
connect/disconnect endpoint。本文提供的 Runtime 启动命令会在启动 API 前显式
|
||||
注册并连接 iPhone。
|
||||
|
||||
## 2. 迁移前准备
|
||||
|
||||
建议迁移源代码和必要配置,不要复制原操作系统生成的虚拟环境或缓存。
|
||||
|
||||
需要迁移:
|
||||
|
||||
- Git 仓库中的源代码。
|
||||
- 未提交的必要代码和文档变更。
|
||||
- 自己维护的 API Key、模型配置等敏感环境变量,使用安全渠道单独迁移。
|
||||
- 如确实需要保留设备登记和任务历史,可单独备份 `tasks/*.sqlite3`;通常建议在
|
||||
Mac 上重新登记设备,避免沿用旧 UDID、端口和路径。
|
||||
|
||||
不要迁移:
|
||||
|
||||
- `.venv/`
|
||||
- `__pycache__/`
|
||||
- `.pytest_cache/`
|
||||
- IDE 缓存
|
||||
- Windows 专用路径或 PowerShell profile
|
||||
|
||||
在 Mac 上优先通过 Git 重新 clone:
|
||||
|
||||
```bash
|
||||
git clone <repository-url> agentic-modile-control
|
||||
cd agentic-modile-control
|
||||
git status
|
||||
```
|
||||
|
||||
注意:仓库目录仍叫 `agentic-modile-control`,其中 `modile` 是历史拼写;Python
|
||||
包名已经是 `device-agent-runtime`。不要在迁移时顺手改目录名或数据库路径,除非
|
||||
准备单独处理相关脚本和配置。
|
||||
|
||||
## 3. 准备 Mac、Xcode 和 iPhone
|
||||
|
||||
### 3.1 必要条件
|
||||
|
||||
- 一台能运行目标 iOS 对应 Xcode 版本的 Mac。
|
||||
- 完整版 Xcode,不只是 Command Line Tools。
|
||||
- Apple ID;真机 WDA 签名需要可用的 Apple Development team。免费 Personal
|
||||
Team 可用于本地验证,但证书和 provisioning profile 有期限限制。
|
||||
- 数据线连接的 iPhone。第一次配置建议先使用 USB,稳定后再考虑无线调试。
|
||||
|
||||
### 3.2 安装并初始化 Xcode
|
||||
|
||||
从 App Store 或 Apple Developer 网站安装 Xcode,至少启动一次,然后执行:
|
||||
|
||||
```bash
|
||||
xcode-select --install
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
sudo xcodebuild -license accept
|
||||
xcodebuild -version
|
||||
xcode-select -p
|
||||
```
|
||||
|
||||
如果 Xcode 不在 `/Applications/Xcode.app`,将路径替换成实际位置。
|
||||
|
||||
打开 Xcode,进入 `Settings > Accounts`,登录 Apple ID,并确认能看到用于签名的
|
||||
Team。
|
||||
|
||||
### 3.3 配置 iPhone
|
||||
|
||||
1. 使用数据线连接 iPhone,解锁手机并选择“信任此电脑”。
|
||||
2. 在 Xcode 中打开 `Window > Devices and Simulators`,确认设备可见且没有
|
||||
“Preparing debugger support”之类的未完成状态。
|
||||
3. 在 iPhone 中进入 `设置 > 隐私与安全性 > 开发者模式`,启用后按提示重启并
|
||||
再次确认。
|
||||
4. 保持设备解锁,记录 UDID。可从 Xcode 的 Devices and Simulators 窗口复制,
|
||||
也可运行:
|
||||
|
||||
```bash
|
||||
xcrun devicectl list devices
|
||||
```
|
||||
|
||||
后续示例使用以下环境变量。值必须换成自己的信息:
|
||||
|
||||
```bash
|
||||
export DEVICE_UDID="<iPhone UDID>"
|
||||
export APPLE_TEAM_ID="<Apple Developer Team ID>"
|
||||
export WDA_BUNDLE_ID="com.<your-name>.WebDriverAgentRunner"
|
||||
```
|
||||
|
||||
`WDA_BUNDLE_ID` 必须在自己的 Team 下唯一,不能原样使用示例值。
|
||||
|
||||
## 4. 安装本地工具链
|
||||
|
||||
### 4.1 Homebrew、Python 和 Node.js
|
||||
|
||||
本项目当前在 `pyproject.toml` 中要求 Python `>=3.14`。Appium 3.1 要求 Node.js
|
||||
`^20.19.0 || ^22.12.0 || >=24.0.0`,并要求 npm `>=10`。
|
||||
|
||||
已安装 Homebrew 时执行:
|
||||
|
||||
```bash
|
||||
brew update
|
||||
brew install uv node
|
||||
uv --version
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
确认 Node.js/npm 满足上面的版本范围。Apple Silicon 和 Intel Mac 都应使用本机
|
||||
架构的 Homebrew,不要在 Rosetta shell 与原生 shell 之间混用 Python/Node。
|
||||
|
||||
### 4.2 创建 Python 环境
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
uv python install 3.14
|
||||
uv venv --python 3.14
|
||||
source .venv/bin/activate
|
||||
python --version
|
||||
uv pip install -e .
|
||||
uv pip install "pytest>=8.3.0"
|
||||
```
|
||||
|
||||
如果 Apple Silicon 上安装 `paddleocr` 或其底层推理依赖失败,可以先安装不带 OCR
|
||||
的控制链路,用于截图、点击、滑动、输入、启动 App 和读取 UI tree:
|
||||
|
||||
```bash
|
||||
uv pip install -e . --no-deps
|
||||
uv pip install \
|
||||
"anthropic>=0.69.0" \
|
||||
"Appium-Python-Client>=5.1.1" \
|
||||
"fastapi>=0.115.0" \
|
||||
"httpx>=0.27.0" \
|
||||
"mcp>=1.27,<2" \
|
||||
"openai>=1.0.0" \
|
||||
"uvicorn[standard]>=0.30.0" \
|
||||
"pytest>=8.3.0"
|
||||
```
|
||||
|
||||
这种降级安装不包含 OCR。`find_text` 和基于 OCR 的 screen description 可能返回空
|
||||
结果,但 WDA 基础控制不受影响。不要为了兼容 PaddleOCR 擅自降到 Python 3.13,
|
||||
因为当前项目元数据明确要求 Python 3.14;如需降级,应先作为独立兼容性变更修改
|
||||
和验证 `pyproject.toml`。
|
||||
|
||||
### 4.3 安装 Appium 和 XCUITest Driver
|
||||
|
||||
```bash
|
||||
npm install -g appium
|
||||
appium --version
|
||||
appium driver install xcuitest
|
||||
appium driver list --installed
|
||||
appium driver doctor xcuitest
|
||||
```
|
||||
|
||||
`doctor` 中与 Xcode、签名、设备通信相关的错误必须先处理。仅有 warning 时,应根据
|
||||
是否使用对应功能判断;不要忽略 Xcode command line tools、WDA 或 real-device
|
||||
相关错误。
|
||||
|
||||
## 5. 配置 WebDriverAgent 签名
|
||||
|
||||
本项目会让 Appium 的 XCUITest Driver 创建和管理 WDA session。真机第一次运行时,
|
||||
必须给 WDA 提供签名信息。仓库的 `WDADriverConfig` 支持把未知的
|
||||
`connection_info` 字段作为 Appium extra capabilities 透传,因此可以直接传入:
|
||||
|
||||
- `xcodeOrgId`: Apple Team ID。
|
||||
- `xcodeSigningId`: 推荐使用 `Apple Development`。
|
||||
- `updatedWDABundleId`: 自己 Team 下唯一的 WDA bundle ID。
|
||||
|
||||
如果自动签名失败,可查出 XCUITest Driver 的安装路径:
|
||||
|
||||
```bash
|
||||
appium driver list --installed
|
||||
npm root -g
|
||||
```
|
||||
|
||||
在 Appium 的 XCUITest Driver 目录中找到并用 Xcode 打开
|
||||
`WebDriverAgent.xcodeproj`,然后:
|
||||
|
||||
1. 选择 `WebDriverAgentRunner` target。
|
||||
2. 在 `Signing & Capabilities` 中启用 Automatically manage signing。
|
||||
3. 选择自己的 Team。
|
||||
4. 将 bundle identifier 改成自己可签名的唯一值。
|
||||
5. 选择已连接的 iPhone,运行 `WebDriverAgentRunner` test target。
|
||||
6. 如果 iPhone 提示开发者不受信任,在设备的 VPN 与设备管理设置中信任该开发者。
|
||||
|
||||
Appium/XCUITest Driver 升级后,WDA 项目位置或内容可能变化;优先使用自动签名
|
||||
capabilities,只有自动签名无法工作时再手动打开工程。
|
||||
|
||||
## 6. 启动 Appium
|
||||
|
||||
在单独的 Terminal 窗口执行:
|
||||
|
||||
```bash
|
||||
appium --address 127.0.0.1 --port 4723
|
||||
```
|
||||
|
||||
保持该进程运行。另开一个 Terminal 检查状态:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:4723/status
|
||||
```
|
||||
|
||||
应返回包含 `ready` 或 Appium build 信息的 JSON。若 4723 端口被占用,可以换端口,
|
||||
但后续 `server_url` 必须同步修改。
|
||||
|
||||
## 7. 先做最小真机验证
|
||||
|
||||
回到仓库根目录,激活虚拟环境并设置环境变量:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
export APEX_WDA_SERVER_URL="http://127.0.0.1:4723"
|
||||
export APEX_WDA_UDID="$DEVICE_UDID"
|
||||
```
|
||||
|
||||
第一次建议使用下面的直接验证,它会显式传递签名 capabilities,并把截图写到
|
||||
`/tmp/device-agent-runtime.png`:
|
||||
|
||||
```bash
|
||||
python - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from driver.wda_driver import WDADriver, WDADriverConfig
|
||||
|
||||
driver = WDADriver(
|
||||
WDADriverConfig(
|
||||
server_url="http://127.0.0.1:4723",
|
||||
udid=os.environ["DEVICE_UDID"],
|
||||
device_name="iPhone",
|
||||
extra_capabilities={
|
||||
"xcodeOrgId": os.environ["APPLE_TEAM_ID"],
|
||||
"xcodeSigningId": "Apple Development",
|
||||
"updatedWDABundleId": os.environ["WDA_BUNDLE_ID"],
|
||||
},
|
||||
)
|
||||
)
|
||||
driver.connect()
|
||||
try:
|
||||
image = driver.screenshot()
|
||||
Path("/tmp/device-agent-runtime.png").write_bytes(image)
|
||||
print(f"screenshot ok: {len(image)} bytes")
|
||||
finally:
|
||||
driver.disconnect()
|
||||
PY
|
||||
```
|
||||
|
||||
验证通过后,可以运行仓库的实机 integration test:
|
||||
|
||||
```bash
|
||||
pytest -m integration tests/test_wda_integration.py -v
|
||||
```
|
||||
|
||||
该测试只从环境变量读取 server URL、UDID 和 device name,不传签名 capabilities,
|
||||
因此应在 WDA 已成功签名/安装后运行。
|
||||
|
||||
## 8. 启动可控制真机的 Runtime API
|
||||
|
||||
当前不能只运行 README 中的普通 `uvicorn ... --factory` 命令,因为它只会加载已登记
|
||||
设备,不会调用 `DeviceManager.connect()`。使用下面的启动方式,在同一进程中完成
|
||||
设备注册、WDA 连接和 REST API 启动:
|
||||
|
||||
```bash
|
||||
python - <<'PY'
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
|
||||
from api.rest import create_app
|
||||
from device.manager import DeviceManager
|
||||
from driver.registry import build_driver_factory
|
||||
|
||||
device_id = "iphone-1"
|
||||
connection_info = {
|
||||
"server_url": "http://127.0.0.1:4723",
|
||||
"device_name": "iPhone",
|
||||
"udid": os.environ["DEVICE_UDID"],
|
||||
"xcodeOrgId": os.environ["APPLE_TEAM_ID"],
|
||||
"xcodeSigningId": "Apple Development",
|
||||
"updatedWDABundleId": os.environ["WDA_BUNDLE_ID"],
|
||||
}
|
||||
|
||||
manager = DeviceManager()
|
||||
app = create_app(manager=manager)
|
||||
manager.register_device(
|
||||
device_id,
|
||||
build_driver_factory("wda", connection_info),
|
||||
name="Local iPhone",
|
||||
driver_type="wda",
|
||||
connection_info=connection_info,
|
||||
)
|
||||
manager.connect(device_id, max_retries=1)
|
||||
|
||||
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||
PY
|
||||
```
|
||||
|
||||
保持进程运行,在另一个 Terminal 验证:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8000/devices
|
||||
|
||||
curl -s -X POST http://127.0.0.1:8000/devices/iphone-1/tap \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"x": 100, "y": 200}'
|
||||
|
||||
curl -s -X POST http://127.0.0.1:8000/devices/iphone-1/launch \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"app_id": "com.apple.Preferences"}'
|
||||
```
|
||||
|
||||
点击坐标必须按当前设备屏幕坐标选择。先截图或使用 Appium Inspector 确认坐标,避免
|
||||
误操作。
|
||||
|
||||
如需启动 Web Console,保持 Runtime API 运行,再在第三个 Terminal 执行:
|
||||
|
||||
```bash
|
||||
cd console
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Console 默认连接 `http://127.0.0.1:8000`。已由上面启动脚本连接的
|
||||
`iphone-1` 会出现在设备列表中。不要在 Console 中重复登记同一台设备;当前登记
|
||||
操作只写入配置,不会自动 connect。
|
||||
|
||||
## 9. 多设备与端口
|
||||
|
||||
同时连接多台 iPhone 时,每台设备至少需要:
|
||||
|
||||
- 唯一的 Runtime `device_id`。
|
||||
- 唯一的 iPhone `udid`。
|
||||
- 独立的 `wda_local_port`,例如 8100、8101、8102。
|
||||
|
||||
示例 connection info:
|
||||
|
||||
```json
|
||||
{
|
||||
"server_url": "http://127.0.0.1:4723",
|
||||
"device_name": "iPhone",
|
||||
"udid": "<device-udid>",
|
||||
"wda_local_port": 8101,
|
||||
"xcodeOrgId": "<team-id>",
|
||||
"xcodeSigningId": "Apple Development",
|
||||
"updatedWDABundleId": "com.example.WebDriverAgentRunner"
|
||||
}
|
||||
```
|
||||
|
||||
如果使用预先启动的 WDA,可以通过 extra capability 传
|
||||
`webDriverAgentUrl`。常规单机使用优先让 Appium 管理 WDA,不要一开始就引入
|
||||
`iproxy` 或手工 WDA 生命周期。
|
||||
|
||||
## 10. 常见故障
|
||||
|
||||
### Appium 返回 `device offline`
|
||||
|
||||
项目会把 Appium session 创建阶段的大多数异常统一转换成 `device offline`。真正原因
|
||||
通常在 Appium Terminal 日志中。优先查看最早出现的 Xcode、签名、UDID、端口或 WDA
|
||||
错误,不要只看 Python 端的最终异常。
|
||||
|
||||
### 找不到设备
|
||||
|
||||
```bash
|
||||
xcrun devicectl list devices
|
||||
xcode-select -p
|
||||
xcodebuild -version
|
||||
```
|
||||
|
||||
确认手机已解锁、信任电脑、启用 Developer Mode,并能在 Xcode Devices and
|
||||
Simulators 中看到。
|
||||
|
||||
### WDA 构建或签名失败
|
||||
|
||||
- 确认 `xcodeOrgId` 是 Team ID,不是 Apple ID 邮箱。
|
||||
- 确认 `updatedWDABundleId` 唯一且属于可签名的 Team。
|
||||
- 确认使用 `Apple Development` signing identity。
|
||||
- 打开 WDA Xcode 工程检查 `WebDriverAgentRunner` target 的 Signing &
|
||||
Capabilities。
|
||||
- 免费 Personal Team 的 profile 过期后需要重新签名和安装。
|
||||
|
||||
### iOS/Xcode 升级后突然失败
|
||||
|
||||
```bash
|
||||
npm install -g appium
|
||||
appium driver list --updates
|
||||
appium driver update xcuitest
|
||||
appium driver doctor xcuitest
|
||||
```
|
||||
|
||||
升级前记录当前 Appium 和 XCUITest Driver 版本。生产或长期运行环境不要在没有回归
|
||||
验证时自动更新 Xcode、iOS、Appium 和 XCUITest Driver。
|
||||
|
||||
### 端口冲突
|
||||
|
||||
```bash
|
||||
lsof -nP -iTCP:4723 -sTCP:LISTEN
|
||||
lsof -nP -iTCP:8100 -sTCP:LISTEN
|
||||
```
|
||||
|
||||
Appium server 默认使用 4723;WDA 通常使用 8100。多设备必须为每台设备设置不同的
|
||||
`wda_local_port`。
|
||||
|
||||
### OCR 不工作但点击/截图正常
|
||||
|
||||
这是 Python/PaddleOCR 依赖问题,不是 WDA 问题。先用 screenshot、tap、swipe、
|
||||
input、launch 和 UI tree 验证基础控制,再单独处理 PaddleOCR/PaddlePaddle 的 macOS
|
||||
wheel 与 Apple Silicon 兼容性。
|
||||
|
||||
## 11. 完成检查表
|
||||
|
||||
- [ ] Xcode 能看到已解锁的 iPhone。
|
||||
- [ ] iPhone 已信任 Mac,并启用 Developer Mode。
|
||||
- [ ] `xcode-select -p` 指向完整 Xcode。
|
||||
- [ ] Node.js/npm 满足 Appium 3 要求。
|
||||
- [ ] `appium driver doctor xcuitest` 没有阻断性错误。
|
||||
- [ ] WDA 使用自己的 Team ID 和唯一 bundle ID 成功签名。
|
||||
- [ ] `curl http://127.0.0.1:4723/status` 返回正常。
|
||||
- [ ] 直接 Python 验证能生成 `/tmp/device-agent-runtime.png`。
|
||||
- [ ] 实机 integration test 通过。
|
||||
- [ ] Runtime API 返回 `iphone-1`,并能执行 screenshot/tap/launch。
|
||||
- [ ] 如需 OCR,另行确认 PaddleOCR 在当前 Mac/Python 架构下可运行。
|
||||
|
||||
## 12. 后续代码改进建议
|
||||
|
||||
为了让后续执行不再依赖内联 Python 启动脚本,建议另开变更实现:
|
||||
|
||||
- 为 Console/REST 增加显式 connect/disconnect endpoint。
|
||||
- 增加正式 CLI,例如 `device-runtime serve --device-config ...`。
|
||||
- 统一将遗留的 `APEX_WDA_*` 环境变量改名为 `DEVICE_RUNTIME_WDA_*`,并保留兼容期。
|
||||
- 将 PaddleOCR 改成 optional dependency,拆分基础控制与 OCR 安装路径。
|
||||
- 增加 macOS CI 的无真机 smoke test,以及受控环境中的真机验收脚本。
|
||||
Reference in New Issue
Block a user