1 minute read

Broad variety of waiting in Espresso

What is «Waiting»?

Lifeguard from flaky tests.

What variety do we have?

Hmm, let me think… 🤔

We knew about Idling Resources

Pros:

  • It’s the most honest, fastest, and technically perfect way to wait;
  • It doesn’t eat up the memory.

Cons:

  • Sometimes it’s not a bit easier than writing production code;
  • Sometimes we’ll have to touch production code;
  • Most cases will probably require the own Idling Resources.

We heard about sleeps

Pros:

  • Kill me then.

Cons:

  • We’ll probably wait longer than we need;
  • We’ll perhaps wait less than we need.

We were guessing about the hacky way of injecting UiAutomator

Pros:

  • That’s something close to us since the time of Selenium.

Cons:

  • We’ll have to set an excess addiction;
  • We’ll be limited by minSdkVersion = 18;
  • We’ll have to play with weird UiObjects.

Go on, what about something else?

Okay… 😏

Let’s try one hacky way using Espresso

At first we have to extend ViewInteration class by adding new methods:

  • isDisplayed(): Boolean
  • doesNotExist(): Boolean
  • waitForAppear(millis: Long): ViewInteraction
  • waitForDisappear(millis: Long): ViewInteraction
fun ViewInteraction.isDisplayed(): Boolean {
    try {
        this.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
    } catch (ignored: NoMatchingViewException) {
        return false
    }
    return true
}

fun ViewInteraction.doesNotExist(): Boolean {
    try {
        this.check(ViewAssertions.doesNotExist())
    } catch (ignored: AssertionError) {
        return false
    }
    return true
}

fun ViewInteraction.waitForAppear(millis: Long = 5000): ViewInteraction {
    val timeout = System.currentTimeMillis() + millis
    while (System.currentTimeMillis() < timeout) {
        if (this.isDisplayed()) { break }
    }
    return this
}

fun ViewInteraction.waitForDisappear(millis: Long = 5000): ViewInteraction {
    val timeout = System.currentTimeMillis() + millis
    while (System.currentTimeMillis() < timeout) {
        if (this.doesNotExist()) { break }
    }
    return this
}

Then we can try to use it in the tests:

@Test
fun whenViewWillAppear() {
    val viewMatcher = ViewMatchers.withId(R.id.sample_view_id)
    Espresso.onView(viewMatcher)
        .waitForAppear()
        .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
}

@Test
fun whenViewWillDisappear() {
    val viewMatcher = ViewMatchers.withId(R.id.sample_view_id)
    Espresso.onView(viewMatcher)
        .waitForDisappear()
        .check(ViewAssertions.doesNotExist())
}

Pros:

  • Nothing is easier than use it for all the cases;
  • We’re able to control the waiting timeout for each case.

Cons:

  • Logcat will be littered;
  • After all, it a bit eats up the memory (comparing with Idling Resources).

Conclusion?

Yeah, waits are the delicate matter:

  • Follow the deadlines, needs, requirements and sanity;
  • Do whatever you want (using Espresso, of course 🧐);
  • Enjoy! 😉

Updated: