Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for async task in Espresso without IdlingResource

I have the following espresso code:

@Test
fun backgroundWorkDisplaysTextAfterLoading() {
    onView(withId(R.id.btn_next_fragment)).perform(click())
    onView(withId(R.id.btn_background_work)).perform(click())

    onView(withId(R.id.hiddenTextView)).check(matches(isDisplayed()))
}

When the btn_background_work is clicked, there is a service call that will return a response in 2 seconds and then the hiddenTextview will become visible.

How can I let Espresso wait to execute the ViewAssertion ( check(matches(isDisplayed)) ) until this service call has returned a value? The service call is done with Retrofit and the service call is done on the Schedulers.io() thread.

I cannot touch the production code, so implementing IdlingResources in the production code is impossible.

Any help is appreciated!

like image 696
JavascriptDeveloper Avatar asked Oct 28 '25 08:10

JavascriptDeveloper


1 Answers

Using idling resources doesn't need an update of the code in production.

But this can also be done without idling resource as you asked for, you could use this custom ViewAction:

/**
 * Perform action of waiting until the element is accessible & not shown.
 * @param viewId The id of the view to wait for.
 * @param millis The timeout of until when to wait for.
 */
public static ViewAction waitUntilShown(final int viewId, final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "wait for a specific view with id <" + viewId + "> is shown during " + millis + " millis.";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            uiController.loopMainThreadUntilIdle();
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + millis;
            final Matcher<View> viewMatcher = withId(viewId);

            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    // found view with required ID
                    if (viewMatcher.matches(child) && child.isShown()) {
                        return;
                    }
                }

                uiController.loopMainThreadForAtLeast(50);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new TimeoutException())
                    .build();
        }
    };
}

And you can use it this way:

onView(isRoot()).perform(waitUntilShown(R.id.hiddenTextView, 5000));

updating the timeout of 5 secs (5000 millis) if necessary.

like image 177
jeprubio Avatar answered Oct 31 '25 00:10

jeprubio