12

Is there a general approach for scrolling to non-list View items that are not yet visible on the screen?

Without any precautions, Espresso will indicate that "No Views in hierarchy found matching with id .....

I found this answer ... is this the best approach?

onView( withId( R.id.button)).perform( scrollTo(), click());
tm1701
  • 7,307
  • 17
  • 79
  • 168

4 Answers4

24

According to the scrollTo JavaDoc, to use the code you specified ( onView( withId( R.id.button)).perform( scrollTo(), click()); ), the preconditions are: "must be a descendant of ScrollView" and "must have visibility set to View.VISIBLE". If that is the case, then that will work just fine.

If it is in an AdapterView, then you should use onData instead. In some cases, you may have to implement the AdapterViewProtocol, if your AdapterView is not well behaved.

If it is neither in an AdapterView nor a child of a ScrollView, then you would have to implement a custom ViewAction.

JJD
  • 50,076
  • 60
  • 203
  • 339
yogurtearl
  • 3,035
  • 18
  • 24
  • 2
    "it is neither in an AdapterView nor a child of a ScrollView" eg. NestedScrollView you can add Barista dependency and use fallowing method: scrollTo(R.id.); https://github.com/SchibstedSpain/Barista#baristas-assertions-api – Kunami Mar 07 '18 at 10:42
  • In fact it is slightly better than only ScrollView's. Looking at the implementation of the ScrollToAction (the ViewAction returned by scrollTo()), HorizontalScrollView's and ListView's are also supported. https://git.io/fh7LO – Jelle Fresen Feb 13 '19 at 16:23
4

If you have a view inside android.support.v4.widget.NestedScrollView instead of scrollView scrollTo() does not work.

In order to work you need to create a class that implements ViewAction just like ScrollToAction but allows NestedScrollViews:

public Matcher<View> getConstraints() {
    return allOf(
        withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), 
        isDescendantOfA(anyOf(
            isAssignableFrom(ScrollView.class), 
            isAssignableFrom(HorizontalScrollView.class), 
            isAssignableFrom(NestedScrollView.class))
        )
    );
}

extra tip and access the action like:

public static ViewAction betterScrollTo() {
    return actionWithAssertions(new AllScrollViewsScrollToAction());
}

But with this scroll it does not trigger events from the layout managers.

JJD
  • 50,076
  • 60
  • 203
  • 339
Bruno Oliveira
  • 255
  • 1
  • 8
1

The code onView( withId( R.id.button)).perform( scrollTo(), click()); will work if the view is descendant of ScrollView, HorizontalScrollView or ListView.

If we have NestedScrollView instead of ScrollView and for those who don't want to look into ScrollToAction class code I wrote the sample.

As Bruno Oliveira said we can do something like this:

class ScrollToActionImproved : ViewAction {

override fun getConstraints(): Matcher<View> {
    return allOf(
        withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
        isDescendantOfA(
            anyOf(
                isAssignableFrom(ScrollView::class.java),
                isAssignableFrom(HorizontalScrollView::class.java),
                isAssignableFrom(NestedScrollView::class.java)
            )
        )
    )
}

override fun getDescription(): String = "scroll to view"

override fun perform(uiController: UiController?, view: View?) {
    if (isDisplayingAtLeast(90).matches(view)) {
        //View is already displayed
        return
    }
    val rect = Rect()
    view!!.getDrawingRect(rect)
    if (!view.requestRectangleOnScreen(rect, true)) {
        //Scrolling to view was requested, but none of the parents scrolled.
    }
    uiController!!.loopMainThreadUntilIdle()
    if (!isDisplayingAtLeast(90).matches(view)) {
        throw PerformException.Builder()
            .withActionDescription(this.description)
            .withViewDescription(HumanReadables.describe(view))
            .withCause(
                RuntimeException(
                    "Scrolling to view was attempted, but the view is not displayed"
                )
            )
            .build()
    }
}

}

And use it like this:

fun scrollToImproved(): ViewAction =
    actionWithAssertions(ScrollToActionImproved())

/* some logic */

onView(withId(R.id.button)).perform(scrollToImproved())

It should work.

SimpleX
  • 46
  • 3
0

Code that worked for me is:

ViewInteraction tabView = onView(allOf(
    childAtPosition(childAtPosition(withId(R.id.bottomControlTabView), 0), 1), 
    isDisplayed()));
tabView.perform(click());
tabView.perform(click());

public static Matcher<View> childAtPosition(final Matcher<View> parentMatcher, 
                                            final int position) {

        return new TypeSafeMatcher<View>() {
            @Override
            public void describeTo(Description description) {
                description.appendText("Child at position " + position + " in parent ");
                parentMatcher.describeTo(description);
            }

            @Override
            public boolean matchesSafely(View view) {
                ViewParent parent = view.getParent();
                return parent instanceof ViewGroup && parentMatcher.matches(parent)
                        && view.equals(((ViewGroup) parent).getChildAt(position));
            }
        };
    }
JJD
  • 50,076
  • 60
  • 203
  • 339
Tarkik
  • 178
  • 1
  • 11