Thread.sleep( ) with Espresso
Asked Answered
T

14

129

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?

Trevino answered 28/1, 2014 at 22:6 Comment(11)
is it possible for you to put unwanted While loop anyway u want blocking call.Laporte
ok.. let me explain. 2 suggestions for you 1st)Implement something like call-back kind of mechanism. on-connection-establish call one method and show the view . 2nd) you want to create the delay in between IP.enterIP(); and onView(....) so you can put the while loop which will create the simillar kind of delay to call onview(..) ... but i feel if possible please prefer option No 1.(creating the call-back mechanism)...Laporte
@Laporte Yeah that is an option, but is that Espresso's solution?Trevino
There are unanswered comments in your question, could you answer them?Defection
@Bolhoso, what question?Trevino
@Binghammer this one. I've asked "Do you do something out of the UI thread or AsyncTasks?". This will guide troubleshooting the test's behaviorDefection
I have many threads and async tasks.Trevino
Im surprised Thread.sleep() works for you....I get this error when the sleep is done: NoActivityResumedException: No activities in stage RESUMED. Did you forget to launch the activity. (test.getActivity() or similar)?Windowpane
Hi Binghammer...are you still using the selected answer? Or did you go with a different approach? AsyncTasks? IdlingResource?Windowpane
Use this below single line of code for dealy any Test Espresso test case: SystemClock.sleep(1000); // 1 SecondInfarct
Adding an artificial delay in such cases should be considered a bad practice. Check this post outLivesay
S
120

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)));
Scyphozoan answered 21/3, 2014 at 15:45 Comment(17)
Thanks Alex, why did you choose this option over IdlingResource or AsyncTasks?Windowpane
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.Scyphozoan
@AlexK this made my day mate!Daniel
for me, it fails for api <= 19, at line throw new PerformException.Builder()Dingo
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 existInterdepartmental
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.Scyphozoan
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.Scyphozoan
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.Scyphozoan
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...Scyphozoan
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.Ilka
@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 itViperous
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!Sweetmeat
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 @bsautermeisterScyphozoan
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().getResourceName(viewId)Radie
@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.Radie
To solve this case when the element is in another activity, the solution in answer https://mcmap.net/q/173663/-thread-sleep-with-espresso worked for me.Carmel
For some reason none of these work for me, they all cause the test to just run/hang/spin forever. I have tried different variations of this, Thread.sleep, Systemclock.sleep etc all of them cause the test to just hang and spin forever.Salts
H
62

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));

Hydromedusa answered 10/3, 2016 at 18:56 Comment(7)
the perform method can even be simplified with one line like this: uiController.loopMainThreadForAtLeast(millis);Morville
Awesome, I didn't know that :thumbs_up @YairKukielkaHydromedusa
Yikes for the busy wait.Tamaru
Awesome. I was searching for that for ages. +1 for a simple solution for waiting problems.Deputy
Much much better way to add delay instead of using Thread.sleep()Paleoasiatic
This is fine but it has a limitation if it's chained to another call, like onData(anything()).atPosition(1).perform(click()).perform(waitFor(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.Handiwork
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.Paramo
K
25

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);
Kernan answered 20/7, 2015 at 16:8 Comment(5)
I get a I/TestRunner: java.lang.NoClassDefFoundError: fr.x.app.y.testtools.ElapsedTimeIdlingResourceerror. Any idea. I use Proguard but with disable obfuscation.Tumular
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-codeKernan
I post a question #36860028. The class is in the seed.txt and mapping.txtTumular
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/espresso/…Dino
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/….Kernan
C
20

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.

Complete answered 6/7, 2016 at 13:13 Comment(3)
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 appiumMenam
More about Espresso not recommending "sleep": developer.android.com/training/testing/espresso/… I'll start testing with it though, and then refactor.Culminant
The funny part is that I tried all the hard solutions first, and this is the one that worked jajajajaDirichlet
L
13

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())
Latvina answered 30/5, 2019 at 20:45 Comment(1)
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 perfectlyVote
C
7

You can just use Barista methods:

BaristaSleepActions.sleep(2000);

BaristaSleepActions.sleep(2, SECONDS);

Barista is a library that wraps Espresso to avoid adding all the code needed by the accepted answer. And here's a link! https://github.com/SchibstedSpain/Barista

Cloudcapped answered 30/3, 2017 at 15:54 Comment(2)
I don't get the difference between this and just doing a thread sleepPhytography
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/19Cloudcapped
L
4

I'm new to coding and Espresso, so while I know the good and reasonable solution is to use idling, I'm not yet intelligent enough to do that.

Until I become more knowledgeable, I still need my tests to somehow run though, so for now I'm using this dirty solution which makes a number of attempts at finding an element, stops if it finds it and if not, briefly sleeps and starts again until it reach the max nr of attempts (the highest number of attempts so far has been around 150).

private static boolean waitForElementUntilDisplayed(ViewInteraction element) {
    int i = 0;
    while (i++ < ATTEMPTS) {
        try {
            element.check(matches(isDisplayed()));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            try {
                Thread.sleep(WAITING_TIME);
            } catch (Exception e1) {
                e.printStackTrace();
            }
        }
    }
    return false;
}

I'm using this in all the methods that are finding elements by ID, text, parent etc:

static ViewInteraction findById(int itemId) {
    ViewInteraction element = onView(withId(itemId));
    waitForElementUntilDisplayed(element);
    return element;
}
Leyla answered 2/6, 2017 at 10:55 Comment(4)
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 okRadie
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.Badge
Yes, this is the only solution that worked for me too, when wanting to wait for elements that are not in the current activity.Carmel
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.Piecrust
D
3

Espresso is built to avoid sleep() calls in the tests. Your test should not open a dialog to enter an IP, that should be the tested activity's responsability.

On the other hand, your UI test should:

  • Wait for the IP dialog to appear
  • Fill in the IP address and click enter
  • Wait for your button to appear and click it

The test should look something like this:

// type the IP and press OK
onView (withId (R.id.dialog_ip_edit_text))
  .check (matches(isDisplayed()))
  .perform (typeText("IP-TO-BE-TYPED"));

onView (withText (R.string.dialog_ok_button_title))
  .check (matches(isDisplayed()))
  .perform (click());

// now, wait for the button and click it
onView (withId (R.id.button))
  .check (matches(isDisplayed()))
  .perform (click());

Espresso waits for everything that is happening both in the UI thread and the AsyncTask pool to finish before executing your tests.

Remember that your tests shouldn't do anything that is your application responsability. It should behave like an "well informed user": a user that clicks, verify that something is shown in the screen, but, as a matter of fact, know the IDs of the components

Defection answered 29/1, 2014 at 10:55 Comment(6)
Your example code is essentially the same code I have written in my question.Trevino
@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?Defection
My comments say what it does. It is just a method in espresso that fills out the IP dialog. It is all UI.Trevino
mm... ok, so you're right, my test is basically doing the same. Do you do something out of the UI thread or AsyncTasks?Defection
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.Squid
@Squid 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.Dino
S
2

You should use Espresso Idling Resource, it's suggested at this CodeLab

An idling resource represents an asynchronous operation whose results affect subsequent operations in a UI test. By registering idling resources with Espresso, you can validate these asynchronous operations more reliably when testing your app.

Example of an Asynchronous call from the Presenter

@Override
public void loadNotes(boolean forceUpdate) {
   mNotesView.setProgressIndicator(true);
   if (forceUpdate) {
       mNotesRepository.refreshData();
   }

   // The network request might be handled in a different thread so make sure Espresso knows
   // that the app is busy until the response is handled.
   EspressoIdlingResource.increment(); // App is busy until further notice

   mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
       @Override
       public void onNotesLoaded(List<Note> notes) {
           EspressoIdlingResource.decrement(); // Set app as idle.
           mNotesView.setProgressIndicator(false);
           mNotesView.showNotes(notes);
       }
   });
}

Dependencies

androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'

For androidx

androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support.test.espresso:espresso-idling-resource:3.0.2'

Official Repo: https://github.com/googlecodelabs/android-testing

IdlingResource Example: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample

Slacker answered 26/2, 2019 at 14:15 Comment(0)
R
1

You can also use CountDownLatch to block the thread, until you receive response from server or timeout.

Countdown latch is a simple yet elegant solution without needing an external library. It also helps your focus on the actual logic to be tested rather than over-engineering the async wait or waiting for a response

void testServerAPIResponse() {


        Latch latch = new CountDownLatch(1);


        //Do your async job
        Service.doSomething(new Callback() {

            @Override
            public void onResponse(){
                ACTUAL_RESULT = SUCCESS;
                latch.countDown(); // notify the count down latch
                // assertEquals(..
            }

        });

        //Wait for api response async
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        assertEquals(expectedResult, ACTUAL_RESULT);

    }
Reticular answered 15/10, 2020 at 10:16 Comment(0)
A
0

While I think it's best to use Idling Resources for this (https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/) you could probably use this as a fallback:

/**
 * Contains view interactions, view actions and view assertions which allow to set a timeout
 * for finding a view and performing an action/view assertion on it.
 * To be used instead of {@link Espresso}'s methods.
 * 
 * @author Piotr Zawadzki
 */
public class TimeoutEspresso {

    private static final int SLEEP_IN_A_LOOP_TIME = 50;

    private static final long DEFAULT_TIMEOUT_IN_MILLIS = 10 * 1000L;

    /**
     * Use instead of {@link Espresso#onView(Matcher)}
     * @param timeoutInMillis timeout after which an error is thrown
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(long timeoutInMillis, @NonNull final Matcher<View> viewMatcher) {

        final long startTime = System.currentTimeMillis();
        final long endTime = startTime + timeoutInMillis;

        do {
            try {
                return new TimedViewInteraction(Espresso.onView(viewMatcher));
            } catch (NoMatchingViewException ex) {
                //ignore
            }

            SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
        }
        while (System.currentTimeMillis() < endTime);

        // timeout happens
        throw new PerformException.Builder()
                .withCause(new TimeoutException("Timeout occurred when trying to find: " + viewMatcher.toString()))
                .build();
    }

    /**
     * Use instead of {@link Espresso#onView(Matcher)}.
     * Same as {@link #onViewWithTimeout(long, Matcher)} but with the default timeout {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
     * @param viewMatcher view matcher to check for view
     * @return view interaction
     */
    public static TimedViewInteraction onViewWithTimeout(@NonNull final Matcher<View> viewMatcher) {
        return onViewWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewMatcher);
    }

    /**
     * A wrapper around {@link ViewInteraction} which allows to set timeouts for view actions and assertions.
     */
    public static class TimedViewInteraction {

        private ViewInteraction wrappedViewInteraction;

        public TimedViewInteraction(ViewInteraction wrappedViewInteraction) {
            this.wrappedViewInteraction = wrappedViewInteraction;
        }

        /**
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction perform(final ViewAction... viewActions) {
            wrappedViewInteraction.perform(viewActions);
            return this;
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(final ViewAction... viewActions) {
            return performWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewActions);
        }

        /**
         * {@link ViewInteraction#perform(ViewAction...)} with a timeout.
         * @see ViewInteraction#perform(ViewAction...)
         */
        public TimedViewInteraction performWithTimeout(long timeoutInMillis, final ViewAction... viewActions) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return perform(viewActions);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to perform view actions: " + viewActions))
                    .build();
        }

        /**
         * @see ViewInteraction#withFailureHandler(FailureHandler)
         */
        public TimedViewInteraction withFailureHandler(FailureHandler failureHandler) {
            wrappedViewInteraction.withFailureHandler(failureHandler);
            return this;
        }

        /**
         * @see ViewInteraction#inRoot(Matcher)
         */
        public TimedViewInteraction inRoot(Matcher<Root> rootMatcher) {
            wrappedViewInteraction.inRoot(rootMatcher);
            return this;
        }

        /**
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction check(final ViewAssertion viewAssert) {
            wrappedViewInteraction.check(viewAssert);
            return this;
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout of {@link #DEFAULT_TIMEOUT_IN_MILLIS}.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(final ViewAssertion viewAssert) {
            return checkWithTimeout(DEFAULT_TIMEOUT_IN_MILLIS, viewAssert);
        }

        /**
         * {@link ViewInteraction#check(ViewAssertion)} with a timeout.
         * @see ViewInteraction#check(ViewAssertion)
         */
        public TimedViewInteraction checkWithTimeout(long timeoutInMillis, final ViewAssertion viewAssert) {
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + timeoutInMillis;

            do {
                try {
                    return check(viewAssert);
                } catch (RuntimeException ex) {
                    //ignore
                }

                SystemClock.sleep(SLEEP_IN_A_LOOP_TIME);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withCause(new TimeoutException("Timeout occurred when trying to check: " + viewAssert.toString()))
                    .build();
        }
    }
}

and then call it in your code as e.g.:

onViewWithTimeout(withId(R.id.button).perform(click());

instead of

onView(withId(R.id.button).perform(click());

This also allows to you to add timeouts for view actions and view assertions.

Adenoidal answered 17/3, 2017 at 14:18 Comment(2)
Use this below single line of code for dealy any Test Espresso test case: SystemClock.sleep(1000); // 1 SecondInfarct
for me this only works by changing this line return new TimedViewInteraction(Espresso.onView(viewMatcher)); with return new TimedViewInteraction(Espresso.onView(viewMatcher).check(matches(isDisplayed())));Gary
A
-1

My utility repeats runnable or callable executing until it passes without errors or throws throwable after a timeout. It works perfectly for Espresso tests!

Suppose the last view interaction (button click) activates some background threads (network, database etc.). As the result, a new screen should appear and we want to check it in our next step, but we don't know when the new screen will be ready to be tested.

The recommended approach is to force your app to send messages about threads states to your test. Sometimes we can use built-in mechanisms like OkHttp3IdlingResource. In other cases, you should insert code pieces in different places of your app sources (you should known app logic!) for testing support only. Moreover, we should turn off all your animations (although it's the part of UI).

The other approach is waiting, e.g. SystemClock.sleep(10000). But we don't know how long to wait and even long delays can't guarantee success. On the other hand, your test will last long.

My approach is to add time condition to view interaction. E.g. we test that new screen should appear during 10000 mc (timeout). But we don't wait and check it as quickly as we want (e.g. every 100 ms) Of course, we block test thread such way, but usually, it's just what we need in such cases.

Usage:

long timeout=10000;
long matchDelay=100; //(check every 100 ms)
EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);

ViewInteraction loginButton = onView(withId(R.id.login_btn));
loginButton.perform(click());

myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));

This is my class source:

/**
 * Created by alexshr on 02.05.2017.
 */

package com.skb.goodsapp;

import android.os.SystemClock;
import android.util.Log;

import java.util.Date;
import java.util.concurrent.Callable;

/**
 * The utility repeats runnable or callable executing until it pass without errors or throws throwable after timeout.
 * It works perfectly for Espresso tests.
 * <p>
 * Suppose the last view interaction (button click) activates some background threads (network, database etc.).
 * As the result new screen should appear and we want to check it in our next step,
 * but we don't know when new screen will be ready to be tested.
 * <p>
 * Recommended approach is to force your app to send messages about threads states to your test.
 * Sometimes we can use built-in mechanisms like OkHttp3IdlingResource.
 * In other cases you should insert code pieces in different places of your app sources (you should known app logic!) for testing support only.
 * Moreover, we should turn off all your animations (although it's the part on ui).
 * <p>
 * The other approach is waiting, e.g. SystemClock.sleep(10000). But we don't known how long to wait and even long delays can't guarantee success.
 * On the other hand your test will last long.
 * <p>
 * My approach is to add time condition to view interaction. E.g. we test that new screen should appear during 10000 mc (timeout).
 * But we don't wait and check new screen as quickly as it appears.
 * Of course, we block test thread such way, but usually it's just what we need in such cases.
 * <p>
 * Usage:
 * <p>
 * long timeout=10000;
 * long matchDelay=100; //(check every 100 ms)
 * EspressoExecutor myExecutor = new EspressoExecutor<ViewInteraction>(timeout, matchDelay);
 * <p>
 * ViewInteraction loginButton = onView(withId(R.id.login_btn));
 * loginButton.perform(click());
 * <p>
 * myExecutor.callForResult(()->onView(allOf(withId(R.id.title),isDisplayed())));
 */
public class EspressoExecutor<T> {

    private static String LOG = EspressoExecutor.class.getSimpleName();

    public static long REPEAT_DELAY_DEFAULT = 100;
    public static long BEFORE_DELAY_DEFAULT = 0;

    private long mRepeatDelay;//delay between attempts
    private long mBeforeDelay;//to start attempts after this initial delay only

    private long mTimeout;//timeout for view interaction

    private T mResult;

    /**
     * @param timeout     timeout for view interaction
     * @param repeatDelay - delay between executing attempts
     * @param beforeDelay - to start executing attempts after this delay only
     */

    public EspressoExecutor(long timeout, long repeatDelay, long beforeDelay) {
        mRepeatDelay = repeatDelay;
        mBeforeDelay = beforeDelay;
        mTimeout = timeout;
        Log.d(LOG, "created timeout=" + timeout + " repeatDelay=" + repeatDelay + " beforeDelay=" + beforeDelay);
    }

    public EspressoExecutor(long timeout, long repeatDelay) {
        this(timeout, repeatDelay, BEFORE_DELAY_DEFAULT);
    }

    public EspressoExecutor(long timeout) {
        this(timeout, REPEAT_DELAY_DEFAULT);
    }


    /**
     * call with result
     *
     * @param callable
     * @return callable result
     * or throws RuntimeException (test failure)
     */
    public T call(Callable<T> callable) {
        call(callable, null);
        return mResult;
    }

    /**
     * call without result
     *
     * @param runnable
     * @return void
     * or throws RuntimeException (test failure)
     */
    public void call(Runnable runnable) {
        call(runnable, null);
    }

    private void call(Object obj, Long initialTime) {
        try {
            if (initialTime == null) {
                initialTime = new Date().getTime();
                Log.d(LOG, "sleep delay= " + mBeforeDelay);
                SystemClock.sleep(mBeforeDelay);
            }

            if (obj instanceof Callable) {
                Log.d(LOG, "call callable");
                mResult = ((Callable<T>) obj).call();
            } else {
                Log.d(LOG, "call runnable");
                ((Runnable) obj).run();
            }
        } catch (Throwable e) {
            long remain = new Date().getTime() - initialTime;
            Log.d(LOG, "remain time= " + remain);
            if (remain > mTimeout) {
                throw new RuntimeException(e);
            } else {
                Log.d(LOG, "sleep delay= " + mRepeatDelay);
                SystemClock.sleep(mRepeatDelay);
                call(obj, initialTime);
            }
        }
    }
}

https://gist.github.com/alexshr/ca90212e49e74eb201fbc976255b47e0

Aphorize answered 2/5, 2017 at 5:36 Comment(0)
O
-1

This is a helper I'm using in Kotlin for Android Tests. In my case I'm using the longOperation to mimic the server response but you can tweak it to your purpose.

@Test
fun ensureItemDetailIsCalledForRowClicked() {
    onView(withId(R.id.input_text))
        .perform(ViewActions.typeText(""), ViewActions.closeSoftKeyboard())
    onView(withId(R.id.search_icon)).perform(ViewActions.click())
    longOperation(
        longOperation = { Thread.sleep(1000) },
        callback = {onView(withId(R.id.result_list)).check(isVisible())})
}

private fun longOperation(
    longOperation: ()-> Unit,
    callback: ()-> Unit
){
    Thread{
        longOperation()
        callback()
    }.start()
}
Oversight answered 26/2, 2019 at 14:1 Comment(0)
I
-1

I will add my way of doing this to the mix:

fun suspendUntilSuccess(actionToSucceed: () -> Unit, iteration : Int = 0) {
    try {
        actionToSucceed.invoke()
    } catch (e: Throwable) {
        Thread.sleep(200)
        val incrementedIteration : Int = iteration + 1
        if (incrementedIteration == 25) {
            fail("Failed after waiting for action to succeed for 5 seconds.")
        }
        suspendUntilSuccess(actionToSucceed, incrementedIteration)
    }
}

Called like this:

suspendUntilSuccess({
    checkThat.viewIsVisible(R.id.textView)
})

You can add parameters like max iterations, iteration length, etc to the suspendUntilSuccess function.

I still prefer using idling resources, but when the tests are acting up due to slow animations on the device for instance, I use this function and it works well. It can of course hang for up to 5 seconds as it is before failing, so it could increase the execution time of your tests if the action to succeed never succeeds.

Ivers answered 31/8, 2019 at 0:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.