Android UI 测试长按拖拽
Posted
技术标签:
【中文标题】Android UI 测试长按拖拽【英文标题】:Android UI Test Long Click and Drag 【发布时间】:2017-12-31 18:45:58 【问题描述】:在我的应用程序中,我有一个功能,即用户可以通过长按创建拖动阴影来重新排序列表中的项目,然后用户可以将其拖动并插入到选择的位置。丢弃后,这些物品会重新排序。
我正在努力为此开发 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】:这是我的想法。我严重依赖long press 和swipe 的现有实现将它们组合在一起。有一些分歧的想法 - 例如,默认实现中的拖动插值器往往会下冲,所以我不得不稍微调整一下。
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
还有很大的改进空间,例如,理想情况下,我希望我的操作可以在视图匹配器中进行而不是位置。除此之外,它似乎一切正常。
【讨论】:
感谢您的帮助。不幸的是,它仍然无法正常工作。请查看我的编辑以获取更多信息。 它似乎开始接触系统 UI 元素之一(键盘、通知面板或底部按钮栏)。我怀疑您需要将滚动合并到您的视图操作中。就我而言,我正在使用回收站视图并滚动到项目并拖动项目。如果你不在recyclerview中,可以使用ScrollToAction作为参考 实际上,您可以从自定义ViewMatchers.withParentIndex()
调用视图操作可能很方便,或者编写自己的匹配器来匹配给定视图(默认情况下,espresso 不提供这样的匹配器是理所当然的)因为它可能被滥用)以上是关于Android UI 测试长按拖拽的主要内容,如果未能解决你的问题,请参考以下文章
RecyclerView侧滑菜单,滑动删除,长按拖拽,下拉刷新上拉加载
RecyclerView侧滑菜单,滑动删除,长按拖拽,下拉刷新上拉加载
Swift 4.0iOS 11 UICollectionView 长按拖拽删除崩溃的问题