使用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回收机制导致元素找不到:
- 先获取外层RecyclerView(RecyclerView1)的子项总数
- 遍历外层每个位置的子项:
- 调用
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
相关产品推荐
相关产品推荐

