Performance and Efficiency
Is XPath slow? Where the time actually goes in a locator lookup, which expressions are genuinely expensive, and the two habits that matter more than XPath versus CSS.
Performance and Efficiency
“XPath is slower than CSS” is repeated in every interview and most of the time it does not matter. This lesson explains where the cost really is, so you can stop worrying about the wrong thing and fix the right one.
Where the time goes
A Selenium findElement call spends its time in roughly this order:
- Network round trip to the driver (WebDriver protocol over HTTP): milliseconds.
- Waiting for the element to appear (implicit or explicit wait): up to seconds.
- Evaluating the selector in the browser: microseconds to a few milliseconds.
The selector engine is the smallest part. On a page with a few thousand nodes, both CSS and XPath evaluate in well under a millisecond. On a page with 100,000 nodes, a badly written XPath can take tens of milliseconds, which is still less than a single WebDriver round trip.
Historically, XPath was much slower in Internet Explorer, which lacked a native engine. That is where the reputation came from. Modern Chromium, Firefox and WebKit all have native XPath.
What makes an XPath genuinely expensive
The evaluator has to visit nodes. Expressions that visit more nodes cost more.
| Pattern | Why it costs | Cheaper alternative |
|---|---|---|
//*[contains(@class,'x')] | every element, string compare on each | //div[contains(@class,'x')] if you know the tag |
//div//div//div//span | repeated descendant scans | anchor higher: //*[@id='app']//span[@data-x] |
//*[contains(., 'text')] | string value of every element, which concatenates all descendant text | //p[contains(., 'text')] or //*[text()[contains(., 'text')]] |
//a[//div[@class='x']] | inner path re-evaluated from root for every a | .//div, or move the condition to an anchor |
//tr[count(td) > 3] on a huge table | fine; count is linear | no change needed |
//*[local-name()='svg']//* | every svg descendant | add an attribute predicate |
The pattern to avoid is a wildcard with an expensive predicate at the root. Naming the element or anchoring to a container cuts the candidate set dramatically.
Text matching costs
normalize-space() and contains(., ...) on an element compute its whole string value. On a //div that is the page wrapper, that means concatenating every piece of text on the page. Apply text predicates to leaf-ish elements (button, a, td, span, h2), not to containers.
XPath versus CSS in practice
| Concern | Reality |
|---|---|
| Raw speed | comparable in modern browsers; differences are noise next to waits |
| Traversing up | XPath only |
| Text matching | XPath only (CSS :contains was never standard) |
| Shadow DOM | CSS pierces in Playwright and Selenium’s shadow root context; XPath does not |
| Readability | CSS wins for simple attribute and class selectors |
Pick the tool by capability, not by benchmark folklore. Many teams use CSS for simple cases and XPath when they need text or axes.
The two habits that actually speed up suites
1. Stop using implicit waits with polling for elements that do not exist. A findElements call that returns empty waits the full implicit timeout. Negative assertions (“the error is not shown”) with a 10-second implicit wait cost 10 seconds each. Use explicit, short waits for those.
2. Locate once, act many. Cache a container element and search within it (container.findElement(By.xpath(".//button"))) instead of repeating a full-document search for each control. Note the leading . in .//button: without it, //button searches the whole document even when called on an element.
Measuring
In DevTools:
console.time('xpath');
for (let i = 0; i < 1000; i++) document.evaluate("//button[normalize-space()='Save']", document, null, 7, null);
console.timeEnd('xpath');
Divide by 1000. If it is under a millisecond, XPath is not your bottleneck. Profile the waits instead.
Try It Yourself
Open in Playground →
Note how many elements match: contains(., ...) on * is true for every ancestor of the text as well. Then change * to td.
Next Steps
- Common Errors and Fixes - Correctness before speed
- XPath in Selenium - Waits, caching and search contexts
- XPath vs CSS Selectors - The full comparison