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
UI Automator 2.4 führt eine optimierte, Kotlin-freundliche domänenspezifische Sprache (Domain Specific Language, DSL) ein, die das Schreiben von UI-Tests für Android vereinfacht. Diese neue API Oberfläche konzentriert sich auf die prädikatbasierte Elementfindung und die explizite Steuerung von App Zuständen. Damit können Sie wartungsfreundlichere 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 Reduzierung testen. UI Automator ist auch beim Schreiben von Makrobenchmark-Tests hilfreich.
Zu den wichtigsten Funktionen des modernen Ansatzes gehören:
- Ein dedizierter
uiAutomatorTestbereich für saubereren und aussagekräftigeren Test code. - Methoden wie
onElement,onElementsundonElementOrNullzum Suchen von UI-Elementen mit eindeutigen Prädikaten. - Integrierter Wartemechanismus für bedingte Elemente
onElement*(timeoutMs: Long = 10000). - Explizite Verwaltung des App-Zustands wie
waitForStableundwaitForAppToBeVisible. - Direkte Interaktion mit Knoten von Barrierefreiheitsfenstern für Mehrfenster-Testszenarien.
- Integrierte Screenshot-Funktionen und ein
ResultsReporterfür visuelle Tests und Fehlerbehebung.
Projekt einrichten
Wenn Sie die modernen UI Automator-APIs verwenden möchten, aktualisieren Sie die Datei Ihres Projekts
build.gradle.kts und fügen Sie die neueste Abhängigkeit hinzu:
Kotlin
dependencies {
...
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}
Groovy
dependencies {
...
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}
Grundlegende API-Konzepte
In den folgenden Abschnitten werden die grundlegenden Konzepte der modernen UI Automator API beschrieben.
Der uiAutomator-Testbereich
Greifen Sie auf alle neuen UI Automator-APIs innerhalb des uiAutomator { ... }-Blocks zu. Mit dieser Funktion wird ein UiAutomatorTestScope erstellt, der 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 suchen
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 Zustand 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 es kein übereinstimmendes 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 { viewIdResourceName == "my_button_id" }.click() // Allow a permission request watchFor(PermissionDialog) { clickAllow() }onElementOrNull { predicate }: Ähnlich wieonElement, gibt abernullzurü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 existsonElements { predicate }: Wartet, bis mindestens ein UI-Element mit dem angegebenen Prädikat übereinstimmt, 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 einige Tipps zur Verwendung von onElement-Aufrufen:
onElement-Aufrufe für verschachtelte Elemente verketten: Sie könnenonElementAufrufe verketten, um Elemente in anderen Elementen zu finden, wobei eine Über-/Untergeordnet- Hierarchie verwendet 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 { viewIdResourceName == "first" } .onElement { viewIdResourceName == "second" } .onElement { viewIdResourceName == "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) { viewIdResourceName == "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
Sie können 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-Zustände und Watcher verarbeiten
Verwalten Sie den Lebenszyklus Ihrer App und verarbeiten Sie unerwartete UI-Elemente, die während Ihrer Tests angezeigt werden können.
Verwaltung des App-Lebenszyklus
Die APIs bieten Möglichkeiten, den Zustand 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 UI 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, doch es wird mehr Flexibilität geboten.
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 basierend auf diesem Muster 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 dabei helfen.
The waitForAppToBeVisible("com.example.targetapp") wartet innerhalb eines anpassbaren Zeitlimits, 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 UI 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 { viewIdResourceName == "my_loading_indicator" }.waitForStable()
UI Automator für Makrobenchmarks und Baseline Profiles verwenden
Verwenden Sie UI Automator für Leistungstests mit Jetpack Macrobenchmark und zum Generieren von Baseline Profiles, da es eine zuverlässige Möglichkeit bietet, mit Ihrer App zu interagieren und die Leistung aus Endnutzersicht zu messen.
Macrobenchmark verwendet UI Automator-APIs, um die UI zu steuern und Interaktionen zu messen.
Bei Startbenchmarks können Sie beispielsweise onElement verwenden, um zu erkennen, wann UI
Inhalte vollständig geladen sind, sodass Sie die Zeit bis zur vollständigen Anzeige (Time to Full Display
TTFD) messen können. Bei Jank-Benchmarks werden UI Automator-APIs verwendet, um Listen zu scrollen oder
Animationen auszuführen, um die Frame-Zeiten zu messen. Funktionen wie startActivity() oder
startIntent() sind nützlich, um die App in den richtigen Zustand zu versetzen, bevor
die Messung beginnt.
Beim Generieren von Baseline Profiles automatisieren Sie die kritischen Nutzer
prozesse (Critical User Journeys, CUJs) Ihrer App, um aufzuzeichnen, welche Klassen und Methoden vorkompiliert werden müssen. UI
Automator ist ein ideales Tool zum Schreiben dieser Automatisierungsskripts. Die prädikatbasierte Elementfindung und die integrierten Wartemechanismen (onElement) der modernen
DSL's
führen im Vergleich zu anderen Methoden zu einer robusteren und deterministischeren Testausführung.
Diese Stabilität reduziert die Instabilität und sorgt dafür, dass das generierte Baseline Profile
die Codepfade genau widerspiegelt, die während Ihrer wichtigsten Nutzer
prozesse ausgeführt werden.
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 prüfen. Dies ist besonders nützlich für Szenarien mit mehreren Fenstern, z. B. im Bild-in-Bild-Modus (Picture-in-Picture, PiP) 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 Assertions
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 Debugging.
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 { viewIdResourceName == "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"))
}
Mit der saveToFile Erweiterungsfunktion für Bitmap lässt sich das aufgenommene
Bild einfach unter einem angegebenen Pfad speichern.
ResultsReporter für das Debugging verwenden
Mit ResultsReporter können Sie Testartefakte wie Screenshots
direkt mit Ihren Testergebnissen in Android Studio verknüpfen, um die Überprüfung und das
Debugging zu erleichtern.
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 vorhandene UI Automator-Tests mit älteren API-Oberflächen geschrieben 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()) |
Testlogik in den uiAutomator { ... } Bereich einschließen. |
| UI-Elemente suchen | device.findObject(By.res("com.example.app:id/my_button")) |
onElement { viewIdResourceName == "my\_button" } |
| UI-Elemente suchen | device.findObject(By.text("Click Me")) |
onElement { textAsString() == "Click Me" } |
| Auf inaktive UI warten | device.waitForIdle() |
Bevorzugen des integrierten Zeitlimitmechanismus von onElement; andernfalls activeWindow().waitForStable() |
| Untergeordnete Elemente suchen | Manuell verschachtelte findObject-Aufrufe |
onElement().onElement()-Verkettung |
| Berechtigungsdialogfelder verarbeiten | UiAutomator.registerWatcher() |
watchFor(PermissionDialog) |