UI 自动化测试

在远程 Mac 上跑
iOS 模拟器与 XCUITest

iOS 的 UI 测试需要模拟器,而模拟器只能运行在 macOS 上。把测试放到一台远程 Mac 上,可以不占用自己的电脑,也方便接入 CI。这篇整理 simctl 常用命令、通过 SSH 无界面运行 XCUITest、并行测试、录屏截图,以及把它们串进 CI 流水线的做法。

三万主机 · 更新于

01 / PREPARE

准备机器:图形会话与权限

模拟器本身可以在没有 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 进行配置。

02 / SIMCTL

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,可以避免上一次运行留下的数据影响结果。

03 / RUN

在 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 在命令行读取。

04 / PARALLEL

并行测试与失败重试

Xcode 支持把测试类分配到多个模拟器克隆上并行执行:

xcodebuild test-without-building ... \
  -parallel-testing-enabled YES \
  -parallel-testing-worker-count 3 \
  -retry-tests-on-failure -test-iterations 2

并行的粒度是测试类,单个很大的测试类无法被拆开,必要时把用例拆到多个类中。worker 数量受内存限制,建议结合机器配置逐步调整,内存更大的机型可并行的模拟器更多,配置可在 机型对比 中查看。

05 / MEDIA

截图与录屏

排查偶发失败时,录屏比日志直观得多。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 把截图附加到结果包中。

06 / CI

接入 CI 流水线

把这台 Mac 注册为 GitHub Actions 自托管 runner(见 iOS 构建机配置指南),在 job 里依次执行:重置模拟器、build-for-testing、test-without-building,最后无论成败都上传 .xcresult。runner 服务需要运行在已登录的 CI 用户下,才能访问图形会话。

常驻的远程 Mac 保留模拟器和编译缓存,每次测试不必重新下载运行时。三万主机的机器为整机独享,不与他人共享 CPU 和内存,测试时长相对稳定。托管 runner 与自托管的费用对比见 GitHub macOS runner 费用。

FAQ

常见问题

通过 SSH 跑 UI 测试,必须开着屏幕共享吗?

不需要一直开着屏幕共享窗口,但测试用户最好有一个已登录的图形会话。做法是为 CI 用户开启自动登录,机器重启后自动进入桌面,SSH 或 runner 服务在这个用户下运行测试。

一台 Mac 同时开几个模拟器合适?

取决于内存和测试负载。每个模拟器都会占用数 GB 内存和一定 CPU,建议从 2 个并行开始,观察活动监视器中的内存压力和测试时长,再逐步增加。机器内存越大,可并行的数量越多。

UI 测试偶发失败怎么排查?

先打开 result bundle 看失败时的截图和日志;再给不稳定的用例录屏复现。常见原因是等待不足(应使用 waitForExistence 而非固定 sleep)、动画干扰、上一次测试遗留的数据。可以用 -retry-tests-on-failure 临时缓解,但根因仍要修复。

微信
微信客服二维码扫码加微信客服手机上长按二维码识别
Telegram 客服