Espresso oferuje mechanizmy przewijania do konkretnego elementu lub wykonywania na nim czynności w przypadku 2 typów list: widoków adaptera i widoków Recykling.
W przypadku list, zwłaszcza utworzonych za pomocą obiektu RecyclerView
lub AdapterView
, widok, który Cię interesuje, może w ogóle nie znajdować się na ekranie, ponieważ wyświetla się tylko niewielka liczba dzieci, które są ponownie wykorzystywane podczas przewijania. W tym przypadku nie można użyć metody scrollTo()
, ponieważ wymaga ona istniejącego widoku.
Interakcja z elementami listy widoku adaptera
Zamiast korzystać z metody onView()
, rozpocznij wyszukiwanie od parametru onData()
i podaj dopasowanie do danych opartych na widoku, który chcesz dopasować.
Espresso znajdzie wiersz w obiekcie Adapter
i umieści go w widocznym obszarze.
Dopasowanie danych za pomocą dopasowania widoku niestandardowego
Poniższa aktywność zawiera element ListView
, na którym opiera się SimpleAdapter
, który przechowuje dane z poszczególnych wierszy w obiekcie Map<String, Object>
.
Każda mapa ma 2 wpisy: klucz "STR"
zawierający ciąg znaków, np. "item: x"
, i klucz "LEN"
zawierający wartość Integer
, która określa długość treści. Na przykład:
{"STR" : "item: 0", "LEN": 7}
Kod kliknięcia w wierszu z wartością „item: 50” wygląda tak:
Kotlin
onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo("STR"), `is`("item: 50")))).perform(click())
Java
onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50")))) .perform(click());
Pamiętaj, że Espresso przewija listę automatycznie w razie potrzeby.
Rozróżnimy Matcher<Object>
w środku onData()
. Metoda is(instanceOf(Map.class))
zawęża wyszukiwanie do dowolnego elementu właściwości AdapterView
, który jest obsługiwany przez obiekt Map
.
W naszym przypadku ten aspekt zapytania pasuje do każdego wiersza widoku listy, a my chcemy kliknąć określony element, więc możemy zawęzić wyszukiwanie za pomocą:
Kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))
Java
hasEntry(equalTo("STR"), is("item: 50"))
Ten obiekt Matcher<String, Object>
będzie pasował do każdej mapy zawierającej wpis z kluczem "STR"
i wartością "item: 50"
. Kod, który należy wyszukać, jest długi, a chcemy wykorzystać go ponownie w innych lokalizacjach, więc napiszmy niestandardowe dopasowanie parametru withItemContent
:
Kotlin
return object : BoundedMatcher<Object, Map>(Map::class.java) { override fun matchesSafely(map: Map): Boolean { return hasEntry(equalTo("STR"), itemTextMatcher).matches(map) } override fun describeTo(description: Description) { description.appendText("with item content: ") itemTextMatcher.describeTo(description) } }
Java
return new BoundedMatcher<Object, Map>(Map.class) { @Override public boolean matchesSafely(Map map) { return hasEntry(equalTo("STR"), itemTextMatcher).matches(map); } @Override public void describeTo(Description description) { description.appendText("with item content: "); itemTextMatcher.describeTo(description); } };
Używasz BoundedMatcher
jako podstawy, ponieważ dopasowujesz tylko obiekty typu Map
. Zastąp metodę matchesSafely()
, umieszczając wcześniej znaleziony odpowiednik i dopasowując go do obiektu Matcher<String>
, który można przekazać jako argument. Dzięki temu możesz zadzwonić pod numer withItemContent(equalTo("foo"))
. Aby zapewnić przejrzystość kodu, możesz utworzyć kolejną funkcję dopasowywania, która wywołuje już equalTo()
i akceptuje obiekt String
:
Kotlin
fun withItemContent(expectedText: String): Matcher<Object> { checkNotNull(expectedText) return withItemContent(equalTo(expectedText)) }
Java
public static Matcher<Object> withItemContent(String expectedText) { checkNotNull(expectedText); return withItemContent(equalTo(expectedText)); }
Teraz kod do klikania elementu jest prosty:
Kotlin
onData(withItemContent("item: 50")).perform(click())
Java
onData(withItemContent("item: 50")).perform(click());
Pełny kod tego testu znajdziesz w metodzie testClickOnItem50()
w klasie AdapterViewTest
oraz tym niestandardowym module dopasowywania LongListMatchers
na GitHubie.
Dopasuj do określonego widoku podrzędnego
W przykładzie powyżej kliknięcie znajduje się w środku całego wiersza wartości ListView
.
A co, jeśli chcemy wykonać działanie na określonym elemencie podrzędnym wiersza? Na przykład chcemy kliknąć drugą kolumnę w wierszu LongListActivity
, która wyświetli wartość String.length treści z pierwszej kolumny:
Wystarczy dodać specyfikację onChildView()
do implementacji DataInteraction
:
Kotlin
onData(withItemContent("item: 60")) .onChildView(withId(R.id.item_size)) .perform(click())
Java
onData(withItemContent("item: 60")) .onChildView(withId(R.id.item_size)) .perform(click());
Interakcja z elementami listy w widoku Recyklingu
Obiekty RecyclerView
działają inaczej niż obiekty AdapterView
, więc nie można używać onData()
do interakcji.
Aby korzystać z obiektów RecyclerView za pomocą Espresso, możesz użyć pakietu espresso-contrib
zawierającego kolekcję RecyclerViewActions
, których można używać do przewijania elementów lub wykonywania na nich działań:
scrollTo()
– przewija do pasującego widoku (jeśli istnieje).scrollToHolder()
– przewija widok do pasującego właściciela widoku (jeśli istnieje).scrollToPosition()
– przewija do określonej pozycji.actionOnHolderItem()
– wykonuje działanie związane z wyświetleniem na pasującego posiadaczu wyświetleń.actionOnItem()
– wykonuje działanie związane z wyświetleniem dopasowanego widoku.actionOnItemAtPosition()
– wykonuje działanie typu ViewAction w odniesieniu do widoku w określonej pozycji.
Poniższe fragmenty kodu zawierają kilka przykładów z przykładu RecyclerViewSample:
Kotlin
@Test(expected = PerformException::class) fun itemWithText_doesNotExist() { // Attempt to scroll to an item that contains the special text. onView(ViewMatchers.withId(R.id.recyclerView)) .perform( // scrollTo will fail the test if no item matches. RecyclerViewActions.scrollTo( hasDescendant(withText("not in the list")) ) ) }
Java
@Test(expected = PerformException.class) public void itemWithText_doesNotExist() { // Attempt to scroll to an item that contains the special text. onView(ViewMatchers.withId(R.id.recyclerView)) // scrollTo will fail the test if no item matches. .perform(RecyclerViewActions.scrollTo( hasDescendant(withText("not in the list")) )); }
Kotlin
@Test fun scrollToItemBelowFold_checkItsText() { // First, scroll to the position that needs to be matched and click on it. onView(ViewMatchers.withId(R.id.recyclerView)) .perform( RecyclerViewActions.actionOnItemAtPosition( ITEM_BELOW_THE_FOLD, click() ) ) // Match the text in an item below the fold and check that it's displayed. val itemElementText = "${activityRule.activity.resources .getString(R.string.item_element_text)} ${ITEM_BELOW_THE_FOLD.toString()}" onView(withText(itemElementText)).check(matches(isDisplayed())) }
Java
@Test public void scrollToItemBelowFold_checkItsText() { // First, scroll to the position that needs to be matched and click on it. onView(ViewMatchers.withId(R.id.recyclerView)) .perform(RecyclerViewActions.actionOnItemAtPosition(ITEM_BELOW_THE_FOLD, click())); // Match the text in an item below the fold and check that it's displayed. String itemElementText = activityRule.getActivity().getResources() .getString(R.string.item_element_text) + String.valueOf(ITEM_BELOW_THE_FOLD); onView(withText(itemElementText)).check(matches(isDisplayed())); }
Kotlin
@Test fun itemInMiddleOfList_hasSpecialText() { // First, scroll to the view holder using the isInTheMiddle() matcher. onView(ViewMatchers.withId(R.id.recyclerView)) .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle())) // Check that the item has the special text. val middleElementText = activityRule.activity.resources .getString(R.string.middle) onView(withText(middleElementText)).check(matches(isDisplayed())) }
Java
@Test public void itemInMiddleOfList_hasSpecialText() { // First, scroll to the view holder using the isInTheMiddle() matcher. onView(ViewMatchers.withId(R.id.recyclerView)) .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle())); // Check that the item has the special text. String middleElementText = activityRule.getActivity().getResources() .getString(R.string.middle); onView(withText(middleElementText)).check(matches(isDisplayed())); }
Dodatkowe materiały
Więcej informacji o używaniu list espresso w testach na Androidzie znajdziesz w tych materiałach.
Próbki
- DataAdapterSample: pokazuje punkt wejścia
onData()
dla Espresso dla list i obiektówAdapterView
.