kandroidj
kandroidj

Reputation: 13932

Espresso with Custom KeyboardView button press

I am implementing a custom KeyboardView in my app and it's all working at the moment, however, when I attempt to press a key on the keyboard using Espresso ViewAction, I am getting an exception saying:

android.support.test.espresso.PerformException: 
Error performing 'single click - At Coordinates: 1070, 2809 and 
precision: 16, 16' on view 'with id: 
com.example.app.mvpdemo:id/keyboardLayout'.

The code throwing the exception is:

@Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip(){
    onView(withId(R.id.editTextCheckAmount))
            .perform(typeText("100"), closeSoftKeyboard())
    val appContext = InstrumentationRegistry.getTargetContext()
    val displayMetrics = appContext.resources.displayMetrics
    onView(withId(R.id.keyboardLayout)).perform(clickXY(displayMetrics.widthPixels - 10, displayMetrics.heightPixels - 10))
    onView(withText("$120.00")).check(matches(isDisplayed()))
}

and the click XY function which came from this post

 private fun clickXY(x: Int, y: Int): ViewAction {
    return GeneralClickAction(
            Tap.SINGLE,
            CoordinatesProvider { view ->
                val screenPos = IntArray(2)
                view.getLocationOnScreen(screenPos)

                val screenX = (screenPos[0] + x).toFloat()
                val screenY = (screenPos[1] + y).toFloat()

                floatArrayOf(screenX, screenY)
            },
            Press.FINGER, 0, 0)
}

Here is my keyboard layout (pinned to the bottom of the screen inside a ConstraintLayout):

enter image description here

Does anyone know why? Any help is appreciated.

Upvotes: 0

Views: 1164

Answers (1)

kandroidj
kandroidj

Reputation: 13932

Answering my own question after determining a flexible solution:

  1. First attempt - get DisplayMetrics of the root View and subtract an arbitrary number to attempt to hit the Keyboard.Key
    • this didn't work because clickXY function uses the position of the view
    • this ended up being the reason for the exception since the view is smaller than the DisplayMetrics values and adding to the Views on screen position would give a very high number for the x and y.

So I tried again,

  1. Second attempt - use check method on the ViewMatcher to check the KeyBoardView.
    • by doing so I was able to get access to the KeyboardView's position x
    • then I was able to get the KeyboardView's width and height
    • by performing some math, I was able to figure out target index for x & y

the math:

  • take the widthPercent for the Keyboard.Key (in my case 33.3%)
  • take the rowCount of the keyboard.xml (in my case 3)
  • use (viewWidth * widthPercent) / 4 to get relativeButtonX
  • use (viewHeight / rowCount) / 2 to get relativeButtonY
  • then for targetY, I took viewHeight - relativeButtonY
  • finally, for targetX, I took (viewPosX + viewWidth) - relativeButtonX

So enough explanation, here is the code:

@Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip() {
    onView(withId(R.id.editTextCheckAmount))
            .perform(typeText("100"), closeSoftKeyboard())

    // call the function to get the targets
    val (viewTargetY, viewTargetX) = getTargetXAndY()

    // perform the action
    onView(withId(R.id.keyboardLayout)).perform(clickXY(viewTargetX.toInt(), viewTargetY))
    onView(withText("Tip: $20.00")).check(matches(isDisplayed()))
    onView(withText("Total: $120.00")).check(matches(isDisplayed()))
}

and the helper method with all the math:

private fun getTargetXAndY(): Pair<Int, Double> {
    var viewHeight = 0
    var viewWidth = 0
    var viewPosX = 0F
    val viewMatcher = onView(withId(R.id.keyboardLayout))
    viewMatcher.check { view, _ ->
        viewWidth = view.width
        viewHeight = view.height
        viewPosX = view.x
    }

    val keyboardKeyWidthPercent = 0.333
    val keyboardRowsCount = 3

    val keyboardButtonWidthQuarter = (viewWidth * keyboardKeyWidthPercent) / 4
    val keyboardButtonHeightHalf = (viewHeight / keyboardRowsCount) / 2

    val viewTargetY = viewHeight - keyboardButtonHeightHalf
    val viewTargetX = (viewPosX + viewWidth) - keyboardButtonWidthQuarter
    return Pair(viewTargetY, viewTargetX)
}

Now, the click is not perfectly centered but it clicks the button pretty close to the center.

Upvotes: 1

Related Questions