So a cleaner way to handle this is to use ViewActions.
The ViewAction class is part of the public test framework API which allows devs to write their own ViewAction implementations when necessary. Here you can see how a wait-for-ui-element implementation would look like:
// CustomViewActions.kt:
/**
* 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)
*/
fun waitForView(viewId: Int, timeout: Long): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher {
return isRoot()
}
override fun getDescription(): String {
return "wait for a specific view with id $viewId; during $timeout millis."
}
override fun perform(uiController: UiController, rootView: View) {
uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis()
val endTime = startTime + timeout
val viewMatcher = withId(viewId)
do {
// Iterate through all views on the screen and see if the view we are looking for is there already
for (child in TreeIterables.breadthFirstViewTraversal(rootView)) {
// found view with required ID
if (viewMatcher.matches(child)) {
return
}
}
// Loops the main thread for a specified period of time.
// Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
uiController.loopMainThreadForAtLeast(100)
} while (System.currentTimeMillis() < endTime) // in case of a timeout we throw an exception -> test fails
// LoginTest.kt:
@Test
fun shouldCloseDialog(){
onView(withId(R.id.show_dialog_button)).perform(click())
// here espresso needs to wait till the dialog actually shows and the close_button is visible
onView(isRoot()).perform(waitForView(R.id.close_button, 5000))
}
Waiting for an element in Jetpack Compose
You can use the waitUntil function in Compose, which accepts a function that returns a boolean.
The function blocks until the specified condition is met or the specified timeout has passed.
If the main clock auto advancement is enabled, as it is by default, the clock will also continue to advance frame by frame.
Example:
// Click on a button
composeTestRule.onNodeWithText("Login").performClick()
// Wait until there’s one element with a “Success” text
composeTestRule.waitUntil {
composeTestRule
.onAllNodesWithText(“Successfully logged in”)
.fetchSemanticsNodes().size == 1
}
// Assert that the right username is shown
composeTestRule.onNodeWithText(“Martin”).assertExists()