skip to content

In Appium, why can a find succeed and the very next command still act on a stale screen?

level: seniorimportance: should knowfreq 46%

answer

  1. no driver streams the screen
  2. a handle is a photograph
  3. the screen moves between round trips
  4. re-find, do not reuse
  5. coordinates go stale silently

basics

~20 s

Because neither driver streams the screen. Android's on-device server reads the accessibility node tree when asked and Apple's WebDriverAgent takes an XCUITest snapshot, so every handle and rectangle describes an instant that has already passed by the time the next command arrives.

solid answer

~40 s

Appium never hands a test a live view. A find is answered from a tree read at that moment — on Android by the UiAutomator2 server from the accessibility node tree, on Apple platforms by WebDriverAgent from an XCUITest snapshot — and the handle and rectangle you get back describe that instant only. Anything that re-lays out before the next round trip invalidates them: in a greenhouse climate app, a sensor row that refreshes, a warning banner that pushes the list down, a chart that finishes loading. The symptoms are a stale-element error, a tap that lands on the neighbouring row, or an assertion reading a value the screen has already replaced. The mitigation is structural — re-find immediately before acting, and never hold a handle across a state change.

go deeper

for a junior

Remember that a found element is a reference to a tree that was read once, not a live pointer at the screen. Find the element again just before you use it.

for a middle

Explain where each platform's tree comes from and why a second round trip can meet a different screen, then name the visible symptoms: a stale-element error, a mis-hit, or an assertion on a value already replaced.

for a senior

Diagnose from evidence: show that the find succeeded and the following command failed, correlate it with what changed on screen in between, and describe the Android and Apple failure signatures when the app itself falls behind.

for a principal

Own the design rule that no handle survives a state change, and decide where that is enforced — in the element-access layer everyone uses, rather than as advice each author is expected to remember.

## What a find actually hands back When a test asks Appium for an element, the driver does not open a window onto the device. It asks the device-side agent for the current UI tree, matches the locator against it, and returns an identifier for the node it matched. On Android that tree is read by the UiAutomator2 server from the platform's accessibility node tree; on Apple platforms WebDriverAgent takes an XCUITest snapshot. Either way the answer describes one instant, and `GET /session/:sessionId/source` returns the human-readable form of the same instant. Nothing about it updates afterwards. That single fact is the mechanism behind a whole family of intermittent failures. The test's next command — a click, a text read, an attribute read — is a second round trip that happens milliseconds or seconds later, and the screen is free to change in between. ## The three shapes it takes - **A stale reference.** The node the handle points at is no longer in the current tree, and the driver answers the follow-up command with an error instead of acting. This is the honest version: it fails loudly. - **A silent mis-hit.** The node is gone or moved but something else now occupies its place, so the interaction lands on the wrong control. A greenhouse climate run that meant to open zone 3 opens zone 4 because a new alert row was inserted above it. - **A reading that was already replaced.** An attribute or text captured from the snapshot is compared against an expectation, while the live screen has moved on. The assertion is about a screen nobody is looking at any more. ## Why a live screen is the hard case A greenhouse climate dashboard is close to the worst case for this. Temperature and humidity refresh on a timer, a vent-status row reorders when a zone changes state, and a warning banner appears when CO2 crosses a threshold and pushes everything below it down by its own height. Every one of those invalidates rectangles the driver handed out moments earlier. On a workstation the round trip is short enough that the race usually resolves harmlessly. On a shared CI emulator or simulator the same round trip is long enough that it sometimes does not — which is why the suite is green locally and amber in the pipeline, with a different case failing each run. ## When the app itself is the slow part The two platforms degrade differently when the app under test stops keeping up, and that difference is worth knowing before you read a log. | | Android, UiAutomator2 driver | Apple platforms, XCUITest driver | |---|---|---| | Who reads the tree | the on-device UiAutomator2 server, a separate process | WebDriverAgent, through the app's own accessibility layer | | A busy app UI thread | the session keeps answering, but from a tree the app has not updated | the driver's own request stalls along with the app | | A bound worth knowing | `actionAcknowledgmentTimeout`, the wait for an action's acknowledgement | `accessibilityDeadline`, the bound on waiting for the app's accessibility layer | The practical consequence is that the same underlying problem reads differently in the two logs. On Android you tend to see plausible answers that are simply out of date; on Apple platforms you tend to see the command hang and then fail. Both are the same story — the app is not keeping up — told by two different mechanisms. ## Reducing exposure 1. **Re-find immediately before acting.** One extra round trip is cheap next to a mis-hit that costs a re-run and an investigation. 2. **Do not cache handles across a state change.** A page object that holds elements from a previous screen is holding references into a tree that no longer exists. 3. **Assert on a fresh read.** If a value can change on a timer, read it as part of the assertion rather than earlier in the test. 4. **Anchor on something that does not move.** On a live list, scope the search from a stable header or container rather than by position among rows that reorder. 5. **Treat a screen change as invalidating everything you hold.** That includes rectangles used for coordinate-based interactions, which have no staleness check at all — nothing errors, the tap simply lands somewhere. ## What this is not This is not an argument about how long to wait or how often to poll; those are general automation questions with their own answers. It is a statement about the shape of the data an Appium test works with. Both drivers describe the device in snapshots, so correctness comes from asking again rather than from asking earlier and waiting longer. A longer timeout does not make an old rectangle current, and on the greenhouse climate dashboard — which changes on its own schedule, not the test's — nothing you set on the client side makes the snapshot live.

  • How would you tell a stale-tree failure apart from a genuinely missing element?
    A stale-tree failure has a find that already succeeded: the log shows the element resolved and the *next* command failed or hit the wrong place. A missing element fails at the find itself. The stale case also correlates with a screen that changed in between — a refresh, a banner, a load completing — and it usually disappears when the find is moved next to the action.
  • Why does caching elements in a page object make this worse?
    A cached handle is a reference into a tree that no longer exists, so every use after the first state change is a gamble. Re-resolving on each use costs one round trip to the device and removes the whole class of failure; caching trades a correctness property for a saving that the round trip itself dwarfs.

The element tree is a photograph of a crowd, not a live feed: by the time you point at someone in the print, the crowd has shuffled.

saying these in an interview costs you the question

  • Thinks the page source is a live view of the screen
  • Caches element handles across screens to save round trips
  • Says a longer timeout fixes a stale element reference
  • Assumes an element rectangle stays valid after a list reflows
  • Believes only Apple platforms work from a snapshot