測試 iOS 螢幕旋轉,同時保住草稿與操作區

測試 iOS 螢幕旋轉,同時保住草稿與操作區

編輯表單在直向時可以正常輸入,切到橫向後草稿卻被清空;或者儲存按鈕仍在檢視樹中,卻被鍵盤擋住。只在雲端 Mac 上執行一次建置,無法發現這類問題。UI 測試必須實際走過「輸入—旋轉—儲存—轉回」的操作。以下以具有草稿編輯頁面的 iOS App 為例,建立可重複執行的檢查流程。

先定義旋轉後不可改變的狀態

測試前,分別為編輯欄位、儲存按鈕及儲存結果設定穩定的 accessibilityIdentifier:draftEditor、saveDraft、savedStatus。這些識別碼屬於 App 程式碼,不應依賴在地化後的按鈕文字來尋找元素。測試約定如下:旋轉不會清空尚未儲存的輸入;橫向時儲存按鈕可以點擊;儲存後會出現結果提示;轉回直向後,提示仍然可見。

exists 只能證明元素仍在輔助使用樹中,不能證明使用者點得到。旋轉測試也應檢查 isHittable,並實際點擊一次。

先確認 App 支援要測試的方向。如果專案的裝置方向設定停用了橫向,測試就不應把無法旋轉誤判為版面配置問題。平板與手機也不要共用未經核對的預期結果:分欄版面、鍵盤尺寸和安全區域都可能不同。

固定模擬器與執行環境

在 XcodeOps 雲端 Mac 的圖形工作階段中,先執行 xcrun simctl list devices available,選擇一台位於已安裝執行階段下的手機模擬器,並記下其 UDID。CI 不要只憑裝置名稱比對:同名裝置可能屬於不同的執行階段。將 UDID 以 SIMULATOR_UDID 環境變數傳入工作,啟動前再次確認它仍列在可用裝置中。

同一輪基準測試應固定 Xcode 的選用路徑、模擬器 UDID、App 測試資料及測試入口。將 xcodebuild -version 和 xcrun simctl list devices available 的輸出隨工作一併記錄即可;不要把另一台模擬器的結果混入這組基準。UI 測試也需要可用的圖形登入工作階段:SSH 連得上,不代表測試程序一定能操作模擬器視窗。

撰寫涵蓋完整往返流程的 UI 測試

將以下測試加入專案的 UI Test target。範例假設 App 啟動後可直接看到編輯頁面,且已提供上述三個識別碼。如果必須先導覽至編輯頁面,應在輸入前明確完成導覽,而不是依賴上次執行留下的畫面。

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 是為了盡可能讓下一項測試從直向開始;它不能取代每項測試自行設定初始狀態。如果 App 在旋轉時重建編輯檢視,原本的元素查詢通常仍能重新解析,但不要快取透過查詢取得的座標或 frame。對於非同步儲存,光是等待提示出現還不夠:最好讓 App 提供明確的成功狀態,並斷言其值,而不是把「儲存中」視為已完成。

從命令列執行並保留佐證

在專案目錄執行測試時,先設定專案路徑、共用 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