Espresso wait for view

8 March 2020 Stephan Petzl Leave a comment Tech-Help

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.

<span class="token comment">// CustomViewActions.kt: </span>
<span class="token comment">/**
 * 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)
 */</span>
<span class="token keyword">fun</span> <span class="token function">waitForView</span><span class="token punctuation">(</span>viewId<span class="token operator">:</span> Int<span class="token punctuation">,</span> timeout<span class="token operator">:</span> Long<span class="token punctuation">)</span><span class="token operator">:</span> ViewAction <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">object</span> <span class="token operator">:</span> ViewAction <span class="token punctuation">{</span>
        <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">getConstraints</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> Matcher <span class="token punctuation">{</span>
            <span class="token keyword">return</span> <span class="token function">isRoot</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> String <span class="token punctuation">{</span>
            <span class="token keyword">return</span> <span class="token string">"wait for a specific view with id <span class="token interpolation variable">$viewId</span>; during <span class="token interpolation variable">$timeout</span> millis."</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">perform</span><span class="token punctuation">(</span>uiController<span class="token operator">:</span> UiController<span class="token punctuation">,</span> rootView<span class="token operator">:</span> View<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            uiController<span class="token punctuation">.</span><span class="token function">loopMainThreadUntilIdle</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token keyword">val</span> startTime <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token keyword">val</span> endTime <span class="token operator">=</span> startTime <span class="token operator">+</span> timeout
            <span class="token keyword">val</span> viewMatcher <span class="token operator">=</span> <span class="token function">withId</span><span class="token punctuation">(</span>viewId<span class="token punctuation">)</span>

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

And then it’s actually quite simple to use:

<span class="token comment">// LoginTest.kt: </span>
<span class="token annotation builtin">@Test</span>
<span class="token keyword">fun</span> <span class="token function">shouldCloseDialog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
    <span class="token function">onView</span><span class="token punctuation">(</span><span class="token function">withId</span><span class="token punctuation">(</span>R<span class="token punctuation">.</span>id<span class="token punctuation">.</span>show_dialog_button<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span><span class="token function">click</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token comment">// here espresso needs to wait till the dialog actually shows and the close_button is visible</span>
    <span class="token function">onView</span><span class="token punctuation">(</span><span class="token function">isRoot</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span><span class="token function">waitForView</span><span class="token punctuation">(</span>R<span class="token punctuation">.</span>id<span class="token punctuation">.</span>close_button<span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
Kotlin

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)

Make Espresso wait for a text to appear

Since text fields are also just views, you can simply use the ViewAction above to check if the text is on stage. However: If you don’t want to use an ID (or you are not allowed to set an ID on the view), you can use this ViewAction:

<span class="token comment">/**
 * A [ViewAction] that waits up to [timeout] milliseconds for a text string.
 *
 * @param text the text to wait for.
 * @param timeout the length of time in milliseconds to wait for.
 */</span>
<span class="token keyword">class</span> <span class="token function">WaitForTextAction</span><span class="token punctuation">(</span><span class="token keyword">private</span> <span class="token keyword">val</span> text<span class="token operator">:</span> String<span class="token punctuation">,</span>
                        <span class="token keyword">private</span> <span class="token keyword">val</span> timeout<span class="token operator">:</span> Long<span class="token punctuation">)</span> <span class="token operator">:</span> ViewAction <span class="token punctuation">{</span>

    <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">getConstraints</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> Matcher<span class="token operator"><</span>View<span class="token operator">></span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token function">isAssignableFrom</span><span class="token punctuation">(</span>TextView<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">.</span>java<span class="token punctuation">)</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> String <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token string">"wait up to <span class="token interpolation variable">$timeout</span> milliseconds for the view to have text <span class="token interpolation variable">$text</span>"</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">perform</span><span class="token punctuation">(</span>uiController<span class="token operator">:</span> UiController<span class="token punctuation">,</span> view<span class="token operator">:</span> View<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">val</span> endTime <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> timeout

        <span class="token keyword">do</span> <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>view <span class="token keyword">as</span><span class="token operator">?</span> TextView<span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span>text <span class="token operator">==</span> text<span class="token punctuation">)</span> <span class="token keyword">return</span>
            uiController<span class="token punctuation">.</span><span class="token function">loopMainThreadForAtLeast</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span>
        <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><</span> endTime<span class="token punctuation">)</span>

        <span class="token keyword">throw</span> PerformException<span class="token punctuation">.</span><span class="token function">Builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">withActionDescription</span><span class="token punctuation">(</span>description<span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">withCause</span><span class="token punctuation">(</span><span class="token function">TimeoutException</span><span class="token punctuation">(</span><span class="token string">"Waited <span class="token interpolation variable">$timeout</span> milliseconds"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">withViewDescription</span><span class="token punctuation">(</span>HumanReadables<span class="token punctuation">.</span><span class="token function">describe</span><span class="token punctuation">(</span>view<span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
Kotlin

Further readings

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

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

Leave a Reply