Android UI测试长按并拖动

时间:2017-12-31 18:45:58

标签: java android android-espresso barista

在我的应用程序中,我有一个功能,即用户可以通过长按以创建拖动阴影来重新排序列表中的项目,然后用户可以在所选位置拖动和插入拖动阴影。丢弃后,物品将被重新订购。

我正在努力为此开发UI测试。我能够成功地长按项目,创建拖动阴影或实现拖动动作。我似乎无法将两者结合成一个动作。

我在Android UI测试中使用Espresso和Barista。

长按一下,我使用了Barista的API:

longClickOn("ITEM");

对于拖动动作,我尝试创建自己的Espresso ViewAction:

return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isAssignableFrom(ViewGroup.class);
        }

        @Override
        public String getDescription() {
            return "Swiping child " + srcIndex + " to child " + destIndex;
        }

        @Override
        public void perform(UiController uiController, View view) {
            ViewGroup parent = (ViewGroup) view;

            final View srcChild = parent.getChildAt(srcIndex);
            final View destChild = parent.getChildAt(destIndex);

            final CoordinatesProvider srcCoordinatesProvider = new CoordinatesProvider() {
                @Override
                public float[] calculateCoordinates(View view) {
                    int[] location = new int[2];
                    srcChild.getLocationInWindow(location);
                    float x = location[0] + (view.getMeasuredWidth() / 2);
                    float y = location[1] + (view.getMeasuredHeight() / 2);

                    return new float[] {x, y};
                }
            };

            final CoordinatesProvider destCoordinatesProvider = new CoordinatesProvider() {
                @Override
                public float[] calculateCoordinates(View view) {
                    int[] location = new int[2];
                    destChild.getLocationInWindow(location);
                    float x = location[0] + (view.getMeasuredWidth() / 2);
                    float y = location[1] + (view.getMeasuredHeight() / 2);

                    return new float[] {x, y};
                }
            };

            GeneralSwipeAction swipe = new GeneralSwipeAction(Swipe.FAST,
                    srcCoordinatesProvider, destCoordinatesProvider, Press.FINGER);
            swipe.perform(uiController, parent);
        }
    };

编辑:

根据@Be_Negative的回答,我已经定制了给定的答案并提出了这个问题:

private static ViewAction drag(final int srcIndex, final int destIndex) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isAssignableFrom(ViewGroup.class);
        }

        @Override
        public String getDescription() {
            return "Swiping child " + srcIndex + " to child " + destIndex;
        }

        @Override
        public void perform(UiController uiController, View view) {
            ViewGroup parent = (ViewGroup) view;

            uiController.loopMainThreadUntilIdle();
            final View srcChild = parent.getChildAt(srcIndex);
            final View destChild = parent.getChildAt(destIndex);

            final CoordinatesProvider coordinatesProvider = getCoordinatesProdvider();

            float[] precision = Press.PINPOINT.describePrecision();
            MotionEvent downEvent = MotionEvents.sendDown(uiController, coordinatesProvider.calculateCoordinates(srcChild), precision).down;

            try {
                long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5);
                uiController.loopMainThreadForAtLeast(longPressTimeout);

                float[][] steps = interpolateDragging(
                        coordinatesProvider.calculateCoordinates(srcChild),
                        coordinatesProvider.calculateCoordinates(destChild)
                );

                uiController.loopMainThreadUntilIdle();

                for(float[] step : steps) {
                    if( !MotionEvents.sendMovement(uiController, downEvent, step)) {
                        MotionEvents.sendCancel(uiController, downEvent);
                    }
                }

                if(!MotionEvents.sendUp(uiController, downEvent, coordinatesProvider.calculateCoordinates(destChild))) {
                    MotionEvents.sendCancel(uiController, downEvent);
                }
            } catch(Exception e) {
                System.out.println(e);
            } finally {
                downEvent.recycle();
            }
        }
    };
}

不幸的是,它会在MotionEvents.sendMovement处抛出错误,如下所示:

Error performing 'inject motion event (corresponding down event: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=540.0, y[0]=302.0, toolType[0]=TOOL_TYPE_UNKNOWN, buttonState=BUTTON_PRIMARY, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=34586525, downTime=34586525, deviceId=0, source=0x1002 })' on view 'unknown'.

1 个答案:

答案 0 :(得分:0)

这是我对此的看法。我严重依赖long pressswipe的现有实现将它们组合在一起。有一些分歧思想 - 例如默认实现中的拖动插补器倾向于下冲,所以我不得不稍微调整一下。

class DragAndDropAction(private val sourceViewPosition: Int,
                        private val targetViewPosition: Int) : ViewAction {

    override fun getConstraints(): Matcher<View> {
        return allOf(isDisplayed(), isAssignableFrom(RecyclerView::class.java))
    }

    override fun getDescription(): String {
        return "Drag and drop action"
    }

    override fun perform(uiController: UiController, view: View) {
        val recyclerView: RecyclerView = view as RecyclerView
        //Sending down
        recyclerView.scrollToPosition(sourceViewPosition)
        uiController.loopMainThreadUntilIdle()
        val sourceView = recyclerView.findViewHolderForAdapterPosition(sourceViewPosition).itemView

        val sourceViewCenter = GeneralLocation.VISIBLE_CENTER.calculateCoordinates(sourceView)
        val fingerPrecision = Press.FINGER.describePrecision()

        val downEvent = MotionEvents.sendDown(uiController, sourceViewCenter, fingerPrecision).down
        try {
            // Factor 1.5 is needed, otherwise a long press is not safely detected.
            val longPressTimeout = (ViewConfiguration.getLongPressTimeout() * 1.5f).toLong()
            uiController.loopMainThreadForAtLeast(longPressTimeout)

            //Drag to the position
            recyclerView.scrollToPosition(targetViewPosition)
            uiController.loopMainThreadUntilIdle()
            val targetView = recyclerView.findViewHolderForAdapterPosition(targetViewPosition).itemView
            val targetViewLocation = if (targetViewPosition > sourceViewPosition) {
                GeneralLocation.BOTTOM_CENTER.calculateCoordinates(targetView)
            } else {
                GeneralLocation.TOP_CENTER.calculateCoordinates(targetView)
            }

            val steps = interpolate(sourceViewCenter, targetViewLocation)

            for (i in 0 until steps.size) {
                if (!MotionEvents.sendMovement(uiController, downEvent, steps[i])) {
                    MotionEvents.sendCancel(uiController, downEvent)
                }
            }

            //Release
            if (!MotionEvents.sendUp(uiController, downEvent, targetViewLocation)) {
                MotionEvents.sendCancel(uiController, downEvent)
            }
        } finally {
            downEvent.recycle()
        }
    }

    private val SWIPE_EVENT_COUNT = 10

    private fun interpolate(start: FloatArray, end: FloatArray): Array<FloatArray> {
        val res = Array(SWIPE_EVENT_COUNT) { FloatArray(2) }

        for (i in 1..SWIPE_EVENT_COUNT) {
            res[i - 1][0] = start[0] + (end[0] - start[0]) * i / SWIPE_EVENT_COUNT
            res[i - 1][1] = start[1] + (end[1] - start[1]) * i / SWIPE_EVENT_COUNT
        }

        return res
    }
}

还有很大的改进空间,例如理想情况下,我希望我的行动能够吸引观察者的职位。除此之外,似乎工作正常。