Espresso to framework do zautomatyzowanego testowania UI aplikacji Android, opracowany przez zespół Google i wchodzący w skład AndroidX Test. W przeciwieństwie do testów instrumentalnych, które sprawdzają izolowane komponenty, Espresso współdziała z rzeczywistym UI: klika przyciski, wprowadza tekst, sprawdza wyświetlanie elementów. Według Google Android Developers, Espresso zapewnia automatyczną synchronizację z wątkiem UI, co eliminuje konieczność ręcznego Thread.sleep().
Najważniejsze
Espresso to biblioteka do pisania zautomatyzowanych testów UI dla Androida, która wchodzi w skład Google AndroidX Test. Udostępnia API do wyszukiwania elementów View na ekranie, wykonywania na nich działań (kliknięcie, wprowadzanie, przesunięcie) i sprawdzania ich stanu (jest wyświetlany, zawiera tekst, jest aktywny).
Kluczową cechą Espresso jest automatyczna synchronizacja z głównym wątkiem aplikacji. Framework oczekuje na zakończenie wszystkich zadań asynchronicznych (korutyny, AsyncTask, Handler) przed wykonaniem kolejnego sprawdzenia. Eliminuje to flaky-testy związane z race condition i sprawia, że testy UI są stabilne i niezawodne — żaden test nie zawiera Thread.sleep() ani pętli oczekiwania.
Espresso stosuje zasadę Three-Legged Dog — test składa się z trzech kroków: znajdź element (ViewMatcher), wykonaj działanie (ViewAction), sprawdź wynik (ViewAssertion). Wszystkie trzy kroki zapisywane są w łańcuchu wywołań onView().perform().check(). Ta koncepcja sprawia, że testy są przewidywalne i łatwe do czytania — każdy test wyraźnie opisuje, czego szuka, co robi i co sprawdza.
Architektura Espresso opiera się na trzech komponentach: Espresso (punkt wejścia — statyczne metody onView i onData), ViewMatchers (wyszukiwanie elementów), ViewActions (działania) i ViewAssertions (sprawdzenia). Wewnętrznie framework wykorzystuje Idling Resource do synchronizacji z wątkiem UI.
Najprostszy test znajduje przycisk po ID, wykonuje kliknięcie i sprawdza, czy pojawił się tekst „Gotowe“. Wszystkie operacje wykonywane są synchronicznie z perspektywy testu — Espresso gwarantuje, że wątek UI zakończył przetwarzanie zdarzenia zanim test będzie kontynuowany. Osiąga się to dzięki wbudowanemu mechanizmowi oczekiwania: onView blokuje wykonanie testu, dopóki UI nie stanie się stabilne.
@Test
fun buttonClick_showsSuccessText() {
// Znajdź przycisk po ID i kliknij
onView(withId(R.id.button_submit))
.perform(click())
// Sprawdź, że tekst „Gotowe“ jest wyświetlany
onView(withText("Gotowe"))
.check(matches(isDisplayed()))
}
Do uruchomienia testu Espresso używa się ActivityScenario (AndroidX Test), który tworzy Activity w żądanym stanie — uruchomiona, wstrzymana, zniszczona. ActivityScenario pozwala testować cykl życia Activity oprócz czystego UI. Można na przykład sprawdzić, czy dane są zapisywane podczas obrotu ekranu (rekreacja Activity) i przywracane po zniszczeniu.
ViewMatchers to zestaw metod z klasy Espresso.onView, które pozwalają znaleźć View na ekranie według różnych kryteriów: identyfikatora zasobu (R.id), tekstu, podpowiedzi hint, elementu nadrzędnego i hierarchii. Jeśli jeden matcher nie daje unikalnego wyniku, matchery łączy się przez allOf().
| Matcher | Przeznaczenie |
|---|---|
| withId(R.id.name) | Wyszukiwanie po ID zasobu |
| withText("tekst") | Wyszukiwanie po wyświetlanym tekście |
| withHint("podpowiedź") | Wyszukiwanie po atrybucie hint EditText |
| isDisplayed() | Sprawdzenie, czy element jest widoczny na ekranie |
| hasSibling(matcher) | Wyszukiwanie po sąsiednim elemencie |
| allOf(m1, m2) | Kombinacja wielu matcherów |
Jeśli na ekranie znajduje się kilka identycznych elementów (np. dwa TextView z różnym tekstem), wygodnie jest łączyć matchery przez allOf: onView(allOf(withId(R.id.title), withText("Cześć"))). Gwarantuje to wybór pojedynczego elementu. Operator odwrotny — not() — wyklucza elementy z wyszukiwania, a hasSibling() szuka elementu obok znanego.
ViewActions to działania, które Espresso wykonuje na znalezionym View: click(), typeText(), clearText(), scrollTo(), swipeLeft() i inne. Działania przekazywane są do metody perform(), która może przyjmować kilka działań po kolei.
Metoda perform() przyjmuje vararg ViewAction, co pozwala wykonać sekwencję działań na jednym elemencie: wyczyść pole, wprowadź nowy tekst, zamknij klawiaturę i kliknij przycisk. Wszystkie działania są wykonywane w kolejności ich wyliczenia, a Espresso gwarantuje, że poprzednie działanie zostało zakończone przed rozpoczęciem następnego.
// Wprowadzanie tekstu w EditText i kliknięcie przycisku
onView(withId(R.id.edit_email))
.perform(
clearText(),
typeText("user@example.com"),
closeSoftKeyboard()
)
onView(withId(R.id.button_login))
.perform(click())
Dla elementów wewnątrz AdapterView (ListView, RecyclerView) używa się metody onData() zamiast onView. Działa ona z danymi adaptera, a nie z View — znajduje element po zawartości modelu i zwraca odpowiedni View do dalszych działań. onData używa matcherów hamcrest do wyszukiwania elementu po polach modelu danych.
ViewAssertions sprawdzają, czy View znajduje się w określonym stanie. Podstawowa metoda — matches(matcher), która sprawdza, czy element odpowiada zadanemu matcherowi. Dodatkowo Espresso oferuje doesNotExist() (element nie istnieje) i selectedDescendantsMatch() (sprawdzenie elementów zagnieżdżonych).
Najczęstsze sprawdzenia w testach UI: element jest wyświetlany (isDisplayed), element zawiera określony tekst (withText), element jest aktywny (isEnabled), element nie jest zaznaczony (isNotChecked). Każde sprawdzenie zgłasza szczegółowy wyjątek w przypadku niepowodzenia — z podaniem hierarchii View na ekranie. Ułatwia to debugowanie: w komunikacie błędu widać, jakie elementy rzeczywiście znajdowały się na ekranie w momencie sprawdzenia.
Jeśli standardowe sprawdzenia są niewystarczające, można stworzyć własne poprzez interfejs ViewAssertion. Niestandardowy assertion otrzymuje View i może sprawdzić jego stan programowo — na przykład kolor tekstu, marginesy lub stan niestandardowego komponentu, nieujawnionego przez standardowe matchery.
// Sprawdzenie: TextView jest wyświetlane i zawiera tekst
onView(withId(R.id.text_welcome))
.check(matches(isDisplayed()))
.check(matches(withText("Witaj")))
// Sprawdzenie: element NIE jest wyświetlany
onView(withId(R.id.progress_bar))
.check(doesNotExist())
Idling Resource to mechanizm Espresso do synchronizacji testu z operacjami asynchronicznymi. Domyślnie Espresso oczekuje na zakończenie Handler, AsyncTask i korutyn (przez coroutinesIdlingResource). Jeśli aplikacja wykonuje pracę w tle przez własne wątki lub usługi Callback, należy zarejestrować niestandardowy Idling Resource.
Począwszy od AndroidX Test 1.4.0, Espresso obsługuje korutyny przez CoroutinesIdlingResource. Test automatycznie oczekuje na zakończenie wszystkich uruchomionych korutyn przed wykonaniem sprawdzeń UI. W przypadku bardziej złożonych scenariuszy używa się CountingIdlingResource — licznika, który jest inkrementowany przy starcie zadania i dekrementowany przy jego zakończeniu.
// Rejestracja IdlingResource dla OkHttp
class OkHttpIdlingResource(
private val client: OkHttpClient
) : IdlingResource {
private var isIdle = true
private var watcher: IdlingResource.ResourceCallback? = null
override fun getName() = "OkHttp"
override fun isIdleNow() = isIdle
override fun registerIdleTransitionCallback(
callback: IdlingResource.ResourceCallback
) {
watcher = callback
}
}
Podłączenie Espresso w projekcie Android wykonuje się przez dodanie zależności w build.gradle poziomu modułu. Espresso wchodzi w skład AndroidX Test, więc wystarczy wskazać zależności dla jądra Espresso, rozszerzeń i integracji JUnit. Testy umieszcza się w katalogu src/androidTest i uruchamia na fizycznym urządzeniu lub emulatorze przez AndroidJUnitRunner.
Minimalny zestaw zależności obejmuje espresso-core (jądro), espresso-contrib (dodatkowe matchery dla RecyclerView, Drawer, Picker) i runner (testowy runner AndroidX). Wszystkie testy uruchamiane są na emulatorze lub fizycznym urządzeniu przez Android Test Orchestrator.
// build.gradle.kts (zależności androidTest)
android {
defaultConfig {
testInstrumentationRunner =
"androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.6.1")
androidTestImplementation("androidx.test:runner:1.6.1")
androidTestImplementation("androidx.test:rules:1.6.1")
}
Testy Espresso można uruchamiać przez Google Android Test Orchestrator, który izoluje każdy test w osobnym procesie i czyści stan między uruchomieniami. Eliminuje to flaky-testy związane z pozostałościami danych poprzednich testów i zwiększa stabilność na serwerach CI. Do równoległego uruchamiania używa się shardingu — dystrybucji testów pomiędzy wieloma emulatorami.
Często zadawane pytania
Espresso działa wewnątrz procesu aplikacji i używa automatycznej synchronizacji z wątkiem UI. UI Automator działa na poziomie systemu, może współdziałać z innymi aplikacjami, ale wymaga ręcznego zarządzania oczekiwaniem.
To metafora z prezentacji Google: test Espresso stoi na trzech nogach — ViewMatcher (wyszukiwanie), ViewAction (działanie) i ViewAssertion (sprawdzenie). Jeśli zabraknie którejkolwiek z nich, test traci stabilność, jak trójnogi pies.
Do RecyclerView używa się biblioteki espresso-contrib i metod onView(withId(R.id.recycler)).perform(actionOnItemAtPosition(0, click())). Alternatywą jest onData() dla AdapterView lub niestandardowy ViewAction do wyszukiwania elementu po tekście wewnątrz RecyclerView. Dodatkowo można użyć RecyclerViewActions z espresso-contrib do przewijania do elementu i działań na nim.
Flaky-test — test, który czasami pada bez zmiany kodu, z powodu race condition lub asynchroniczności. Espresso rozwiązuje ten problem przez Idling Resource — oczekiwanie na zakończenie wszystkich zadań tła przed wykonaniem sprawdzenia.
Sam Espresso nie jest przeznaczony do testów zrzutów ekranu, ale można go łączyć z bibliotekami takimi jak Shot lub Paparazzi. Espresso przygotowuje UI w żądanym stanie, a biblioteka porównania wykonuje zrzut ekranu i porównuje z wzorcem. Takie podejście nazywa się wizualnym testowaniem regresji i pomaga znajdować nieoczekiwane zmiany w interfejsie.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również