在云端 Mac 上测试 iOS 屏幕旋转:保住输入状态与可点击区域

在云端 Mac 上测试 iOS 屏幕旋转:保住输入状态与可点击区域

编辑表单在竖屏输入正常,切到横屏后却清空了草稿;或者保存按钮仍在视图树里,却被键盘盖住。这类问题在云端 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。三档机型支持按天、周、月、季租用;节点可用状态以控制台实时返回为准。

立即租用云端 Mac