Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Espresso testing NestedScrollView - "Error performing 'scroll to' on view 'with id:"

I need to scroll down through my NestedScrollView in order to test my xml file with Espresso, but I get the error message: "Error performing 'scroll to' on view 'with id:"

Several other posts that seem to have a similar problem.

I have followed instructions from this: Android espresso NestedScrollView, how to scroll to bottom

Now I get the forementioned error, and found this post: Scrolling to view was attempted, but the view is not displayed

I have no padding in my NestedScrollView - I even tried removing padding from the XML alltogether, for testing purposes, but it made no difference.

This is my test (so far it is not supposed to do anything, but scroll down):

    @Test
    public void testScrollDownAbilityOfDetailsScrollView(){
        goToSpecificItemOnStream(streamItemWithOneImage);

        onView(withId(R.id.end_of_details))
                .perform(ScrollToAction.betterScrollTo());

    }

It uses a custom made Scroll To Action class:

    public final class ScrollToAction implements ViewAction {

    private static final String TAG = ScrollToAction.class.getSimpleName();

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

    @Override
    public void perform(UiController uiController, View view) {
        if (isDisplayingAtLeast(80).matches(view)) {
            Log.i(TAG, "View is already displayed. Returning.");
            return;
        }
        Rect rect = new Rect();
        view.getDrawingRect(rect);
        if (!view.requestRectangleOnScreen(rect, true /* immediate */)) {
            Log.w(TAG, "Scrolling to view was requested, but none of the parents      scrolled.");
        }
        uiController.loopMainThreadUntilIdle();
        if (!isDisplayingAtLeast(80).matches(view)) {
            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new RuntimeException(
                            "Scrolling to view was attempted, but the view is not displayed"))
                    .build();
        }
    }
    public static ViewAction betterScrollTo() {
        return ViewActions.actionWithAssertions(new ScrollToAction());
    }

    @Override
    public String getDescription() {
        return "scroll to";
    }}

The custom made ScrollToAction class is there because the normal scrollTo method is hardcoded to ScrollView and HorizontalScrollView, but not NestedScrollView.

This is the XML file that I am trying to test:

       <?xml version="1.0" encoding="utf-8"?>
       <android.support.v4.widget.NestedScrollView    xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/scrollView"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">



            <FrameLayout
                android:id="@+id/fl"
                android:background="#FBFBFB"
                android:layout_margin="0dp"
                android:layout_width="match_parent"
                android:layout_height="350dp">

                <android.support.v4.view.ViewPager
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

                <ImageView
                    android:id="@+id/location"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="@dimen/text_margin"
                    android:layout_gravity="left|top"
                    android:background="@null"
                    android:src="@drawable/ic_location_white"
                    android:paddingLeft="-8dp" />


                <TextView
                    android:id="@+id/textViewDistance"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_toRightOf="@id/location"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:layout_margin="@dimen/text_margin"
                    android:layout_gravity="left|top"
                    android:shadowColor="#262424"
                    android:shadowDx="1"
                    android:shadowDy="1"
                    android:shadowRadius="2"
                    android:textColor="#FBFBFB"
                    android:textSize="22dp"
                    android:singleLine="false"
                    android:paddingLeft="24dp" />


                <TextView
                    android:id="@+id/textViewPrice"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceMedium"
                    android:layout_margin="@dimen/text_margin"
                    android:layout_gravity="right|top"
                    android:shadowColor="#262424"
                    android:shadowDx="1"
                    android:shadowDy="1"
                    android:shadowRadius="2"
                    android:textColor="#FBFBFB"

                    android:textSize="22dp"/>

                <me.relex.circleindicator.CircleIndicator
                    android:id="@+id/indicator"
                    android:layout_width="match_parent"
                    android:layout_height="40dp"
                    android:layout_gravity="bottom"
                    android:shadowColor="#262424"
                    android:shadowDx="1"
                    android:shadowDy="1"
                    android:shadowRadius="1"/>



            </FrameLayout>


            <LinearLayout

                android:layout_below="@id/fl"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                >

                <TextView
                    android:id="@+id/textViewTitle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:gravity="left"
                    android:layout_margin="@dimen/text_margin"
                    android:textColor="@color/colorCheckTomBlack"
                    android:textStyle="bold"
                    android:textSize="20dp" />

                <TextView
                    android:id="@+id/textViewDescription"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="@dimen/text_margin"
                    android:layout_marginRight="@dimen/text_margin"
                    android:gravity="left"
                    android:textColor="@color/colorCheckTomBlack"
                    android:textSize="18dp"
                    android:layout_weight="0.56" />

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="65dp"
                    android:paddingTop="30dp">


                    <ImageButton
                        android:id="@+id/buttonWatchlist"
                        android:src="@drawable/ic_checktom"
                        android:background="@null"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:gravity="left"
                        android:layout_marginLeft="55dp"
                        android:layout_marginStart="55dp"
                        android:layout_alignParentTop="true"
                        android:layout_alignParentLeft="true"
                        android:layout_alignParentStart="true"
                        android:onClick="launchWatchlistActivity"
                        android:paddingTop="2dp"/>


                    <ImageButton
                        android:id="@+id/buttonMessage"
                        android:src="@drawable/ic_messages"
                        android:background="@null"
                        android:scaleX="1.2"
                        android:scaleY="1.2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_horizontal"
                        android:layout_alignParentTop="true"
                        android:layout_centerHorizontal="true"
                        android:onClick="launchMessageActivity"
                        android:paddingTop="7dp"/>


                    <ImageButton
                        android:id="@+id/buttonShare"
                        android:src="@drawable/ic_share"
                        android:background="@null"
                        android:scaleX="1.5"
                        android:scaleY="1.5"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginRight="54dp"
                        android:layout_marginEnd="54dp"
                        android:layout_alignParentTop="true"
                        android:layout_alignParentRight="true"
                        android:layout_alignParentEnd="true"
                        android:onClick="launchShareActivity"/>



                </RelativeLayout>


                <RelativeLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="2dp">


                    <TextView
                        android:id="@+id/textViewWatchlist"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Watchlist"
                        android:layout_marginLeft="41dp"
                        android:layout_marginStart="41dp"
                        android:layout_alignParentTop="true"
                        android:layout_alignParentLeft="true"
                        android:layout_alignParentStart="true" />


                    <TextView
                        android:id="@+id/textViewMessage"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Message"
                        android:layout_gravity="center_horizontal"
                        android:layout_alignParentTop="true"
                        android:layout_centerHorizontal="true" />

                    <TextView
                        android:id="@+id/textViewShare"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Share"
                        android:layout_gravity="center_horizontal"
                        android:layout_alignParentTop="true"
                        android:layout_alignParentRight="true"
                        android:layout_alignParentEnd="true"
                        android:layout_marginRight="52dp"
                        android:layout_marginEnd="52dp" />


                </RelativeLayout>
                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center">


                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="_________________________________________"
                    android:paddingTop="25dp"/>


                </LinearLayout>

                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center">

                    <de.hdodenhof.circleimageview.CircleImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_below="@+id/imageView"
                        android:layout_centerHorizontal="true"
                        android:id="@+id/circleView"
                        android:scaleX="0.4"
                        android:scaleY="0.4"
                        android:layout_marginTop="-20dp"
                        android:layout_marginBottom="-60dp"
                        />       

                </LinearLayout>

                <LinearLayout
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center">     
                    <LinearLayout
                        android:orientation="horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center">     
                    <TextView
                        android:id="@+id/textViewSellerName"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:textSize="18dp"/>     
                        </LinearLayout>        
                    <LinearLayout
                        android:orientation="horizontal"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center">     
                    <TextView
                        android:id="@+id/textViewSellerDestination"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:paddingBottom="20dp" />     
                        <TextView
                            android:id="@+id/end_of_details"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content" />
                        </LinearLayout>        
                </LinearLayout>
            </LinearLayout>
        </RelativeLayout>
    </android.support.v4.widget.NestedScrollView>

When the test fails, this is the full output I get:

android.support.test.espresso.PerformException: Error performing 'scroll to' on view 'with id: com.checktom.checktom:id/end_of_details'. at android.support.test.espresso.PerformException$Builder.build(PerformException.java:83) at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:80) 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.doPerform(ViewInteraction.java:115) at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:87) at com.checktom.checktom.ApplicationTest.testScrollDownAbilityOfDetailsScrollView(ApplicationTest.java:279) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) 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 org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) 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:1933) Caused by: java.lang.RuntimeException: Scrolling to view was attempted, but the view is not displayed at com.checktom.checktom.ScrollToAction.perform(ScrollToAction.java:52) at android.support.test.espresso.ViewInteraction$1.run(ViewInteraction.java:144) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422) at java.util.concurrent.FutureTask.run(FutureTask.java:237) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:6938) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199) Tests ran to completion.

I can tell that I run into the runtime exception in my ScrollToAction class's perform method, but I have yet to find a way to solve it.

In the first post I linked, the new BetterScrollTo method seemed to work like a charm.

like image 837
Silas Avatar asked Sep 22 '16 15:09

Silas


3 Answers

I did this:

onView(withId(R.id.viewToScroll)
                .perform(nestedScrollTo())
                .check(matches(isDisplayed()));

Where nestedScrollTo() is:

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 "View is not NestedScrollView";
        }

        @Override
        public void perform(UiController uiController, View view) {
            try {
                NestedScrollView nestedScrollView = (NestedScrollView)
                        findFirstParentLayoutOfClass(view, NestedScrollView.class);
                if (nestedScrollView != 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 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();
}
like image 170
Damia Fuentes Avatar answered Sep 22 '22 08:09

Damia Fuentes


The answer of @Eric with imports is: Thanks a lot Eric :)

import android.view.View
import android.widget.HorizontalScrollView
import android.widget.ScrollView
import androidx.core.widget.NestedScrollView
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ScrollToAction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.anyOf
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import org.hamcrest.Matcher


// scroll-to action that also works with NestedScrollViews
class BetterScrollToAction:ViewAction by ScrollToAction()
{
    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)
                )
            )
        )
    }
}

// convenience method
fun betterScrollTo(): ViewAction
{
    return ViewActions.actionWithAssertions(BetterScrollToAction())
}
like image 29
moralejaSinCuentoNiProverbio Avatar answered Sep 26 '22 08:09

moralejaSinCuentoNiProverbio


i used this code (inspired by this SO answer)

class MainMenuActivityTest
{
    @Test
    fun textInputDialog_opens_on_button_clicked()
    {
        onView(withId(R.id.text_input_button))
            .perform(betterScrollTo())
            .perform(click())
    }
}

// scroll-to action that also works with NestedScrollViews
class BetterScrollToAction:ViewAction by ScrollToAction()
{
    override fun getConstraints():Matcher<View>
    {
        return allOf(
                withEffectiveVisibility(Visibility.VISIBLE),
                isDescendantOfA(anyOf(
                        isAssignableFrom(ScrollView::class.java),
                        isAssignableFrom(HorizontalScrollView::class.java),
                        isAssignableFrom(NestedScrollView::class.java))))
    }
}

// convenience method
fun betterScrollTo():ViewAction
{
    return ViewActions.actionWithAssertions(BetterScrollToAction())
}

and it works now


at first, i was using this answer here, but while using the following code for my test:

class MainMenuActivityTest
{
    @Test
    fun textInputDialog_opens_on_button_clicked()
    {
        onView(withId(R.id.text_input_button))
            .perform(betterScrollTo())
            .perform(click())
    }
}

but i got the following exceptions...

E/TestRunner: failed: textInputDialog_opens_on_button_clicked(com.github.ericytsang.example.app.android.MainMenuActivityTest)
----- begin exception -----
E/TestRunner: androidx.test.espresso.PerformException: Error performing 'scroll to' on view 'Animations or transitions are enabled on the target device.
For more info check: https://developer.android.com/training/testing/espresso/setup#set-up-environment

with id: com.github.ericytsang.app.example.android:id/text_input_button'.
............
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2196)
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 or is assignable from class: class android.widget.ListView))
Target view: "AppCompatButton{id=2131296521, res-name=text_input_button, visibility=VISIBLE, width=1008, height=107, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=false, is-selected=false, layout-params=androidx.appcompat.widget.LinearLayoutCompat$LayoutParams@cec11da, tag=null, root-is-layout-requested=false, has-input-connection=false, x=18.0, y=516.0, text=text input dialog, input-type=0, ime-target=false, has-links=false}"
at androidx.test.espresso.ViewInteraction.doPerform(ViewInterac
----- end exception -----

E/TestRunner: failed: textInputDialog_opens_on_button_clicked(com.github.ericytsang.example.app.android.MainMenuActivityTest)
----- begin exception -----
E/TestRunner: androidx.test.espresso.PerformException: Error performing 'scroll to' on view 'with id: com.github.ericytsang.app.example.android:id/text_input_button'.
........
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 or is assignable from class: class android.widget.ListView))
Target view: "AppCompatButton{id=2131296521, res-name=text_input_button, visibility=VISIBLE, width=1008, height=107, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=false, is-selected=false, layout-params=androidx.appcompat.widget.LinearLayoutCompat$LayoutParams@3e84212, tag=null, root-is-layout-requested=false, has-input-connection=false, x=18.0, y=516.0, text=text input dialog, input-type=0, ime-target=false, has-links=false}"
at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:252)
at androidx.test.espresso.ViewInteraction.access$100(ViewInteraction.java:65)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.jav
----- end exception -----
like image 31
Eric Avatar answered Sep 25 '22 08:09

Eric