Espresso предлагает механизмы для прокрутки до определенного элемента или выполнения действий с ним для двух типов списков: представлений адаптера и представлений утилизатора.
При работе со списками, особенно созданными с помощью объекта RecyclerView или AdapterView, нужного представления может не быть на экране, поскольку отображается лишь небольшое количество дочерних элементов, которые обновляются при прокрутке. Метод scrollTo() в этом случае использовать нельзя, так как для него требуется существующее представление.
Взаимодействие с элементами списка в представлении адаптера
Вместо метода onView() начните поиск с onData() и укажите сопоставитель для данных, которые поддерживают представление, которое вы хотите сопоставить.
Espresso найдет строку в объекте Adapter и сделает элемент видимым в области просмотра.
Как сопоставить данные с помощью специального средства сопоставления представлений
Действие ниже содержит ListView, который поддерживается SimpleAdapter, хранящим данные для каждой строки в объекте Map<String, Object>.

Каждая карта содержит две записи: ключ "STR", содержащий строку, например "item: x", и ключ "LEN", содержащий Integer, который представляет длину контента. Пример:
{"STR" : "item: 0", "LEN": 7}
Код для клика по строке с товаром 50 выглядит так:
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());
Обратите внимание, что Espresso автоматически прокручивает список по мере необходимости.
Давайте разберем Matcher<Object> внутри onData(). Метод is(instanceOf(Map.class)) сужает поиск до любого элемента AdapterView, который поддерживается объектом Map.
В нашем случае этот аспект запроса соответствует каждой строке в списке, но мы хотим нажать на определенный элемент, поэтому сужаем область поиска:
Kotlin
hasEntry(equalTo("STR"), `is`("item: 50"))
Java
hasEntry(equalTo("STR"), is("item: 50"))
Этот Matcher<String, Object> будет соответствовать любой карте, содержащей запись с ключом "STR" и значением "item: 50". Поскольку код для поиска этого элемента длинный и мы хотим повторно использовать его в других местах, давайте напишем для этого собственный сопоставитель 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); } };
В качестве основы используется BoundedMatcher, чтобы сопоставлять только объекты типа Map. Переопределите метод matchesSafely(), добавив в него найденный ранее сопоставитель, и сопоставьте его с Matcher<String>, который можно передать в качестве аргумента. Это позволит вам звонить на номер withItemContent(equalTo("foo")). Чтобы сократить код, можно создать другой сопоставитель, который уже вызывает equalTo() и принимает объект 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)); }
Теперь код для нажатия на элемент выглядит просто:
Kotlin
onData(withItemContent("item: 50")).perform(click())
Java
onData(withItemContent("item: 50")).perform(click());
Полный код этого теста можно найти в методе testClickOnItem50() класса AdapterViewTest и в этом пользовательском сопоставителе LongListMatchers на GitHub.
Соответствие определенному дочернему представлению
В примере выше клик выполняется в середине строки ListView.
Но что, если мы хотим работать с конкретным дочерним элементом строки? Например, мы хотим нажать на второй столбец строки LongListActivity, в котором отображается длина строки контента в первом столбце:

Просто добавьте спецификацию onChildView() в реализацию 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());
Как взаимодействовать с элементами списка RecyclerView
Объекты RecyclerView работают иначе, чем объекты AdapterView, поэтому onData() нельзя использовать для взаимодействия с ними.
Для взаимодействия с RecyclerViews с помощью Espresso можно использовать пакет espresso-contrib, который содержит набор RecyclerViewActions, позволяющих прокручивать до нужных позиций или выполнять действия с элементами:
scrollTo()– прокручивает экран до соответствующего представления, если оно существует.scrollToHolder()– прокручивает до соответствующего держателя представления, если он существует.scrollToPosition()– прокрутка до определенной позиции.actionOnHolderItem()– выполняет действие View Action для соответствующего View Holder.actionOnItem()– выполняет действие с представлением, соответствующим условию.actionOnItemAtPosition()– выполняет ViewAction для представления в определенной позиции.
Ниже приведены примеры из проекта 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())); }
Дополнительные ресурсы
Дополнительную информацию об использовании списков Espresso в тестах Android можно найти в следующих ресурсах.
Образцы
- DataAdapterSample:
демонстрирует точку входа
onData()для Espresso, для списков и объектовAdapterView.