WWDC Quick Look 💓 By SwiftGGTeam
Handle interruptions and alerts in UI tests

Handle interruptions and alerts in UI tests

元の動画を見る

ハイライト

XCTest は、Xcode 12 でバナー通知を暗黙的に処理し、addUIInterruptionMonitorそしてXCUIApplication.resetAuthorizationStatus(for:)これにより、UI テストが非決定的な中断、予想されるアラート、プライバシー許可の初期状態をそれぞれ処理できるようになります。

主要内容

UI テストには非常に微妙な失敗への経路があります。テストで要素をクリックしようとすると、別の UI がそれを一時的にブロックします。レシピ一覧にお知らせバナーが表示されます。現在のフォーカス セルをカバーしていない場合は、テストを続行できます。詳細ページに入ると、戻るボタンにも同じバナーが表示されます。テストするには、最初にこのパネルを処理してから、「戻る」をクリックし続ける必要があります。

Apple はこのシナリオに明確な境界線を設けています。UI 中断とは、予期せず非決定的に表示され、テスト ターゲット要素をブロックする UI です。バナー、アラート、ダイアログ、ウィンドウはすべて中断となる可能性があります。レシピの削除後に表示される確認ボックスなど、テスト アクションによって直接トリガーされるアラートは、このカテゴリに属しません。テスト プロセスの一部として、クエリを実行し、待機し、アサートする必要があります。

XCTestが提供addUIInterruptionMonitorレジスター閉鎖。複数のハンドラーはスタックを形成し、後から登録されたハンドラーが最初に実行されます。処理が成功すると、ハンドラーは戻ります。true、テストは続行されます。処理できない場合は返すfalse, XCTest は引き続き次のハン​​ドラーを検索します。最下位レベルには、XCTest に付属する暗黙的なハンドラーもあります。iOS はキャンセル ボタンまたはデフォルト ボタンによる割り込みを処理し、Xcode 12 以降ではバナー通知も処理できます。 macOS は、一般的な許可ダイアログ ボックスと Bluetooth セットアップ アシスタントを処理します。

プライバシーの許可は別の種類の問題を引き起こします。システムは、写真、マイク、位置情報などの保護されたリソースのユーザーの選択を記憶します。初めてテストを実行した後、デバイスはクリーンではなくなりました。 Xcode 11.4、iOS および tvOS 13.4、macOS 10.15.4 以降、XCUIApplication保護されたリソースの認証ステータスをリセットして、初回の認証プロセスを反復可能なテスト パスにすることができます。このセッションでは、開発者に、これらのアラートはシステムから送信されるものであり、クエリ レベルはシステム UI に従って作成する必要があることを思い出させます。認証ステータスをリセットすると、アプリのプロセスが終了する場合もあります。

詳細

最初に非決定的割り込みのハンドラーを登録します

(05:04) テストするには、まず割り込みモニターを登録します。このハンドラーは、アラートがターゲット要素をブロックしていることを XCTest が検出したときに呼び出されます。最小限のスケルトンは中断要素のみを取得し、戻り値を使用して XCTest にそれが処理されたかどうかを伝えます。

addUIInterruptionMonitor(withDescription: "Handle recipe update failures") { element -> Bool in
    // TODO: Use this handler to retry the update action
    return false
}

キーポイント:

  • withDescription障害診断時にハンドラがどのような種類の割り込みを処理するかを知ることができるように、ハンドラに名前を付けます。
  • 閉鎖elementこれは、現在のテスト アクションをブロックする UI 要素です。
  • 戻るfalse現在のハンドラーが処理されず、XCTest がスタック内の次のハンドラーの呼び出しを続けることを示します。
  • ハンドラーはテストの終了時に自動的に削除されますが、テスト中に手動で削除することもできます。
  • XCTest に付属する暗黙的なハンドラーはスタックの一番下にあります。カスタムハンドラーが戻りますfalseその後初めて、「キャンセル」、デフォルトのボタン、または Xcode 12 のバナー通知が試行されます。

(05:16) レシピ詳細テスト自体は通常の UI テストです。アプリを起動し、「Pancakes」をクリックし、タイトル、写真、材料、手順をアサートし、「Return」をクリックします。問題は、サーバーがレシピの更新に失敗すると、アプリがアラートをポップアップ表示し、テストの次のステップでアクセスする要素をブロックしてしまうことです。

func testRecipeDetailsNavigation() throws {

    let app = XCUIApplication()
    app.launch()

    let pancakeRecipe = app.cells.staticTexts["Fluffy Pancakes"].firstMatch
    pancakeRecipe.tap()

    // In the detail view
    let detailTitle = app.navigationBars.staticTexts["Fluffy Pancakes"].firstMatch
    XCTAssert(detailTitle.waitForExistence(timeout: 30))

    let expectedImage = app.images["Fluffy Pancakes Image"].firstMatch
    XCTAssert(expectedImage.exists)

    let ingredientsTitle = app.staticTexts["Ingredients Title"].firstMatch
    XCTAssert(ingredientsTitle.exists)

    let ingredientsContent = app.textViews["Ingredients Content"].firstMatch
    XCTAssert(ingredientsContent.exists)
    XCTAssert((ingredientsContent.value as! String).count > 0)

    let instructionsTitle = app.staticTexts["Instructions Title"].firstMatch
    XCTAssert(instructionsTitle.exists)

    let instructionsTextView = app.textViews["Instructions Content"].firstMatch
    XCTAssert(instructionsTextView.exists)
    XCTAssert((instructionsTextView.value as! String).count > 0)

    // Make sure we received the latest instructions:
    let expectedInstructions = """
                               1. Mix the flour, sugar, baking powder and salt.

                               2. Pour the milk, melted butter and egg into a well in the center and mix.

                               3. Heat a frying pan (don't forget to add a little bit of oil, grandson!), and fry until they're golden.

                               4. Optionally add maple syrup and/or fruits (they're healthy!).
                               """
    XCTAssertEqual((instructionsTextView.value as! String), expectedInstructions)

    // Go back to the list of recipes
    let backButton = app.navigationBars.buttons["Grandma's Recipes"].firstMatch
    backButton.tap()

    XCTAssert(pancakeRecipe.waitForExistence(timeout: 30))
}

キーポイント:

  • waitForExistence(timeout:)ページが表示される前に要素を読み取ることを避けるために、ナビゲーション結果で使用されます。 -firstMatchクエリが複数の候補の間で変動しないように、最初に一致した要素を明示的に使用します。
  • テストでは完全な指示テキストをチェックして、最新のレシピ データが取得されていることを確認します。
  • その後戻るボタンをクリックしたときに、アラートまたはバナーがボタンを覆っている場合、割り込みハンドラーが介入する可能性があります。

本当に必要なボタンをハンドラーに処理させます

(06:20) XCTest の暗黙的ハンドラーは、「キャンセル」またはデフォルトのボタンをクリックできます。ここでのビジネス目標は最新のレシピを取得することであるため、テストは再試行を示す必要があります。ハンドラーは、要素がアラートであるかどうかを確認し、[再試行] ボタンを探す必要があります。

addUIInterruptionMonitor(withDescription: "Handle recipe update failures") { element -> Bool in
    let retryButton = element.buttons["Retry"].firstMatch
    if element.elementType == .alert && retryButton.exists {
        retryButton.tap()
        return true
    } else {
        return false
    }
}

キーポイント:

  • element.elementType == .alertハンドラーをアラート シナリオに限定します。 -element.buttons["Retry"].firstMatchグローバル アプリ クエリよりも狭いスコープで、中断要素内からボタンを検索します。 -retryButton.tap()ビジネスに適した回復アクションを実行します。
  • 戻るtrueその後、XCTest は元のテスト アクションを継続します。

テスト アサーションに入るアラートが予期されています

(07:37) レシピを削除した後に表示される確認ボックスは、予期されるアラートです。これはテスト アクションによって直接トリガーされ、予測可能な時間に発生します。 Apple は、通常のクエリと待機 API でこれを処理し、アラートのコピーと確認アクションをアサーションに参加させることを推奨しています。

func testDeleteRecipe() throws {
    let breadCell = cell(recipeName: "Banana Bread")
    deleteCell(breadCell)

    let alert = app.alerts["Delete Recipe"].firstMatch
    let alertExists = alert.waitForExistence(timeout: 30)
    XCTAssert(alertExists, "Expected alert to show up")

    let description = """
                      Are you sure you want to \
                      delete this recipe?
                      """

    let alertDescription = alert.staticTexts[description]
    XCTAssert(alertDescription.exists)

    alert.buttons["Delete"].tap()
    XCTAssertFalse(breadCell.exists)
}

キーポイント:

  • deleteCell(breadCell)これは、アラートをトリガーするテスト アクションです。アラートの経路は制御可能です。 -app.alerts["Delete Recipe"].firstMatchターゲットのアラートを直接クエリします。 -waitForExistence(timeout:)待機メッセージと失敗メッセージをテストに残します。
  • 最初に説明のコピーをアサートし、次に「削除」をクリックして、最後に元のセルが消えることを確認します。

保護されたリソースの認可ステータスをリセットします

(10:29) システムは、写真、マイク、カメラ、位置情報などの保護されたリソースに対する認証の選択を保存します。初めて権限をリクエストするプロセスをテストするには、アプリを起動する前に、リソースに対するアプリの承認ステータスをリセットする必要があります。

func testAddingPhotosFirstTime() throws {
    let app = XCUIApplication()
    app.resetAuthorizationStatus(for: .photos)

    app.launch()

    // Test code…
}

キーポイント:

  • resetAuthorizationStatus(for: .photos)写真の許可が要求されなかったかのようにアプリが動作するようにします。
  • 写真の認証ステータスをリセットするとアプリのプロセスが終了する可能性があるため、この例はリセット後に呼び出されますlaunch()
  • 権限アラートはシステムから送信されるため、クエリはシステム アラート レベルに従って作成する必要があります。
  • この API は、許可、拒否、および初回起動プロセスを反復可能な UI テストに分割するのに適しています。
  • セッションによって明示的に言及されるクロスプラットフォーム リソースには、連絡先、カレンダー、写真、マイク、カメラ、位置情報が含まれます。 iOS では、Xcode 12 と iOS 14 のキーボード、ネットワーク アクセス、Bluetooth、ヘルスケアもカバーしています。

重要ポイント

  • 対処方法: ネットワーク更新に失敗した UI テストに再試行ハンドラーを追加します。 実行する価値がある理由: 10220 のデモでは、サーバーに時折障害が発生するとアラートがポップアップ表示されます。デフォルトの「キャンセル」では、テストで古いデータが引き続き使用されます。 開始方法: テストセットアップに登録しますaddUIInterruptionMonitor、でのみelementType == .alertそして存在するRetryボタンをクリックして戻ってくださいtrue

  • 対処方法: 削除の確認や編集の中止など、予期されるアラートを明示的なアサーションとして記述します。 実行する価値がある理由: アラートがユーザー フローの一部であることを見越して、テストではタイトル、説明テキスト、確認ボタンをチェックする必要があります。 開始方法: を使用しますapp.alerts["Alert Title"].firstMatch問い合わせる、協力するwaitForExistence(timeout:)待ってからアサートするstaticTextsそして最終的な UI の状態。

  • やるべきこと: 写真、マイク、位置情報の許可に関する最初の認可テスト マトリックスを作成します。 実行する価値がある理由: システムは承認の決定を保存するため、状態をリセットせずに最初のリクエスト パスを繰り返し上書きすることが困難になります。 開始方法: 各権限テストの開始時に呼び出されますapp.resetAuthorizationStatus(for:)、その後、再度launch()、システム アラートはシステム UI レベルのクエリを使用し、許可ブランチと拒否ブランチをテストします。

  • 対処方法: 割り込みハンドラを一般層と業務層に分割します。 実行する価値がある理由: XCTest は複数のハンドラーを後入れ先出しの順序で呼び出します。ビジネス テストでは、最初に特定のアラートを処理し、次にハンドラーを使用してキャンセル/デフォルト クラスの割り込みを処理できます。 開始方法: 非決定的アラートの種類ごとに説明を含むハンドラーを作成し、処理が成功した場合にのみ返します。true、認識されない要素は一律に返されますfalse

関連セッション

コメント

GitHub Issues · utterances