在远程 Mac 上跑
iOS 模拟器与 XCUITest
iOS 的 UI 测试需要模拟器,而模拟器只能运行在 macOS 上。把测试放到一台远程 Mac 上,可以不占用自己的电脑,也方便接入 CI。这篇整理 simctl 常用命令、通过 SSH 无界面运行 XCUITest、并行测试、录屏截图,以及把它们串进 CI 流水线的做法。
准备机器:图形会话与权限
模拟器本身可以在没有 Simulator.app 窗口的情况下启动,xcodebuild test 会自动启动目标设备。但 UI 测试需要与 WindowServer 交互,最稳妥的做法是让测试用户保持登录状态:在「系统设置 → 用户与群组」中为 CI 用户开启自动登录,并关闭自动休眠。
sudo pmset -a sleep 0 disksleep 0
# 允许开发工具调试进程,避免测试时弹出授权
sudo DevToolsSecurity -enable
# 安装模拟器运行时
xcodebuild -downloadPlatform iOS如果通过 SSH 运行时遇到自动化模式(Automation Mode)的授权提示导致测试卡住,可以在屏幕共享里手动运行一次测试完成授权,或按 Apple 文档使用 automationmodetool 进行配置。
simctl 常用命令
xcrun simctl 是管理模拟器的命令行工具,下面是最常用的一组:
xcrun simctl list devices available # 列出可用设备
xcrun simctl list runtimes # 列出已安装运行时
xcrun simctl create "CI-iPhone" "iPhone 16" # 新建设备,名称可自定
xcrun simctl boot "CI-iPhone"
xcrun simctl bootstatus "CI-iPhone" -b # 等待启动完成
xcrun simctl install booted build/MyApp.app
xcrun simctl launch booted com.example.myapp
xcrun simctl openurl booted "myapp://login"
xcrun simctl privacy booted grant photos com.example.myapp
xcrun simctl shutdown all
xcrun simctl erase all # 清空所有设备数据
xcrun simctl delete unavailable # 删除已失效设备设备类型名称和运行时以 list 的输出为准。为 CI 单独创建命名设备,测试前 erase,可以避免上一次运行留下的数据影响结果。
在 SSH 中运行 XCUITest
推荐把构建和测试拆开:先 build-for-testing 产出测试包,再 test-without-building 执行,重试或换设备时不必重新编译。
xcodebuild build-for-testing \
-workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=CI-iPhone' \
-derivedDataPath build/dd
xcodebuild test-without-building \
-workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=CI-iPhone' \
-derivedDataPath build/dd \
-only-testing:MyAppUITests \
-resultBundlePath build/UITests.xcresult-only-testing 可以精确到类或方法(如 MyAppUITests/LoginTests/testLogin),-skip-testing 用于排除。结果包 .xcresult 包含日志、失败截图和附件,可以拷回本地用 Xcode 打开,或用 xcrun xcresulttool 在命令行读取。
并行测试与失败重试
Xcode 支持把测试类分配到多个模拟器克隆上并行执行:
xcodebuild test-without-building ... \
-parallel-testing-enabled YES \
-parallel-testing-worker-count 3 \
-retry-tests-on-failure -test-iterations 2并行的粒度是测试类,单个很大的测试类无法被拆开,必要时把用例拆到多个类中。worker 数量受内存限制,建议结合机器配置逐步调整,内存更大的机型可并行的模拟器更多,配置可在 机型对比 中查看。
截图与录屏
排查偶发失败时,录屏比日志直观得多。simctl 可以直接对运行中的模拟器截图或录像,录像按 Ctrl+C 结束:
xcrun simctl io booted screenshot shot.png
xcrun simctl io booted recordVideo --codec=h264 run.mov
# 统一状态栏,便于截图对比或制作商店截图
xcrun simctl status_bar booted override --time "9:41" --batteryLevel 100在 CI 中可以把录屏放到后台进程,测试结束后发送 SIGINT 让它正常写完文件,再作为制品上传。测试代码内也可以用 XCTAttachment 把截图附加到结果包中。
接入 CI 流水线
把这台 Mac 注册为 GitHub Actions 自托管 runner(见 iOS 构建机配置指南),在 job 里依次执行:重置模拟器、build-for-testing、test-without-building,最后无论成败都上传 .xcresult。runner 服务需要运行在已登录的 CI 用户下,才能访问图形会话。
常驻的远程 Mac 保留模拟器和编译缓存,每次测试不必重新下载运行时。三万主机的机器为整机独享,不与他人共享 CPU 和内存,测试时长相对稳定。托管 runner 与自托管的费用对比见 GitHub macOS runner 费用。
常见问题
通过 SSH 跑 UI 测试,必须开着屏幕共享吗?
不需要一直开着屏幕共享窗口,但测试用户最好有一个已登录的图形会话。做法是为 CI 用户开启自动登录,机器重启后自动进入桌面,SSH 或 runner 服务在这个用户下运行测试。
一台 Mac 同时开几个模拟器合适?
取决于内存和测试负载。每个模拟器都会占用数 GB 内存和一定 CPU,建议从 2 个并行开始,观察活动监视器中的内存压力和测试时长,再逐步增加。机器内存越大,可并行的数量越多。
UI 测试偶发失败怎么排查?
先打开 result bundle 看失败时的截图和日志;再给不稳定的用例录屏复现。常见原因是等待不足(应使用 waitForExistence 而非固定 sleep)、动画干扰、上一次测试遗留的数据。可以用 -retry-tests-on-failure 临时缓解,但根因仍要修复。
扫码加微信客服手机上长按二维码识别