Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Espresso Recylerview in ViewPager match multiple views

How do I target a Viewpager with a Fragment that implements a RecylerView with an ID matching multiple views?

I have a MainActivity which has a Viewpager. The Viewpager has 5 tabs. In those 5 tabs, I use RecylerViews to load images in each of them.

The RecylerView XML is reused in different Fragments, so when accessing it with Espresso it keeps complaining ID matches multiple views.

<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

My RecylerView doesn't load any text, only loads images, so I can't even do a withText("Text here"). I can't use onData() for RecylerViews neither.

@RunWith(AndroidJUnit4.class)
@LargeTest
public class ExampleTest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
            MainActivity.class);

    Activity activity = mActivityRule.getActivity();

    @Test
    public void ExampleMethod() {

        // perform a swipe
        onView(withId(R.id.viewpager)).perform(swipeLeft());  

       // try to click on one of the recycler view items.
       // this crashes:
       onView(withId(R.id.recycler_view))
           .perform(RecyclerViewActions.actionOnItemAtPosition(2, click()));

    }
}

So it crashes and points out an error

android.support.test.espresso.AmbiguousViewMatcherException: 'with id: com.example.app:id/recycler_view' matches multiple views in the hierarchy. Problem views are marked with '****MATCHES****' below.

//List different hierarchies

View Hierarchy:

View Hierarchy:
+>DecorView{id=-1, visibility=VISIBLE, width=720, height=1280, 
  has-focus=false, has-focusable=true, has-window-focus=true, 
  is-clickable=false, is-enabled=true, is-focused=false, 
  is-focusable=false, is-layout-requested=false, is-selected=false, 
  root-is-layout-requested=false, has-input-connection=false, x=0.0, 
  y=0.0, child-count=1}

//...
like image 781
Tosin Onikute Avatar asked Oct 11 '16 13:10

Tosin Onikute


3 Answers

I ran into the same issue when using a ViewPager, and adding the isDisplayed() clause to the ViewMatcher solved it for me (though, as with everything in Espresso, it can be flaky at times)...

onView(allOf(isDisplayed(), withId(R.id.recycler_view)))
    .perform(RecyclerViewActions.actionOnItemAtPosition(2, click()));
like image 171
Demonsoul Avatar answered Oct 09 '22 22:10

Demonsoul


What I did, was to set different content description for each fragment, and Espresso supports hasContentDescription() and withContentDescription()...

like image 37
phq Avatar answered Oct 09 '22 21:10

phq


I've solved this with such a workaround that the only actually visible item has width and height that exactly match values designated in corresponding layout. All other view pager items have default dimensions.

So I've created a simple matcher:

private val sizeMatcher = object : TypeSafeMatcher<View>(){
    override fun describeTo(description: Description?) {
        description?.appendText("with expected size")
    }
    override fun matchesSafely(item: View?): Boolean {
        return item != null && (item.height > DEFAULT_HEIGHT || item.width > DEFAULT_WIDTH)
    } 
}

Where default values may vary for different views

Next, I've added this matcher in check expression:

fun checkDisplayed(viewId: Int) {
    Espresso.onView(Matchers.allOf(ViewMatchers.withId(viewId), ViewMatchers.isDisplayingAtLeast(80), sizeMatcher)).check(ViewAssertions.matches(ViewMatchers.isDisplayingAtLeast(80)))
}

Displaying threshold is so small (80) to pass cases when view pager has just swiped and Espresso doesn't wait for it to scroll view to the screen's border and display it completely

like image 42
Max Elkin Avatar answered Oct 09 '22 21:10

Max Elkin