Espresso wait for view

8 March 2020 stoefln Leave a comment Espresso

While waiting for a view to show up is usually handled by Espresso itself, there might be cases where your app is doing work on another thread (fetching data from a server, calculating something) and therefore Espresso does not know about it.

Make Espresso wait for the view to show up

There are different ways to handle this, most commonly idling resources. Since we have covered the use and downsides of idling resources in other posts, we want to introduce another way how to go about this problem.

 

Waiting for a view to show up without the need for an idling resource

A nice way to handle this case is to use ViewActions. ViewActions are responsible for performing an interaction on a given View element.
The ViewAction class is part of the public test framework API which allows devs to write their own ViewAction implementations when necessary.

// CustomViewActions.kt: 
/**
 * This ViewAction tells espresso to wait till a certain view is found in the view hierarchy.
 * @param viewId The id of the view to wait for.
 * @param timeout The maximum time which espresso will wait for the view to show up (in milliseconds)
 */
fun waitForView(viewId: Int, timeout: Long): ViewAction {
    return object : ViewAction {
        override fun getConstraints(): Matcher {
            return isRoot()
        }

        override fun getDescription(): String {
            return "wait for a specific view with id $viewId; during $timeout millis."
        }

        override fun perform(uiController: UiController, rootView: View) {
            uiController.loopMainThreadUntilIdle()
            val startTime = System.currentTimeMillis()
            val endTime = startTime + timeout
            val viewMatcher = withId(viewId)

            do {
                // Iterate through all views on the screen and see if the view we are looking for is there already
                for (child in TreeIterables.breadthFirstViewTraversal(rootView)) {
                    // found view with required ID
                    if (viewMatcher.matches(child)) {
                        return
                    }
                }
                // Loops the main thread for a specified period of time.
                // Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
                uiController.loopMainThreadForAtLeast(100)
            } while (System.currentTimeMillis() < endTime) // in case of a timeout we throw an exception -> test fails
            throw PerformException.Builder()
                .withCause(TimeoutException())
                .withActionDescription(this.description)
                .withViewDescription(HumanReadables.describe(rootView))
                .build()
        }
    }
}

And then it’s actually quite simple to use:

// LoginTest.kt: 
@Test
fun shouldCloseDialog(){
    onView(withId(R.id.show_dialog_button)).perform(click())
    // here espresso needs to wait till the dialog actually shows and the close_button is visible
    onView(isRoot()).perform(waitForView(R.id.close_button, 5000))
}

The official documentation mentions a couple of rules you should follow when implementing ViewActions:

  • Inject motion events or key events via the UiController to simulate user interactions.
  • Do not mutate the view directly via setter methods and other state changing methods on the view parameter.
  • Do not throw AssertionErrors. Assertions belong in ViewAssertion classes.
  • View action code will executed on the UI thread, therefore you should not block, perform sleeps, or perform other expensive computations.
  • The test framework will wait for the UI thread to be idle both before and after perform() is called. This means that the
  • action is guaranteed to be synchronized with any other view operations.
  • Downcasting the View object to an expected subtype is allowed, so long as the object expresses the subtype matches the constraints as specified in getConstraints.

Advantages of using ViewActions instead of idling resources

We use ViewActions in favor of Idling resources whenever we can. For the following reasons:

  1. No need to modify your application code
  2. ViewActions are more intuitive to use than idling resources
  3. Easy to use in a generic way (see above: the waitForView ViewAction could be used for all kinds of views)

Further readings

Learn more about Espresso in this overview on Espresso use and challenges.

Tags: , , ,

Like this article? there’s more where that came from.

Leave a Reply