android-test
android-test copied to clipboard
toolType[0] on motion event differs from `ACTION_DOWN ` to `ACTION_UP` in events sent by Espresso `click()`
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
- clone https://github.com/mr-thierry/ComposeEspressoBug
- run
com.example.espressocomposebug.ExampleInstrumentedTest#teston 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
I have tested this fix and it works: https://github.com/android/android-test/pull/1459
This looks like a really nice investigation and fix! 👏🏻
The workaround doesn't seem to work for us 🤔 Thanks for the analysis and the fix.