32

Is there a reliable way to make Espresso wait for WebViews to finish loading?

I've tried the approach outlined here but found it unreliable. It also has other drawbacks:

  • It relies on replacing the WebView's WebChromeClient. Any existing WebChromeClient can't be wrapped either, since WebViewrt doesn't have a getWebChromeClient() method for some reason.
  • It requires a specific WebView instance, so every time I start an Activity with a WebView I have to get the WebView instance and register a new WebviewIdlingResource for it.

I'm hoping someone has a solution without any of these drawbacks. I had hopes that the espresso-web package might provide a solution, but it doesn't appear to offer anything relating to loading.

vaughandroid
  • 4,315
  • 1
  • 27
  • 33
  • I've found this: https://github.com/awslabs/aws-device-farm-sample-app-for-android/blob/a8aa3218fda2a0c72b039f371a1b5d12e522051f/app/src/androidTest/java/com/amazonaws/devicefarm/android/referenceapp/IdlingResources/WebViewIdlingResource.java – piotrek1543 Dec 14 '15 at 11:40
  • `WebView` seems to have a [getWebChromeClient()](https://developer.android.com/reference/android/webkit/WebView#getWebChromeClient()) method. Maybe it was introduced after the original problem was posted. – Michael Osofsky May 24 '20 at 23:04

2 Answers2

6

As you mentioned, there is an espresso add-on called espresso-web for dealing with webviews and their content.

"finish loading" is a bit of a vague concept. It will depend on what specifically to you want to finish.

If you use onWebView().check(<the you want to finish is in your webview>) that will return only after the web view has loaded and the check has succeeded.

Espresso-web is an entry point to work with WebViews on Android. It uses Atoms from the popular WebDriver API to introspect into and control the behavior of a WebView.

Similar to onData, WebView interactions are actually composed of several View Atoms. An Atom can be seen as a ViewAction, a self contained unit which performs an action in your UI. However, they need to be properly orchestrated and are quite verbose. Web and WebInteraction wrap this boilerplate and give an Espresso-like feel to interacting with WebViews.

https://developer.android.com/training/testing/espresso/web

yogurtearl
  • 3,035
  • 18
  • 24
0

If the unreliability experienced with Wait for it...a deep dive into Espresso's Idling Resources was caused by using WebChromeClient, consider using WebViewClient instead.

I found that WebViewClient fires onPageFinished() reliably when a web page finishes loading. WebChromeClient and WebViewClient are similar but WebViewClient is more general in that WebChromeClient handles the Chrome-specific aspects of the WebView (see What's the difference between setWebViewClient vs. setWebChromeClient?).

In a sample project, https://github.com/mosofsky/how-to-coredux, I register the onPageFinished() function to the WebView as follows:

playVideoWebView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
        return false
    }

    override fun onPageFinished(view: WebView?, url: String?) {
        if (null != url && url != ABOUT_URL) {
            // the next line dispatches an action that calls decrement()
            howToViewModel.dispatchAction(LoadVideo_Finish)
        }
    }
}

I hope this is helpful although admittedly I have not addressed the other drawbacks you mentioned. As for the lack of getWebChromeClient(), that seems to be available now. As for requiring a specific WebView instance, I don't know how to avoid that. Somehow you have to make the WebView decrement the idling resource counter when it's finished loading. It seems like any solution would have to touch a specific instance of the WebView in some way to complete this registration process. Some developers might feel it's tedious or inappropriate to litter their production code with CountingIdlingResource's calls to increment() and decrement().

To minimize the amount of code instrumented with idling resource counting calls, you can isolate these calls to what I'll call a "side effect" of asynchronous code. For this, I've found using a Redux data store can be very helpful, for example CoRedux. When an asynchronous action begins, you can update the state to "in progress"; when it ends, clear that "in progress" flag. The "side effect" can be any mechanism that listens for "in progress". CoRedux literally has a construct called a "side effect" that you can use, but any mechanism that responds to state changes is fine.

In the side effect for the how-to-coredux sample project, it calls increment() and decrement() like this:

when (action) {

    is Initialize_Start, is ShowVideoFragment_Start, LoadVideo_Start, HideVideoFragment_Start -> {
        espressoTestIdlingResource.increment()
    }

    Initialize_Finish, ShowVideoFragment_Finish, LoadVideo_Finish, HideVideoFragment_Finish -> {
        espressoTestIdlingResource.decrement()
    }
}

By isolating the idling resource logic to a side effect, an entire Android project could rely on a single piece of code for calling increment()/decrement() every time any asynchronous UI event begins/ends, respectively.

Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113