Platforma testowa UI Automator udostępnia zestaw interfejsów API do tworzenia testów interfejsu, które wchodzą w interakcję z aplikacjami użytkownika i aplikacjami systemowymi.
Wprowadzenie do nowoczesnego testowania za pomocą UI Automator
UI Automator 2.4 wprowadza uproszczony, przyjazny dla Kotlina język DSL, który ułatwia pisanie testów interfejsu na Androidzie. Ten nowy interfejs API koncentruje się na wyszukiwaniu elementów na podstawie predykatów i na wyraźnej kontroli stanu aplikacji. Używaj go do tworzenia łatwiejszych w utrzymaniu i bardziej niezawodnych testów automatycznych.
UI Automator umożliwia testowanie aplikacji spoza jej procesu. Dzięki temu możesz testować wersje z zastosowaną minifikacją. UI Automator pomaga też w pisaniu testów Macrobenchmark.
Główne funkcje nowoczesnego podejścia:
- Dedykowany zakres testowy
uiAutomatorzapewniający czystszy i bardziej ekspresyjny kod testowy. - Metody takie jak
onElement,onElementsionElementOrNulldo wyszukiwania elementów interfejsu za pomocą jasnych predykatów. - Wbudowany mechanizm oczekiwania na elementy warunkowe
onElement*(timeoutMs: Long = 10000). - Wyraźne zarządzanie stanem aplikacji, np.
waitForStableiwaitForAppToBeVisible. - Bezpośrednia interakcja z węzłami okien ułatwień dostępu w scenariuszach testowania wielu okien.
- Wbudowane funkcje robienia zrzutów ekranu i
ResultsReporterdo testowania wizualnego i debugowania.
Konfigurowanie projektu
Aby zacząć korzystać z nowoczesnych interfejsów API UI Automator, zaktualizuj plik projektu
build.gradle.kts w celu uwzględnienia najnowszej zależności:
Kotlin
dependencies {
...
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}
Dynamiczny
dependencies {
...
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}
Podstawowe koncepcje interfejsu API
W sekcjach poniżej opisujemy podstawowe koncepcje nowoczesnego interfejsu API UI Automator.
Zakres testowy uiAutomator
Dostęp do wszystkich nowych interfejsów API UI Automator uzyskasz w bloku uiAutomator { ... }. Ta funkcja tworzy UiAutomatorTestScope, który zapewnia zwięzłe i bezpieczne środowisko do wykonywania operacji testowych.
uiAutomator {
// All your UI Automator actions go here
startApp("com.example.targetapp")
onElement { textAsString() == "Hello, World!" }.click()
}
Znajdowanie elementów interfejsu
Do lokalizowania elementów interfejsu używaj interfejsów API UI Automator z predykatami. Te predykaty umożliwiają definiowanie warunków dla właściwości takich jak tekst, stan zaznaczenia lub fokus oraz opis treści.
onElement { predicate }: zwraca pierwszy element interfejsu, który pasuje do predykatu w domyślnym czasie oczekiwania. Jeśli funkcja nie znajdzie pasującego elementu, zgłosi wyjątek.// 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 }: podobna doonElement, ale zwracanulljeśli funkcja nie znajdzie pasującego elementu w czasie oczekiwania. Nie zgłasza wyjątku. Używaj tej metody w przypadku elementów opcjonalnych.val optionalButton = onElementOrNull { textAsString() == "Skip" } optionalButton?.click() // Click only if the button existsonElements { predicate }: czeka, aż co najmniej 1 element interfejsu będzie pasować do danego predykatu, a następnie zwraca listę wszystkich pasujących elementów interfejsu.// Get all items in a list Ui element val listItems = onElements { className == "android.widget.TextView" && isClickable } listItems.forEach { it.click() }
Oto kilka wskazówek dotyczących używania wywołań onElement:
Łączenie wywołań
onElementw przypadku elementów zagnieżdżonych: możesz łączyć wywołaniaonElement, aby znajdować elementy w innych elementach, zgodnie z hierarchią nadrzędny-podrzędny.// 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()Określ czas oczekiwania dla funkcji
onElement*, przekazując wartość reprezentującą milisekundy.// 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()
Interakcja z elementami interfejsu
Wchodź w interakcję z elementami interfejsu, symulując kliknięcia lub ustawiając tekst w polach edytowalnych.
// 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()
Obsługa stanów aplikacji i obserwatorów
Zarządzaj cyklem życia aplikacji i obsługuj nieoczekiwane elementy interfejsu, które mogą się pojawić podczas testów.
Zarządzanie cyklem życia aplikacji
Interfejsy API umożliwiają kontrolowanie stanu testowanej aplikacji:
// 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")
Obsługa nieoczekiwanego interfejsu
Interfejs API watchFor umożliwia definiowanie obsługi nieoczekiwanych elementów interfejsu, takich jak okna z prośbą o uprawnienia, które mogą się pojawić podczas testu. Wykorzystuje on wewnętrzny mechanizm obserwatora, ale zapewnia większą elastyczność.
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 to przykład ScopedWatcher<T>, gdzie T jest
obiektem przekazywanym jako zakres do bloku w watchFor. Na podstawie tego wzorca możesz tworzyć niestandardowe obserwatory.
Oczekiwanie na widoczność i stabilność aplikacji
Czasami testy muszą poczekać, aż elementy staną się widoczne lub stabilne. UI Automator udostępnia kilka interfejsów API, które mogą w tym pomóc.
waitForAppToBeVisible("com.example.targetapp") czeka, aż element interfejsu o podanej nazwie pakietu pojawi się na ekranie w konfigurowalnym czasie oczekiwania.
// Wait for the app to be visible after launching it
startApp("com.example.targetapp")
waitForAppToBeVisible("com.example.targetapp")
Użyj interfejsu API waitForStable(), aby sprawdzić, czy interfejs aplikacji jest stabilny, zanim zaczniesz z nim wchodzić w interakcję.
// 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()
Używanie UI Automator w przypadku Macrobenchmark i profili podstawowych
Używaj UI Automator do testowania wydajności za pomocą Jetpack Macrobenchmark i do generowania profili podstawowych, ponieważ zapewnia on niezawodny sposób interakcji z aplikacją i pomiaru wydajności z perspektywy użytkownika.
Macrobenchmark używa interfejsów API UI Automator do sterowania interfejsem i pomiaru interakcji.
Na przykład w testach porównawczych uruchamiania możesz użyć onElement, aby wykryć, kiedy treść interfejsu
jest w pełni załadowana, co umożliwia pomiar czasu do pełnego wyświetlenia
(TTFD). W testach porównawczych dotyczących zacięć interfejsy API UI Automator służą do przewijania list lub uruchamiania animacji w celu pomiaru czasu trwania klatek. Funkcje takie jak startActivity() czy startIntent() są przydatne do ustawiania aplikacji w odpowiednim stanie przed rozpoczęciem pomiaru.
Podczas generowania profili podstawowych automatyzujesz najważniejsze ścieżki użytkownika
w aplikacji, aby rejestrować, które klasy i metody wymagają wstępnej kompilacji. UI Automator to idealne narzędzie do pisania tych skryptów automatyzacji. Wyszukiwanie elementów na podstawie predykatów w nowoczesnym języku DSL i wbudowane mechanizmy oczekiwania (onElement) zapewniają bardziej niezawodne i deterministyczne wykonywanie testów w porównaniu z innymi metodami.
Ta stabilność zmniejsza niestabilność i zapewnia, że wygenerowany profil podstawowy dokładnie odzwierciedla ścieżki kodu wykonywane podczas najważniejszych ścieżek użytkownika.
Funkcje zaawansowane
Te funkcje są przydatne w bardziej złożonych scenariuszach testowania.
Interakcja z wieloma oknami
Interfejsy API UI Automator umożliwiają bezpośrednią interakcję z elementami interfejsu i ich sprawdzanie. Jest to szczególnie przydatne w scenariuszach obejmujących wiele okien, takich jak tryb obrazu w obrazie czy układy z podziałem ekranu.
// 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()
Zrzuty ekranu i asercje wizualne
Rób zrzuty ekranu całego ekranu, określonych okien lub poszczególnych elementów interfejsu bezpośrednio w testach. Jest to przydatne do testowania regresji wizualnej i debugowania.
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"))
}
Funkcja rozszerzająca saveToFile dla Bitmap ułatwia zapisywanie przechwyconego obrazu w określonej ścieżce.
Używanie ResultsReporter do debugowania
ResultsReporter ułatwia powiązanie artefaktów testowych, takich jak zrzuty ekranu, bezpośrednio z wynikami testów w Android Studio, co ułatwia sprawdzanie i debugowanie.
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()
}
Migracja ze starszych wersji UI Automator
Jeśli masz już testy UI Automator napisane za pomocą starszych interfejsów API, użyj tabeli poniżej jako odniesienia, aby przeprowadzić migrację do nowoczesnego podejścia:
| Typ działania | Stara metoda UI Automator | Nowa metoda UI Automator |
|---|---|---|
| Punkt wejścia | UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) |
Umieść logikę testu w zakresie uiAutomator { ... }. |
| Znajdowanie elementów interfejsu | device.findObject(By.res("com.example.app:id/my_button")) |
onElement { viewIdResourceName == "my\_button" } |
| Znajdowanie elementów interfejsu | device.findObject(By.text("Click Me")) |
onElement { textAsString() == "Click Me" } |
| Oczekiwanie na bezczynny interfejs | device.waitForIdle() |
Zalecamy używanie wbudowanego mechanizmu czasu oczekiwania onElement; w przeciwnym razie użyj activeWindow().waitForStable(). |
| Znajdowanie elementów podrzędnych | Ręcznie zagnieżdżone wywołania findObject |
Łączenie onElement().onElement() |
| Obsługa okien z prośbą o uprawnienia | UiAutomator.registerWatcher() |
watchFor(PermissionDialog) |