Finding Elements with AppiumBy in Java

8 min read

AppiumBy is the class that extends Selenium's By with mobile-specific locator strategies. Choosing the right strategy affects both test stability and execution speed. Accessibility ID is the gold standard; platform-specific selectors are the power tool for elements that don't have accessibility attributes.

AppiumBy vs By

Appium's locator strategies aren't in Selenium's By class — they're in io.appium.java_client.AppiumBy:

import io.appium.java_client.AppiumBy;
 
// Mobile-specific (AppiumBy required)
driver.findElement(AppiumBy.accessibilityId("loginButton"));
driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Login\")"));
driver.findElement(AppiumBy.iOSNsPredicateString("label == 'Login'"));
 
// Standard Selenium strategies also work on mobile
driver.findElement(By.id("com.example.app:id/login_button")); // Android resource-id
driver.findElement(By.xpath("//XCUIElementTypeButton[@name='Login']")); // iOS XPath

Accessibility ID — the preferred strategy

Accessibility IDs are cross-platform. The same locator works on Android (content-description) and iOS (accessibility identifier):

WebElement loginBtn = driver.findElement(AppiumBy.accessibilityId("loginButton"));
loginBtn.click();

Set accessibility IDs in the app:

  • Android: android:contentDescription="loginButton" in XML, or view.setContentDescription("loginButton") in code
  • iOS: view.accessibilityIdentifier = "loginButton" in Swift/ObjC, or accessibilityIdentifier("loginButton") modifier in SwiftUI

When the same ID is used on both platforms, your locator layer becomes platform-agnostic.

Android UIAutomator selector

androidUIAutomator accepts UiAutomator2's selector syntax as a string. It's the most powerful Android-specific locator:

// By text
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiSelector().text(\"Sign In\")"
));
 
// By text contains
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiSelector().textContains(\"Sign\")"
));
 
// By resource-id
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiSelector().resourceId(\"com.example.app:id/login_btn\")"
));
 
// By class and index
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiSelector().className(\"android.widget.EditText\").instance(0)"
));
 
// Scroll to element (most powerful — finds elements off-screen)
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView("
    + "new UiSelector().text(\"Terms of Service\"))"
));

The UiScrollable.scrollIntoView() pattern is the cleanest way to find elements in long lists — it scrolls until the element appears, then returns it.

iOS Predicate String

Predicate strings are faster than XPath on iOS because the XCUITest engine evaluates them natively:

// By label (visible text)
driver.findElement(AppiumBy.iOSNsPredicateString("label == 'Sign In'"));
 
// By name (accessibility identifier)
driver.findElement(AppiumBy.iOSNsPredicateString("name == 'loginButton'"));
 
// By type and enabled state
driver.findElement(AppiumBy.iOSNsPredicateString(
    "type == 'XCUIElementTypeButton' AND enabled == true"
));
 
// Contains predicate
driver.findElement(AppiumBy.iOSNsPredicateString("label CONTAINS 'Sign'"));
 
// Multiple conditions
driver.findElement(AppiumBy.iOSNsPredicateString(
    "type == 'XCUIElementTypeTextField' AND placeholderValue == 'Email'"
));

iOS Class Chain

Class chains are more specific than predicate strings and allow parent-child traversal:

// Direct child
driver.findElement(AppiumBy.iOSClassChain(
    "**/XCUIElementTypeButton[`label == 'Sign In'`]"
));
 
// Nested — second button inside a table cell
driver.findElement(AppiumBy.iOSClassChain(
    "**/XCUIElementTypeCell[1]/XCUIElementTypeButton[2]"
));
 
// By index within a container
driver.findElement(AppiumBy.iOSClassChain(
    "**/XCUIElementTypeTable/XCUIElementTypeCell[3]"
));

Class chains are your first tool after accessibility ID fails on iOS. They're faster than XPath and more expressive than predicate strings for hierarchical selection.

XPath — last resort

XPath works on both platforms but is the slowest strategy. Appium has to dump the full element tree, then run the XPath query against it. On complex screens with hundreds of elements, this can take seconds:

// Avoid when other strategies are available
driver.findElement(By.xpath(
    "//android.widget.Button[@text='Sign In']"
));
 
// iOS XPath uses XCUIElement type names
driver.findElement(By.xpath(
    "//XCUIElementTypeButton[@name='Sign In']"
));

Use XPath only when: the element has no accessibility ID, UIAutomator/predicate strings can't reach it, and Appium Inspector shows it clearly in the tree.

Finding multiple elements

All strategies work with findElements() for lists:

List<WebElement> cells = driver.findElements(
    AppiumBy.androidUIAutomator("new UiSelector().className(\"android.widget.LinearLayout\")")
);
System.out.println("Found " + cells.size() + " items");

Locator constants pattern

Keep locators in constants at the top of the page object so they're easy to update when the UI changes:

public class LoginPage {
    private static final By EMAIL_FIELD = AppiumBy.accessibilityId("emailInput");
    private static final By PASSWORD_FIELD = AppiumBy.accessibilityId("passwordInput");
    private static final By LOGIN_BUTTON = AppiumBy.accessibilityId("loginButton");
    private static final By ERROR_MESSAGE = AppiumBy.androidUIAutomator(
        "new UiSelector().resourceId(\"com.example.app:id/error_text\")"
    );
 
    private final AppiumDriver driver;
 
    public LoginPage(AppiumDriver driver) {
        this.driver = driver;
    }
 
    public void login(String email, String password) {
        driver.findElement(EMAIL_FIELD).sendKeys(email);
        driver.findElement(PASSWORD_FIELD).sendKeys(password);
        driver.findElement(LOGIN_BUTTON).click();
    }
}

// tip to track lessons you complete and pick up where you left off across devices.