tm1701
tm1701

Reputation: 7601

Android - Espresso - scrolling to a non-list View item

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());

Upvotes: 12

Views: 22427

Answers (4)

SimpleX
SimpleX

Reputation: 66

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.

Upvotes: 1

Tarkik
Tarkik

Reputation: 177

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));
            }
        };
    }

Upvotes: 0

Bruno Oliveira
Bruno Oliveira

Reputation: 265

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.

Upvotes: 4

yogurtearl
yogurtearl

Reputation: 3065

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.

Upvotes: 24

Related Questions