skip to content

Query Expressions

The two query languages a driver hands to the browser: what a CSS selector can and cannot express, what XPath adds on top of it, and what each one actually costs per call.

on this pageshow

explore

questions

12

In Selenium, what does each driver.findElement call cost when a test loops over many rows?

level: juniorimportance: must knowfreq 66%

answer

  1. A find is not a local operation
  2. Something crosses a process boundary each time
  3. One HTTP command per call
  4. The plural form brings them all back at once
  5. Three hundred rows means three hundred POSTs

basics

~20 s

Each findElement call is one HTTP command to the browser driver, sent as POST /session/{id}/element. A loop over 300 rows sends 300 separate commands, and those round trips add up no matter how simple the selector is.

solid answer

~40 s

A find is a remote command, not a local operation - there is no copy of the DOM in the test process. `driver.findElement(By.cssSelector("#reviewQueue tbody tr"))` sends `POST /session/{id}/element` to the remote end, which runs the match inside the page and returns one element reference. `driver.findElements` sends `POST /session/{id}/elements` and brings the whole matching set back in that same single response. A find scoped to an element you already hold is a further command, `POST /session/{id}/element/{elementId}/element`. So a loop that calls `findElement` once per row of a scholarship-application review queue costs one round trip per row, plus another for every value you then read with `getText`. Cutting the number of commands is the lever; changing selector syntax is not.

code

java · 12 lines
java
WebDriver driver = new ChromeDriver();
driver.get("https://grants.example.org/review-queue");

List<WebElement> rows = driver.findElements(By.cssSelector("#reviewQueue tbody tr"));

List<String> applicants = new ArrayList<>();
for (WebElement row : rows) {
    applicants.add(row.findElement(By.cssSelector("td.applicant")).getText());
}

System.out.println(applicants.size());
driver.quit();

go deeper

for a junior

Be ready to say that every findElement call travels to the driver as its own request and comes back with a reference. The count of calls, not the selector syntax, is what drives the cost.

for a middle

Explain the endpoints: POST /session/{id}/element for a find, a separate scoped-find command against an element reference, and another command for each read such as getText. Then do the arithmetic for a loop over a table.

for a senior

Demonstrate that you count commands before optimising anything. Show how you collapse an N-round-trip loop into one or two commands, and be honest about the readability you trade away when you do.

for a principal

Frame the round trip as the unit of cost the whole harness is priced in, and set the expectation that selector-syntax rules will not move it. Decide where the team may trade clarity for command count.

## A find is a remote call, not a library call The most common junior misconception about Selenium is that `driver.findElement(...)` searches something the test process holds. It does not. There is no copy of the DOM in your JVM. Every find is a **command**: the client serialises the locator, sends an HTTP request to the **remote end** (the browser driver, or a Grid that forwards to one), waits for the response, and turns the returned **element reference** into a `WebElement` handle. The matching happens in the browser; the test process only asks and waits. That is why the cost of finding elements is counted in **commands**, not in characters of selector. Three consequences follow, and a junior is expected to name all three: - A locator is only data until it is sent. `By.cssSelector("td.applicant")` allocates a small local object and does nothing to the page on its own. - What comes back is an **element reference**, a handle the remote end recognises, not a copy of the node. Reading anything off it is another command. - If the browser is on another machine the call still costs exactly one command; it simply costs a longer one, because the request has further to travel. ## The four find endpoints | Java call | Command name | HTTP request | |---|---|---| | `driver.findElement(by)` | `findElement` | `POST /session/{id}/element` | | `driver.findElements(by)` | `findElements` | `POST /session/{id}/elements` | | `row.findElement(by)` | `findChildElement` | `POST /session/{id}/element/{elementId}/element` | | `row.findElements(by)` | `findChildElements` | `POST /session/{id}/element/{elementId}/elements` | The singular forms return one reference or fail; the plural forms return the whole matching set in a **single response**. That asymmetry is the whole optimisation story: one request can bring back three hundred references just as easily as one. ## The arithmetic of a loop Take a scholarship-application review queue with 300 rows, and a loop that reads each applicant's name: 1. `driver.findElements(By.cssSelector("#reviewQueue tbody tr"))` - **1 command**, 300 references back. 2. `row.findElement(By.cssSelector("td.applicant"))` inside the loop - **300 commands**, one scoped find per row. 3. `.getText()` on each result - **300 more commands**, because every `WebElement` method is its own request too. That is 601 round trips. Written the naive way, with a fresh `driver.findElement` per row instead of the plural call at step 1, it becomes 900. Nothing in that arithmetic changes if you rewrite every locator from `By.xpath` to `By.cssSelector`: the syntax only affects what happens inside each of those requests. ## What reduces the count and what does not - **Does reduce it:** replacing N singular finds with one plural `findElements`, so the set arrives in one response. - **Does reduce it:** not re-finding an element you already hold a reference to, as long as the page has not re-rendered it away. - **Does not reduce it:** shortening the selector string. A shorter body is a smaller payload, not a smaller number of requests. - **Does not reduce it:** switching locator strategy. `By.id`, `By.cssSelector` and `By.xpath` all cost exactly one command per call. - **Does not reduce it:** reusing the same `By` object across iterations. `By` instances are cheap local objects; the request is what costs. ## What one round trip is made of Each command carries fixed overhead independent of your selector: - JSON encoding in the client and decoding in the driver, then the same again on the way back. - One HTTP request and response over a socket. On loopback that is small; against a remote end reached over a network it is larger, and every command pays it. - The driver's own bookkeeping: resolving the session, checking the browsing context is still open, handling any open user prompt, and minting or looking up element references. None of that scales with how clever your selector is. It scales with **how many times you ask**. ## The habit this should build When a Selenium test that touches a long list feels slow, the first question is not "should this be CSS?" - it is "how many commands does this method issue?" Count them from the code before you profile anything: one per find, one per scoped find, one per read such as `getText`, `getDomAttribute` or `isDisplayed`, one per interaction. On a 300-row review queue, a method that reads three fields per row is issuing well over a thousand requests, and that number is the only one worth attacking. The corollary matters for design too: a plural find that returns references you then use is usually the cheapest shape available in the API, and it costs the same one command whether it matches one element or five hundred.

  • How many commands does row.findElement(By.cssSelector("td.applicant")).getText() send?
    Two. The scoped find is `POST /session/{id}/element/{elementId}/element` and returns a new element reference, then reading the text is a further command against that new reference. Every WebElement method you chain after a find is another request on the wire.
  • Does findElements cost more than findElement when many elements match?
    Only marginally. Both are one command; the plural form serialises every match into the response instead of one, so the payload grows with the match count. That is far cheaper than issuing one find per match, which is the alternative it replaces.
  • If the driver runs on the same machine as the test, is the round trip still worth worrying about?
    Yes, at volume. A loopback request plus JSON encoding and decoding on both sides is small individually, but a suite issuing tens of thousands of finds pays it tens of thousands of times. Against a remote end reached over a network the same arithmetic gets worse.

saying these in an interview costs you the question

  • Thinks findElement searches a local copy of the DOM in the test process
  • Believes a simpler selector reduces the number of commands sent
  • Assumes findElements is slower than findElement because it returns more
  • Counts only the find calls and forgets each getText is another command
  • Says holding a WebElement avoids all further round trips to the browser
open as a page

In Selenium, why can a CSS locator never match an element by its visible text?

level: middleimportance: must knowfreq 72%

basics

~20 s

Because a CSS selector matches on tags, ids, classes, attributes, structural position and state, never on the characters inside an element. Selenium sends the string straight to the browser's querySelectorAll, so a locator gets exactly that power and no more.

open as a page

In Selenium XPath, why does //tr[1] match several rows while (//tr)[1] matches only one?

level: middleimportance: must knowfreq 66%

basics

~10 s

A predicate attaches to the step in front of it and is evaluated once per context node, so //tr[1] keeps the first row under every parent. Parentheses index the finished node-set instead.

open as a page

In Selenium 4, what does the browser actually run for a By.cssSelector find versus a By.xpath find?

level: middleimportance: must knowfreq 74%

basics

~20 s

A CSS find makes the browser call querySelectorAll on its native selector engine. An XPath find makes it call evaluate in the separate DOM-XPath evaluator. Both run inside one WebDriver command, so the gap is usually tiny.

open as a page

In Selenium, can a CSS locator select an element by what it contains, and when must XPath take over?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Yes, with the :has() relational pseudo-class, as long as the browser under test implements it. It still cannot test rendered text, so a lookup that depends on what an element reads must fall back to an XPath expression.

open as a page

A Selenium test reading 300 review-queue rows is slow, and swapping every XPath for CSS barely helped. Why?

level: seniorimportance: must knowfreq 57%

basics

~20 s

Because the time is going into the number of commands, not the matching engine. Three hundred finds plus three hundred text reads is six hundred round trips, and a faster in-page match saves only microseconds on each one.

open as a page

In Selenium, what happens when By.cssSelector is given a string the browser cannot parse?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Nothing checks the string locally. It travels to the browser, which runs querySelectorAll with it; the parse failure comes back as the invalid selector error and the Java client raises InvalidSelectorException, which is not the same as NoSuchElementException.

open as a page

In Selenium, how does an absolute XPath differ from a relative XPath that begins with a double slash?

level: juniorimportance: should knowfreq 78%

basics

~10 s

An absolute XPath spells out every step from the document root, such as /html/body/table/tbody/tr[2]/td[1]. A path starting with a double slash searches the whole document and matches at any depth.

open as a page

In Selenium, why can no CSS locator find a ::before pseudo-element, and how do you read its content?

level: middleimportance: should knowfreq 38%

basics

~20 s

A pseudo-element is generated by the style engine and never becomes a DOM node, so the browser's querySelectorAll cannot return one and Selenium has nothing to wrap in a WebElement. Read it with getComputedStyle inside an injected script instead.

open as a page

In Selenium XPath, why does ancestor::tr[1] select the nearest row rather than the outermost one?

level: seniorimportance: should knowfreq 42%

basics

~10 s

The ancestor axis is a reverse axis, so XPath numbers its positions outward from the context node: index 1 is the closest matching ancestor, not the outermost. Forward axes number in document order instead.

open as a page

In Selenium, what does an absolute XPath like /html/body/div[2]/table/tr[7]/td[3] cost on every find call?

level: seniorimportance: should knowfreq 45%

basics

~10 s

It 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.

open as a page

In Selenium XPath locators, why is ends-with() unavailable when starts-with() works fine?

level: middleimportance: nice to knowfreq 28%

basics

~10 s

Browsers evaluate locators with document.evaluate, which implements XPath 1.0 only, and ends-with() arrived in XPath 2.0. Selenium 4 offers no way to raise that version, so you emulate it with substring and string-length.

open as a page