android-test icon indicating copy to clipboard operation
android-test copied to clipboard

toolType[0] on motion event differs from `ACTION_DOWN ` to `ACTION_UP` in events sent by Espresso `click()`

Open yogurtearl opened this issue 3 years ago • 3 comments

Description

If you try to click() on a android.widget.Button inside an androidx.compose.ui.platform.AndroidComposeView (via androidx.compose.ui.viewinterop.AndroidView ) using Espresso, the click never happens and is cancelled

When Espresso ( 3.5.0-alpha07 ) performs a click() action it injects an ACTION_DOWN and then an ACTION_UP MotionEvent.

Something like:

 MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=327.5, y[0]=1046.5, toolType[0]=TOOL_TYPE_UNKNOWN, buttonState=BUTTON_PRIMARY, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=48700898, downTime=48700898, deviceId=0, source=0x1002, displayId=0, eventId=-532043644 }
 MotionEvent { action=ACTION_UP, actionButton=0, id[0]=0, x[0]=327.5, y[0]=1046.5, toolType[0]=TOOL_TYPE_FINGER, buttonState=BUTTON_PRIMARY, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=48701639, downTime=48700898, deviceId=0, source=0x1002, displayId=0, eventId=-1016641128 }

Notice that the ACTION_DOWN has toolType[0]=TOOL_TYPE_UNKNOWN but the ACTION_UP has toolType[0]=TOOL_TYPE_FINGER

AndroidComposeView checks the toolType[0] and if they differs cancels the click.

See: https://github.com/androidx/androidx/blob/85f5556f9488a908af7ce4476e172a4e701a1fa0/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt#L1284-L1287

    private fun hasChangedDevices(event: MotionEvent, lastEvent: MotionEvent): Boolean {
        return lastEvent.source != event.source ||
            lastEvent.getToolType(0) != event.getToolType(0)
    }

and here: https://github.com/androidx/androidx/blob/85f5556f9488a908af7ce4476e172a4e701a1fa0/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt#L1241-L1246

                if (lastEvent != null &&
                    hasChangedDevices(motionEvent, lastEvent)
                ) {
                    if (isDevicePressEvent(lastEvent)) {
                        // Send a cancel event
                        pointerInputEventProcessor.processCancel()

Steps to Reproduce

This was filed on the Compose tracker (https://issuetracker.google.com/issues/238057230), but I think this is an Espresso bug.

Reproduced here: https://github.com/mr-thierry/ComposeEspressoBug

  1. clone https://github.com/mr-thierry/ComposeEspressoBug
  2. run com.example.espressocomposebug.ExampleInstrumentedTest#test on API 32 emulator

Expected Results

Prints this in logcat:

D/TRLOG: TRLOG **********************************
D/TRLOG: TRLOG BUTTON INSIDE CLICKED
D/TRLOG: TRLOG **********************************

Actual Results

Does NOT print this, because the click gets cancelled as describes above.

D/TRLOG: TRLOG **********************************
D/TRLOG: TRLOG BUTTON INSIDE CLICKED
D/TRLOG: TRLOG **********************************

Workaround

As a work-around, you can use this instead of ViewActions.click(). This uses SOURCE_TOUCHSCREEN instead of SOURCE_UNKNOWN

fun fixedClick() =
    ViewActions.actionWithAssertions(
        GeneralClickAction(
            SINGLE,
            VISIBLE_CENTER,
            FINGER,
            InputDevice.SOURCE_TOUCHSCREEN,
            MotionEvent.BUTTON_PRIMARY
        )
    )

... 
   
   Espresso.onView(withId(R.id.button_inside)).perform(fixedClick())

AndroidX Test and Android OS Versions

Espresso: 3.5.0-alpha07 Android API 32

Link to a public git repo demonstrating the problem:

https://github.com/mr-thierry/ComposeEspressoBug

yogurtearl avatar Aug 04 '22 00:08 yogurtearl

I have tested this fix and it works: https://github.com/android/android-test/pull/1459

yogurtearl avatar Aug 04 '22 00:08 yogurtearl

This looks like a really nice investigation and fix! 👏🏻

TWiStErRob avatar Aug 04 '22 07:08 TWiStErRob

The workaround doesn't seem to work for us 🤔 Thanks for the analysis and the fix.

hrach avatar Aug 04 '22 09:08 hrach