Hero Background

Next-Gen App & Browser Testing Cloud

Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Next-Gen App & Browser Testing Cloud

Why is Appium not able to handle dynamic mobile application elements?

Appium can handle dynamic mobile elements. The failures you see almost always come from how the element is located and synchronized, not from a limitation in Appium itself. Dynamic elements have attributes that change at runtime, such as auto-generated resource-id values, shifting text, list rows recycled inside a RecyclerView or UITableView, content rendered asynchronously, or WebView markup that repaints, so a locator that matched a moment ago no longer matches.

The result is a NoSuchElementException or a StaleElementReferenceException, and tests that pass on one run and fail on the next. The fix is a combination of stable, attribute-flexible locators, explicit waits, stale-element retries, and correct context handling for hybrid apps. Below is the full playbook with Java and Python examples on Appium 2.x.

Why Dynamic Elements Break Appium Locators

Before fixing the problem, it helps to know exactly what makes an element dynamic. Each of these causes maps to a specific fix later in this guide.

  • Auto-generated or build-dependent ids: values like com.app:id/tab_3f8a change between builds, so an exact resource-id match fails after the next compile.
  • Changing visible text: localized strings, counters, timestamps, and user data mean the text you matched yesterday is different today.
  • Async and lazy rendering: the element is not yet in the native tree or DOM when the script looks for it, producing a NoSuchElementException.
  • Lists and recyclers: RecyclerView on Android and UITableView on iOS reuse views, so index-based locators point at the wrong row after a scroll.
  • WebView and hybrid repaint: markup is regenerated on render, which shatters absolute XPath built from the page structure.
  • Absolute XPath fragility: a path like /hierarchy/.../[3] breaks the moment one wrapper view is added or removed.

Note the distinction from a related problem: this page is about elements that exist but mutate. If the element is simply not rendered or not visible at all, see Why Is Appium Not Detecting Elements on the Page?.

Robust Locator Strategies for Dynamic Elements

Order your locators by stability: accessibility id first, then the stable part of an id, then platform engines such as UiAutomator or iOS predicate, then relative XPath. Avoid absolute XPath entirely. On Appium 2.x prefer AppiumBy, since the legacy MobileBy class is deprecated.

// 1. Accessibility ID - most stable, cross-platform (content-desc / accessibility-id)
driver.findElement(AppiumBy.accessibilityId("login_button"));

// 2. Relative XPath with contains() / starts-with() for changing attributes
driver.findElement(AppiumBy.xpath("//*[contains(@resource-id,'tab_')]"));
driver.findElement(AppiumBy.xpath("//android.widget.TextView[starts-with(@text,'Order #')]"));

// 3. Android UiAutomator - flexible, runs on-device (fast)
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiSelector().resourceIdMatches(\".*tab_.*\")"));

// 4. iOS predicate string (CONTAINS / BEGINSWITH)
driver.findElement(AppiumBy.iOSNsPredicateString("label CONTAINS 'Order #'"));

// 5. iOS class chain - index-safe last/first
driver.findElement(AppiumBy.iOSClassChain("**/XCUIElementTypeCell[`label CONTAINS 'Order'`][-1]"));

The same strategies in Python with the Appium-Python-Client 3.x and Appium 2.x:

from appium.webdriver.common.appiumby import AppiumBy

driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
driver.find_element(AppiumBy.XPATH, "//*[contains(@resource-id,'tab_')]")
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().textStartsWith("Order #")')
driver.find_element(AppiumBy.IOS_PREDICATE, "label CONTAINS 'Order #'")

The pattern across all of these is the same: match the part of the element that stays constant and ignore the part that changes. A full reference is available in the guide toLocators in Appium.

Explicit Waits vs Implicit Waits

Synchronization is the single biggest reason dynamic-element tests are flaky. An implicit wait sets one global timeout for every lookup, which is blunt and does not understand the state you actually care about. Worse, mixing implicit and explicit waits is an anti-pattern: the two timeouts compound in unpredictable ways and you end up with inconsistent, unexpectedly long waits.

For dynamic content, use explicit waits with a specific condition. They poll until the condition is true and proceed the instant it is met, so they are both faster and more reliable.

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));

WebElement el = wait.until(
    ExpectedConditions.presenceOfElementLocated(
        AppiumBy.accessibilityId("order_total")));

wait.until(ExpectedConditions.elementToBeClickable(
        AppiumBy.accessibilityId("checkout_btn"))).click();
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 15)
el = wait.until(EC.presence_of_element_located(
        (AppiumBy.ACCESSIBILITY_ID, "order_total")))

For values that load asynchronously, a custom condition that waits until the text is non-empty or has stabilized is more reliable than waiting only for presence. If your tests are timing out rather than mislocating, see Why Are Appium Tests Running Slow or Timing Out?.

Handling Stale Elements and Retries

A StaleElementReferenceException means you held a reference to an element and the screen re-rendered before you acted on it, so the reference now points at a node that no longer exists. The cure is to never cache a reference across a re-render. Re-find the element immediately before you use it, lean on ExpectedConditions.refreshed(...), or wrap the lookup in a bounded retry loop for genuinely volatile screens.

public WebElement findWithRetry(By locator, int attempts) {
    for (int i = 0; i < attempts; i++) {
        try {
            return driver.findElement(locator);
        } catch (StaleElementReferenceException | NoSuchElementException e) {
            try { Thread.sleep(500); } catch (InterruptedException ignored) {}
        }
    }
    throw new NoSuchElementException(
        "Element not stable after " + attempts + " tries: " + locator);
}

Keep retries bounded and short. An unbounded retry hides a real defect and turns a quick failure into a slow one.

Dynamic Scrolling Into View for Lists and Recyclers

Long lists are a classic source of dynamic-element pain. Because RecyclerView and UITableView recycle their views, only the rows currently on screen exist in the tree, and a hard-coded index points at whatever happens to be rendered. Scroll to a predicate instead so you find the right item regardless of its position.

// Android - scroll a RecyclerView to text without index guessing
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiScrollable(new UiSelector().scrollable(true))"
    + ".scrollIntoView(new UiSelector().textContains(\"Settings\"))"));
// iOS - mobile: scroll
driver.executeScript("mobile: scroll", Map.of("direction", "down"));

Scrolling to text or a predicate beats a hard-coded list index every time, because the target is matched by what it contains rather than where it sits in a recycled, ever-changing collection.

Hybrid App Context Switching (NATIVE_APP to WEBVIEW)

In hybrid apps a large share of dynamic content lives inside a WebView, and those elements are invisible to native locators no matter how clever your XPath is. You have to switch into the WebView context, work with web selectors there, then switch back to the native context.

Set<String> contexts = driver.getContextHandles(); // [NATIVE_APP, WEBVIEW_com.app]

for (String ctx : contexts) {
    if (ctx.contains("WEBVIEW")) {
        driver.context(ctx);
        break;
    }
}

driver.findElement(AppiumBy.cssSelector("#dynamic-id")); // now CSS works
driver.context("NATIVE_APP"); // switch back

For this to work the WebView must be debuggable: Android needs a matching chromedriver for the embedded Chrome version, and iOS needs WKWebView debugging enabled. You can find dynamic ids quickly with the Appium Inspector for Apps in both native and web contexts.

Best Practices Checklist for Dynamic Elements

  • Prefer accessibility id: Ask developers to add stable content-desc on Android and accessibilityIdentifier on iOS so tests do not depend on auto-generated values.
  • Use flexible matching for variable attributes: Reach for contains(), starts-with(), and resourceIdMatches() instead of exact values.
  • Never use absolute XPath, and never cache a reference across a re-render.
  • Use explicit waits only: Commit to one synchronization strategy and keep implicit and explicit waits from mixing.
  • Scroll to a predicate, not an index: So recycled lists resolve to the correct item.
  • Switch context for WebView elements: Then switch back to NATIVE_APP when done.

Finally, where you run matters as much as how you write your tests. Emulators and simulators mask the async rendering and OS-level timing that cause many dynamic-element failures in the first place. Running on a Real Device Cloud means dynamic rendering, WebView versions, and timing behave like production across 5000+ real devices, so flaky lookups surface in CI instead of in the field. TestMu AI supports this directly through its Appium Testing platform, so the same locators and waits run unchanged at scale.

Frequently Asked Questions

How do I handle dynamic XPath in Appium?

Stop matching the whole attribute value and match the stable part instead. Use relative XPath with contains() for substrings, for example //*[contains(@resource-id,'tab_')], or starts-with() for fixed prefixes such as //android.widget.TextView[starts-with(@text,'Order #')]. Avoid absolute XPath built from the hierarchy, because a single layout change breaks it.

Why does Appium throw StaleElementReferenceException?

You grabbed a reference to an element and the screen re-rendered before you acted on it, so the old reference no longer points at a live node. This is common with lists, async-loaded data, and WebView repaints. Re-find the element immediately before acting, use ExpectedConditions.refreshed(), or wrap the lookup in a bounded retry loop.

Should I use implicit or explicit waits in Appium?

Use explicit waits for dynamic content. They poll for a specific condition such as presence or clickability and continue as soon as it is met. Mixing implicit and explicit waits is an anti-pattern because the two timeouts compound unpredictably, producing inconsistent waits and slow, flaky runs. Pick one strategy, and for dynamic elements that strategy should be explicit waits.

What is the most stable locator for dynamic elements?

Accessibility id is the most stable and cross-platform choice. It maps to content-desc on Android and accessibilityIdentifier on iOS, and developers can keep it constant even when ids, text, and layout change. Ask the team to add stable accessibility identifiers to dynamic widgets so your tests do not depend on auto-generated values.

How do I find a dynamic element inside a WebView or hybrid app?

Switch context. Elements rendered inside a WebView are not visible to native locators. Call getContextHandles(), switch to the WEBVIEW context, locate the element with web selectors such as CSS or id, then switch back to NATIVE_APP. This requires a matching chromedriver on Android and a debuggable WKWebView on iOS.

How do I locate an item in a long, scrolling list with changing data?

Scroll to a predicate, not an index. On Android, UiScrollable.scrollIntoView with a textContains selector scrolls until the target is on screen. On iOS, use mobile: scroll or an iOS class chain. RecyclerView and UITableView recycle views, so a hard-coded index points at the wrong row after scrolling, while a predicate finds the right item regardless of position.

Related Questions

Test Your Website on 3000+ Browsers

Get 100 minutes of automation test minutes FREE!!

Test Now...

KaneAI - Testing Assistant

World’s first AI-Native E2E testing agent.

...

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests