Skip to content

Instantly share code, notes, and snippets.

@Frank1234
Last active November 23, 2019 13:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Frank1234/1824d6b88fe6901523aaa99673577ca7 to your computer and use it in GitHub Desktop.
Save Frank1234/1824d6b88fe6901523aaa99673577ca7 to your computer and use it in GitHub Desktop.
Helper methods for Espresso and React Native.
import android.graphics.Rect
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.PerformException
import android.support.test.espresso.UiController
import android.support.test.espresso.ViewAction
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.espresso.util.HumanReadables
import android.support.test.espresso.util.TreeIterables
import android.view.View
import android.view.ViewGroup
import com.facebook.react.uimanager.RootView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import java.util.concurrent.TimeoutException
object EspressoViewFinder {
private const val CHECK_INTERVAL = 50L
private const val TIMEOUT_MS = 10 * 1000L
/**
* Waits for the view referenced in [viewMatcher] to become visible, with a timeout of [timeOut]. If it
* becomes visible, [onDisplayedHandler] will be invoked.
*
* This method is needed because Espresso idling resources are not sufficient in combination with RN;
* it does not wait on the javascript thread and the bridge.
*
* Throws a TimeoutException wrapped in a PerformException when the view is not displayed within [timeOut].
*/
fun waitForDisplayed(viewMatcher: Matcher<View>,
timeOut: Long = TIMEOUT_MS,
onDisplayedHandler: ((Matcher<View>) -> Unit)? = null) {
// wait for view
onView(isRoot()).perform(createWaitForDisplayedViewAction(viewMatcher, timeOut))
// call handler
onDisplayedHandler?.invoke(viewMatcher)
}
private fun createWaitForDisplayedViewAction(viewMatcher: Matcher<View>,
timeOut: Long = TIMEOUT_MS) = object : ViewAction {
override fun getConstraints(): Matcher<View> {
return isRoot()
}
override fun getDescription(): String {
return "waitForDisplayed on viewMatcher <$viewMatcher> without timeOut $timeOut ms."
}
override fun perform(uiController: UiController, view: View) {
// wait for idle, so that we don't timeout while waiting on Espresso idling resources:
uiController.loopMainThreadUntilIdle()
val found = waitForView(uiController, view)
if (!found) {
throw createPerformException(view)
}
}
private fun waitForView(uiController: UiController, view: View): Boolean {
val timeOutTimeStamp = System.currentTimeMillis() + timeOut
do {
// find view with required matcher:
for (child in TreeIterables.breadthFirstViewTraversal(view)) {
if (viewMatcher.matches(child) && isDisplayed(child)) {
return true
}
}
uiController.loopMainThreadForAtLeast(CHECK_INTERVAL)
} while (System.currentTimeMillis() < timeOutTimeStamp)
return false
}
private fun createPerformException(view: View) = PerformException.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(TimeoutException())
.build()
}
private fun isDisplayed(view: View) = view.getGlobalVisibleRect(Rect()) && withEffectiveVisibility(Visibility.VISIBLE).matches(view)
/**
* Finds a matcher's view's child at the given index.
*/
fun childAtIndex(parentMatcher: Matcher<View>, childPosition: Int): Matcher<View> = object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("childAtIndex $childPosition of type $parentMatcher")
}
override fun matchesSafely(view: View): Boolean {
if (view.parent !is ViewGroup) {
return parentMatcher.matches(view.parent)
}
val group = view.parent as ViewGroup
return parentMatcher.matches(view.parent) && group.getChildAt(childPosition) == view
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment