iOS の画面回転で入力状態を失わないためのテスト

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 で接続できても、テストプロセスがシミュレータのウィンドウを操作できるとは限りません。

入力から縦画面への復帰までを 1 本の UI テストにする

次のテストをプロジェクトの UI Test target に追加します。この例は、起動時に編集画面が直接表示され、上記 3 つの識別子がアプリに用意されていることを前提とします。編集画面への移動が必要なら、前回の実行で残った画面に頼らず、入力前に明示的に移動してください。

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 を追加しないでください。画面に実際の遷移処理があるなら、特定の要素が検証可能な状態になるまで待ちます。ボタンがキーボードに隠れたままなら、長く待っても押せるようにはなりません。特定の画面サイズだけで起きる問題には、画面サイズの異なる 2 台目のシミュレータを追加します。すべてのデバイスを 1 本のテストに詰め込む必要はありません。まず固定した対象で往復する 1 本の経路を安定させ、それから対象範囲を広げることで、失敗の記録を比較しやすくなります。

よくある質問

シミュレータだけで回転後のレイアウトを検証できますか?

表示状態と操作可能性は検証できます。センサーの挙動や実際の持ちやすさは実機でも確認してください。

ボタンが存在するのにタップできない原因は?

キーボードや重なったビューによる遮蔽を調べ、次にセーフエリアの制約と画面回転時の状態再生成を確認します。

専用物理ノードで試す

次のタスクに合わせてクラウドMacを用意

モデルのメモリ要件やビルド負荷に合わせてMac miniをお選びください。3つの構成を、日・週・月・四半期単位でご利用いただけます。ノードの空き状況は管理画面にリアルタイムで表示されます。

クラウドMacを借りる