【问题标题】:AmbiguousViewMatcherException multiple RecyclerViewsAmbiguousViewMatcherException 多个 RecyclerViews
【发布时间】:2025-11-28 00:05:01
【问题描述】:

我有一个包含RecyclerViewFragment。但是由于我在这个Fragment 中有很多元素,我想向上滑动列表来查看并检查这个Fragment 中的所有元素。

以前这种方法对我有帮助,但现在由于某种原因它不起作用:

Espresso.onView(ViewMatchers.withId(R.id.recyclerView)).perform(ViewActions.swipeUp())

我的项目中有很多 RecyclerViews 和相同的 id

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical"/>

在我的测试中我也写过这样的东西:

onView(allOf( withId(R.id.recyclerView), isDisplayed()))
onView(withId(R.id.recyclerView)).perform(swipeUp())

但只在第二行发现错误。

android.support.test.espresso.AmbiguousViewMatcherException: 'with id: com.fentury.android:id/recyclerView' 匹配层次结构中的多个视图。 问题视图在下方标有“**** MATCHES****”。

【问题讨论】:

  • 澄清 “不起作用” 应该是什么意思。显示您的视图层次结构,以便我们了解可能出了什么问题。

标签: android android-fragments android-recyclerview swipe android-espresso


【解决方案1】:

您的视图层次结构中有多个 ID 为 R.id.recyclerView 的视图,因此 espresso 无法执行正确的匹配。使 ids 中的 RecyclerViews 独一无二。


onView(allOf(withId(R.id.recyclerView), isDisplayed())) onView(withId(R.id.recyclerView)).perform(swipeUp())

但只在第二行发现错误。

然后这样进行匹配:

onView(allOf(withId(R.id.recyclerView), isDisplayed())).perform(swipeUp())

【讨论】:

  • 不好的建议,因为在我的应用程序中这个 recyclerView 在很多地方都用到了。
  • 那么你必须使用some other criteria进行匹配,即allOf(withParent(withId(R.id.parentId)), withId(R.id.recyclerView))或其他一些组合。
【解决方案2】:

解决办法:

onView(allOf(withId(R.id.recyclerview), isDisplayed())).perform(swipeUp());

【讨论】:

    【解决方案3】:

    您应该将数据用于回收站视图,您可以在其中使用回收站视图的 id 以及它保存的数据类型进行断言。这应该有助于假设不同的回收器视图不会具有相同类型的数据,但更好的做法是根据其用途为不同的视图使用不同的 id。

    您可能还想使用 perform(ViewActions.scrollTo())

    【讨论】:

    • 有时您希望为两个不同的片段重用相同的布局文件。然后你有相同的ID。
    【解决方案4】:

    当我发现这个问题时,我在 ViewPager 内的两个片段中遇到了多个 RecyclerViews 的类似问题。 两个片段使用相同的布局文件,仅包含 id = @id/list 的 RecyclerView。

    由于没有匹配的父级,我制作了这个自定义 ViewMatcher 来匹配 Adapter-Class 的列表:(Kotlin)

    fun hasAdapterOfType(T: Class<out RecyclerView.Adapter<out RecyclerView.ViewHolder>>): BoundedMatcher<View, RecyclerView> {
    
        return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
            override fun describeTo(description: Description) {
                description.appendText("RecycleView adapter class matches " + T.name)
            }
    
            override fun matchesSafely(view: RecyclerView): Boolean {
                return (view.adapter.javaClass.name == T.name)
            }
        }
    }
    

    这样的用法:

    onView(allOf(withId(list), hasAdapterOfType(AccessAttemptListAdapter::class.java))).check(blabla)
    

    【讨论】: