Appium's annotation-based locator system lets you declare element locators as field annotations instead of explicit findElement() calls. @AndroidFindBy, @iOSXCUITFindBy, and @FindBys keep the locator declarations visible at the top of the class and remove the per-method locator noise.
@AndroidFindBy and @iOSXCUITFindBy
import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.iOSXCUITFindBy;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
public class LoginPage {
@AndroidFindBy(accessibility = "emailInput")
@iOSXCUITFindBy(iOSNsPredicate = "name == 'emailField'")
private WebElement emailField;
@AndroidFindBy(accessibility = "passwordInput")
@iOSXCUITFindBy(iOSNsPredicate = "name == 'passwordField'")
private WebElement passwordField;
@AndroidFindBy(accessibility = "loginButton")
@iOSXCUITFindBy(iOSNsPredicate = "label == 'Sign In'")
private WebElement loginButton;
public LoginPage(AppiumDriver driver) {
PageFactory.initElements(new AppiumFieldDecorator(driver), this);
}
public void login(String email, String password) {
emailField.sendKeys(email);
passwordField.sendKeys(password);
loginButton.click();
}
}AppiumFieldDecorator is the mobile-aware replacement for Selenium's standard DefaultFieldDecorator. It understands @AndroidFindBy and @iOSXCUITFindBy and picks the right locator based on the running platform.
Annotation parameters
Each annotation supports multiple locator strategies as parameters:
// By accessibility ID
@AndroidFindBy(accessibility = "loginButton")
// By resource-id
@AndroidFindBy(id = "com.example.app:id/login_btn")
// By UiAutomator expression
@AndroidFindBy(uiAutomator = "new UiSelector().text(\"Sign In\")")
// By XPath (avoid unless necessary)
@AndroidFindBy(xpath = "//android.widget.Button[@text='Sign In']")For iOS:
// By predicate string
@iOSXCUITFindBy(iOSNsPredicate = "label == 'Sign In'")
// By class chain
@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeButton[`label == 'Sign In'`]")
// By accessibility ID
@iOSXCUITFindBy(accessibility = "signInButton")List of elements
Annotate List<WebElement> fields for collections:
@AndroidFindBy(uiAutomator = "new UiSelector().className(\"android.widget.TextView\").resourceId(\"com.example.app:id/product_title\")")
@iOSXCUITFindBy(iOSNsPredicate = "type == 'XCUIElementTypeStaticText' AND name ENDSWITH 'Title'")
private List<WebElement> productTitles;
public List<String> getAllProductTitles() {
return productTitles.stream()
.map(WebElement::getText)
.collect(Collectors.toList());
}@FindBys — requiring multiple conditions
@FindBys chains multiple @FindBy annotations with AND logic — the element must match all conditions:
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.FindBy;
@FindBys({
@FindBy(className = "android.widget.LinearLayout"),
@FindBy(id = "com.example.app:id/active_item")
})
private WebElement activeListItem;This is less common in mobile tests because UIAutomator expressions can handle compound selectors natively. Use @FindBys when you need Selenium-standard compound locators.
@FindAll — matching any condition
@FindAll uses OR logic — the element matches if it satisfies any of the annotations:
@FindAll({
@FindBy(id = "com.example.app:id/submit_btn"),
@FindBy(id = "com.example.app:id/confirm_btn")
})
private WebElement submitButton;Useful when different app versions or flavors use different element IDs for the same control.
Lazy initialisation with AppiumFieldDecorator
PageFactory.initElements with AppiumFieldDecorator doesn't locate elements at construction time — it wraps each field in a proxy that finds the element when you first call a method on it. This means:
public LoginPage(AppiumDriver driver) {
PageFactory.initElements(new AppiumFieldDecorator(driver), this);
// emailField is NOT located yet — no network call
}
public void login(String email, String password) {
emailField.sendKeys(email); // element located here, on first use
}The implication: a LoginPage can be constructed before the login screen is visible, as long as you don't interact with its elements before the screen appears.
When annotations work against you
The annotation approach has two failure modes:
StaleElementReferenceException: Annotated fields re-locate on every access, which prevents stale references for most cases. But if the driver session is recreated between test methods and the page object is reused, the proxy still holds a reference to the old session. Always construct page objects from the current driver.
No per-call wait: AppiumFieldDecorator has a default timeout (configurable), but the wait applies to the first element location, not to re-checks. For elements that appear after animation or data loading, use explicit waits with WebDriverWait rather than relying on decorator timeouts.
// Configure decorator timeout
PageFactory.initElements(
new AppiumFieldDecorator(driver, Duration.ofSeconds(10)),
this
);