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

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

Просто добавьте спецификацию onChildView() в вашу реализацию DataInteraction :
Котлин
onData(withItemContent("item: 60")) .onChildView(withId(R.id.item_size)) .perform(click())
Ява
onData(withItemContent("item: 60")) .onChildView(withId(R.id.item_size)) .perform(click());
Взаимодействие с элементами списка просмотра переработчика
Объекты RecyclerView работают иначе, чем объекты AdapterView , поэтому onData() нельзя использовать для взаимодействия с ними.
Чтобы взаимодействовать с RecyclerViews с помощью Espresso, вы можете использовать пакет espresso-contrib , в котором есть коллекция RecyclerViewActions , которую можно использовать для прокрутки к позициям или выполнения действий над элементами:
-
scrollTo()— прокручивает до соответствующего представления, если оно существует. -
scrollToHolder()— прокручивает до соответствующего держателя представления, если он существует. -
scrollToPosition()— Прокручивает до определенной позиции. -
actionOnHolderItem()— выполняет действие просмотра для соответствующего держателя представления. -
actionOnItem()— выполняет действие просмотра для соответствующего представления. -
actionOnItemAtPosition()— выполняет ViewAction для представления в определенной позиции.
В следующих фрагментах представлены некоторые примеры из образца RecyclerViewSample :
Котлин
@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")) ) ) }
Ява
@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")) )); }
Котлин
@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())) }
Ява
@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())); }
Котлин
@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())) }
Ява
@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.