Espresso: Thread.sleep( )

76,182

Solution 1

On my mind correct approach will be:

/** Perform action of waiting for a specific view id. */
public static ViewAction waitId(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 + "> 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)) {
                        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 then pattern of usage will be:

// wait during 15 seconds for a view
onView(isRoot()).perform(waitId(R.id.dialogEditor, TimeUnit.SECONDS.toMillis(15)));

Solution 2

Thanks to AlexK for his awesome answer. There are cases that you need to make some delay in code. It is not necessarily waiting for server response but might be waiting for animation to get done. I personally have problem with Espresso idolingResources (I think we are writing many lines of code for a simple thing) so I changed the way AlexK was doing into following code:

/**
 * Perform action of waiting for a specific time.
 */
public static ViewAction waitFor(final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return "Wait for " + millis + " milliseconds.";
        }

        @Override
        public void perform(UiController uiController, final View view) {
            uiController.loopMainThreadForAtLeast(millis);
        }
    };
}

So you can create a Delay class and put this method in it in order to access it easily. You can use it in your Test class same way: onView(isRoot()).perform(waitFor(5000));

Solution 3

I stumbled upon this thread when looking for an answer to a similar problem where I was waiting for a server response and changing visibility of elements based on response.

Whilst the solution above definitely helped, I eventually found this excellent example from chiuki and now use that approach as my go-to whenever I'm waiting for actions to occur during app idle periods.

I've added ElapsedTimeIdlingResource() to my own utilities class, can now effectively use that as an Espresso-proper alternative, and now usage is nice and clean:

// Make sure Espresso does not time out
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);

// Now we wait
IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);

// Stop and verify
onView(withId(R.id.toggle_button))
    .check(matches(withText(R.string.stop)))
    .perform(click());
onView(withId(R.id.result))
    .check(matches(withText(success ? R.string.success: R.string.failure)));

// Clean up
Espresso.unregisterIdlingResources(idlingResource);

Solution 4

I think it's more easy to add this line:

SystemClock.sleep(1500);

Waits a given number of milliseconds (of uptimeMillis) before returning. Similar to sleep(long), but does not throw InterruptedException; interrupt() events are deferred until the next interruptible operation. Does not return until at least the specified number of milliseconds has elapsed.

Solution 5

This is similar to this answer but uses a timeout instead of attempts and can be chained with other ViewInteractions:

/**
 * Wait for view to be visible
 */
fun ViewInteraction.waitUntilVisible(timeout: Long): ViewInteraction {
    val startTime = System.currentTimeMillis()
    val endTime = startTime + timeout

    do {
        try {
            check(matches(isDisplayed()))
            return this
        } catch (e: AssertionFailedError) {
            Thread.sleep(50)
        }
    } while (System.currentTimeMillis() < endTime)

    throw TimeoutException()
}

Usage:

onView(withId(R.id.whatever))
    .waitUntilVisible(5000)
    .perform(click())
Share:
76,182
Chad Bingham
Author by

Chad Bingham

Arguing with a software engineer is like wrestling with a pig in the mud; after a while you realize the pig likes it. -Unknown

Updated on September 17, 2021

Comments

  • Chad Bingham
    Chad Bingham almost 3 years

    Espresso claims that there is no need for Thread.sleep() but my code doesn't work unless I include it. I am connecting to an IP and, while connecting, a progress dialog is shown. I need a Thread.sleep() call to wait for the dialog to dismiss. This is my test code where I use it:

        IP.enterIP(); // fills out an IP dialog (this is done with espresso)
    
        //progress dialog is now shown
        Thread.sleep(1500);
    
        onView(withId(R.id.button).perform(click());
    

    I have tried this code without the Thread.sleep() call but it says R.id.Button doesn't exist. The only way I can get it to work is with the Thread.sleep() call.

    Also, I have tried replacing Thread.sleep() with things like getInstrumentation().waitForIdleSync() and still no luck.

    Is this the only way to do this? Or am I missing something?

    Thanks in advance.

  • Chad Bingham
    Chad Bingham over 10 years
    Your example code is essentially the same code I have written in my question.
  • Bolhoso
    Bolhoso over 10 years
    @Binghammer what I mean is the test should behave like the user behaves. Maybe the point I'm missing is what your IP.enterIP() method does. Can you edit your question and clarify that?
  • Chad Bingham
    Chad Bingham over 10 years
    My comments say what it does. It is just a method in espresso that fills out the IP dialog. It is all UI.
  • Bolhoso
    Bolhoso over 10 years
    mm... ok, so you're right, my test is basically doing the same. Do you do something out of the UI thread or AsyncTasks?
  • haffax
    haffax about 10 years
    Espresso does not work like the code and text of this answer seems to imply. A check call on a ViewInteraction will not wait till the given Matcher succeeds, but rather fail immediately if the condition is not met. The right way to do this is to either use AsyncTasks, as mentioned in this answer, or, if somehow not possible, implement an IdlingResource that will notify Espresso's UiController on when it is OK to proceed with the test execution.
  • Tim Boland
    Tim Boland over 9 years
    Thanks Alex, why did you choose this option over IdlingResource or AsyncTasks?
  • Oleksandr Kucherenko
    Oleksandr Kucherenko over 9 years
    This is workaround approach, in most cases Espresso doing the job without any problems and special 'wait code'. I actually try several different ways, and think that this is one is the most matching Espresso architecture/design.
  • dawid gdanski
    dawid gdanski over 8 years
    @AlexK this made my day mate!
  • Anthony
    Anthony about 8 years
    I get a I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceerror. Any idea. I use Proguard but with disable obfuscation.
  • MattMatt
    MattMatt about 8 years
    Try adding a -keep statement for classes that are not being found to make sure that ProGuard isn't removing them as unnecessary. More info here: developer.android.com/tools/help/proguard.html#keep-code
  • Anthony
    Anthony about 8 years
    I post a question stackoverflow.com/questions/36859528/…. The class is in the seed.txt and mapping.txt
  • Yair Kukielka
    Yair Kukielka about 8 years
    the perform method can even be simplified with one line like this: uiController.loopMainThreadForAtLeast(millis);
  • Hesam
    Hesam about 8 years
    Awesome, I didn't know that :thumbs_up @YairKukielka
  • Prabin Timsina
    Prabin Timsina about 8 years
    for me, it fails for api <= 19, at line throw new PerformException.Builder()
  • Teo Inke
    Teo Inke about 8 years
    It's not running, Matcher import is not working for me. Any help with that? I tried org.hamcrest.Matcher but it just doesn't exist
  • Oleksandr Kucherenko
    Oleksandr Kucherenko almost 8 years
    use static import for Matcher methods. Code is working fine, You just should confirm that all dependencies are included: Hamcrest + Espresso And if it not working right now for you than double check the versions of the dependencies.
  • Oleksandr Kucherenko
    Oleksandr Kucherenko almost 8 years
    I hope you understand that its a sample, you can copy/paste and modify for own needs. Its completely yours responsibility to use it properly in own business needs, not mine.
  • TWiStErRob
    TWiStErRob almost 8 years
    Yikes for the busy wait.
  • Jose Alcérreca
    Jose Alcérreca almost 8 years
    If you need to change the idling policies, you're probably not implementing idling resources correctly. In the long run it's much better to invest time in fixing that. This method will eventually lead to slow and flaky tests. Check out google.github.io/android-testing-support-library/docs/espres‌​so/…
  • Jose Alcérreca
    Jose Alcérreca almost 8 years
    @haffax Espresso does not work like that because the application under test is not prepared to do so. With a correct implementation of idling resources this is the only correct answer. The rest are shortcuts that will backfire at some point.
  • MattMatt
    MattMatt almost 8 years
    You're quite right. This answer's over a year old, and since then the behaviour of idling resources has improved such that the same use case that I used the above code for now works out of the box, properly detecting the mocked API client - we no longer use the above ElapsedTimeIdlingResource in our instrumented tests for that reason. (You could also of course Rx all the things, which negates the need to hack in a waiting period). That said, the Google way of doing things isn't always the best: philosophicalhacker.com/post/….
  • Oleksandr Kucherenko
    Oleksandr Kucherenko over 7 years
    Idling Resources is a new thing. Post was originally written 2 years ago. So For espresso library version that were available 2 years ago it was and OK solution and based on internal logic of Espresso lib.
  • Oleksandr Kucherenko
    Oleksandr Kucherenko over 7 years
    Should work. Can you provide the line of call? Potentially it may stay in "endless" loop only if during the call used the wrong timeout value. For example minutes instead of milliseconds...
  • Tobias Reich
    Tobias Reich over 7 years
    Awesome. I was searching for that for ages. +1 for a simple solution for waiting problems.
  • Kaveesh Kanwal
    Kaveesh Kanwal over 7 years
    I am using record espresso test feature, the code get's generated on it's own, but i am still facing NoMatchingViewException...anybody has any idea regarding how to fix this issue? thanks.
  • Wahib Ul Haq
    Wahib Ul Haq over 7 years
    Much much better way to add delay instead of using Thread.sleep()
  • M Tomczynski
    M Tomczynski over 7 years
    @AlexK have you used this code in real life scenario? This bit of code doesn't work for me because when perform is being called view that I'm looking for isn't yet in the view hierarchy, so it always throws timeout exception. Perform method doesn't respond to newest views on the screen. The view is clearly displayed but your method doesn't see it
  • b3nk4n
    b3nk4n almost 7 years
    I'm having the same problem like @MTomczyński. Most of the times it works fine, but in some scenarios the new view elements or not passed in the View parameter, so it is not able to wait for the view even that it is clearly visible on the device. Otherwise it works fine so far!
  • Oleksandr Kucherenko
    Oleksandr Kucherenko almost 7 years
    onView(isRoot()) is critical for code. If view visible on the device screen, but it cannot be catched by a test, than highly possible that selected a wrong view in onView(...) call. @MTomczyński @bsautermeister
  • mbob
    mbob over 6 years
    in addition, in case of an error if you want to have the name of the id you're waiting for you might want to use this: getInstrumentation().getTargetContext().getResources().getRe‌​sourceName(viewId)
  • mbob
    mbob over 6 years
    in your example, the findById(int itemId) method will return an element(which could be NULL) whether the waitForElementUntilDisplayed(element); returns true or false....so, that's not ok
  • mbob
    mbob over 6 years
    @AlexK: however, you are searching the element in its root, but if you want to search for an element which is in root, but the root is in the next screen(activity), your element won't be found anymore. Example: Lets say we have a LoginActivity -> input credentials and hit login button. I want to wait for the next activity(HomeActivity) to be displayed. In this case, isRoot in your method will know only for LoginActivity.
  • oaskamay
    oaskamay over 6 years
    Just wanted to chime in and say this is the best solution in my opinion. IdlingResources aren't enough for me due to the 5-second polling rate granularity (way too big for my use case). The accepted answer doesn't work for me either (explanation of why is already included in that answer's long comment feed). Thanks for this! I took your idea and made my own solution and it works like a charm.
  • guilhermekrz
    guilhermekrz over 6 years
    Yes, this is the only solution that worked for me too, when wanting to wait for elements that are not in the current activity.
  • guilhermekrz
    guilhermekrz over 6 years
    To solve this case when the element is in another activity, the solution in answer stackoverflow.com/a/44327230/2871861 worked for me.
  • Nikunjkumar Kapupara
    Nikunjkumar Kapupara over 6 years
    Use this below single line of code for dealy any Test Espresso test case: SystemClock.sleep(1000); // 1 Second
  • Manuel Schmitzberger
    Manuel Schmitzberger almost 6 years
    for me this only works by changing this line return new TimedViewInteraction(Espresso.onView(viewMatcher)); with return new TimedViewInteraction(Espresso.onView(viewMatcher).check(matc‌​hes(isDisplayed())))‌​;
  • Emjey
    Emjey about 5 years
    Expresso is to avoid these hardcoded sleep that causes flaky tests. if this is the case I can as well go for blackbox tools like appium
  • Pablo Caviglia
    Pablo Caviglia over 4 years
    I don't get the difference between this and just doing a thread sleep
  • Roc Boronat
    Roc Boronat over 4 years
    Honestly, I don't remember in which video from Google a guy said that we should use this way to do a sleep instead of making a common Thread.sleep(). Sorry! It was in some of the first videos Google made about Espresso but I don't remember which one... it was some years ago. Sorry! :·) Oh! Edit! I put the link to the video in the PR I opened three years ago. Check it out! github.com/AdevintaSpain/Barista/pull/19
  • Mr-IDE
    Mr-IDE almost 4 years
    This is fine but it has a limitation if it's chained to another call, like onData(anything()).atPosition(1).perform(click()).perform(wa‌​itFor(2000)); : If your UI test moves to a different Activity/screen, then the waitFor() part will fail with "androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching ...". However, if waitFor() is called independently afterwards, it seems to work fine.
  • Adil Hussain
    Adil Hussain over 3 years
    I don't see how calling this ViewAction is different to calling SystemClock.sleep(millis). Both wait a fixed number of milliseconds before returning. I would strongly suggest defining your ViewAction classes to wait on a particular condition (as demonstrated here and here) so they return faster in most cases and only wait up to the maximum number of milliseconds in the case of error.
  • Thiago Neves
    Thiago Neves almost 3 years
    For me doesn't work, even with try-catch block the test showed failure (because any exception, prevents the test results ok). For me, I combine the recursive approach with Thread Sleep and withFailureHandler, which works fine.
  • moyo
    moyo almost 3 years
    I used this approach, but it didn't exactly work for me. I had to catch AssertionFailedError instead of NoMatchingViewException. With that change, it works perfectly
  • JCarlosR
    JCarlosR over 2 years
    More about Espresso not recommending "sleep": developer.android.com/training/testing/espresso/… I'll start testing with it though, and then refactor.