用 xcodebuild 启动 WebDriverAgent (WDA) 深度解析
场景:基于 Appium 文档 wda-custom-server,深入理解如何用 xcodebuild 手动启动并定制 WebDriverAgent Server(用于 CI 长稳运行、并行测试、复用编译产物等场景)。
一、原理:xcodebuild 为什么能"启动"一个 HTTP Server
WebDriverAgent(WDA) 本质不是普通 App,而是一个 XCTest UI 测试 Target(WebDriverAgentRunner)。
xcodebuild test 设备上
│ ┌──────────────────────┐
├─ 编译 WDA.app ───────► │ WebDriverAgentRunner │
├─ 编译 Runner.xctest │ (XCTest 测试进程) │
└─ 启动测试 ─────────────►│ 起一个 HTTP Server │ :8100
│ 监听 /status /session│
└──────────────────────┘
▲
你的脚本 ── HTTP ──────────────────────┘
关键点:WDA 把"跑测试"劫持成"常驻起一个 REST 服务"。测试方法里是一个死循环 server,所以 xcodebuild test 不会结束、会一直挂着——它就是 server 进程本身。
这解释了几个设计:
BUILD_ID=dontKillMe:防止 Jenkins 杀掉后台测试进程
isRunning() 同时检查 进程存活 + HTTP /status 可达
二、核心命令逐参数解析
xcodebuild clean build test \
-project /path/to/WebDriverAgent/WebDriverAgent.xcodeproj \
-scheme WebDriverAgentRunner \
-destination 'id=<UDID>' \
-configuration Debug \
IPHONEOS_DEPLOYMENT_TARGET=<平台版本> \
> /var/log/appium/build.log 2>&1 &
| 参数 |
作用 |
注意点 |
clean build test |
清理→编译→跑测试,test 触发 server |
生产常去掉 clean,改 build-for-testing + test-without-building |
-project |
指向 .xcodeproj |
workspace 用 -workspace |
-scheme WebDriverAgentRunner |
必须是 Runner(含 UITest target),不是 Lib |
|
-destination 'id=<UDID>' |
指定设备 |
真机用 xcrun xctrace list devices,模拟器用 xcrun simctl list |
-configuration Debug |
构建配置 |
一般 Debug 即可 |
IPHONEOS_DEPLOYMENT_TARGET |
部署目标版本 |
匹配设备系统版本 |
> build.log 2>&1 & |
日志重定向 + 后台运行 |
& 必须,test 进程永不退出 |
三、真机 vs 模拟器:区别在"网络"和"签名"
模拟器
- 直接监听宿主机
localhost:8100,不需要 iproxy
- 不需要代码签名
真机
(1) 网络打通 —— iproxy 端口转发(通过 USB/usbmuxd)
iproxy 8100 8100 & # 老语法: iproxy <本地端口> <设备端口>
# 新版语法: iproxy 8100:8100
文档里 npm install -g iproxy 已过时。现在 iproxy 来自 libimobiledevice,推荐 brew install libimobiledevice。
(2) 代码签名 —— 真机必须签名
xcodebuild ... \
-allowProvisioningUpdates \
CODE_SIGN_IDENTITY="Apple Development" \
DEVELOPMENT_TEAM=<你的TeamID> \
PRODUCT_BUNDLE_IDENTIFIER=com.yourname.WebDriverAgentRunner
CI(Jenkins) 场景 keychain 默认锁定,构建前要解锁:
security list-keychains -s MyKeychain.keychain
security unlock-keychain -p <password> MyKeychain.keychain
security set-keychain-settings -t 3600 MyKeychain.keychain
四、验证 WDA 已启动
不能只看进程,要探活 HTTP 端点:
curl http://127.0.0.1:8100/status
返回示例:
{
"value": {
"ready": true,
"ios": { "ip": "..." },
"build": { "time": "..." }
}
}
之后告诉 Appium 跳过自管理 WDA:
capabilities.setCapability("webDriverAgentUrl", "http://127.0.0.1:8100");
五、现代化改进(文档较老)
1) 分离编译与运行(CI 提速关键)
# 一次性编译,缓存到 derivedData
xcodebuild build-for-testing \
-project WebDriverAgent.xcodeproj \
-scheme WebDriverAgentRunner \
-destination 'id=<UDID>' \
-derivedDataPath /tmp/wda_build
# 之后每次只跑,不重新编译
xcodebuild test-without-building \
-xctestrun /tmp/wda_build/Build/Products/*.xctestrun \
-destination 'id=<UDID>'
2) 自定义端口(环境变量,不改代码)
xcodebuild test ... USE_PORT=8101
3) iOS 17+ 真机:推荐用 go-ios / pymobiledevice3 替代 iproxy(需要 RemoteXPC tunnel/DVT)。
4) 何时手动 xcodebuild:长时间运行稳定性 + CI 并行 + 复用已编译产物;否则让 Appium 自动构建更省心。
六、常见踩坑
| 现象 |
原因 / 解决 |
xcodebuild test 立刻退出 |
scheme 选成 Lib 不是 Runner;或没加 & |
真机 Failed to install |
签名问题,加 -allowProvisioningUpdates |
curl /status 拒绝连接 |
真机没起 iproxy;端口被占;还在编译 |
| CI 上签名失败 |
keychain 锁定,先 unlock-keychain |
| 后台进程被 Jenkins 杀掉 |
设 BUILD_ID=dontKillMe |
| iOS 17+ 真机连不上 |
需要 RemoteXPC tunnel,改用 pymobiledevice3 / go-ios |
| 进程冻结重启失败 |
重启前先 kill 残留的 xcodebuild/iproxy |
总结
xcodebuild test 把 WDA 的 UITest target 当成一个永不退出的 HTTP server 进程跑起来;真机还需 iproxy 端口转发 + 代码签名;最后轮询 /status 确认就绪,再把 webDriverAgentUrl 交给 Appium。文档里那个 Java 类的全部复杂度,都是在解决 CI 长稳运行(日志、探活、防杀进程、keychain 解锁、失败重启)这一个目标。
用 xcodebuild 启动 WebDriverAgent (WDA) 深度解析
一、原理:xcodebuild 为什么能"启动"一个 HTTP Server
WebDriverAgent(WDA) 本质不是普通 App,而是一个 XCTest UI 测试 Target(
WebDriverAgentRunner)。关键点:WDA 把"跑测试"劫持成"常驻起一个 REST 服务"。测试方法里是一个死循环 server,所以
xcodebuild test不会结束、会一直挂着——它就是 server 进程本身。这解释了几个设计:
BUILD_ID=dontKillMe:防止 Jenkins 杀掉后台测试进程isRunning()同时检查 进程存活 + HTTP /status 可达二、核心命令逐参数解析
clean build testtest触发 serverclean,改build-for-testing+test-without-building-project.xcodeproj-workspace-scheme WebDriverAgentRunner-destination 'id=<UDID>'xcrun xctrace list devices,模拟器用xcrun simctl list-configuration DebugIPHONEOS_DEPLOYMENT_TARGET> build.log 2>&1 &&必须,test 进程永不退出三、真机 vs 模拟器:区别在"网络"和"签名"
模拟器
localhost:8100,不需要 iproxy真机
(1) 网络打通 —— iproxy 端口转发(通过 USB/usbmuxd)
(2) 代码签名 —— 真机必须签名
CI(Jenkins) 场景 keychain 默认锁定,构建前要解锁:
四、验证 WDA 已启动
不能只看进程,要探活 HTTP 端点:
返回示例:
{ "value": { "ready": true, "ios": { "ip": "..." }, "build": { "time": "..." } } }之后告诉 Appium 跳过自管理 WDA:
五、现代化改进(文档较老)
1) 分离编译与运行(CI 提速关键)
2) 自定义端口(环境变量,不改代码)
xcodebuild test ... USE_PORT=81013) iOS 17+ 真机:推荐用
go-ios/pymobiledevice3替代 iproxy(需要 RemoteXPC tunnel/DVT)。4) 何时手动 xcodebuild:长时间运行稳定性 + CI 并行 + 复用已编译产物;否则让 Appium 自动构建更省心。
六、常见踩坑
xcodebuild test立刻退出&Failed to install-allowProvisioningUpdatescurl /status拒绝连接unlock-keychainBUILD_ID=dontKillMepymobiledevice3/go-ioskill残留的 xcodebuild/iproxy总结
xcodebuild test把 WDA 的 UITest target 当成一个永不退出的 HTTP server 进程跑起来;真机还需 iproxy 端口转发 + 代码签名;最后轮询/status确认就绪,再把webDriverAgentUrl交给 Appium。文档里那个 Java 类的全部复杂度,都是在解决 CI 长稳运行(日志、探活、防杀进程、keychain 解锁、失败重启)这一个目标。