编辑表单在竖屏输入正常,切到横屏后却清空了草稿;或者保存按钮仍在视图树里,却被键盘盖住。这类问题在云端 Mac 上跑一次构建无法发现,需要让 UI 测试真正经历「输入—旋转—保存—转回」的操作。下面以一个带草稿编辑页的 iOS 应用为例,建立可重复的检查路径。
先定义旋转后不能变的状态
测试前给编辑框、保存按钮和保存结果分别设置稳定的 accessibilityIdentifier:draftEditor、saveDraft、savedStatus。这些标识属于应用代码,不应靠本地化后的按钮文案定位元素。测试约定:旋转不清空尚未保存的输入;横屏时保存按钮可点;保存后出现结果提示;回到竖屏,提示仍可见。
exists只说明元素还在可访问性树中,不说明用户摸得到。旋转测试应同时检查isHittable,并实际执行一次点击。
先确认应用支持待测方向。如果项目在设备方向配置中禁用了横屏,测试不能把无法旋转误判成布局故障。平板和手机也不要共用未经核对的预期:分栏布局、键盘尺寸及安全区域可能不同。
固定模拟器与执行环境
在 XcodeOps 云端 Mac 的图形会话中,先运行 xcrun simctl list devices available,选择已安装运行时下的一台手机模拟器,记录其 UDID。CI 不要只按设备名称匹配:同名设备可能隶属不同运行时。将 UDID 作为 SIMULATOR_UDID 环境变量传入任务,启动前再检查它是否仍列在可用设备中。
同一轮基线应固定 Xcode 选择路径、模拟器 UDID、应用测试数据和测试入口。xcodebuild -version 与 xcrun simctl list devices available 的输出随任务记录即可;不要把另一台模拟器的结果混进这条基线。UI 测试还依赖可用的图形登录会话,SSH 连通并不等于测试进程一定能操作模拟器窗口。
写一条覆盖完整往返的 UI 测试
把下面的测试加入工程的 UI Test target。示例假定编辑页启动时直接可见,且应用已提供上述三个标识;如果需要先导航到编辑页,应在输入前显式完成导航,而不是依赖上次运行留下的界面。
import XCTest
final class RotationTests: XCTestCase {
func testDraftSurvivesRotation() {
let device = XCUIDevice.shared
device.orientation = .portrait
defer { device.orientation = .portrait }
let app = XCUIApplication()
app.launch()
let editor = app.textViews["draftEditor"]
XCTAssertTrue(editor.waitForExistence(timeout: 10))
editor.tap()
editor.typeText("Rotation check 42")
device.orientation = .landscapeLeft
XCTAssertTrue(
NSPredicate(format: "value == %@", "Rotation check 42")
.evaluate(with: editor)
)
let save = app.buttons["saveDraft"]
XCTAssertTrue(save.waitForExistence(timeout: 10))
XCTAssertTrue(save.isHittable)
save.tap()
let status = app.staticTexts["savedStatus"]
XCTAssertTrue(status.waitForExistence(timeout: 10))
device.orientation = .portrait
XCTAssertTrue(status.waitForExistence(timeout: 10))
}
}
这里的 defer 是为了尽量把下一项测试带回竖屏;它不能替代每条测试自己的初始状态设置。若应用在旋转时重建编辑视图,旧的元素查询通常还能重新解析,但不要缓存由查询得到的坐标或 frame。对异步保存,单纯等待提示出现还不够:最好让应用提供明确的成功状态,并断言其值,而不是把「正在保存」当作完成。
命令行运行与留证
在项目目录执行测试时,先设置工程路径、共享 Scheme 与先前记录的 UDID。下面的命令会在必要变量为空时直接退出,并把测试结果写入新的结果包目录;重复运行前应换一个目录名。
: "${PROJECT:?Set the Xcode project path}"
: "${SCHEME:?Set a shared test scheme}"
: "${SIMULATOR_UDID:?Set an available simulator UDID}"
xcodebuild test \
-project "$PROJECT" \
-scheme "$SCHEME" \
-destination "platform=iOS Simulator,id=$SIMULATOR_UDID" \
-only-testing:AppUITests/RotationTests/testDraftSurvivesRotation \
-resultBundlePath "$PWD/rotation-run.xcresult"
AppUITests 要与工程里的 UI Test target 名称一致。测试失败时保留 .xcresult,结合失败步骤、截图和测试日志判断是输入被重置,还是按钮被遮挡;不要只看终端最后一行。若需要脚本读取结果,先用 xcrun xcresulttool help 核对当前 Xcode 提供的子命令,再选对应的结构化输出。
按现象排查,不先加等待
| 现象 | 优先检查 | 修复方向 |
|---|---|---|
| 横屏后草稿清空 | 旋转时视图是否重建、状态由谁持有 | 将编辑状态放在能跨视图重建存活的位置 |
| 按钮存在但不可点 | 键盘、弹层、安全区域与滚动容器 | 修正约束或提供明确的收起键盘路径 |
| 只在 CI 失败 | 图形会话、模拟器运行时、测试数据 | 固定执行目标并保证任务起点一致 |
| 转回竖屏后提示消失 | 提示是否只是短暂动画 | 改查持久的保存结果或重新读取数据 |
不要一见横屏失败就增加固定 sleep。如果界面确实有过渡过程,等待具体元素达到可验证状态;如果按钮永远被键盘盖住,等更久也不会变得可点。对于特定尺寸才出现的问题,再增加第二台不同屏幕尺寸的模拟器,而不是一次把所有设备都塞进单条测试。先让一条固定目标的往返路径稳定,再扩充覆盖面,失败记录才有比较价值。
常见问题
模拟器能验证屏幕旋转后的布局吗?
能验证旋转后的视图状态、元素位置和可点击性;传感器行为与具体设备体验仍需在实体设备上复核。
为什么旋转后元素存在,测试仍然失败?
元素存在不代表可点击。先检查键盘或遮挡层是否覆盖控件,再检查安全区域约束和旋转期间的状态重建。
为下一项任务配置云端 Mac
按模型内存或构建负载选择 Mac mini。三档机型支持按天、周、月、季租用;节点可用状态以控制台实时返回为准。