skip to content

In Selenium, why does re-finding a parent WebElement not refresh the child elements found through it?

level: seniorimportance: nice to knowfreq 36%

answer

  1. What exactly does a find hand back?
  2. A handle on a node, not a recipe
  3. References do not remember their parent
  4. Re-finding re-runs only that one locator
  5. Both nested lookups must be repeated

basics

~20 s

A nested find resolves once and returns a handle on one node, with no link back to the parent. Re-finding the parent only re-runs the parent's locator, so the child references still point at the nodes a re-render discarded.

solid answer

~40 s

In Selenium a `WebElement` is a reference to a specific DOM node, not a stored locator. `card.findElement(By.cssSelector("span.balance"))` sends one find-from-element command, resolves the selector against the card, and returns a reference; later calls on that object name the node id directly and never re-evaluate the selector. Re-finding the card is simply another find that produces a card reference — it cannot re-issue the child's command. Under the W3C rules Selenium 4 follows, an element is stale when its node is not connected to the active document, so if the re-render replaced the balance node the child reference fails with `StaleElementReferenceException` while the card may still be perfectly usable. Both lookups have to be re-run, parent first.

code

java · 14 lines
java
WebDriver driver = new ChromeDriver();
driver.get("https://transit.example/bus-pass/top-up");

WebElement card = driver.findElement(By.cssSelector("#saved-passes li.pass-card"));
WebElement balance = card.findElement(By.cssSelector("span.balance"));
System.out.println(balance.getText());

card.findElement(By.cssSelector("button.top-up")).click();

card = driver.findElement(By.cssSelector("#saved-passes li.pass-card"));
balance = card.findElement(By.cssSelector("span.balance"));
System.out.println(balance.getText());

driver.quit();

go deeper

for a junior

Remember that a WebElement is a handle on one node rather than a saved locator. If the page rebuilds that node the handle stops working, and it has to be looked up again from scratch.

for a middle

Explain that a nested find resolves once. The child reference it produced has no link back to the parent, so looking the parent up again re-runs only the parent's locator and leaves the child pointing at a discarded node.

for a senior

Trace which of the two references actually died. If only the subtree was rebuilt, the parent handle can still be fine while every child found through it is stale, and that asymmetry tells you how much of the page was re-rendered.

for a principal

Own the general shape: an element reference is a snapshot of a node whose lifetime the page controls, not your code. Be able to state that lifetime precisely before deciding what may be held across a step at all.

## What a find actually hands back A `WebElement` in Selenium is a handle on one specific DOM node, not a stored recipe for finding it again. When `card.findElement(By.cssSelector("span.balance"))` runs, the client sends `POST /session/{sessionId}/element/{elementId}/element`, the remote end evaluates `span.balance` against the card as its start node, and returns a **web element reference** for the node it matched. That reference is resolved exactly once. Every later call on the returned object — `getText`, `click`, another scoped find — names the node id directly and never re-runs the locator. Two consequences fall straight out of that, and together they are the whole answer: - The child object holds **no link back to the parent**. It came from a command that named the parent, but what it stores is a node, not a relationship. - Looking the parent up again is just another find. It resolves the parent's own locator and produces a parent reference. It does not, and cannot, re-issue the child's command. ## Why the child fails while the parent looks fine The W3C WebDriver specification that **Selenium 4** implements defines the rule precisely: an element **is stale** if its node document is not the active document, or if it is not connected. Before any element command runs, the remote end performs a step the spec calls *get a known element*; if the node is stale, the command fails immediately with `stale element reference`, which the Java bindings raise as `StaleElementReferenceException`. Note what that rule is about: the individual node, and whether it is still attached. It says nothing about parents, subtrees or locators. So the outcome after a re-render depends only on which nodes were replaced. | What the re-render did | The card reference | The balance reference found inside it | |---|---|---| | Replaced the whole `li.pass-card` node | stale | stale | | Kept the card node, rebuilt its contents | still usable | stale | | Touched neither | still usable | still usable | | Navigated to a new document | stale | stale | The middle row is the one that produces the confusing bug report: the card still answers commands, so the test looks like it recovered, and only the children fail. ## The bus-pass sequence, step by step On a top-up page whose saved passes render as `li.pass-card`, a run that tops up a pass and then reads the new balance goes like this: 1. `driver.findElement(By.cssSelector("#saved-passes li.pass-card"))` resolves the card and returns reference **A**. 2. `card.findElement(By.cssSelector("span.balance"))` resolves the balance **inside A** and returns reference **B**. 3. The top-up button is clicked and the page re-renders that card's contents with the new balance. 4. The test re-finds the card. The locator runs again and yields a reference for whatever node matches now. 5. Reading through **B** still fails, because **B** names the node from step 2, which the re-render disconnected. Step 4 is where the intuition goes wrong. Re-finding the parent feels like refreshing the card "and everything in it", but only one locator was re-evaluated — the card's. The nested find from step 2 is a separate command that nobody re-issued. ## What has to be re-run, and in what order The fix inside the scoping model is simply to redo both resolutions, parent first: ```java card = driver.findElement(By.cssSelector("#saved-passes li.pass-card")); balance = card.findElement(By.cssSelector("span.balance")); ``` The order is not stylistic. The child command carries the parent's element id in its URL path, and the remote end resolves that id before it looks at the locator. Re-running the child find against the **old** card reference therefore fails on the parent, not on the child, and the error names staleness rather than a missing element — a genuinely misleading message if you have not seen it before. ## How to reason about it in general - Treat every `WebElement` as a snapshot of a node whose lifetime the page controls, not your code. - A nested lookup is **two** resolutions, so a rebuilt subtree invalidates as many references as you took out of it. - The depth of the nesting is the number of finds to redo: three levels of scoping means three commands, in order, from the outermost. - Do not infer the parent's state from the child's. A stale child is perfectly compatible with a live parent, and that asymmetry is the fastest way to work out how much of the page was actually rebuilt.

  • If the card element's own node survived the re-render, is its reference still usable?
    Yes. Staleness is a property of one node, not of a subtree: a reference stays valid while its node is connected to the active document. A card whose node was kept but whose contents were rebuilt still answers commands, while every child found inside it does not.
  • Does Selenium re-run a nested find automatically the next time the child element is used?
    No. The nested find is a single command that resolves once and returns a reference. Every later call names that node id directly, so the selector is never evaluated again and a replaced node can never be rediscovered through the old handle.
  • In what order do the two finds have to be repeated after a re-render?
    Parent first, then the child from the new parent. The child command carries the parent's element id in its URL path and the remote end resolves that id before reading the locator, so re-running the child against the old parent fails on the parent.

saying these in an interview costs you the question

  • Says re-finding the parent refreshes the children found through it
  • Believes a child reference keeps a live link back to its parent element
  • Thinks a scoped find is re-evaluated every time the child is used
  • Assumes the parent must be stale whenever one of its children is
  • Claims an element goes stale because its text or attributes changed