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}
//...
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()));
What I did, was to set different content description for each fragment, and Espresso supports hasContentDescription()
and withContentDescription()
...
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With