You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Espresso点击RecyclerView中显示特定View的条目

问题诊断与解决方案

核心问题分析

从异常Found 0 items matching holder with view: has child: with text: is "Open"来看,核心问题是匹配器未正确识别OPEN状态的店铺条目,可能原因包括:

  • 自定义withOpenText匹配器中,直接使用Android原生isVisible判断可见性,和Espresso的显示判断逻辑不一致(Espresso会检查View是否在屏幕内且真正可交互)
  • actionOnItem里的withChild(withText("Open"))匹配器,可能因文本大小写、空格或动态加载问题导致匹配失败
  • 分开执行scrollToHolder和actionOnItem,两次操作的匹配逻辑不统一,导致状态不一致

解决方案1:修复匹配器并合并操作

直接使用统一的自定义匹配器,合并滚动与点击操作,确保逻辑一致:

@Test
fun openShopTest() {
    // 检查页面与RecyclerView可见性
    onView(withText("Shops")).check(matches(isDisplayed()))
    onView(withId(R.id.rvShops)).check(matches(isDisplayed()))

    // 滚动到第一个OPEN店铺并点击
    onView(withId(R.id.rvShops))
        .perform(RecyclerViewActions.actionOnHolderItem<ListAdapter.ListViewHolder>(
            first(withOpenText()), click()
        ))

    // 验证详情页显示
    onView(withText("Details")).check(matches(isDisplayed()))
}

// 修复自定义匹配器:使用Espresso标准判断逻辑
fun withOpenText(): Matcher<RecyclerView.ViewHolder> {
    return object : BoundedMatcher<RecyclerView.ViewHolder, ListAdapter.ListViewHolder>(
        ListAdapter.ListViewHolder::class.java
    ) {
        override fun describeTo(description: Description) {
            description.appendText("ViewHolder with visible Open status")
        }

        override fun matchesSafely(item: ListAdapter.ListViewHolder): Boolean {
            val openTextView = item.itemView.findViewById<TextView>(R.id.openTextView)
            // 用Espresso的isDisplayed()判断,而非Android原生isVisible
            return openTextView != null && ViewMatchers.isDisplayed().matches(openTextView)
        }
    }
}

解决方案2:获取OPEN店铺位置并使用atPosition点击

如果需要明确获取条目位置,可自定义ViewAction获取匹配位置后执行点击:

// 测试用例
@Test
fun openShopTest() {
    onView(withText("Shops")).check(matches(isDisplayed()))
    onView(withId(R.id.rvShops)).check(matches(isDisplayed()))

    // 获取第一个OPEN店铺的位置并滚动到该位置
    val positionHolder = object { var pos = -1 }
    onView(withId(R.id.rvShops)).perform(object : ViewAction {
        override fun getConstraints(): Matcher<View> = isAssignableFrom(RecyclerView::class.java)
        override fun getDescription(): String = "get open shop position"
        override fun perform(uiController: UiController, view: View) {
            val rv = view as RecyclerView
            val adapter = rv.adapter as ListAdapter
            // 根据业务数据模型判断OPEN状态,这里假设数据类有status字段
            positionHolder.pos = adapter.currentList.indexOfFirst { it.status == "OPEN" }
            rv.scrollToPosition(positionHolder.pos)
            // 等待UI线程处理完成
            uiController.loopMainThreadUntilIdle()
        }
    })

    // 点击对应位置的条目
    onView(withId(R.id.rvShops))
        .perform(RecyclerViewActions.actionOnItemAtPosition<ListAdapter.ListViewHolder>(positionHolder.pos, click()))

    onView(withText("Details")).check(matches(isDisplayed()))
}

关键注意事项

  • 确保数据模型的状态判断逻辑与业务代码一致(比如item.status == "OPEN"要和实际业务字段匹配)
  • 优先用withId(R.id.openTextView)结合isDisplayed()匹配,避免因文本国际化、大小写问题导致匹配失败
  • 执行滚动或数据操作后,务必调用uiController.loopMainThreadUntilIdle()等待UI线程处理完成

内容的提问来源于stack exchange,提问作者Nayab

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:05:26