Espresso clicks implementation sometimes causes additional problems while devices are unstable. It could be a click that was registered as long click. Click that is not registered at all. And so on.
From the [androidx.test.espresso.action.GeneralClickAction.perform] comment:
Native event injection is quite a tricky process. A tap is actually 2
seperate motion events which need to get injected into the system. Injection
makes an RPC call from our app under test to the Android system server, the
system server decides which window layer to deliver the event to, the system
server makes an RPC to that window layer, that window layer delivers the event
to the correct UI element, activity, or window object. Now we need to repeat
that 2x. for a simple down and up. Oh and the down event triggers timers to
detect whether or not the event is a long vs. short press. The timers are
removed the moment the up event is received (NOTE: the possibility of eventTime
being in the future is totally ignored by most motion event processors).
Phew.
The net result of this is sometimes we'll want to do a regular tap, and for
whatever reason the up event (last half) of the tap is delivered after long
press timeout (depending on system load) and the long press behaviour is
displayed (EG: show a context menu). There is no way to avoid or handle this more
gracefully. Also the longpress behavour is app/widget specific. So if you have
a seperate long press behaviour from your short press, you can pass in a
'RollBack' ViewAction which when executed will undo the effects of long press.
If you experience unreliable tap/click in UI tests you can try our naive but sometimes more reliable implementation, that dispatches events directly to View.
Custom clicks distributed as separate artifact:
Maven
<dependency>
<groupId>io.github.kakaocup</groupId>
<artifactId>kakao-ext-clicks</artifactId>
<version>
<latest version>
</version>
<type>pom</type>
</dependency>
or Gradle:
dependencies {
androidTestImplementation 'io.github.kakaocup:kakao-ext-clicks:<latest version>'
}
There are multiple ways to apply custom clicks:
If you want to apply it directly to single test class the TestRule is an obvious choice.
For example:
@Rule
@JvmField
var chain: TestRule = RuleChain.outerRule(ActivityScenarioRule(MyActivity::class.java))
.around(KakaoClicksTestRule())
If you need to change it globally, you can override static variables of kakao, like that:
Kakao {
singleClickAction = KakaoSingleClick()
doubleClickAction = KakaoDoubleClick()
longClickAction = KakaoLongClick()
}
To revert this behavior you can set it back:
Kakao {
singleClickAction = EspressoSingleClick()
doubleClickAction = EspressoDoubleClick()
longClickAction = EspressoLongClick()
}
To make a precise change for single click you can implement your own function and execute your test code wrapped in it:
fun withCustomClicks(block: () -> Unit) {
Kakao {
singleClickAction = KakaoSingleClick()
doubleClickAction = KakaoDoubleClick()
longClickAction = KakaoLongClick()
}
block.invoke()
Kakao {
singleClickAction = EspressoSingleClick()
doubleClickAction = EspressoDoubleClick()
longClickAction = EspressoLongClick()
}
}
button {
withCustomClicks {
click() // clicked with custom mechanism
}
click() // clicked by espresso
}
Click visualization is a useful debug tool. Usually it's enabled with Android option Developer Options > Show taps
. Or using ADB:
adb shell settings put system show_touches 1
On different setups it can be impossible to set or it can simply not working.
To enable visual taps programmatically with custom clicks, use custom click constructor:
KakaoSingleClick(visualClicksConfig = VisualClicksConfig()) // null is the default argument
KakaoDoubleClick(visualClicksConfig = VisualClicksConfig())
KakaoLongClick(visualClicksConfig = VisualClicksConfig())
or if you are using TestRule:
KakaoClicksTestRule(visualClicksConfig = VisualClicksConfig())
to apply config to all types of clicks
VisualClicksConfig
data class has some customization options: like color and radius of the tap circle.
There are some cases when standard espresso coordinates not working.
For example clicking on center of the view with applied property animations or transitions with help of GeneralLocation.VISIBLE_CENTER
.
See explanation on why it happens here.
VisibleCenterGlobalCoordinatesProvider
to the rescue.
Pass it as a replacement for your click location like that:
tranformedView.click(VisibleCenterGlobalCoordinatesProvider())
You can also tune emulator a bit that could help with long click registration.
adb shell "settings put secure long_press_timeout 1500" // default can vary but usually it's 400ms
Initial idea and implementation by Avito