Skip to content

Commit

Permalink
Fix dialog button click not recognized
Browse files Browse the repository at this point in the history
  • Loading branch information
nenick committed Apr 23, 2021
1 parent 7090847 commit ae00610
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
package de.nenick.espressomacchiato.dialog

import android.os.Build
import androidx.test.espresso.Espresso
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.platform.app.InstrumentationRegistry
import de.nenick.espressomacchiato.internals.ClickWithRetryAction
import de.nenick.espressomacchiato.widget.EspButton

@Suppress("UNCHECKED_CAST")
class EspAlertDialogButton(id: Int, interactions: EspAlertDialogButton.() -> Unit = {}) : EspButton(id, RootMatchers.isDialog(), interactions as EspButton.() -> Unit) {
class EspAlertDialogButton(
id: Int,
interactions: EspAlertDialogButton.() -> Unit = {}
) : EspButton(id, RootMatchers.isDialog(), interactions as EspButton.() -> Unit) {

override fun performClick() {
fixForFastDialogTests()
super.performClick()
}

private fun fixForFastDialogTests() {
// When fast open/close multiple dialogs, the click is not always performed properly.
// You see the button, but you don't see any click event happen.
// https://github.com/android/android-test/issues/444
// Issue was only observed on the following android versions.
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
// Weird, but is enough to just give the dialog a short moment to recognize the
// click properly. Perhaps the click listener isn't added yet, missing focusable?
// 20ms - still flaky on fast emulator
// 50ms - still flaky on slow emulator
// 100ms - still flaky on slow emulator (more rarely)
Thread.sleep(200)
// When fast open/close multiple dialogs, the click is not always performed properly.
// You see the button, but you don't see any click event happen.
// https://github.com/android/android-test/issues/444
perform(ClickWithRetryAction())
Espresso.onIdle()
} else {
super.performClick()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package de.nenick.espressomacchiato.internals

import android.view.View
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers
import org.hamcrest.Matcher

class ClickWithRetryAction : ViewAction {

class ClickInterceptor(
private val view: View,
private val originClickListener: Any,
private val clickListener: () -> Unit
) : View.OnClickListener {
override fun onClick(v: View?) {
originClickListener.javaClass.declaredMethods[0].invoke(originClickListener, view)
clickListener()
}
}

private val startTime by lazy { System.currentTimeMillis() }

override fun getConstraints(): Matcher<View> = ViewMatchers.isDisplayed()

override fun getDescription() = "click with retry"

override fun perform(uiController: UiController, view: View) {
var clickPerformed = false

interceptOnClickListener(view) {
clickPerformed = true
}

while (!clickPerformed) {
if (isTimeout()) {
throw InterruptedException("Click wasn't recognize.")
}

view.performClick()
Thread.sleep(delayNextAttemptMilliseconds)
}
}

private fun interceptOnClickListener(view: View, clickListener: () -> Unit) {
val listenerInfoField = View::class.java.getDeclaredField("mListenerInfo").apply { isAccessible = true }
val listenerInfo = listenerInfoField.get(view)
val onClickListenerField = listenerInfo.javaClass.getDeclaredField("mOnClickListener")
val originClickListener = onClickListenerField.get(listenerInfo)!!
val clickInterceptor = ClickInterceptor(view, originClickListener, clickListener)
onClickListenerField.set(listenerInfo, clickInterceptor)
}

private fun isTimeout() = System.currentTimeMillis() - startTime > timeoutMilliseconds

companion object {
private const val delayNextAttemptMilliseconds = 20L
private const val timeoutMilliseconds = 2 * 1000L
}
}

0 comments on commit ae00610

Please sign in to comment.