skip to content

In Selenium 4, why does a By.id lookup reach the remote end as a css selector find?

level: seniorimportance: must knowfreq 46%

answer

  1. The client asks first and adapts after
  2. One keyword is not in the table
  3. A specific exception is swallowed, others are not
  4. The answer is remembered per driver
  5. Invalid argument, then the CSS fallback

basics

~20 s

Because id is not a WebDriver locator strategy. The Java client first tries sending using id, the remote end refuses it with invalid argument, and the client then repeats the lookup with the CSS fallback the By carries.

solid answer

~40 s

`By.id` declares remote parameters of `using: "id"` with the raw value, but `id` is not one of the five keywords the W3C table of location strategies defines. In Selenium 4 the Java client's internal `ElementLocation` maps each `By` subclass to a finder; the five specification-backed classes are registered as remote up front, while `By.id`, `By.name` and `By.className` are not. The first lookup optimistically sends the legacy pair, catches the resulting `InvalidArgumentException`, and retries through the locator itself, which delegates to its `ByCssSelector` fallback. That decision is then cached against the class, so only the first lookup per driver instance pays an extra round trip. `RemoteWebElement.setFoundBy` still records the declared `id` keyword, so the element reports itself in terms of the locator you wrote.

go deeper

for a junior

Know that By.id works and that the browser is really answering a CSS query. You are not expected to describe the client internals that make that happen at this stage.

for a middle

Explain that the wire has no id keyword, that the client carries a CSS fallback, and that a rejected command is what triggers it. Naming the invalid argument error is the key detail.

for a senior

An interviewer expects you to read a trace where the code says By.id and the command says css selector and explain it without alarm, including that the extra request happens once per driver, not per lookup.

for a principal

Own the implication for shared tooling: anything intercepting find commands sees the rewritten strategy, so locator analytics, routing or reporting built on the wire keyword will misreport three of the eight factories.

## What By.id actually declares Every locator in Selenium 4's Java client implements `By.Remotable`, whose `getRemoteParameters()` returns a `using`/`value` pair. For `By.id("recall-due")` that pair is `id` and `recall-due` — the **legacy** keyword, not a CSS selector. The five specification-aligned locators declare `css selector`, `link text`, `partial link text`, `tag name` and `xpath`; `By.id`, `By.name` and `By.className` declare keywords the **W3C WebDriver** table of location strategies does not contain. A find command whose `using` is not in that table is not a soft failure. The specification's Find Element algorithm says to return `invalid argument` before it looks at the browsing context at all, so the command never reaches the DOM. ## Two finders, chosen per locator class The Java client keeps this decision in an internal, deliberately package-private class called `ElementLocation`, which holds a map from `By` subclass to one of two finders: - **`REMOTE`** takes `getRemoteParameters()` and issues the find command with that `using` and `value`. - **`CONTEXT`** calls `findElement`/`findElements` **on the locator object itself** and lets it decide what to do. For a `PreW3CLocator` — the base class of `By.id`, `By.name` and `By.className` — that method does not search; it delegates to the private `ByCssSelector` fallback the constructor built from `#%s`, `.%s` or `*[name='...']`. The map is seeded at construction with the five specification-backed classes mapped to `REMOTE`. The three legacy ones are deliberately left out. ## What happens on the first By.id lookup 1. `RemoteWebDriver.findElement(By)` hands the locator to `ElementLocation`, which finds no entry for `By.ById` in its map. 2. The locator implements `By.Remotable`, so the client *optimistically* tries `REMOTE` and sends `{"using":"id","value":"recall-due"}`. 3. A compliant remote end rejects that with `invalid argument`, which the client surfaces as `InvalidArgumentException`. 4. That exception — and only that exception — is swallowed, and the code falls through to `CONTEXT`. 5. `CONTEXT` calls the locator's own `findElement`, which searches with the CSS fallback, producing a second command carrying `{"using":"css selector","value":"#recall\-due"}`. 6. `CONTEXT` is stored in the map against `By.ById`, so the probe is never repeated for that class. `NoSuchElementException` is treated differently: it means the strategy was *accepted* and simply matched nothing, so the client caches the finder that produced it and rethrows. ## What is cached and what is not | | Scope | Consequence | |---|---|---| | The finder decision | one `RemoteWebDriver` instance | one extra round trip per legacy `By` class per driver | | The CSS fallback object | one `By` instance | built once in the constructor, reused for every search | | The declared parameters | one `By` instance | always the legacy pair, never the CSS one | So a suite that creates a fresh driver per test pays the probe once per test, not once per lookup, and never for `By.cssSelector`, `By.xpath`, `By.tagName`, `By.linkText` or `By.partialLinkText`. ## What the found element says afterwards When the element comes back, the client calls `RemoteWebElement.setFoundBy` with the locator's **declared** parameters, not with the strategy that actually ran. A recall row found through the fallback therefore describes itself in terms of `id` and the raw, unescaped value — matching the code you wrote rather than the command the browser answered. `By.id("recall-due").toString()` is likewise `By.id: recall-due`. This is a genuinely useful design choice and a genuinely confusing one. It keeps error messages readable in terms of the locator you wrote, at the cost of making a driver trace and a stack trace describe the same lookup with two different strategies. ## Where this matters in practice - **Reading a trace of a recall-list suite.** Seeing `css selector` for code that plainly says `By.id` is expected, not a sign that something rewrote your locators. - **Anything sitting between client and browser.** An intermediary that logs, counts or routes find commands sees `css selector` for these three factories and must not assume it can recover the original keyword. - **A custom locator.** A `By` subclass that does not implement `By.Remotable` skips the remote path entirely and always goes through `CONTEXT`, composing whatever lookups its own `findElements` performs. - **The first-lookup cost.** It is one extra request per legacy locator class per driver instance. Real, measurable in a trace, and not worth restructuring a suite over. None of this is public API. `ElementLocation` and `PreW3CLocator` are internal to the Java client and the mechanism can change; the observable behaviour — `By.id` resolves as a CSS selector while still reporting itself as `id` — is the part worth knowing.

  • Does the extra round trip happen on every By.id lookup?
    No. `ElementLocation` caches the chosen finder against the `By` subclass, so after the first rejected attempt every later `By.id` lookup goes straight to the CSS fallback. The cache lives on the `RemoteWebDriver` instance, so a suite that builds a fresh driver per test pays the probe once per driver.
  • What strategy does the element report after a fallback find?
    The declared one. The client calls `RemoteWebElement.setFoundBy` with `getRemoteParameters()`, which for `By.id` is still the keyword `id` and the unescaped value, not the CSS selector the remote end actually executed. The element therefore describes itself in terms of the locator you wrote.
  • What happens if a custom By does not implement By.Remotable?
    `ElementLocation` skips the remote path entirely and uses the context finder, which calls `findElement` or `findElements` on the locator itself and lets that code compose whatever lookups it needs. Only a locator implementing `By.Remotable` can hand a `using` and `value` pair straight to the remote end.

saying these in an interview costs you the question

  • Says the remote end silently translates id into a CSS query
  • Thinks every By.id lookup costs two HTTP round trips
  • Believes the fallback decision is cached globally for the JVM
  • Claims the element afterwards reports itself as found by CSS
  • Assumes an unknown using keyword returns no such element