Android UI测试(一) 使用Espresso点击Textview中指定文字

905 阅读1分钟

在使用Espresso进行UI测试时,通常使用ViewActions.click()方法来点击控件。如果要测试TextView中的url链接,还可以用另外一个方法ViewAction.openLinkWithText(),但是如果这些链接是自定义的ClickSpan,或者需要点击非链接的文字区域,使用以上的方法无法做到。 我们先来看看系统原生apiViewActions.click()的实现。

public static ViewAction click() {
  return actionWithAssertions(
      new GeneralClickAction(
          Tap.SINGLE,
          GeneralLocation.VISIBLE_CENTER,
          Press.FINGER,
          InputDevice.SOURCE_UNKNOWN,
          MotionEvent.BUTTON_PRIMARY));
}

查看上面的源码可以发现GeneralClickAction构造函数传的是GeneralLocation.VISIBLE_CENTERclick()方法默认点击的是控件的中心位置。此外GeneralLocation枚举中还提供了其它值用来点击控件的不同位置。GeneralLocation这其实是CoordinatesProvider的实现类。GeneralLocation.VISIBLE_CENTER实现了CoordinatesProvider接口的calculateCoordinates(View view)方法,最终是返回一个坐标数组,点击事件最后就是针对这个位置模拟发送点击事件。因此我们就可以通过实现自己的CoordinatesProvider来返回任意想要点击的坐标。

下面我们要实现一个点击TextView中指定文字的CoordinatesProvider

class TextCoordinatesProvider constructor(val text: String): CoordinatesProvider {

    companion object {
        const val TAG = "TextCoordinatesProvider"
    }

    override fun calculateCoordinates(view: View?): FloatArray {
        //需要判断控件类型
        if (view is TextView) {
            // 指定文字开始的索引位置
            val offset = view.text.indexOf(text)
            Log.v(TAG, "Full text = ${view.text}, content = ${text}")
            Log.v(TAG, "Offset = ${offset}")

            view.layout.let {
                // 获取TextView在屏幕上的坐标(控件左上角为原点)
                val xy = IntArray(2)
                view.getLocationOnScreen(xy)
                val lineOfText = it.getLineForOffset(offset)
                // 指定文字起始处的x坐标
                val xCoord = xy[0] + it.getPrimaryHorizontal(offset) + Press.FINGER.describePrecision()[0]
                // 指定文字起始处所在行的y坐标
                val yCoord = xy[1] + it.getLineTop(lineOfText) + (it.getLineBottom(lineOfText) - it.getLineTop(lineOfText)) / 2
                Log.d(TAG, "LineOfText = ${lineOfText}, xCoord = ${xCoord}, yCoord = ${yCoord}")
                return floatArrayOf(xCoord, yCoord.toFloat())
            }
        } else {
            throw Exception("View is not TextView.")
        }

    }
}

使用上面定义的CoordinatesProvider封装一下点击TextView中指定文字的方法:

fun clickText(text: String): ViewAction? {
    return ViewActions.actionWithAssertions(
            GeneralClickAction(
                    Tap.SINGLE,
                    TextCoordinatesProvider(text),
                    Press.FINGER,
                    InputDevice.SOURCE_UNKNOWN,
                    MotionEvent.BUTTON_PRIMARY))
}

这是长按的方法:

fun longClickText(text: String): ViewAction? {
    return ViewActions.actionWithAssertions(
            GeneralClickAction(
                    Tap.LONG,
                    TextCoordinatesProvider(text),
                    Press.FINGER,
                    InputDevice.SOURCE_UNKNOWN,
                    MotionEvent.BUTTON_PRIMARY))
}

要注意这里点击的是指定字符串首字符的位置,而且TextView中如果有多个相同的字符串,计算第一个匹配到的字符串的坐标。