Search code examples
androidandroid-edittextandroid-drawableandroid-espresso

Click on EditText's drawable right with Espresso


How could be possible to click on EditText's right drawable (check the screenshot)? I have tried several ways but I always get stuck.

public static Matcher<View> withEditTextDrawable(final int resourceId) {
    return new BoundedMatcher<View, EditText>(EditText.class) {
        @Override
        protected boolean matchesSafely(final EditText editText) {
            // ????

            return false;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("with drawable from resource id: ");
            description.appendValue(resourceId);
        }
    };
}

enter image description here


Solution

  • Had to develop this custom action and matcher to test our discard drawables on the right of some text fields, discard edittext this will click any drawable (left, top, right, bottom) of a TextView in an Espresso test.

    Usage:

    onView(withId(id)).perform(clickDrawables());
    

    Method:

    public static ViewAction clickDrawables()
    {
        return new ViewAction()
        {
            @Override
            public Matcher<View> getConstraints()//must be a textview with drawables to do perform
            {
                return allOf(isAssignableFrom(TextView.class), new BoundedMatcher<View, TextView>(TextView.class)
                {
                    @Override
                    protected boolean matchesSafely(final TextView tv)
                    {
                        if(tv.requestFocusFromTouch())//get fpocus so drawables become visible
                            for(Drawable d : tv.getCompoundDrawables())//if the textview has drawables then return a match
                                if(d != null)
                                    return true;
    
                        return false;
                    }
    
                    @Override
                    public void describeTo(Description description)
                    {
                        description.appendText("has drawable");
                    }
                });
            }
    
            @Override
            public String getDescription()
            {
                return "click drawables";
            }
    
            @Override
            public void perform(final UiController uiController, final View view)
            {
                TextView tv = (TextView)view;
                if(tv != null && tv.requestFocusFromTouch())//get focus so drawables are visible
                {
                    Drawable[] drawables = tv.getCompoundDrawables();
    
                    Rect tvLocation = new Rect();
                    tv.getHitRect(tvLocation);
    
                    Point[] tvBounds = new Point[4];//find textview bound locations
                    tvBounds[0] = new Point(tvLocation.left, tvLocation.centerY());
                    tvBounds[1] = new Point(tvLocation.centerX(), tvLocation.top);
                    tvBounds[2] = new Point(tvLocation.right, tvLocation.centerY());
                    tvBounds[3] = new Point(tvLocation.centerX(), tvLocation.bottom);
    
                    for(int location = 0; location < 4; location++)
                        if(drawables[location] != null)
                        {
                            Rect bounds = drawables[location].getBounds();
                            tvBounds[location].offset(bounds.width() / 2, bounds.height() / 2);//get drawable click location for left, top, right, bottom
                            if(tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, tvBounds[location].x, tvBounds[location].y, 0)))
                                tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, tvBounds[location].x, tvBounds[location].y, 0));
                        }
                }
            }
        };
    }
    



    This is a refinement I made to select a specific drawable to click:

    Usage:

    onView(withId(id)).perform(new ClickDrawableAction(ClickDrawableAction.Right));
    

    Method:

    public static class ClickDrawableAction implements ViewAction
    {
        public static final int Left = 0;
        public static final int Top = 1;
        public static final int Right = 2;
        public static final int Bottom = 3;
    
        @Location
        private final int drawableLocation;
    
        public ClickDrawableAction(@Location int drawableLocation)
        {
            this.drawableLocation = drawableLocation;
        }
    
        @Override
        public Matcher<View> getConstraints()
        {
            return allOf(isAssignableFrom(TextView.class), new BoundedMatcher<View, TextView>(TextView.class)
            {
                @Override
                protected boolean matchesSafely(final TextView tv)
                {
                    //get focus so drawables are visible and if the textview has a drawable in the position then return a match
                    return tv.requestFocusFromTouch() && tv.getCompoundDrawables()[drawableLocation] != null;
    
                }
    
                @Override
                public void describeTo(Description description)
                {
                    description.appendText("has drawable");
                }
            });
        }
    
        @Override
        public String getDescription()
        {
            return "click drawable ";
        }
    
        @Override
        public void perform(final UiController uiController, final View view)
        {
            TextView tv = (TextView)view;//we matched
            if(tv != null && tv.requestFocusFromTouch())//get focus so drawables are visible
            {
                //get the bounds of the drawable image
                Rect drawableBounds = tv.getCompoundDrawables()[drawableLocation].getBounds();
    
                //calculate the drawable click location for left, top, right, bottom
                final Point[] clickPoint = new Point[4];
                clickPoint[Left] = new Point(tv.getLeft() + (drawableBounds.width() / 2), (int)(tv.getPivotY() + (drawableBounds.height() / 2)));
                clickPoint[Top] = new Point((int)(tv.getPivotX() + (drawableBounds.width() / 2)), tv.getTop() + (drawableBounds.height() / 2));
                clickPoint[Right] = new Point(tv.getRight() + (drawableBounds.width() / 2), (int)(tv.getPivotY() + (drawableBounds.height() / 2)));
                clickPoint[Bottom] = new Point((int)(tv.getPivotX() + (drawableBounds.width() / 2)), tv.getBottom() + (drawableBounds.height() / 2));
    
                if(tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, clickPoint[drawableLocation].x, clickPoint[drawableLocation].y, 0)))
                    tv.dispatchTouchEvent(MotionEvent.obtain(android.os.SystemClock.uptimeMillis(), android.os.SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, clickPoint[drawableLocation].x, clickPoint[drawableLocation].y, 0));
            }
        }
    
        @IntDef({ Left, Top, Right, Bottom })
        @Retention(RetentionPolicy.SOURCE)
        public @interface Location{}
    }