Delayed rendering of WebView components in UI DOM hierarchy

Hello,

we are experiencing an issue with our Android app during automated testing with Appium (v2.19.0). Some text elements rendered using WebView components appear in the accessibility/UI hierarchy (XML) with a significant delay after the screen is opened.

Specifically:
• When the screen is displayed, the element is not present in the initial UI hierarchy (no TextView node or text).
• Only after leaving the screen and coming back (or after a full redraw), the element finally appears in the hierarchy.
• Because of this, Appium cannot locate the element in time, and tests fail.

Example:
<android.view.View resource-id="wrap"> <android.widget.TextView text="Vyber pracoviště k přihlášení"> </android.view.View>

This TextView sometimes appears late in the XML, even though it is already visible on the screen for the user.

This behavior causes automated tests to fail randomly because the element is missing from the DOM when Appium queries it.

We use the following setup:
• Framework: WebdriverIO (Mocha)
• Automation: Appium 2.19.0 with UiAutomator2
• Platform: Android Emulator
• Capabilities:
• appium:ignoreUnimportantViews = true // We need this for the speed of the test.
• appium:appWaitActivity = *
• appium:autoGrantPermissions = true
• appium:noReset = true
• appium:fullReset = false

Thank you for your help!

1 Like

how about update settings after starting driver with →

https://github.com/appium/appium-uiautomator2-driver

waitForIdleTimeout -> e.g. 500
waitForSelectorTimeout -> 0
actionAcknowledgmentTimeout -> 0

We tried setting it in wdio.config.ts to before… but it didn’t help… it still crashes when opening the window and only displays WebView, and when I refresh it, there is a view with ID WRAP, and then after another refresh of the window (I have to go back to the previous window in the application and return), the TextView may also appear in that View (wrap). But it’s unstable, sometimes it’s there, and then it’s not there even after three window refreshes. I’m quite unhappy about it, and our testing isn’t working because of it.

Thanks.

This does not look as normal app behavior.

Try also ‘appium:autoWebview’ make true or maybe better switch to webView in code and find element in Web contex?

Hi,

we are facing a recurring issue with WebView context handling in our hybrid Android app.

  • We can see the WebView in getContexts() output, but sometimes switching context to it (driver.switchContext(WEBVIEW_…)) either hangs or getContexts() itself fails with a timeout.

  • When this happens, the test just freezes because the WebView context never gets attached, even though the app is still running and responsive.

  • It occurs randomly — sometimes the context is found and works, other times it just times out.

Here are the actual capabilities we use:

capabilities: [{
    platformName: "Android",
    "appium:deviceName": "emulator-5554",
    "appium:app": apkPath,
    "appium:appPackage": customerConfig.aliasApk,
    "appium:ignoreUnimportantViews": true,
    "appium:appWaitActivity": "*",
    "appium:automationName": "UiAutomator2",
    "appium:autoGrantPermissions": true,
    "appium:noReset": true,
    "appium:fullReset": false,
    "appium:adbExecTimeout": 120000,
    "appium:ensureWebviewsHavePages": true,
    "appium:recreateChromeDriverSessions": true,
    "appium:webviewConnectTimeout": 30000,
    "appium:newCommandTimeout": 180,
    "appium:chromedriverAutodownload": true,
    "appium:nativeWebScreenshot": true
}]


We run the tests on:

  • Emulator: Android Emulator (Android 16) API 36

  • Automation: Appium + WebdriverIO + UiAutomator2

  • OS: Windows 11

  • Appium version: 2.x

Here is the relevant part of our code for WebView handling:

private async getWebviewsWithBackoff(): Promise<string[]> {
  const once = async () => {
    // @ts-ignore
    const list = await driver
      .getContexts({ returnDetailedContexts: true })
      .catch(() => driver.getContexts());
    return (list as any[])
      .map((c) => (typeof c === 'string' ? c : c.id))
      .filter(
        (id) =>
          id &&
          id.toUpperCase().startsWith('WEBVIEW') &&
          id.toLowerCase().includes('company.product')
      ) as string[];
  };
  let wv = await once();
  if (wv.length) return wv;
  await driver.pause(500);
  return await once();
}

private async tryWebview(totalTimeout: number): Promise<boolean> {
  return HybridGate.run(async () => {
    const end = Date.now() + totalTimeout;
    const original = await this.getContextSafe();

    try {
      let attempt = 0;
      while (Date.now() < end && attempt < this.opts.webviewRetries) {
        attempt++;

        let webviews: string[] = [];
        try {
          webviews = await this.withTimeout(
            this.getWebviewsWithBackoff(),
            this.opts.opTimeoutMs,
            'getContexts/backoff'
          );
        } catch (e) {
          this.warn(`getContexts failed: ${e}`);
          await driver.pause(this.opts.retryIntervalMs);
          continue;
        }

        this.log(`contexts: ${JSON.stringify(webviews)}`);
        if (!webviews.length) {
          await driver.pause(this.opts.retryIntervalMs);
          continue;
        }

        for (const id of webviews) {
          try {
            await this.withTimeout(
              driver.switchContext(id),
              this.opts.opTimeoutMs,
              `switchContext(${id})`
            );

            // Check if DOM is ready
            const hasDom = await this.safeExecute<boolean>(() => {
              try {
                return !!document.body && document.body.innerText.trim().length > 0;
              } catch {
                return false;
              }
            }, 'hasDom');
            this.log(`WEBVIEW ${id} → hasDom=${hasDom}`);
            if (hasDom) return true;
          } catch (e) {
            this.warn(`switchContext(${id}) failed: ${e}`);
          }
        }

        await driver.pause(this.opts.retryIntervalMs);
      }
      return false;
    } catch (e) {
      this.warn(`WEBVIEW exception: ${e}`);
      return false;
    } finally {
      try {
        if (original) await driver.switchContext(original);
        else await driver.switchContext('NATIVE_APP');
      } catch {}
      await driver.pause(120);
    }
  });
}

WDIO console logs (examples)

[TitleTextElement] ℹ NATIVE not found – going to WEBVIEW…
[TitleTextElement] contexts: ["WEBVIEW_company.procuct.mobileclient.androidclient.customerApp"]
[TitleTextElement] handles(WEBVIEW_company.procuct.mobileclient.androidclient.customerApp):
["76AF18C84...","B56A1E96...","16DDDCEB...","46C0B639...","274F2610...","6B31FBA1...","EFDD5248...","D30082FA..."]
[TitleTextElement] window 76AF18C84... → hasDom=true

Sometimes we get a direct timeout during context discovery:

[TitleTextElement] getContexts failed: Error: [timeout] getContexts/backoff (8000ms)

Appium server logs (examples)

[AndroidUiautomator2Driver] Getting a list of available webviews
[ADB] Running '... adb.exe -s emulator-5554 shell cat /proc/net/unix'
[AndroidUiautomator2Driver] Parsed 3 active devtools sockets: ["@stetho_...","@webview_devtools_remote_30194","@webview_devtools_remote_20494"]
[AndroidUiautomator2Driver] Collecting CDP data of 3 webviews
[AndroidUiautomator2Driver] Forwarding remote port ... to local ...
[AndroidUiautomator2Driver] CDP data for 'WEBVIEW_stetho_...' cannot be collected. Original error: timeout of 2000ms exceeded
[AndroidUiautomator2Driver] Removing forwarded port socket connection: 12000
...
[AndroidUiautomator2Driver] CDP data collection completed
[AndroidUiautomator2Driver] WEBVIEW_30194 mapped to pid 30194
[AndroidUiautomator2Driver] Getting process name for webview 'WEBVIEW_30194'
[ADB] Running '... adb.exe -s emulator-5554 shell ps -A'

And on the client side, we see timeouts waiting for contexts right after:

[TitleTextElement] ℹ NATIVE not found – going to WEBVIEW…
[TitleTextElement] getContexts failed: Error: [timeout] getContexts/backoff (8000ms)

In other runs we see:

WebDriverError: no such window: target window already closed
from unknown error: web view not found (Session info: chrome=133.0.6943.137)

I tried to describe it properly. Please advise. I can already switch between the native app and web view, but the test is unstable in the web view and gets stuck when searching for context. It seems to me that after a while, the server can’t keep up, and it just runs slower and slower until it freezes completely, and the test stops, and what used to take a second now takes minutes. It’s as if it’s overloaded.

Thank you for your advice. It’s very important for me to fix this. We can’t test our product right now.

If anyone would like to help me further, I can provide my contact details for better communication.

Thank you very much for your advice.

1 Like

not sure but in case:

1 try increase timeouts

2 check SDK tools you are using try to update them

What timeouts do you mean? I tried setting them differently, but it didn’t help. It seems to me that the test simply gets overwhelmed when searching for contexts. In every application window, the title in the webview and other elements on the screen are accessible natively.

We have the latest SDK.

Thanks

I get the exact same thing, just hangs on trying to switch contexts. Started happening after a MAUI version update on the app under testing. Been trying to figure out a solution for months now.

What kind of Android do you have? I have Baklava API 36. I’m wondering if that’s the reason.