Automatisierte Tests mit UI Automator schreiben

Das UI Automator-Testframework bietet eine Reihe von APIs zum Erstellen von UI-Tests, die mit Nutzer- und System-Apps interagieren.

Einführung in moderne UI Automator-Tests

Mit UI Automator 2.4 wird eine optimierte, Kotlin-freundliche Domain Specific Language (DSL) eingeführt, die das Schreiben von UI-Tests für Android vereinfacht. Diese neue API-Oberfläche konzentriert sich auf das Suchen von Elementen auf Grundlage von Prädikaten und die explizite Steuerung von App-Zuständen. Damit können Sie verwaltbarere und zuverlässigere automatisierte Tests erstellen.

Mit UI Automator können Sie eine App außerhalb des App-Prozesses testen. So können Sie Release-Versionen mit angewendeter Minimierung testen. UI Automator ist auch beim Schreiben von Makrobenchmark-Tests hilfreich.

Zu den wichtigsten Funktionen des modernen Ansatzes gehören:

  • Ein dedizierter uiAutomator-Testbereich für übersichtlicheren und ausdrucksstärkeren Testcode.
  • Methoden wie onElement, onElements und onElementOrNull zum Suchen von UI-Elementen mit eindeutigen Attributen.
  • Integrierter Wartemechanismus für bedingte Elemente onElement*(timeoutMs: Long = 10000)
  • Explizite Verwaltung des App-Status, z. B. waitForStable und waitForAppToBeVisible.
  • Direkte Interaktion mit Knoten von Barrierefreiheitsfenstern für Multi-Window-Testszenarien.
  • Integrierte Screenshot-Funktionen und eine ResultsReporter für visuelle Tests und Fehlerbehebung.

Projekt einrichten

Wenn Sie die modernen UI Automator-APIs verwenden möchten, müssen Sie die Datei build.gradle.kts Ihres Projekts aktualisieren, um die neueste Abhängigkeit einzufügen:

Kotlin

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

Groovy

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

Wichtige API-Konzepte

In den folgenden Abschnitten werden die wichtigsten Konzepte der modernen UI Automator API beschrieben.

uiAutomator-Testbereich

Greifen Sie auf alle neuen UI Automator-APIs innerhalb des uiAutomator { ... }-Blocks zu. Mit dieser Funktion wird ein UiAutomatorTestScope erstellt, das eine prägnante und typsichere Umgebung für Ihre Testvorgänge bietet.

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

UI-Elemente finden

Verwenden Sie UI Automator-APIs mit Prädikaten, um UI-Elemente zu finden. Mit diesen Prädikaten können Sie Bedingungen für Eigenschaften wie Text, ausgewählter oder fokussierter Status und Inhaltsbeschreibung definieren.

  • onElement { predicate }: Gibt das erste UI-Element zurück, das innerhalb eines Standardzeitlimits mit dem Prädikat übereinstimmt. Die Funktion löst eine Ausnahme aus, wenn sie kein passendes Element findet.

    // 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 }: Ähnlich wie onElement, gibt aber null zurück, wenn die Funktion innerhalb des Zeitlimits kein übereinstimmendes Element findet. Es wird keine Ausnahme ausgelöst. Verwenden Sie diese Methode für optionale Elemente.

    val optionalButton = onElementOrNull { textAsString() == "Skip" }
    optionalButton?.click() // Click only if the button exists
    
  • onElements { predicate }: Wartet, bis mindestens ein UI-Element dem angegebenen Prädikat entspricht, und gibt dann eine Liste aller übereinstimmenden UI-Elemente zurück.

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

Hier sind einige Tipps zur Verwendung von onElement-Aufrufen:

  • onElement-Aufrufe für verschachtelte Elemente verketten: Sie können onElement-Aufrufe verketten, um Elemente in anderen Elementen zu finden, wobei eine Über-/Unterordnungshierarchie berücksichtigt wird.

    // 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()
    
  • Geben Sie ein Zeitlimit für onElement*-Funktionen an, indem Sie einen Wert übergeben, der Millisekunden darstellt.

    // 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()
    

Mit UI-Elementen interagieren

Mit UI-Elementen interagieren, indem Sie Klicks simulieren oder Text in bearbeitbare Felder eingeben.

// 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()

App-Status und Watcher verarbeiten

Sie können den Lebenszyklus Ihrer App verwalten und unerwartete UI-Elemente behandeln, die während Ihrer Tests möglicherweise angezeigt werden.

Verwaltung des App-Lebenszyklus

Die APIs bieten Möglichkeiten, den Status der zu testenden App zu steuern:

// 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")

Unerwartete Benutzeroberfläche verarbeiten

Mit der watchFor API können Sie Handler für unerwartete UI-Elemente wie Berechtigungsdialogfelder definieren, die während des Testablaufs angezeigt werden können. Dabei wird der interne Watcher-Mechanismus verwendet, der jedoch mehr Flexibilität bietet.

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()
}

PermissionDialog ist ein Beispiel für einen ScopedWatcher<T>, wobei T das Objekt ist, das als Bereich an den Block in watchFor übergeben wird. Sie können benutzerdefinierte Watcher auf Grundlage dieses Musters erstellen.

Auf App-Sichtbarkeit und -Stabilität warten

Manchmal müssen Tests warten, bis Elemente sichtbar oder stabil werden. UI Automator bietet mehrere APIs, die Ihnen dabei helfen.

Mit waitForAppToBeVisible("com.example.targetapp") wird bis zu einem anpassbaren Zeitlimit gewartet, bis ein UI-Element mit dem angegebenen Paketnamen auf dem Bildschirm angezeigt wird.

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

Verwenden Sie die waitForStable() API, um zu prüfen, ob die Benutzeroberfläche der App als stabil gilt, bevor Sie mit ihr interagieren.

// 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()

Erweiterte Funktionen

Die folgenden Funktionen sind für komplexere Testszenarien nützlich.

Mit mehreren Fenstern interagieren

Mit den UI Automator-APIs können Sie direkt mit UI-Elementen interagieren und sie untersuchen. Das ist besonders nützlich für Szenarien mit mehreren Fenstern, z. B. im Bild-im-Bild-Modus (BiB) oder bei Splitscreen-Layouts.

// 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()

Screenshots und visuelle Behauptungen

Sie können Screenshots des gesamten Bildschirms, bestimmter Fenster oder einzelner UI-Elemente direkt in Ihren Tests erstellen. Das ist hilfreich für visuelle Regressionstests und das Debuggen.

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"))
}

Die Erweiterungsfunktion saveToFile für Bitmap vereinfacht das Speichern des aufgenommenen Bildes in einem angegebenen Pfad.

ResultsReporter zur Fehlerbehebung verwenden

Mit dem ResultsReporter können Sie Testartefakte wie Screenshots direkt mit Ihren Testergebnissen in Android Studio verknüpfen, um sie einfacher zu prüfen und zu debuggen.

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()
}

Von älteren UI Automator-Versionen migrieren

Wenn Sie bereits UI Automator-Tests mit älteren API-Oberflächen haben, können Sie die folgende Tabelle als Referenz für die Migration zum modernen Ansatz verwenden:

Aktionstyp Alte UI Automator-Methode Neue UI Automator-Methode
Einstiegspunkt UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) Schließen Sie die Testlogik in den uiAutomator { ... }-Bereich ein.
UI-Elemente finden device.findObject(By.res("com.example.app:id/my_button")) onElement { id == "my\_button" }
UI-Elemente finden device.findObject(By.text("Click Me")) onElement { textAsString() == "Click Me" }
Auf inaktive Benutzeroberfläche warten device.waitForIdle() Bevorzuge den integrierten Zeitlimitmechanismus von onElement; andernfalls activeWindow().waitForStable()
Untergeordnete Elemente finden Manuell verschachtelte findObject-Aufrufe onElement().onElement() Verkettung
Berechtigungsdialogfelder verarbeiten UiAutomator.registerWatcher() watchFor(PermissionDialog)