Espresso: Thread.sleep( )
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)));
How is uiController.loopMainThreadForAtLeast different from Thread.sleep?
Espresso uses a queue to check if app is idle.
Thread.sleep(x) = //Waits x
uiController.loopMainThreadForAtLeast(x) // Waits x + more if app is still not idle
Now both of this are really bad approaches, that will result in some problems in production code.
Espresso can detect if app is idle if you are using AsyncTasks
but in some scenarios it can't. So even when your app isn't idle,uiController.loopMainThreadForAtLeast(x)
will be skipped.Using
IdlingResources
inside production code to specify when
app is not idle is not a good solution. You shouldn't change
production code to test your app and idling resources checks if app
is idle with 5 second intervals which will cause lots of overhead in
your tests. Assume you have 100 test cases where you will experience
this interval, the overhead you will experience will be hugeUsing
Thread.sleep(x)
by itself is obviously a bad idea. As your
tests needs to be stable regardless of the speed of internet
connection, phone etc you shouldn't be waiting a fixed amount of
time. And you can't know how much time you need to wait when you
request something from a server sox
you decide will either be too
big or too small.
The best solution is to check if the target view you have in your assertion/action exists and if not check the target view again after X seconds (where X is a small number like 200 millis). Also using Thread.sleep()
worked better for me than using
uiController.loopMainThreadForAtLeast() when my app was showing animations (even when animations were closed from testOptions and phone settings)
What is the best way to await functions in UI testing in Android Studio without Thread.Sleep()?
There are several options:
Repeated retries
You can use Awaitility to repeatedly retry an assertion/check, up to a specified time allowance:
app/build.gradle
dependencies {
// Note: Awaitility version 4 has dependency conflicts with JUnit's
// Hamcrest. See: https://github.com/awaitility/awaitility/issues/194
androidTestImplementation 'org.awaitility:awaitility:3.1.6'
}
// Set the retry time to 0.5 seconds, instead of the default 0.1 seconds
Awaitility.setDefaultPollInterval(500, TimeUnit.MILLISECONDS);
Kotlin:
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted {
onView(withId(R.id.fieldPassword)).perform(click())
}
Java 7:
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(new ThrowingRunnable() {
@Override
public void run() throws Throwable {
onView(withId(R.id.fieldPassword)).perform(click());
}
});
This means that if the assertion fails the first time, it will retry for up to 2 seconds, until it's true
or until a timeout happens (fail
).
Repeated retries with an initial time delay
You can also set an initial time delay before making the first assertion:
Awaitility.await().pollDelay(1, TimeUnit.SECONDS).atMost(3, TimeUnit.SECONDS).untilAsserted {
onView(withId(R.id.fieldPassword)).perform(click())
}
Simple time delay
Alternatively, you can add simple time delays in between statements, just like Thread.sleep()
, but in a more verbose way:
Kotlin:
Awaitility.await().pollDelay(2, TimeUnit.SECONDS).until { true }
Java 7:
Awaitility.await().pollDelay(2, TimeUnit.SECONDS).until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return true;
}
});
Use Espresso or Barista functions to create the timed waits:
- Espresso: Thread.sleep( )
Use Espresso Idling Resources
- https://dev.to/keyopstech/robust-ui-tests-with-espresso-and-idling-resources-157f
- How to use Espresso Idling Resource for network calls
- https://tudip.com/blog-post/espresso-idling-resources/
- https://medium.com/androiddevelopers/android-testing-with-espressos-idling-resources-and-testing-fidelity-8b8647ed57f4
- https://www.repeato.app/the-problem-of-espresso-idling-resources/ (some disadvantages)
General note: Although using Thread.sleep()
or some other time delay should normally be avoided in unit tests, there will be times where you need to use it, and there is no alternative. An example is using IntentsTestRule
to click on a web link in your app to launch the external web browser. You don't know how long the browser will take to launch the web page, so you need to add a time delay.
More info about Awaitility:
- https://medium.com/stepstone-tech/taming-the-ui-test-monster-26c017848ae0
- https://www.jfokus.se/jfokus12/preso/jf12_StopSleepingStartAwaiting.pdf
- https://github.com/awaitility/awaitility/wiki/Usage
Related Topics
How to Transfer an Image from Its Url to the Sd Card
Change the Text Color of Numberpicker
How to Resolve Target 'Android-Xx'
Android Facebook Sdk 3.0 Gives "Remote_App_Id Does Not Match Stored Id" While Logging In
Android: Programmatically Detect If Device Has Hardware Menu Button
Android: How to Select Texts from Webview
Android: Change Shape Color in Runtime
Android: Implementing Progressbar and "Loading..." for Endless List Like Android Market
How to Scale/Resize Text to Fit a Textview
Disable the Notification Panel from Being Pulled Down
Creating Custom Lockscreen in Android
Geocoder.Getfromlocation Throws Ioexception on Android Emulator
Lazy Download Images into Gridview
How to Crop an Image in Android
Only Load Layout When Firebase Calls Are Complete
Ndk Resolution Outcome: Project Settings: Gradle Model Version=5.4.1, Ndk Version Is Unknown Error