使用 UI Automator 編寫自動化測試

UI Automator 測試架構提供一組 API,可建構與使用者應用程式和系統應用程式互動的 UI 測試。

新版 UI Automator 測試簡介

UI Automator 2.4 推出簡化的 Kotlin 友善領域特定語言 (DSL),可簡化 Android 的 UI 測試編寫作業。這個新 API 介面著重於以述詞為基礎的元素尋找作業,以及對應用程式狀態的明確控制。您可以使用這項功能,建立更易於維護且可靠的自動化測試。

您可以使用 UI Automator 從應用程式程序外部測試應用程式。這樣您就能測試已套用縮減功能的發布版本。編寫巨集基準測試時,UI Automator 也很有幫助。

現代方法的關鍵功能包括:

  • 專屬 uiAutomator 測試範圍,可讓測試程式碼更簡潔且更具表達力。
  • 使用 onElementonElementsonElementOrNull 等方法,透過明確的述詞尋找 UI 元素。
  • 條件式元素 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"
}

核心 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 }:等待至少一個 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> 的範例,其中 T 是以範圍形式傳遞至 watchFor 中區塊的物件。您可以根據這個模式建立自訂監控程式。

等待應用程式顯示和穩定

有時測試需要等待元素顯示或穩定。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() Prefer onElement's built-in timeout mechanism; otherwise, activeWindow().waitForStable()
尋找子元素 手動巢狀 findObject 呼叫 onElement().onElement() 鏈結
處理權限對話方塊 UiAutomator.registerWatcher() watchFor(PermissionDialog)