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 Automatora
UI Automator 2.4 wprowadza uproszczony język DSL (Domain Specific Language) przyjazny dla języka Kotlin, który ułatwia pisanie testów interfejsu na Androida. Ten nowy interfejs API skupia się na wyszukiwaniu elementów na podstawie predykatów i jawnej kontroli nad stanami aplikacji. Używaj go do tworzenia łatwiejszych w utrzymaniu i bardziej niezawodnych testów automatycznych.
UI Automator umożliwia testowanie aplikacji z zewnątrz jej procesu. Dzięki temu możesz testować wersje do publikacji z zastosowaną minimalizacją. UI Automator pomaga też w pisaniu testów makrobenchmarkowych.
Najważniejsze cechy nowoczesnego podejścia to:
- Dedykowany
uiAutomator
zakres testowy, który zapewnia bardziej przejrzysty i wyrazisty kod testowy. - Metody takie jak
onElement
,onElements
ionElementOrNull
do znajdowania elementów interfejsu z jasnymi predykatami. - Wbudowany mechanizm oczekiwania na elementy warunkowe
onElement*(timeoutMs: Long = 10000)
- Jawne zarządzanie stanem aplikacji, np.
waitForStable
iwaitForAppToBeVisible
. - Bezpośrednia interakcja z węzłami okna ułatwień dostępu w scenariuszach testowania wielu okien.
- Wbudowane funkcje robienia zrzutów ekranu i
ResultsReporter
do testowania wizualnego i debugowania.
Konfigurowanie projektu
Aby zacząć korzystać z nowoczesnych interfejsów API UI Automator, zaktualizuj plik build.gradle.kts
projektu, aby zawierał najnowszą zależność:
Kotlin
dependencies {
...
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha05")
}
Groovy
dependencies {
...
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.4.0-alpha05"
}
Podstawowe pojęcia związane z interfejsem API
W sekcjach poniżej opisujemy podstawowe pojęcia związane z nowoczesnym interfejsem UI Automator API.
Zakres testu uiAutomator
Dostęp do wszystkich nowych interfejsów API UI Automator w bloku uiAutomator { ... }
. Ta funkcja tworzy UiAutomatorTestScope
, które zapewnia zwięzłe i bezpieczne pod względem typów środowisko dla operacji testowych.
uiAutomator {
// All your UI Automator actions go here
startApp("com.example.targetapp")
onElement { textAsString() == "Hello, World!" }.click()
}
Znajdowanie elementów interfejsu
Używaj interfejsów UI Automator API z predykatami, aby lokalizować elementy interfejsu. Te predykaty umożliwiają określanie 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 { id == "my_button_id" }.click() // Allow a permission request watchFor(PermissionDialog) { clickAllow() }
onElementOrNull { predicate }
: Podobna do funkcjionElement
, ale zwraca wartośćnull
, jeśli w określonym czasie nie znajdzie pasującego elementu. 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 exists
onElements { predicate }
: czeka, aż co najmniej 1 element interfejsu użytkownika będzie pasować do podanego predykatu, a następnie zwraca listę wszystkich pasujących elementów interfejsu użytkownika.// 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 korzystania z połączeń onElement
:
Łączenie wywołań
onElement
w przypadku zagnieżdżonych elementów: możesz łączyć wywołaniaonElement
, aby znajdować elementy w innych elementach zgodnie z hierarchią elementu nadrzędnego i podrzędnego.// 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()
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) { id == "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 wpisują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ą pojawić się 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 watchFor
API umożliwia definiowanie procedur obsługi nieoczekiwanych elementów interfejsu, takich jak okna dialogowe z prośbą o uprawnienia, które mogą pojawić się podczas testu. Korzysta on z wewnętrznego mechanizmu 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
to obiekt przekazywany jako zakres do bloku w watchFor
. Na podstawie tego wzorca możesz tworzyć niestandardowe obserwatory.
Poczekaj 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 na pojawienie się na ekranie elementu interfejsu o podanej nazwie pakietu 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 waitForStable()
API, aby przed interakcją z interfejsem aplikacji sprawdzić, czy jest on stabilny.
// 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()
Funkcje zaawansowane
Poniższe funkcje są przydatne w bardziej złożonych scenariuszach testowych.
Interakcja z wieloma oknami
Interfejsy API UI Automator umożliwiają bezpośrednie interakcje z elementami interfejsu i ich sprawdzanie. Jest to szczególnie przydatne w scenariuszach obejmujących wiele okien, takich jak tryb obrazu w obrazie lub układy z podzielonym ekranem.
// 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 całego ekranu, konkretnych okien lub poszczególnych elementów interfejsu bezpośrednio w testach. Jest to przydatne w przypadku 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 { 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"))
}
Funkcja rozszerzenia saveToFile
dla mapy bitowej upraszcza zapisywanie przechwyconego obrazu w określonej ścieżce.
Używanie ResultsReporter do debugowania
ResultsReporter
pomaga powiązać artefakty testowe, takie 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 Automatora
Jeśli masz już testy UI Automator napisane przy użyciu starszych interfejsów API, skorzystaj z tej tabeli, aby przenieść je na nowoczesne podejście:
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 { id == "my\_button" } |
Znajdowanie elementów interfejsu | device.findObject(By.text("Click Me")) |
onElement { textAsString() == "Click Me" } |
Czekaj na bezczynny interfejs | device.waitForIdle() |
Preferuj wbudowany mechanizm limitu czasu onElement ; w przeciwnym razie activeWindow().waitForStable() |
Znajdowanie elementów podrzędnych | Ręcznie zagnieżdżone wywołania findObject |
onElement().onElement() łączenie |
Obsługa okien z prośbą o uprawnienia | UiAutomator.registerWatcher() |
watchFor(PermissionDialog) |