Espresso — problemy z animacjami

Autor
Damian
Terlecki
5 minut
Testy

V/InstrumentationResultParser: androidx.test.espresso.PerformException: Error performing 'single click - At Coordinates: 383, 1177 and precision: 16, 16' on view 'Animations or transitions are enabled on the target device.

Praktycznie każdy programista zaczynający swoją przygodę z biblioteką Espresso — wspomagającą testowanie interfejsu użytkownika spotkał się chociaż raz z powyższym błędem. Pierwszym krokiem w takiej sytuacji jest właśnie odwiedzenie dokumentacji na temat konfiguracji środowiska. Najczęstszym powodem tego błędu są działające w tle animacje. Ich wyłączenie zazwyczaj rozwiązuje problem, ale są też inne przypadki, w których błąd ten może się ujawniać mimo zastosowania się do instrukcji. Przyjrzyjmy się więc różnym sposobom radzenia sobie z tym problemem. Animacje można wyłączyć ręcznie, poprzez opcje urządzenia / emulatora:

Przejdź do Ustawienia > Opcje programisty i wyłącz następujące opcje:
Skala animacji okna Skala animacji przejścia Skala animacji trwania animatora

Protip, jeśli nie widzisz „Opcji programisty”, możesz je włączyć, przechodząc do (w zależności od urządzenia) Ustawienia > System > Informacje o telefonie, a następnie odszukując „Numer kompilacji” i przyciskając go kilkukrotnie.

Jesteś teraz X kroków od bycia programistą.

adb & Gradle

Biorąc pod uwagę środowisko CI, dostęp do ustawień jest równie prosty. Animacje możemy wyłączyć przy pomocy wiersza poleceń i narzędzia adb (tylko w przypadku API 17+):

adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0

Jeśli biegle władasz Gradlem, możesz równie dobrze utworzyć specjalne do tego zadanie i powiązać je przed fazą testowania:

task disableAnimations(type: Exec) {
    def adb = "$System.env.ANDROID_HOME/platform-tools/adb"
    commandLine "$adb", 'shell', 'settings', 'put', 'global', 'window_animation_scale', '0'
    commandLine "$adb", 'shell', 'settings', 'put', 'global', 'transition_animation_scale', '0'
    commandLine "$adb", 'shell', 'settings', 'put', 'global', 'animator_duration_scale', '0'
}

project.gradle.taskGraph.whenReady {
    connectedDebugAndroidTest.dependsOn disableAnimations
}

Istnieje także specjalny parametr, który można ustawić w build.gradle (parametr działa w przypadku uruchamiania z poziomu Gradle'a):

android {
  testOptions {
    animationsDisabled = true
  }
}

Ghostbuster91 wykonał pewne testy (z powodu słabej dokumentacji) i wygląda na to, że działa on na większości API, ale niestety nie jest to złote rozwiązanie.

Java

Jeśli chcesz wyłączyć animacje tylko dla wybranych testów, możesz to zrobić, implementując własną regułę testową lub po prostu zaimportować ją z tej biblioteki (API 21+) na licencji Apache 2.0. Istnieją również inne rozwiązania (API 21+), które wymagają uprawnień SET_ANIMATION_SCALE. Ostatecznie mamy również do dyspozycji specjalną bibliotekę TestButler (API 14+), która specjalizuje się w tej tematyce. Niestety sama w sobie wymaga czystego emulatora (np. bez Google API).

Powyższe rozwiązania mogą być ciągle zawodne, np. możemy mieć problem w przypadku niestandardowych animacji:

View.startAnimation(AnimationUtils.loadAnimation(this, R.anim.blink));

W takim przypadku możemy ratować się stworzeniem niestandardowego wariantu testowego bez animacji, z podmienionymi zasobami lub warunkując wykonanie naszego kodu od zmiennych ustawianych w fazie kompilacji. Nie jest to jednak zbyt eleganckie i w takim momencie nasze testy zaczynają budzić wątpliwości.

Ostatnia deska ratunku

Caused by: androidx.test.espresso.AppNotIdleException: Looped for 1218 iterations over 60 SECONDS. The following Idle Conditions failed .

W zależności od sposobu implementacji animacji (android.animation/android.view.animation/zewnętrzne biblioteki animacji) wyeliminowanie komunikatu "Animacje lub przejścia są włączone na urządzeniu docelowym", niekoniecznie będzie skutkować rozwiązaniem powyższego problemu. Animacje mogą nadal utrzymywać wątek UI w stanie pracy i w takim wypadku Espresso będzie czekać w nieskończoność, a właściwie przez określony czas, co zakończy się niepowodzeniem testu.

Tonący brzytwy się chwyta, jednak w tym przypadku rozwiązanie nie jest aż tak bolesne. Aby nasze testy przeszły z powodzeniem, wystarczy przełączyć się z biblioteki Espresso na bibliotekę UIAutomator (API 18+) i ręcznie ustalić okres oczekiwania na załadowanie się widoku:

//onView(withId(R.id.action_search)).perform(click())
UiDevice device = UiDevice.getInstance(getInstrumentation());
device.wait(By.res("io.github.t3r1jj.pbmap:id/action_search"), TIMEOUT_MS).click();

UIAutomator jest bardziej liberalny pod względem blokowania się przez animacje, ale jednocześnie wymaga wyraźnej synchronizacji od programisty. W porównaniu do Espresso otrzymujemy większą kontrolę w zamian za większą odpowiedzialność przy tworzeniu poprawnych testów.