In Selenium, what does an absolute XPath like /html/body/div[2]/table/tr[7]/td[3] cost on every find call?
answer
- Nothing survives between two find commands
- The walk restarts at the document root
- Depth is cheap, breadth is not
- A leading double slash is the descendant axis
- Predicates that read subtree text cost most
basics
~10 sIt is re-evaluated from the document root on every call, because WebDriver hands the expression to the browser afresh each time. The walk is cheap per step; a leading double slash is what costs.
solid answer
~40 sNothing is carried over between find commands. The W3C WebDriver find algorithm that Selenium 4 relies on calls `evaluate()` with the expression string, the start node and `ORDERED_NODE_SNAPSHOT_TYPE` every single time, so an absolute path is walked from `/html` again on each call. That walk is cheap: each step is a child-axis move plus a positional filter, so `/html/body/div[2]/table/tr[7]/td[3]` inspects the children of five nodes and nothing else. The expensive shapes are different ones - a leading `//`, which is the descendant-or-self axis and considers every element in the document, and predicates such as `contains(normalize-space(.), 'Awarded')` that build the whole string value of each candidate's subtree. On a scholarship-application review queue of 500 rows those are what show up in a measurement; the path's length is not.
code
java · 10 linesBy absolutePath = By.xpath("/html/body/main/table/tbody/tr[7]/td[3]/button");
By unanchoredScan = By.xpath("//button[@data-action='review']");
By textPredicate = By.xpath("//*[contains(normalize-space(.), 'Review')]");
for (By locator : List.of(absolutePath, unanchoredScan, textPredicate)) {
long start = System.nanoTime();
int matches = driver.findElements(locator).size();
long micros = (System.nanoTime() - start) / 1000;
System.out.printf("%s -> %d matches in %d us%n", locator, matches, micros);
}go deeper
Be ready to say that an XPath is sent to the browser as text and evaluated inside the page every time you call findElement. Reasoning about axis cost is not expected of you yet.
Explain that the expression is re-evaluated from the start node on each command with nothing cached, and that a leading double slash scans the whole document while a child-axis path does not.
Correct the folklore with the mechanism. Name the descendant-or-self axis and string-value predicates as the expensive parts, and show that you measured the difference on a real page before acting on it.
Own the cost model the team reasons with. Expression breadth and command count are worth a guideline; the length of a path and the choice of syntax in general are not.
## What the remote end does on every single call Nothing is carried over between two find commands. The W3C WebDriver **find** algorithm that Selenium 4 relies on takes the location strategy and the **selector string**, and for the XPath strategy calls `evaluate()` with that string, the start node, `null`, `ORDERED_NODE_SNAPSHOT_TYPE` and `null`. It then reads `snapshotLength` and walks `snapshotItem`. There is no compiled-expression cache at the WebDriver level, no memo of the previous result, and no element reference reused from last time. Whatever your expression costs, you pay it again on the next call. So the question "what does `/html/body/div[2]/main/table/tbody/tr[7]/td[3]` cost per call?" has two parts: what the evaluator does with that shape, and how often you make it do it. ## Depth is cheap: what an absolute path actually walks An absolute path is a chain of **child-axis** steps, each with an optional positional predicate. Starting at the document root, the evaluator takes the children of the current node, filters by name, applies the index, and moves on. For the path above that means it inspects the children of seven nodes and nothing else. It never looks at a sibling subtree it has not selected. Concretely, for that path the evaluator: - takes the document node's children and keeps `html`, - takes `html`'s children and keeps `body`, - keeps the second `div` among `body`'s children by position, - and repeats to the end of the path, one short child-list scan per step. The path being long is therefore close to irrelevant to its cost. Each extra step adds one more child-list scan, which on ordinary markup is a handful of nodes. The folklore that "absolute XPath is slow because it is long" describes the wrong mechanism. ## Breadth is expensive: the descendant-or-self axis `//` is shorthand for the **descendant-or-self** axis. `//button` starting at the document root asks the evaluator to consider every element in the document. Chain two of them - `//div//span` - and the second axis walks every descendant of every match of the first. That is where node counts explode, and it has nothing to do with how many characters the expression contains. On a scholarship-application review queue rendering 500 rows with a dozen elements each, the difference between anchoring the scan (`//table[@id='reviewQueue']//button`) and not anchoring it (`//button`) is the difference between walking one table's subtree and walking the whole page. ## Predicates that read text are the other multiplier Axis breadth decides how many candidates are considered. The predicate decides what is done to each one. - An attribute test such as `[@data-action='review']` is a cheap lookup on the candidate itself. - A positional predicate such as `[7]` is an index into a list the evaluator already has. - `text()` reads the candidate's text nodes. - `contains(normalize-space(.), 'Awarded')` forces the evaluator to build the **string value** of the candidate's entire subtree, then normalise whitespace, then search it. Run that over every element on a long page and the work grows with the page's total text, not with its element count. `//*[contains(normalize-space(.), 'Awarded')]` is the worst of both: every element considered, and every element's whole subtree text materialised. ## A cost ranking for the same queue page | Expression | Candidates considered | Work per candidate | |---|---|---| | `/html/body/main/table/tbody/tr[7]/td[3]/button` | children of six nodes | name and index test | | `//table[@id='reviewQueue']/tbody/tr[7]/td[3]/button` | all elements once, then child steps | attribute test, then index | | `//button[@data-action='review']` | every element in the document | one attribute test | | `//*[contains(normalize-space(.), 'Review')]` | every element in the document | full subtree string value | ## What this means for a locator you call in a loop 1. Establish how many times the expression is evaluated. A locator inside a loop over 500 rows is evaluated 500 times; a locator inside a polling wait is evaluated once per poll for the life of the wait. 2. Multiply that by the shape cost above. A cheap child-axis path re-evaluated 500 times is still cheap. A wildcard scan with a text predicate re-evaluated 500 times is not. 3. Fix breadth before you fix anything else - anchor the scan to a container, and replace subtree-text predicates with attribute tests where the markup allows it. The last point worth holding onto: on ordinary pages the whole in-page evaluation, absolute path or not, is smaller than the HTTP round trip that carried the command to the remote end. An absolute path is worth attention for reasons this cost model does not cover, but per-call evaluation expense is rarely the strongest of them.
- Does the driver cache a compiled XPath expression between two identical find calls?Not at the WebDriver level. The find algorithm passes the expression string to the browser's evaluate call on every command, so each call is a fresh evaluation from the given start node. Whatever a browser does internally is an implementation detail you cannot rely on across browsers.
- Why can //div//span be far more expensive than /html/body/div/span?The double slash is the descendant-or-self axis. `//div` considers every element in the document, and the second `//span` then walks every descendant of each match. The absolute path moves along the child axis only, so it examines the children of three nodes. Breadth of axis, not depth of path, drives the count.
- What makes a predicate expensive rather than the axis?Predicates that compute a node's string value, such as `text()`, `contains(.)` and `normalize-space(.)`, force the evaluator to build the concatenated text of a candidate's whole subtree. Run that over every element of a long queue and the work grows with the page's total text, not with its element count.
saying these in an interview costs you the question
- Says absolute XPath is slow because the path string is long
- Believes the browser caches the compiled expression between find calls
- Thinks a leading double slash only means the expression is relative
- Assumes any XPath is slower than any CSS selector on the same page
- Claims the driver rewrites XPath into CSS before evaluating it