Search code examples
testingandroid-espressogui-testingui-testing

How can I perform a multi touch swipe with Espresso?


How can I perform a multi touch swipe with Espresso? e.g. a two finger swipe to the right.


Solution

  • As @Daniel suggested, I created a custom version of MotionEvents, because when using multiple fingers you have to inject ACTION_POINTER_DOWN/UP instead of ACTION_DOWN/UP. And I created a twoFinger version of LinearSwipe, like this:

    private static Swiper.Status sendLinearSwipe(UiController uiController,
                                                 float[] startCoordinates,
                                                 float[] startCoordinatesSecondFinger,
                                                 float[] endCoordinates,
                                                 float[]endCoordinatesSecondFinger,
                                                 float[] precision,
                                                 float[] precisionSecond,
                                                 int duration) {
        checkNotNull(uiController);
        checkNotNull(startCoordinates);
        checkNotNull(startCoordinatesSecondFinger);
        checkNotNull(endCoordinates);
        checkNotNull(endCoordinatesSecondFinger);
        checkNotNull(precision);
        checkNotNull(precisionSecond);
    
        float[][] steps = interpolate(startCoordinates, endCoordinates, SWIPE_EVENT_COUNT);
        float[][] stepsSecondFinger = interpolate(startCoordinatesSecondFinger, endCoordinatesSecondFinger, SWIPE_EVENT_COUNT);
        final int delayBetweenMovements = duration / steps.length;
        final int delayBetweenMovementsSecondFinger = duration / stepsSecondFinger.length;
    
        int maxLength=Math.min(steps.length, stepsSecondFinger.length);
    
        MotionEvent downEvent;
        downEvent = MotionEvents.sendDown(uiController, steps[0], precision,true).down;
        MotionEvent downEventSecondFinger;
        downEventSecondFinger = MotionEvents.sendDown(uiController,stepsSecondFinger[0], precisionSecond,false).down;
    
        try {
            for (int i = 1; i < maxLength; i++) {
    
                if (sendMovement(uiController, steps[i], downEvent)) return Status.FAILURE;
                if (sendMovement(uiController, stepsSecondFinger[i], downEventSecondFinger)) return Status.FAILURE;
    
    
                long desiredTime = downEvent.getDownTime() + delayBetweenMovements * i;
                long desiredTimeSecondFinger = downEventSecondFinger.getDownTime() + delayBetweenMovementsSecondFinger * i;
    
                long timeUntilDesired = desiredTime - SystemClock.uptimeMillis();
                long timeUntilDesiredSecondFinger = desiredTimeSecondFinger - SystemClock.uptimeMillis();
                loopMainThread(uiController, timeUntilDesired);
                loopMainThread(uiController, timeUntilDesiredSecondFinger);
    
            }
    
            if (!MotionEvents.sendUp(uiController, downEventSecondFinger, endCoordinatesSecondFinger,false)) {
                Log.e(TAG, "Injection of up event as part of the swipe failed. Sending cancel event.");
                MotionEvents.sendCancel(uiController, downEventSecondFinger);
                return Swiper.Status.FAILURE;
            }
        } finally {
            downEvent.recycle();
            downEventSecondFinger.recycle();
        }
        return Swiper.Status.SUCCESS;
    }
    
    private static void loopMainThread(UiController uiController, long timeUntilDesired) {
        if (timeUntilDesired > 10) {
            uiController.loopMainThreadForAtLeast(timeUntilDesired);
        }
    }
    
    private static boolean sendMovement(UiController uiController, float[] step, MotionEvent downEvent) {
        if (!MotionEvents.sendMovement(uiController, downEvent, step)) {
            Log.e(TAG, "Injection of move event as part of the swipe failed. Sending cancel event.");
            MotionEvents.sendCancel(uiController, downEvent);
            return true;
        }
        return false;
    }
    

    Now it works perfectly! Thanks @Daniel