An Appium xpath loop over 40 tram fare-inspection rows crawls on iOS but not on Android - why?
answer
- count serialisations, not expressions
- one find, one document
- the unit cost is not equal
- one findElements pays once
basics
~20 sEach xpath find serialises the hierarchy again, so 40 single finds cost 40 documents on both platforms. The iOS document is built from an XCTest element snapshot across the app boundary, so its unit cost is far higher than Android's.
solid answer
~40 sTwo costs multiply. First, serialisation is **per find call**: forty single `xpath` finds build forty XML documents, on Android in the UiAutomator2 driver's on-device server and on iOS in `WebDriverAgent`. Second, one document does not cost the same on both sides - the Apple one comes from an XCTest element snapshot, and the XCUITest driver's docs warn an `xpath` lookup can be up to ten times slower than an `-ios predicate string` or `-ios class chain` one. So the loop that is merely wasteful on Android becomes a stall on iOS. The fix is to stop paying per row: one `findElements` call returns every matching ticket row from a single serialisation, and you read what you need off the returned elements.
go deeper
Remember that each xpath find in Appium builds a fresh document, so a loop of finds is a loop of documents on Android and on iOS alike.
Explain why one findElements call is cheaper than forty single finds, and why the same loop hurts an iOS lane far more than an Android one.
Demonstrate the diagnosis: time the find commands per platform, compare against a per-platform strategy on the same screen, and show the cost tracking tree size.
Own the call about where a per-row loop is acceptable at all, and how the suite stops page objects from quietly reintroducing one find per row.
## The loop is not forty reads, it is forty documents A tram fare-inspection case that walks forty scanned-ticket rows and reads each one with its own `xpath` find looks, from the test's side, like forty cheap reads. From the driver's side it is forty full serialisations of the accessibility hierarchy. Neither platform evaluates XPath against a live tree. On Android the UiAutomator2 driver's on-device server builds an XML document from the accessibility node tree; on iOS `WebDriverAgent` builds one from an XCTest element snapshot. Each build serves exactly one find call, and nothing is cached between them. That is the first multiplier, and it applies to both lanes equally. ## The second multiplier is the platform The two documents do not cost the same to produce. - On **Android**, the UiAutomator2 server reads an accessibility node tree the platform already maintains, from a process that can see it directly. - On **iOS**, `WebDriverAgent` has to obtain an XCTest element snapshot of the application under test, which is heavier and crosses into the app. | | Android, UiAutomator2 driver | iOS, XCUITest driver | |---|---|---| | Document built from | the accessibility node tree | an XCTest element snapshot | | Built by | the on-device UiAutomator2 server | `WebDriverAgent` | | Forty single finds cost | forty node-tree serialisations | forty snapshots plus forty XML builds | | Cheaper same-lane strategy | `accessibility id`, `-android uiautomator` | `-ios predicate string`, `-ios class chain` | The XCUITest driver's own documentation puts a figure on it: an `xpath` lookup there can be **up to ten times slower** than the equivalent `-ios class chain` or `-ios predicate string` lookup. Multiply a per-row loop by that unit cost and the iOS lane does not degrade gracefully - it dominates the run, while Android merely wastes time. ## Reading the symptom correctly Before rewriting anything, confirm the cost is where you think it is: 1. **Time the finds, not the test.** The Appium server log records each command, so the duration of the find calls themselves is visible on both platforms without instrumenting the suite. 2. **Compare strategies on the same screen.** Re-run the same case with a per-platform strategy - `accessibility id` on Android, `-ios predicate string` on iOS - and see whether the gap follows the strategy or the application. 3. **Check whether the cost tracks tree size.** If a fare list with more rows costs proportionally more per find, that is serialisation rather than application work. ## The fixes, in order of how much they buy - **Stop paying per row.** One `findElements` call with the same expression returns every matching row from a single serialisation, and you then read attributes off the returned elements. This is the single largest win, and it applies to both lanes. - **Move the hot finds off `xpath` entirely.** On Android the UiAutomator2 driver's `id`, `accessibility id` and `-android uiautomator` are matched by the on-device server; on iOS the XCUITest driver's `-ios predicate string` and `-ios class chain` are what its docs recommend for exactly this reason. - **Scope the find, carefully.** An element-scoped `xpath` find still triggers a serialisation, and whether it is matched against the element's subtree or the whole page source depends on `limitXPathContextScope`, which both drivers expose. Scoping helps only when it genuinely shrinks the work. - **Reduce how much tree there is at all.** Both drivers expose settings that bound what goes into the hierarchy; that is a separate lever from the locator strategy, and it applies to every find rather than just the loop. ## What does not help - Shortening the expression. Evaluation is the cheap half, the build is the expensive half, and the build happens either way. - Replacing a relative path with a long absolute one. Same document, same build, same cost. - Wrapping each find in a retry. That multiplies the serialisations instead of removing them. - Assuming both lanes will respond to the same fix by the same amount. They will not, because the unit costs differ. ## Why this bites on mobile and not in a browser Worth being explicit, because the instinct usually comes from browser work. A browser answers an XPath query out of a document it already holds, so the cost is roughly the cost of the query. A mobile driver has to build the document first, so the cost is roughly the cost of the screen. That inverts the tuning advice: in a browser you tune the expression, and on Android and iOS you tune **how often** you ask and **how much tree** each ask has to cover. A forty-row loop is the worst possible shape under that model, and it is the shape a suite falls into naturally when a page object exposes one row-reading helper and a case calls it once per row.
- Does scoping the find to a parent element make the loop cheaper?It can, but not automatically. An element-scoped `xpath` find still triggers a serialisation, and whether it is matched against the element's subtree or the whole page source depends on `limitXPathContextScope`, which both the UiAutomator2 and XCUITest drivers expose. Scoping helps only when it genuinely shrinks the work per call.
- How would you confirm serialisation is the cost rather than the app being slow?Time the finds rather than the test. The Appium server log carries each find command's duration, so compare a run of `xpath` finds against the same run using `accessibility id` on Android and `-ios predicate string` on iOS. If the gap tracks tree size rather than application work, it is serialisation.
saying these in an interview costs you the question
- Blames the app rather than the locator strategy
- Thinks a shorter xpath expression fixes the loop
- Assumes the page source is built once per test
- Expects Android and iOS to degrade at the same rate
- Adds a retry around each find instead of removing it