01 — Why Element Finding Matters

An element not being found is one of the biggest indicators of instability or flakiness in any test suite — whether you're automating a web app with Selenium or a native mobile app with Appium. Finding elements is one of the most common places for problems to appear. When we try to find an element, we bring together our assumption about an app's state and its actual state. Expectations and actual results may conform, or they may conflict.

When a framework cannot find a directed element, NoSuchElementException is thrown — this applies equally to Selenium and Appium, since both implement the WebDriver protocol. We want to avoid situations that lead here:

  • Using non-unique selectors that match multiple elements
  • Relying on dynamic attributes that change between runs or versions
  • Choosing attributes that are platform-specific when cross-platform code is the goal

Knowledge of the app and its design is essential. We need to know what's likely to change and what isn't — and which elements have stable IDs, accessibility labels, or purpose-built test attributes.

02 — Anatomy of a Locator

In both Selenium and Appium, interacting with a UI element requires finding it first. Every find_element call consists of two parts: a locator strategy (how to search) and a selector (what to look for). The result — if successful — is a WebElement object with a full interaction API for clicking, typing, reading attributes, and more.

Appium (Python)
# strategy: AppiumBy.ACCESSIBILITY_ID  |  selector: "login-button"
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")
Selenium (Python)
# strategy: By.ID  |  selector: "login-button"
driver.find_element(By.ID, "login-button")

The strategies available across both frameworks are listed below. Not all make sense in every context — many were carried over from Selenium, while Appium introduced mobile-specific ones to reflect the native UI stack and take advantage of platform accessibility APIs.

Strategy Framework Platform Notes
By.ID SeleniumAppium Web · Mobile Fast and unique when IDs are stable
By.CSS_SELECTOR Selenium Web Flexible and fast; the preferred web strategy
By.XPATH SeleniumAppium Web · Mobile Powerful but brittle; very slow on mobile
By.CLASS_NAME SeleniumAppium Web · Mobile CSS class or UI object class; rarely unique
By.NAME SeleniumAppium Web · Mobile Often non-unique; limited reliability
By.LINK_TEXT Selenium Web Exact anchor text; fragile if copy changes
ACCESSIBILITY_ID Appium iOS · Android Cross-platform; maps to accessibility label / content-desc
IOS_CLASS_CHAIN Appium iOS Hierarchical XPath alternative; faster, more rigid syntax
IOS_PREDICATE Appium iOS NSPredicate-style attribute queries; fast and expressive
ANDROID_UIAUTOMATOR Appium Android UiSelector API strings; fast XPath alternative

03 — Strategies to Use with Caution

Class Name Both

In Appium, CLASS_NAME refers to platform-specific UI object class names such as XCUIElementTypeButton or android.widget.Button. This immediately makes your code platform-specific — you need separate paths for iOS and Android. In Selenium, CSS class names reflect visual styling rather than element identity, and they change whenever a designer makes updates.

Appium
driver.find_element(AppiumBy.CLASS_NAME, "android.widget.Button")
Selenium
driver.find_element(By.CLASS_NAME, "btn-primary")

The deeper problem in both frameworks: there is almost always more than one element of any given type or class in the UI hierarchy. You may find a button — but not the specific one you want.

Why Class Name falls short

In Appium: platform-specific — forces branched iOS/Android code. In Selenium: tied to styling classes that change with design updates. In both cases: too general to uniquely identify an element.

XPath Both

XPath is attractive because it is hierarchy-based — it can locate any element in the DOM or app hierarchy. It becomes a tempting fallback when no other handle exists:

# Works — but will break the moment the hierarchy changes
driver.find_element(By.XPATH, "//*[1]/*[1]/*[3]/*[2]/*[1]/*[1]")

Position-based XPath is invalidated by any change to the UI structure — which happens frequently between app versions, screen sizes, and feature flags. Even with more descriptive XPath expressions, you still pay the full cost of generating the XML document on every query.

On mobile with Appium's XCUITest driver, this cost is severe: each XPath query requires the entire app hierarchy to be recursively walked and serialized into XML, which can be very slow on complex screens. On web with Selenium, XPath performance is acceptable — but CSS selectors are faster and typically more readable.

The XPath rule

Prefer By.ID, By.CSS_SELECTOR, or ACCESSIBILITY_ID whenever a direct handle exists. On mobile, if XPath is too slow, use IOS_CLASS_CHAIN or IOS_PREDICATE. On web, reach for CSS_SELECTOR before XPath.

For Web — ID and CSS Selectors Selenium

By.ID is the fastest and most reliable strategy for web automation when element IDs are stable and unique. CSS selectors are the next best option — flexible, fast, and supported natively by browsers without any serialization overhead.

from selenium.webdriver.common.by import By

# Fastest when the ID is stable
driver.find_element(By.ID, "submit-button")

# CSS selector — flexible, readable, fast
driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
driver.find_element(By.CSS_SELECTOR, "[data-testid='login-btn']")

# ARIA attributes — semantically stable
driver.find_element(By.CSS_SELECTOR, "[aria-label='Close dialog']")
The data-testid pattern

The most robust web selector strategy is to add data-testid attributes to key UI elements in collaboration with your development team. These test-only attributes don't affect styling or behavior — and they are stable by design. [data-testid='login-btn'] survives CSS redesigns, DOM restructuring, and copy changes.

For Mobile — Accessibility ID Appium

For native mobile apps, ACCESSIBILITY_ID is the clear recommendation. It is (a) cross-platform, (b) typically unique, and (c) fast. As long as accessibility IDs are consistent between your iOS and Android builds, the same line of Appium code finds the element regardless of the platform under test.

from appium.webdriver.common.appiumby import AppiumBy

# Finds the element on both iOS and Android — same line
el = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")

This strategy maps to the accessibility label set by app developers: on iOS it's the Accessibility Label; on Android it's the Content Description (content-desc). Since developers set this as a plain string, they can make it a unique identifier — the recommended path, as long as it doesn't conflict with the app's actual accessibility goals.

When accessibility labels aren't available or aren't unique, the platform-specific strategies below serve as alternatives.

05 — Platform-Specific Strategies Appium

These strategies are available only in Appium for native mobile. They serve as faster, more structured alternatives to XPath when platform-neutral strategies aren't an option.

iOS Class Chain

IOS_CLASS_CHAIN allows hierarchical queries — a constrained form of XPath mixed with iOS predicate syntax. The rigid query structure lets the driver optimize traversal, making it significantly faster than XPath in most cases.

iOS Class Chain
# Find the 10th button inside a cell whose name begins with "C"
driver.find_element(
    AppiumBy.IOS_CLASS_CHAIN,
    "**/XCUIElementTypeCell[`name BEGINSWITH 'C'`]/XCUIElementTypeButton[10]"
)

iOS Predicate String

NSPredicate-style queries provide expressive attribute-based filtering with good performance:

iOS Predicate String
driver.find_element(
    AppiumBy.IOS_PREDICATE,
    "type == 'XCUIElementTypeButton' AND value BEGINSWITH[c] 'foo' AND visible == 1"
)

Android UiAutomator

Appium exposes a parser for the UiSelector API via ANDROID_UIAUTOMATOR. Selectors are strings that must begin with new UiSelector() and follow the UiSelector API — they are parsed and interpreted server-side, not executed as Java:

Android UiAutomator
# First TextView with text "Tabs" that is a child of the first ScrollView
driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().className("ScrollView")'
    '.getChildByText(new UiSelector().className("android.widget.TextView"), "Tabs")'
)
Platform-specific trade-offs

Both IOS_CLASS_CHAIN and ANDROID_UIAUTOMATOR require branching your code — or adding platform distinctions to your page object models. Use them as fallbacks when no unique accessibility label exists. Only a subset of the UiSelector API is supported; the Appium team doesn't execute arbitrary Java.

06 — Discovering Your Selectors

Knowing which strategy to use is only half the problem. You also need to know what selectors are actually available. There are different tools depending on whether you're working on web or mobile.

Page Source Both

Both Selenium and Appium expose a page_source property that returns the UI as an XML (or HTML) document at the moment of the call. Every element the framework can see is represented as a node. If an element isn't in this document, the framework cannot find it through normal means.

# Works in Selenium and Appium
print(driver.page_source)

# Read individual attributes on a found element
value = element.get_attribute("accessibility-id")
label = element.get_attribute("label")

This approach is useful for constructing XPath selectors (the XPath engine queries exactly this document) and for understanding which attributes are available on a given element. It's also a reliable fallback when dedicated tooling is unavailable.

Browser DevTools Selenium

For web automation, the browser's built-in DevTools are the fastest way to inspect elements and verify selectors before writing them into test code. Right-click any element → Inspect. In the console, test selectors interactively:

// Verify a CSS selector — returns the element or null
document.querySelector("[data-testid='login-btn']")

// Check how many elements match a selector
document.querySelectorAll(".btn-primary").length
$() shorthand in DevTools

Chrome and Firefox expose $(selector) as a shorthand for document.querySelector() in the DevTools console. Use $() to test a single match and $$() to see all matches — before committing a selector to your test code.

Appium Inspector Appium

For mobile, Appium Inspector is the preferred tool — a GUI client for running Appium sessions and exploring apps through a point-and-click interface. An Inspector session shows a live screenshot, the full UI hierarchy as XML, and all metadata for any selected element. When you click an element in the tree, the Inspector suggests ranked locator strategies — the top suggestion is almost always ACCESSIBILITY_ID when a label is present.

Inspector ships in two formats:

  • Desktop app — macOS, Windows, and Linux. Download from the Releases page of the appium-inspector repo.
  • Web app — hosted by Appium Pro. Requires Appium server started with --allow-cors. Has a known issue on Safari — use Chrome or Firefox. No installation required; supports multiple tabs.

The Inspector is especially valuable when an element you expect to find doesn't appear findable. Browse the XML tree and verify it actually exists in the hierarchy. If it doesn't, the underlying automation framework can't see it — and the fix belongs with the app developer.

07 — Quick Reference Matrix

All strategies covered in this article, summarized by reliability and recommendation:

Strategy Framework Reliability Performance Verdict
By.ID SeleniumAppium High — when stable & unique Fast ✓ Prefer
By.CSS_SELECTOR Selenium High — with data-testid Fast ✓ Web
ACCESSIBILITY_ID Appium High — cross-platform Fast ✓ Mobile
IOS_CLASS_CHAIN Appium Medium-High Fast iOS fallback
IOS_PREDICATE Appium Medium-High Fast iOS fallback
ANDROID_UIAUTOMATOR Appium Medium Fast Android fallback
By.NAME SeleniumAppium Low-Medium Fast Avoid
By.CLASS_NAME SeleniumAppium Low Fast Avoid
By.XPATH SeleniumAppium Low — fragile Slow on mobile Avoid

Conclusion

The locator strategies you choose have a direct impact on test stability. The decision tree is straightforward:

  • Web (Selenium) — prefer By.ID, then By.CSS_SELECTOR with data-testid attributes or ARIA labels. Avoid XPath unless there is genuinely no other option.
  • Mobile (Appium) — prefer ACCESSIBILITY_ID for cross-platform stability. Fall back to IOS_CLASS_CHAIN or ANDROID_UIAUTOMATOR when no accessibility label exists. Avoid XPath on mobile.
  • Both — avoid CLASS_NAME. It is rarely unique and, on mobile, platform-specific.

Finding an element reliably is the foundation — but not the whole story. Even the best locator strategy can't help you if the element isn't present yet, or has been removed by an async operation. Stay tuned for upcoming posts on explicit waits, timing strategies, and other aspects of making tests fast, reliable, and repeatable.

I welcome comments and contributions on this topic. Find me here: