Espresso – Testing RecyclerViews at Specific Positions

My team recently added a RecyclerView to a screen in an Android app we’re working on. It’s a horizontal view that allows a user to scroll left and right to see content that’s offscreen. One of the challenges we’ve faced while working on this view has been testing it in our Espresso tests—specifically, testing the contents of items at certain positions. In this post, I’ll show you an Espresso matcher that can be used to aid in testing RecyclerViews.

RecyclerViewActions

The espresso-contrib library provides a RecyclerViewActions class that offers a way to click on a specific position in a RecyclerView (see instructions on configuring espress-contrib).


// Click item at position 3
onView(withId(R.id.scroll_view))
    .perform(RecyclerViewActions.actionOnItemAtPosition(3, click()));

This is great for simulating a user who is tapping on an item in the RecyclerView. But most of the tests I wanted to write had to do with verifying the contents of an item at a specific position. Just seeing that some content was on the screen wasn’t good enough. I needed to verify that certain content was showing up in position 0 or position 1, etc.

RecyclerViewMatcher

Using the RecyclerViewMatcher, you can not only perform actions on an item at a specific position in a RecyclerView, but also check that some content is contained within a descendant of a specific item.


// Convenience helper
public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
    return new RecyclerViewMatcher(recyclerViewId);
}

// Check item at position 3 has "Some content"
onView(withRecyclerView(R.id.scroll_view).atPosition(3))
    .check(matches(hasDescendant(withText("Some content"))));

// Click item at position 3
onView(withRecyclerView(R.id.scroll_view).atPosition(3)).perform(click());

Testing Off-Screen Items

The RecyclerViewMatcher matcher worked great until we tried testing items that were somewhere off-screen.

Our first improvement was to force the RecyclerView to scroll so that the desired position was on-screen. The RecyclerViewActions mentioned above provide a scrollToPosition action that can do just that. Here’s a helper for tapping on a specific position that first scrolls and then taps:


public static void tapRecyclerViewItem(int recyclerViewId, int position) {
    onView(withId(recyclerViewId)).perform(scrollToPosition(position));
    onView(withRecyclerView(recyclerViewId).atPosition(position)).perform(click());
}

Doing the same when verifying the content of a RecyclerView item also helped. But Espresso still seemed to have trouble verifying the content of an item whose index was greater than the number of items that could fit on the screen. For example, if four items at a time are visible on-screen, making assertions about the content of the item at position 10 would always fail.

The problem has to do with how the RecyclerViewMatcher retrieves the View at a specific position. It uses the getChildAt(int index)method on RecyclerView class (which is actually inherited from ViewGroup). However, this method only appears to know about the items that are visible in the view. Whatever view is at the top is index 0, the second is index 1, etc.

An alternative is to use the findViewHolderForAdapterPosition(int position) method to locate the view (by way of its ViewHolder) for a specific position. This technique allows you to specify index 32 and have Espresso know which view you’re talking about.

Here’s an updated matchesSafely method for the RecyclerViewMatcher that uses this:


public boolean matchesSafely(View view) {
  this.resources = view.getResources();

  if (childView == null) {
    RecyclerView recyclerView =
        (RecyclerView) view.getRootView().findViewById(recyclerViewId);
    if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
        RecyclerView.ViewHolder viewHolder = 
          recyclerView.findViewHolderForAdapterPosition(position);
        if (viewHolder != null) {
            childView = viewHolder.itemView;
        }
    }
    else {
        return false;
    }
  }

  if (targetViewId == -1) {
    return view == childView;
  } else {
    View targetView = childView.findViewById(targetViewId);
    return view == targetView;
  }
}

And here’s a gist for the entire updated RecycleViewMatcher helper.

Summary

By combining the RecyclerViewActions provided by espresso-contrib with Danny Roa’s RecyclerViewMatcher (slightly modified), it’s possible to write comprehensive Espresso tests for an interface that uses a RecyclerView, including verifying content on items at specific positions.

Note: After writing this post, I noticed that someone had already submitted a pull request to the RecyclerViewMatcher helper with pretty much this same change. It has not been accepted as of yet.

Conversation
  • Aravind says:

    what if I don’t remember my item position in my recyclerview if I had more than 10 items in it.

  • Patrick Bacon Patrick Bacon says:

    Aravind – I’m not sure I understand what you’re asking. Can you clarify?

  • Mohammad Alotol says:

    Thank you PATRICK
    It is really useful
    But How can we check for an item containing “some text” without specifying the position.
    that is how to check if this String exist within any item.

    Thanks again.

  • Koen Van Looveren says:

    No, this doesnt work for me, am I doing something wrong?

    onView(withRecyclerView(R.id.synology_apps_rv)
    .atPositionOnView(7, R.id.not_installed_tv))
    .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));

    android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: be.vanlooverenkoen.automatedsyno.dev:id/synology_apps_rv

    View Hierarchy:
    +>DecorView{id=-1, visibility=VISIBLE, width=1080, height=1920, has-focus=true, 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=3}
    |
    +->LinearLayout{id=-1, visibility=VISIBLE, width=1080, height=1794, has-focus=true, 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=2}
    |
    +–>ViewStub{id=16909288, res-name=action_mode_bar_stub, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0}
    |
    +–>FrameLayout{id=-1, visibility=VISIBLE, width=1080, height=1731, has-focus=true, 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=63.0, child-count=1}
    |
    +—>ActionBarOverlayLayout{id=2131427396, res-name=decor_content_parent, visibility=VISIBLE, width=1080, height=1731, has-focus=true, 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=2}
    |
    +—->ContentFrameLayout{id=16908290, res-name=content, visibility=VISIBLE, width=1080, height=1584, has-focus=true, 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=147.0, child-count=1}
    |
    +—–>LinearLayout{id=-1, visibility=VISIBLE, width=1080, height=1584, has-focus=true, 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}
    |
    +——>RecyclerView{id=2131427413, res-name=synology_apps_rv, visibility=VISIBLE, width=1080, height=1584, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=true, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=4}
    |
    +——->LinearLayout{id=-1, visibility=VISIBLE, width=1028, height=438, has-focus=false, has-focusable=false, 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=26.0, y=13.0, child-count=3}
    |
    +——–>AppCompatTextView{id=2131427438, res-name=installed_tv, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>AppCompatTextView{id=2131427439, res-name=not_installed_tv, visibility=VISIBLE, width=387, height=71, has-focus=false, has-focusable=false, 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, text=Not Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>CardView{id=2131427440, res-name=cardview, visibility=VISIBLE, width=1028, height=367, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, 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=71.0, child-count=1}
    |
    +———>RelativeLayout{id=-1, visibility=VISIBLE, width=1014, height=347, has-focus=false, has-focusable=false, 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=7.0, y=10.0, child-count=2}
    |
    +———->AppCompatImageView{id=2131427441, res-name=synology_app_icon, desc=Synolgy App Icon, visibility=VISIBLE, width=263, height=263, has-focus=false, has-focusable=false, 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=42.0, y=42.0}
    |
    +———->AppCompatTextView{id=2131427442, res-name=name_tv, visibility=VISIBLE, width=218, height=71, has-focus=false, has-focusable=false, 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=331.0, y=138.0, text=DS Video, input-type=0, ime-target=false, has-links=false}
    |
    +——->LinearLayout{id=-1, visibility=VISIBLE, width=1028, height=367, has-focus=false, has-focusable=false, 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=26.0, y=477.0, child-count=3}
    |
    +——–>AppCompatTextView{id=2131427438, res-name=installed_tv, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>AppCompatTextView{id=2131427439, res-name=not_installed_tv, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Not Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>CardView{id=2131427440, res-name=cardview, visibility=VISIBLE, width=1028, height=367, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, 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}
    |
    +———>RelativeLayout{id=-1, visibility=VISIBLE, width=1014, height=347, has-focus=false, has-focusable=false, 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=7.0, y=10.0, child-count=2}
    |
    +———->AppCompatImageView{id=2131427441, res-name=synology_app_icon, desc=Synolgy App Icon, visibility=VISIBLE, width=263, height=263, has-focus=false, has-focusable=false, 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=42.0, y=42.0}
    |
    +———->AppCompatTextView{id=2131427442, res-name=name_tv, visibility=VISIBLE, width=220, height=71, has-focus=false, has-focusable=false, 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=331.0, y=138.0, text=DS Audio, input-type=0, ime-target=false, has-links=false}
    |
    +——->LinearLayout{id=-1, visibility=VISIBLE, width=1028, height=367, has-focus=false, has-focusable=false, 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=26.0, y=870.0, child-count=3}
    |
    +——–>AppCompatTextView{id=2131427438, res-name=installed_tv, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>AppCompatTextView{id=2131427439, res-name=not_installed_tv, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Not Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>CardView{id=2131427440, res-name=cardview, visibility=VISIBLE, width=1028, height=367, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, 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}
    |
    +———>RelativeLayout{id=-1, visibility=VISIBLE, width=1014, height=347, has-focus=false, has-focusable=false, 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=7.0, y=10.0, child-count=2}
    |
    +———->AppCompatImageView{id=2131427441, res-name=synology_app_icon, desc=Synolgy App Icon, visibility=VISIBLE, width=263, height=263, has-focus=false, has-focusable=false, 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=42.0, y=42.0}
    |
    +———->AppCompatTextView{id=2131427442, res-name=name_tv, visibility=VISIBLE, width=111, height=71, has-focus=false, has-focusable=false, 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=331.0, y=138.0, text=Chat, input-type=0, ime-target=false, has-links=false}
    |
    +——->LinearLayout{id=-1, visibility=VISIBLE, width=1028, height=367, has-focus=false, has-focusable=false, 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=26.0, y=1263.0, child-count=3}
    |
    +——–>AppCompatTextView{id=2131427438, res-name=installed_tv, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>AppCompatTextView{id=2131427439, res-name=not_installed_tv, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Not Installed, input-type=0, ime-target=false, has-links=false}
    |
    +——–>CardView{id=2131427440, res-name=cardview, visibility=VISIBLE, width=1028, height=367, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, 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}
    |
    +———>RelativeLayout{id=-1, visibility=VISIBLE, width=1014, height=347, has-focus=false, has-focusable=false, 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=7.0, y=10.0, child-count=2}
    |
    +———->AppCompatImageView{id=2131427441, res-name=synology_app_icon, desc=Synolgy App Icon, visibility=VISIBLE, width=263, height=263, has-focus=false, has-focusable=false, 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=42.0, y=42.0}
    |
    +———->AppCompatTextView{id=2131427442, res-name=name_tv, visibility=VISIBLE, width=163, height=71, has-focus=false, has-focusable=false, 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=331.0, y=138.0, text=DS Get, input-type=0, ime-target=false, has-links=false}
    |
    +—->ActionBarContainer{id=2131427397, res-name=action_bar_container, visibility=VISIBLE, width=1080, height=147, has-focus=false, has-focusable=false, 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=2}
    |
    +—–>Toolbar{id=2131427398, res-name=action_bar, visibility=VISIBLE, width=1080, height=147, has-focus=false, has-focusable=false, 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=3}
    |
    +——>AppCompatTextView{id=-1, visibility=VISIBLE, width=351, height=71, has-focus=false, has-focusable=false, 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=189.0, y=38.0, text=Synology Apps, input-type=0, ime-target=false, has-links=false}
    |
    +——>AppCompatImageButton{id=-1, desc=Navigate up, visibility=VISIBLE, width=147, height=147, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0}
    |
    +——>ActionMenuView{id=-1, visibility=VISIBLE, width=0, height=147, has-focus=false, has-focusable=false, 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=1080.0, y=0.0, child-count=0}
    |
    +—–>ActionBarContextView{id=2131427399, res-name=action_context_bar, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=0}
    |
    +->View{id=16908336, res-name=navigationBarBackground, visibility=VISIBLE, width=1080, height=126, has-focus=false, has-focusable=false, 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=1794.0}
    |
    +->View{id=16908335, res-name=statusBarBackground, visibility=VISIBLE, width=1080, height=63, has-focus=false, has-focusable=false, 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}
    |
    at dalvik.system.VMStack.getThreadStackTrace(Native Method)
    at java.lang.Thread.getStackTrace(Thread.java:1566)
    at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:92)
    at android.support.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:56)
    at android.support.test.espresso.ViewInteraction.runSynchronouslyOnUiThread(ViewInteraction.java:184)
    at android.support.test.espresso.ViewInteraction.check(ViewInteraction.java:158)
    at be.vanlooverenkoen.automatedsyno.settings.ui.TestSynologyAppActivity.testSynologyAppList(TestSynologyAppActivity.java:30)
    at java.lang.reflect.Method.invoke(Native Method)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at android.support.test.internal.statement.UiThreadStatement.evaluate(UiThreadStatement.java:55)
    at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:270)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:27)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
    at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:262)
    at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1932)

    • Patrick Bacon Patrick Bacon says:

      Koen,

      Unfortunately nothing obvious jumps out as wrong in your usage. I will say that I’ve never used the atPositionOnView version, only atPosition and then used hasDescendant in the matcher. I don’t know if it will make any difference, but I might try something like this just to see:

      
      onView(withRecyclerView(R.id.synology_apps_rv)
      .atPosition(7))
      .check(matches(hasDescendant(allOf(
        withId(R.id.not_installed_tv ), 
        withEffectiveVisibility(ViewMatchers.Visibility.GONE)))));
      

      Oh, and if item 7 is off screen don’t forget to scroll before you do the check:

      onView(withId(R.id.synology_apps_rv)).perform(scrollToPosition(7));
  • Mohanraj.S says:

    Hi Mr.Patrick,
    Happy new year, Let me share my query, In my present project, i have implemented view-pager with 3 tabs, On my first tab required design to show first position i always show cover image of user, i tried to implement recycler view but not working perfectly, so place nested scrollview as parent, and Listview as child, on top of listview i have image view to show cover image, can u please gimme suggestion about the design using Recyclerview. I know this is not related the your blog, but i’m help less. if i post my query in stack overflow, they down voted and closed my question. Thankyou. Sorry if anything i asked anonymously.

  • Satyajit Malugu says:

    Have you tried the latest contrib repo – https://developer.android.com/reference/android/support/test/espresso/contrib/RecyclerViewActions.html and does it still repro your problem?

  • Eric Fung says:

    As of 2016-07-12, that PR (https://github.com/dannyroa/espresso-samples/pull/1) has been merged.

  • Damià says:

    This has no sense when you have more than one recycler view with the same id.

  • Goutham says:

    Hi is there any possible to test the recyclerview that create dynamically like
    Recyclerview rv = new RecyclerView();

  • Comments are closed.