Espresso 列表

Espresso 提供两种机制,用于滚动至特定项目或对其执行操作 列表类型:适配器视图和 recycler 视图。

在处理列表时,特别是使用 RecyclerViewAdapterView 对象,那么您感兴趣的视图可能并未出现在 因为只显示了少量的子项 系统会在您滚动页面时被回收在这种情况下不能使用 scrollTo() 方法 因为它需要使用现有的视图

与适配器视图列表项交互

请不要使用 onView() 方法,而是以 onData() 和 针对支持您想要匹配的视图的数据提供一个匹配器。 Espresso 将完成在 Adapter 对象中查找相应行的所有工作, 让相应项在视口中可见。

使用自定义视图匹配器来匹配数据

以下 activity 包含一个由 SimpleAdapter 支持的 ListView 用于存储 Map<String, Object> 对象中每行的数据。

当前显示在屏幕上的列表 activity 包含一个列表,
          23 项内容。每个项都有一个数字,以字符串形式存储,并映射到
          不同的数字,后者存储为一个对象。

每个映射都有两个条目:一个键 "STR",其中包含一个字符串,例如 "item: x" 和包含 Integer(表示"LEN" 内容的长度。例如:

{"STR" : "item: 0", "LEN": 7}

要点击包含“item: 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 会根据需要自动滚动浏览列表。

接下来,我们拆开 onData() 内的 Matcher<Object>。通过 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 匹配器。

匹配特定子视图

上面的示例会在 ListView 的整行中间发出一次点击。 但是,如果我们要对行的特定子级执行操作,该怎么办呢?例如,我们 想要点击“LongListActivity”行的第二列, ,显示第一列中内容的 String.length:

在此示例中,仅提取
          特定内容。此过程涉及确定
          行第二列的值。

只需将 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 对象的工作方式与 AdapterView 对象不同,因此 “onData()”不能用于与之互动。

要使用 Espresso 与 RecyclerView 交互,您可以使用 espresso-contrib 软件包,其中包含一系列 RecyclerViewActions 可用于滚动至各个位置或对项目执行操作:

  • scrollTo() - 滚动到匹配的视图(如果存在)。
  • scrollToHolder() - 滚动到匹配的视图持有者(如果存在)。
  • scrollToPosition() - 滚动到特定位置。
  • actionOnHolderItem() - 对匹配的视图持有者执行视图操作。
  • actionOnItem() - 对匹配的视图执行视图操作。
  • actionOnItemAtPosition() - 在特定位置对视图执行视图操作。

以下代码段是 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()));
}

其他资源

如需详细了解如何在 Android 测试中使用 Espresso 列表,请查阅 以下资源。

示例

  • DataAdapterSample: 展示 Espresso 中适用于列表和 AdapterViewonData() 入口点 对象的操作。