如何用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
相关产品推荐
相关产品推荐

