35

I have a custom ImageButton that is not fully visible, by design, so when I perform a click action I get this error:

android.support.test.espresso.PerformException: Error performing 'single click' on view 'with id: test.com.myproject.app:id/navigationButtonProfile'.
Caused by: java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints:
at least 90 percent of the view's area is displayed to the user.
at android.support.test.espresso.ViewInteraction$1.run(ViewInteraction.java:138)
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:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5356)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
at dalvik.system.NativeStart.main(Native Method)

A small part of the button is outside of the screen (i.e. it is cropped on the top), maybe 12% of the button is outside the screen. This is by design, and there is not possible to scroll or perform any view action to make it visible. Any one know how to get past this 90%-constraint?

Solution: I created my own click action as suggested, and it worked perfectly. I copied the class from Google Espresso and changed from 90 to 75 in this method:

    @Override
    @SuppressWarnings("unchecked")
    public Matcher<View> getConstraints() {
        Matcher<View> standardConstraint = isDisplayingAtLeast(75);
        if (rollbackAction.isPresent()) {
            return allOf(standardConstraint, rollbackAction.get().getConstraints());
        } else {
            return standardConstraint;
        }
    }
Alexander Pacha
  • 9,187
  • 3
  • 68
  • 108
HowieH
  • 2,612
  • 2
  • 23
  • 26

7 Answers7

69

None of the above worked for me. Here is a custom matcher that completely removes that constraint and allows you to click on the view

 onView(withId(yourID)).check(matches(allOf( isEnabled(), isClickable()))).perform(
            new ViewAction() {
                @Override
                public Matcher<View> getConstraints() {
                    return ViewMatchers.isEnabled(); // no constraints, they are checked above
                }

                @Override
                public String getDescription() {
                    return "click plus button";
                }

                @Override
                public void perform(UiController uiController, View view) {
                    view.performClick();
                }
            }
    );
Lavekush Agrawal
  • 6,040
  • 7
  • 52
  • 85
HRVHackers
  • 2,793
  • 4
  • 36
  • 38
14

I don't think there is any easy, elegant solution to this. The 90% constraint is hardcoded in GeneralClickAction, and the class is final so we can't override getConstraints.

I would write your own ViewAction based on GeneralClickAction's code, skipping the isDisplayingAtLeast check.

Alexander Pacha
  • 9,187
  • 3
  • 68
  • 108
Daniel Lubarov
  • 7,796
  • 1
  • 37
  • 56
  • 1
    For those who does not have Guava library, you can use `import android.support.test.espresso.core.deps.guava.base.Optional;` when the compiler complains – satyadeepk Apr 28 '16 at 14:14
  • 1
    This worked for me used with Anna's answer below. Thanks! – ItsJason Jun 14 '16 at 14:53
12

You have to scroll to the button before:

onView(withId(R.id.button_id)).perform(scrollTo(), click());
denys
  • 6,834
  • 3
  • 37
  • 36
  • 1
    The button is visible on the screen, but some parts of the button is not visible(due to design). – HowieH Mar 03 '15 at 15:55
  • 1
    Then your question doesn't have enough information to answer it. Add more details. – denys Mar 03 '15 at 16:37
  • 1
    @denys Why am I seeing this in espresso docs: `Note that in order to scroll the list you shouldn't use {@link ViewActions#scrollTo()} as * {@link Espresso#onData(org.hamcrest.Matcher)} handles scrolling.` – IgorGanapolsky Jul 16 '15 at 19:43
  • 1
    @Igor Is there any info in question above that we are looking for solution to click a button in a list (i.e. adapter)? No. So, we have to use `onView(...)`. If it would be a button in adapter then definitely `onData(...)` should be used according to documentation. – denys Jul 20 '15 at 14:44
  • Please check my question http://stackoverflow.com/questions/36126445/clicking-button-on-second-activity-got-error-performing-single-click-or-scrol `scrollTo()` did not solve the partially displayed button. I think the button is partially displayed because `isDisplayed()` is succeed while `isCompletelyDisplayed()` fails. – Sithu Mar 22 '16 at 07:56
  • 1
    @IgorGanapolsky the reason is that `ListView` creates views lazily, so if you want to scroll to an item which isn't visible yet, the view associated with that item might not exist yet. `onData` lets us safely interact with items which may not have associated views. (I realize this is old, but in case someone has the same question...) – Daniel Lubarov Jun 14 '16 at 19:31
9

This helped me to resolve button visibility while running my tests

public static ViewAction handleConstraints(final ViewAction action, final Matcher<View> constraints)
{
    return new ViewAction()
    {
        @Override
        public Matcher<View> getConstraints()
        {
            return constraints;
        }

        @Override
        public String getDescription()
        {
            return action.getDescription();
        }

        @Override
        public void perform(UiController uiController, View view)
        {
            action.perform(uiController, view);
        }
    };
}

public void clickbutton()
{
    onView(withId(r.id.button)).perform(handleConstraints(click(), isDisplayingAtLeast(65)));
}
pergy
  • 5,285
  • 1
  • 22
  • 36
Shibu
  • 97
  • 1
  • 3
7

Default click of Espresso is requiring visibility of view > 90%. What do you think about creating an own click ViewAction? Like this...

public final class MyViewActions {
   public static ViewAction click() {
      return new GeneralClickAction(SafeTap.SINGLE, GeneralLocation.CENTER, Press.FINGER);
    }
}

This click will click on the center of your view.

Then you could execute click like this: onView(withId(....)).perform(MyViewActions.click());

I hope it could work.

Anna
  • 759
  • 7
  • 9
  • I hoped so too, unfortunately this did not get past the constraint, it throws the same error. Any other suggestions? – HowieH Mar 04 '15 at 10:08
  • I've included it in the question now – HowieH Mar 04 '15 at 12:09
  • 1
    Thanks for adding details. Are you clicking on it from the beginning of test or after opening of something ? Why I am asking.... It could mean also that image is not loaded ..... It could be tested by adding `Thread.sleep(1000);` before clicking on it..... – Anna Mar 04 '15 at 14:27
  • I open a view, and then click on the button. I tried to put in a sleep for a couple of seconds, but it did not help. Would be nice to just get past that 90%-rule.. – HowieH Mar 04 '15 at 14:58
  • Espresso is protecting from such cases(click on views with poor visibility).... Actually this is not a problem to click on ImageButton. We have a lot of places. The problem is to wait until it is loaded....If you are sure that you have no synchronization problem then I don't know.... Sorry. When I am getting this error from espresso then it is really not visible:) – Anna Mar 04 '15 at 15:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/72295/discussion-between-howieh-and-anna). – HowieH Mar 05 '15 at 06:33
  • 2
    In newer versions of Espresso, SafeTap does not exis. Tap.SINGLE could be used instead. – Mavamaarten Aug 24 '16 at 10:12
3

Create your own ViewAction class without view visibility constraints:

 public static ViewAction myClick() {

    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return ViewMatchers.isEnabled(); // no constraints, they are checked above
        }

        @Override
        public String getDescription() {
            return null;
        }

        @Override
        public void perform(UiController uiController, View view) {
            view.performClick();
        }
    };

}

Then to use it:

onView(withId(R.id.your_view_id)).perform(myClick());

Kotlin Version:

fun myClick(): ViewAction = object : ViewAction {
  override fun getConstraints(): Matcher<View> = ViewMatchers.isEnabled() // no constraints, they are checked above

  override fun getDescription(): String = ""

  override fun perform(uiController: UiController, view: View) {
    view.performClick()
  }
}
Philipp E.
  • 3,296
  • 3
  • 34
  • 52
MarcinBLN
  • 39
  • 4
0

For the same error on a text view I ended up doing as below, the answers posted above and other SO questions were a lot of help. I did try scrollTo method, but for some reason the error persisted. Hope this helps someone.

        onView(allOf(withId(R.id.recycler_view), isDisplayed())).perform(RecyclerViewActions
                .actionOnItem(hasDescendant(withText("my text")), click()));