UI Automator で自動テストを作成する

UI Automator テスト フレームワークには、ユーザーアプリとシステムアプリを操作する UI テストを作成するための API のセットが用意されています。

最新の UI Automator テストの概要

UI Automator 2.4 では、Android の UI テストの作成を簡素化する、Kotlin に適した合理的なドメイン固有言語(DSL)が導入されています。この新しい API サーフェスは、述語ベースの要素検索とアプリの状態の明示的な制御に重点を置いています。このツールを使用して、保守性と信頼性の高い自動テストを作成します。

UI Automator を使用すると、アプリのプロセス外からアプリをテストできます。これにより、縮小化を適用したリリース バージョンをテストできます。UI Automator は、マクロベンチマーク テストの作成にも役立ちます。

最新のアプローチの主な特徴は次のとおりです。

  • よりクリーンで表現力豊かなテストコードのための専用の uiAutomator テストスコープ。
  • 明確な述語を使用して UI 要素を見つけるための onElementonElementsonElementOrNull などのメソッド。
  • 条件付き要素 onElement*(timeoutMs: Long = 10000) の組み込み待機メカニズム
  • waitForStablewaitForAppToBeVisible などの明示的なアプリの状態管理。
  • マルチウィンドウ テスト シナリオ用のアクセシビリティ ウィンドウ ノードとの直接的なやり取り。
  • ビジュアル テストとデバッグ用の組み込みのスクリーンショット機能と ResultsReporter

プロジェクトをセットアップする

最新の UI Automator API の使用を開始するには、プロジェクトの build.gradle.kts ファイルを更新して、最新の依存関係を含めます。

Kotlin

dependencies {
  ...
  androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}

Groovy

dependencies {
  ...
  androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}

Core API のコンセプト

以降のセクションでは、最新の UI Automator API のコア コンセプトについて説明します。

uiAutomator テストのスコープ

uiAutomator { ... } ブロック内のすべての新しい UI Automator API にアクセスします。この関数は、テスト オペレーションに簡潔で型安全な環境を提供する UiAutomatorTestScope を作成します。

uiAutomator {
  // All your UI Automator actions go here
  startApp("com.example.targetapp")
  onElement { textAsString() == "Hello, World!" }.click()
}

UI 要素を見つける

述語を使用して UI Automator API を使用し、UI 要素を見つけます。これらの述語を使用すると、テキスト、選択状態、フォーカス状態、コンテンツの説明などのプロパティの条件を定義できます。

  • onElement { predicate }: デフォルトのタイムアウト内で述語に一致する最初の UI 要素を返します。一致する要素が見つからない場合、関数は例外をスローします。

    // Find a button with the text "Submit" and click it
    onElement { textAsString() == "Submit" }.click()
    
    // Find a UI element by its resource ID
    onElement { id == "my_button_id" }.click()
    
    // Allow a permission request
    watchFor(PermissionDialog) {
      clickAllow()
    }
    
  • onElementOrNull { predicate }: onElement と同様ですが、タイムアウト内に一致する要素が見つからない場合は null を返します。例外はスローされません。このメソッドは、省略可能な要素に使用します。

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }: 少なくとも 1 つの UI 要素が指定された述語と一致するまで待機し、一致するすべての UI 要素のリストを返します。

    // Get all items in a list Ui element
    val listItems = onElements { className == "android.widget.TextView" && isClickable }
    listItems.forEach { it.click() }
    

onElement 呼び出しを使用する際のヒントを以下に示します。

  • ネストされた要素の onElement 呼び出しをチェーンする: onElement 呼び出しをチェーンして、親子階層に従って他の要素内の要素を見つけることができます。

    // Find a parent Ui element with ID "first", then its child with ID "second",
    // then its grandchild with ID "third", and click it.
    onElement { id == "first" }
      .onElement { id == "second" }
      .onElement { id == "third" }
      .click()
    
  • ミリ秒を表す値を渡して、onElement* 関数のタイムアウトを指定します。

    // Find a Ui element with a zero timeout (instant check)
    onElement(0) { id == "something" }.click()
    
    // Find a Ui element with a custom timeout of 10 seconds
    onElement(10_000) { textAsString() == "Long loading text" }.click()
    

UI 要素を操作する

クリックをシミュレートしたり、編集可能なフィールドにテキストを設定したりして、UI 要素を操作します。

// Click a Ui element
onElement { textAsString() == "Tap Me" }.click()

// Set text in an editable field
onElement { className == "android.widget.EditText" }.setText("My input text")

// Perform a long click
onElement { contentDescription == "Context Menu" }.longClick()

アプリの状態とウォッチャーを処理する

アプリのライフサイクルを管理し、テスト中に表示される可能性のある予期しない UI 要素を処理します。

アプリのライフサイクル管理

API には、テスト対象のアプリの状態を制御する方法が用意されています。

// Start a specific app by package name. Used for benchmarking and other
// self-instrumenting tests.
startApp("com.example.targetapp")

// Start a specific activity within the target app
startActivity(SomeActivity::class.java)

// Start an intent
startIntent(myIntent)

// Clear the app's data (resets it to a fresh state)
clearAppData("com.example.targetapp")

予期しない UI を処理する

watchFor API を使用すると、テストフロー中に表示される可能性のある権限ダイアログなどの予期しない UI 要素のハンドラを定義できます。これは内部ウォッチャー メカニズムを使用しますが、より柔軟性があります。

import androidx.test.uiautomator.PermissionDialog

@Test
fun myTestWithPermissionHandling() = uiAutomator {
  startActivity(MainActivity::class.java)

  // Register a watcher to click "Allow" if a permission dialog appears
  watchFor(PermissionDialog) { clickAllow() }

  // Your test steps that might trigger a permission dialog
  onElement { textAsString() == "Request Permissions" }.click()

  // Example: You can register a different watcher later if needed
  clearAppData("com.example.targetapp")

  // Now deny permissions
  startApp("com.example.targetapp")
  watchFor(PermissionDialog) { clickDeny() }
  onElement { textAsString() == "Request Permissions" }.click()
}

PermissionDialogScopedWatcher<T> の例です。ここで、TwatchFor のブロックにスコープとして渡されるオブジェクトです。このパターンに基づいてカスタム ウォッチャーを作成できます。

アプリの公開設定と安定性を待つ

テストでは、要素が表示されるか安定するまで待つ必要がある場合があります。UI Automator には、このための API がいくつか用意されています。

waitForAppToBeVisible("com.example.targetapp") は、カスタマイズ可能なタイムアウト内に、指定されたパッケージ名の UI 要素が画面に表示されるのを待ちます。

// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")

waitForStable() API を使用して、アプリの UI が安定していると見なされることを確認してから、操作します。

// Wait for the entire active window to become stable
activeWindow().waitForStable()

// Wait for a specific Ui element to become stable (e.g., after a loading animation)
onElement { id == "my_loading_indicator" }.waitForStable()

高度な機能

次の機能は、より複雑なテスト シナリオで役立ちます。

複数のウィンドウを操作する

UI Automator API を使用すると、UI 要素を直接操作して検査できます。これは、ピクチャー イン ピクチャー(PiP)モードや分割画面レイアウトなど、複数のウィンドウを含むシナリオで特に役立ちます。

// Find the first window that is in Picture-in-Picture mode
val pipWindow = windows()
  .first { it.isInPictureInPictureMode == true }

// Now you can interact with elements within that specific window
pipWindow.onElement { textAsString() == "Play" }.click()

スクリーンショットとビジュアル アサーション

テスト内で画面全体、特定のウィンドウ、個々の UI 要素のスクリーンショットを直接キャプチャします。これは、ビジュアル回帰テストとデバッグに役立ちます。

uiautomator {
  // Take a screenshot of the entire active window
  val fullScreenBitmap: Bitmap = activeWindow().takeScreenshot()
  fullScreenBitmap.saveToFile(File("/sdcard/Download/full_screen.png"))

  // Take a screenshot of a specific UI element (e.g., a button)
  val buttonBitmap: Bitmap = onElement { id == "my_button" }.takeScreenshot()
  buttonBitmap.saveToFile(File("/sdcard/Download/my_button_screenshot.png"))

  // Example: Take a screenshot of a PiP window
  val pipWindowScreenshot = windows()
    .first { it.isInPictureInPictureMode == true }
    .takeScreenshot()
  pipWindowScreenshot.saveToFile(File("/sdcard/Download/pip_screenshot.png"))
}

Bitmap の saveToFile 拡張関数を使用すると、キャプチャした画像を指定したパスに簡単に保存できます。

デバッグに ResultsReporter を使用する

ResultsReporter を使用すると、スクリーンショットなどのテスト アーティファクトを Android Studio のテスト結果に直接関連付けることができるため、検査とデバッグが容易になります。

uiAutomator {
  startApp("com.example.targetapp")

  val reporter = ResultsReporter("MyTestArtifacts") // Name for this set of results
  val file = reporter.addNewFile(
    filename = "my_screenshot",
    title = "Accessible button image" // Title that appears in Android Studio test results
  )

  // Take a screenshot of an element and save it using the reporter
  onElement { textAsString() == "Accessible button" }
    .takeScreenshot()
    .saveToFile(file)

  // Report the artifacts to instrumentation, making them visible in Android Studio
  reporter.reportToInstrumentation()
}

古いバージョンの UI Automator から移行する

古い API サーフェスで記述された既存の UI Automator テストがある場合は、次の表を参照して最新のアプローチに移行してください。

アクション タイプ 古い UI Automator メソッド 新しい UI Automator メソッド
エントリ ポイント UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) テストロジックを uiAutomator { ... } スコープでラップします。
UI 要素を見つける device.findObject(By.res("com.example.app:id/my_button")) onElement { id == "my\_button" }
UI 要素を見つける device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
アイドル状態の UI を待機 device.waitForIdle() onElement の組み込みタイムアウト メカニズムを優先します。それ以外の場合は activeWindow().waitForStable()
子要素を検索する 手動でネストされた findObject 呼び出し onElement().onElement() のチェーン
権限ダイアログを処理する UiAutomator.registerWatcher() watchFor(PermissionDialog)