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

使用Espresso编写嵌套RecyclerView UI测试的正确方法是什么

嵌套RecyclerView Espresso UI测试实现思路

前置准备

首先引入espresso-contrib依赖,该库包含RecyclerView专属操作API,示例依赖配置(Groovy格式):

androidTestImplementation "androidx.test.espresso:espresso-contrib:3.5.1"

核心实现逻辑

1. 封装通用工具方法

先实现两个常用工具Matcher和ViewAction,用于定位元素、获取列表项总数:

  • 定位父布局下第N个子项的Matcher:
fun nthChildOf(parentMatcher: Matcher<View>, childPosition: Int): Matcher<View> {
    return object : TypeSafeMatcher<View>() {
        override fun describeTo(description: Description) {
            description.appendText("第${childPosition}个子项,父布局匹配规则:")
            parentMatcher.describeTo(description)
        }
        override fun matchesSafely(view: View): Boolean {
            val parent = view.parent as? ViewGroup ?: return false
            return parentMatcher.matches(parent) && parent.getChildAt(childPosition) == view
        }
    }
}
  • 获取RecyclerView子项总数的ViewAction:
fun getRecyclerItemCount(resultCallback: (Int) -> Unit): ViewAction {
    return object : ViewAction {
        override fun getConstraints(): Matcher<View> = isAssignableFrom(RecyclerView::class.java)
        override fun getDescription(): String = "读取RecyclerView子项总数"
        override fun perform(uiController: UiController?, view: View) {
            val recycler = view as RecyclerView
            resultCallback.invoke(recycler.adapter?.itemCount ?: 0)
        }
    }
}

2. 分层遍历校验逻辑

按照先外层、后内层的顺序遍历所有子项,每次校验前必须先滚动到对应位置,避免RecyclerView回收机制导致元素找不到:

  1. 先获取外层RecyclerView(RecyclerView1)的子项总数
  2. 遍历外层每个位置的子项:
    • 调用RecyclerViewActions.scrollToPosition将外层列表滚动到当前遍历位置
    • 校验当前外层子项内的TextView1是否可见:
    onView(allOf(
        withId(R.id.text_view1),
        isDescendantOfA(nthChildOf(withId(R.id.recycler_view1), outerPos))
    )).check(matches(isDisplayed()))
    
    • 定位当前外层子项内的内层RecyclerView(RecyclerView2),获取其内部子项总数
    • 遍历内层每个位置的子项:
      • 控制当前内层RecyclerView滚动到对应内层位置:
      onView(allOf(
          withId(R.id.recycler_view2),
          isDescendantOfA(nthChildOf(withId(R.id.recycler_view1), outerPos))
      )).perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(innerPos))
      
      • 校验当前内层子项内的TextView2是否可见:
      onView(allOf(
          withId(R.id.text_view2),
          isDescendantOfA(nthChildOf(
              allOf(
                  withId(R.id.recycler_view2),
                  isDescendantOfA(nthChildOf(withId(R.id.recycler_view1), outerPos))
              ),
              innerPos
          ))
      )).check(matches(isDisplayed()))
      

注意事项

  • 所有控件ID需要替换为你项目内实际对应的ID
  • 如果列表子项存在折叠/展开状态,需要先触发展开操作再校验内层元素
  • 若列表项高度超过一屏,必须执行滚动操作后再做元素匹配,否则会触发NoMatchingViewException

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 23:15:02