10

I am trying to test EditTexts in my form that are within a NestedScrollView. I am running the following code:

onView(withId(R.id.register_scroll_view)).perform(scrollTo()).perform(click());

where register_scroll_view is my NestedScrollView. However, I am getting an exception:

android.support.test.espresso.PerformException: Error performing 'scroll to' on view 'with id: com.eazyigz.myapp:id/register_scroll_view'. Caused by: java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints: (view has effective visibility=VISIBLE and is descendant of a: (is assignable from class: class android.widget.ScrollView or is assignable from class: class android.widget.HorizontalScrollView))

How do I properly devise this test so that I can test my EditTexts which need to be scrolled to to become visible?

IgorGanapolsky
  • 23,124
  • 17
  • 109
  • 132

3 Answers3

14

I don't have any experience with NestedScrollView, but it appears that requestRectangleOnScreen(), which is how espresso scrolls in the regular ScrollView, should work with NestedScrollView just the same.

The only problem is that ScrollView constrain is hardcoded into the scrollTo() action and NestedScrollView doesn't inherit the regular ScrollView.

I believe the only solution here is to copy and paste entire ScrollToAction class into your own implementation of this action and replace the pesky constrain.

Be_Negative
  • 4,532
  • 1
  • 27
  • 32
  • 2
    Even copying the [ScrollToAction](https://android.googlesource.com/platform/frameworks/testing/+/android-support-test/espresso/core/src/main/java/android/support/test/espresso/action/ScrollToAction.java) didn't work in my case where I also have NestedScrollView. What worked for me was just simply using `swipeUp()` instead of `scrollTo()` – Wahib Ul Haq Mar 24 '17 at 16:35
  • 1
    @WahibUlHaq you saved my life. Thanks a lot :) – Ale Sep 22 '17 at 08:09
4

I have written single ViewAction for handling scroll to views that are children of NestedScrollView. It also takes into account that CoordinatorLayout might be a root - so you don't need to be afraid of toolbar changing it's size.

There is some code. You need to copy paste this class to your project somewhere. And then you can use it for example like that:

onView(withId(R.id.register_scroll_view))
        .perform(CustomScrollActions.nestedScrollTo, click());

Important: it is not a replacement for scrollTo() it is another scrolling ViewAction that you should use instead in cases when you deal with NestedScrollView.

So there is a ViewAction I was talking about:

public class CustomScrollActions {

    public static ViewAction nestedScrollTo() {
        return new ViewAction() {

            @Override
            public Matcher<View> getConstraints() {
                return Matchers.allOf(
                        isDescendantOfA(isAssignableFrom(NestedScrollView.class)),
                        withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE));
            }

            @Override
            public String getDescription() {
                return "Find parent with type " + NestedScrollView.class +
                        " of matched view and programmatically scroll to it.";
            }

            @Override
            public void perform(UiController uiController, View view) {
                try {
                    NestedScrollView nestedScrollView = (NestedScrollView)
                            findFirstParentLayoutOfClass(view, NestedScrollView.class);
                    if (nestedScrollView != null) {
                        CoordinatorLayout coordinatorLayout =
                                (CoordinatorLayout) findFirstParentLayoutOfClass(view, CoordinatorLayout.class);
                        if (coordinatorLayout != null) {
                            CollapsingToolbarLayout collapsingToolbarLayout =
                                    findCollapsingToolbarLayoutChildIn(coordinatorLayout);
                            if (collapsingToolbarLayout != null) {
                                int toolbarHeight = collapsingToolbarLayout.getHeight();
                                nestedScrollView.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                                nestedScrollView.dispatchNestedPreScroll(0, toolbarHeight, null, null);
                            }
                        }
                        nestedScrollView.scrollTo(0, view.getTop());
                    } else {
                        throw new Exception("Unable to find NestedScrollView parent.");
                    }
                } catch (Exception e) {
                    throw new PerformException.Builder()
                            .withActionDescription(this.getDescription())
                            .withViewDescription(HumanReadables.describe(view))
                            .withCause(e)
                            .build();
                }
                uiController.loopMainThreadUntilIdle();
            }
        };
    }

    private static CollapsingToolbarLayout findCollapsingToolbarLayoutChildIn(ViewGroup viewGroup) {
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View child = viewGroup.getChildAt(i);
            if (child instanceof CollapsingToolbarLayout) {
                return (CollapsingToolbarLayout) child;
            } else if (child instanceof ViewGroup) {
                return findCollapsingToolbarLayoutChildIn((ViewGroup) child);
            }
        }
        return null;
    }

    private static View findFirstParentLayoutOfClass(View view, Class<? extends View> parentClass) {
        ViewParent parent = new FrameLayout(view.getContext());
        ViewParent incrementView = null;
        int i = 0;
        while (parent != null && !(parent.getClass() == parentClass)) {
            if (i == 0) {
                parent = findParent(view);
            } else {
                parent = findParent(incrementView);
            }
            incrementView = parent;
            i++;
        }
        return (View) parent;
    }

    private static ViewParent findParent(View view) {
        return view.getParent();
    }

    private static ViewParent findParent(ViewParent view) {
        return view.getParent();
    }
}
F1sher
  • 6,346
  • 9
  • 40
  • 80
3

Use

onView(withId(R.id.register_scroll_view))
        .perform(swipeUp(), click())