Locator Strategy for native mobile apps
IDs and accessibility locators are still king.
NSPredicate (iOS) and UIAutomator (Android) are great—mainly better than XPath—but they do not beat a good accessibility id or resource-id. And CSS selectors don’t exist for native apps (only for WebViews).
Locator Strategy Ranking
Rank | iOS (native) | Android (native) | Why |
---|---|---|---|
1 | Accessibility ID → or | Accessibility ID → or
| Fast, readable, stable when set intentionally; improves a11y. |
2 | Stable identifiers (rare on iOS) | resource-id (via or
id is just shorthand for matching the native Android If you pass only If you pass the full value yourself, it still works:
| Most stable app-side handle (Android). |
3 | NSPredicate / Class Chain | UIAutomator ( | Native engine, flexible, much faster and less brittle than XPath. |
4 | XPath | XPath | Slow, brittle, DOM/structure‑dependent. Avoid unless you must. |
CSS selectors: Not supported in native context on iOS/Android. Use CSS only inside WebViews (Chromium/WebKit). In WebViews, CSS is usually preferable to XPath.
When Each Makes Sense
Accessibility ID (best default):
iOS maps to
name
/label
, Android maps tocontent-desc
.Great for buttons, tabs, and anything user-labeled.
Most readable tests, cross‑platform friendly.
resource-id (Android):
Super stable, ideal for inputs, icons without visible text, repeated items.
Prefer this over text queries when available.
NSPredicate (iOS) / UIAutomator (Android):
Use when you can’t get a stable a11y id or resource-id.
Powerful text ops (contains, begins/ends, regex), state filters (enabled/visible), class checks.
Also handy for case/diacritic handling on iOS (
[c]
,[cd]
).
XPath:
Only if nothing else is feasible (legacy screens, dynamic trees).
Keep short and attribute-based if you must use it.
iOS (prefer AccessibilityId; fall back to NSPredicate)
// Best (if set): AccessibilityId
driver.findElement(MobileBy.AccessibilityId("Back")).click();
// Next best: NSPredicate (fast, flexible)
driver.findElement(MobileBy.iOSNsPredicateString("label ==[cd] 'zurück'")).click();
// Contains, case-insensitive:
driver.findElement(MobileBy.iOSNsPredicateString("name CONTAINS[c] 'login'")).click();
Android (prefer accessibility or resource-id; fall back to UIAutomator)
// Best (if set): AccessibilityId (content-desc)
driver.findElement(MobileBy.AccessibilityId("Back")).click();
// Equally great: resource-id
driver.findElement(MobileBy.AndroidUIAutomator("new UiSelector().resourceId('com.app:idsubmit')")).click();
PageFactory Example
Use‑case | iOS (PageFactory) | Android (PageFactory) |
---|---|---|
Accessibility first choice |
|
|
Stable ID | (rare on iOS) |
|
Native fast text match |
|
|
Avoid if possible |
|
|
Whenever you have accessibility ids (and resource-ids on Android), use those first.
Use NSPredicate/UIAutomator when you lack stable IDs but still want speed and robustness. It is better alternative to XPath.
CSS is only for WebViews, where it usually beats XPath.
WebViews (Hybrid Apps)
Use CSS selectors (fast, readable) via the standard Selenium driver once you’ve switched to the WebView context.
Ranking in WebViews: CSS > XPath (IDs still best if present).
Examples (WebView only):
// After switching to WEBVIEW context:
driver.findElement(By.id("email")).sendKeys("x@x.com");
driver.findElement(By.cssSelector("button.primary")).click();
NSPredicate / UIAutomator deep dive
Scenario | iOS (NSPredicate) — why it helps | Android (UIAutomator) — why it helps |
---|---|---|
You only have visible text (no a11y id / resource‑id) | Powerful string ops ( | Text selectors: |
You need case-insensitive (and/or diacritic-insensitive) matching | Add | Use regex |
You must combine conditions (text + state) |
| Chain selectors: |
You want to avoid XPath for performance/stability | Native engine, typically faster and less brittle than XPath | Same advantage: native engine, faster and less brittle than XPath |
You only know the class/type + partial label | Filter by text + | Combine |
Dynamic UIs where exact text varies slightly |
|
|
Rule of thumb: Prefer AccessibilityId / resource‑id first. If unavailable, use NSPredicate (iOS) or UIAutomator (Android). Keep XPath as a last resort.
RAW + PageFactory examples
Exact match (case/diacritic considerations)
| iOS (NSPredicate) | Android (UIAutomator) |
---|---|---|
RAW |
|
|
PageFactory |
|
|
Contains / partial text (case-insensitive)
| iOS (NSPredicate) | Android (UIAutomator) |
---|---|---|
RAW |
|
|
PageFactory |
|
|
Starts/Ends with
| iOS (NSPredicate) | Android (UIAutomator) |
---|---|---|
RAW |
|
|
PageFactory |
|
|
Regex (digits only, badges, ids embedded in text)
| iOS (NSPredicate) | Android (UIAutomator) |
---|---|---|
RAW |
|
|
PageFactory |
|
|
Class/type + partial text
| iOS (NSPredicate) | Android (UIAutomator) |
---|---|---|
RAW |
|
|
PageFactory |
|
|
Handling diacritics (iOS builtin vs Android workaround)
| iOS (NSPredicate) | Android (UIAutomator) |
---|---|---|
RAW |
| `AndroidUIAutomator("new UiSelector().textMatches("(?i)zurück |
PageFactory |
| `@AndroidFindBy(uiAutomator = "new UiSelector().textMatches("(?i)zurück |
Comments
Post a Comment
No spam only genuine comments :)