skip to content

Idiomatic API Differences

The same call in five spellings: implicitlyWait, implicitly_wait and ImplicitWait, By.id against By.ID, promises in JavaScript, blocks in Ruby. Asked so you can read any sample you are shown.

on this pageshow

explore

questions

5

In Selenium 4's JavaScript client, why must nearly every driver call be awaited?

level: middleimportance: must knowfreq 58%

answer

  1. JavaScript cannot block while waiting
  2. Ask what the call actually returns
  3. Selenium 4 dropped an ordering mechanism
  4. Every object is truthy, promises included
  5. The wait timeout is not in seconds

basics

~20 s

Because every command returns a promise rather than a value, and Selenium 4 removed the old control flow that ordered commands for you. Without await you hold a promise, which is always truthy, so conditions silently pass.

solid answer

~40 s

JavaScript cannot block while a command travels to the browser's driver, so `selenium-webdriver` returns a **promise** from every call and you `await` it inside an `async` function. Selenium 4 removed the older control flow that queued commands and let scripts look synchronous, so ordering is now the script's job. The classic bug is not a crash: `if (element.isDisplayed())` without `await` tests a promise object, which is always truthy, so the branch always runs. `findElements` is worse - the promise has no `.length`, so a loop over the report card's rows never executes. One convenience: `driver.findElement(...)` returns a promise that also proxies the element interface, so a chained `.click()` works; anything returning data still needs the `await`.

code

javascript · 19 lines
javascript
const { Builder, By, until } = require('selenium-webdriver');

(async function reportCard() {
  const driver = await new Builder().forBrowser('chrome').build();
  try {
    await driver.get('https://school.example/report-cards');

    const average = await driver.wait(
      until.elementLocated(By.id('term-average')),
      10000
    );
    console.log(await average.getText());

    const rows = await driver.findElements(By.css('.grade-row'));
    console.log(rows.length);
  } finally {
    await driver.quit();
  }
})();

go deeper

for a junior

Recall that every call returns a promise and needs await inside an async function. Get used to reading results into variables so a forgotten await is visible.

for a middle

Explain why the promise exists, that Selenium 4 removed the control flow that once hid it, and why an unawaited boolean read makes a condition always pass.

for a senior

Show the diagnostic instinct: a test that passes when the report card is missing, or a wait that fails instantly, points at a missing await or the millisecond unit rather than the page.

for a principal

Own the standards that keep this class of bug out of a suite - lint rules for floating promises, a review habit around conditions, and whether the team writes its browser tests in this binding at all.

## Every command returns a promise The JavaScript client, `selenium-webdriver`, talks to the browser's driver over the network like every other binding, but JavaScript has no way to block a thread while it waits for the reply. So every command returns a **promise** that settles when the reply arrives. `driver.get(...)`, `element.getText()`, `element.isDisplayed()` and `driver.quit()` all hand back a promise, not a value, and the only way to get the value out is to `await` it inside an `async` function (or chain `.then`). ```javascript const average = await driver.findElement(By.id('term-average')); const text = await average.getText(); console.log(text); ``` ## What Selenium 4 changed Older versions of the JavaScript client shipped a **control flow** - a promise manager that queued commands and ran them in order, letting scripts look synchronous without any `await`. It was deprecated during the 3.x line and **removed in Selenium 4**, so ordering is now entirely the script's responsibility. That is why samples from before the change look so different, and why copying one into a current project produces commands that fire in an unpredictable order rather than a clear error. ## The truthy-promise bug The failure worth memorising is not a crash. A promise object is always **truthy**, so a forgotten `await` in a condition silently takes the wrong branch: ```javascript const average = await driver.findElement(By.id('term-average')); if (average.isDisplayed()) { // always true - this is a promise console.log('report card is visible'); } if (await average.isDisplayed()) { // the actual boolean console.log('report card is visible'); } ``` The same shape bites on collections: `driver.findElements(By.css('.grade-row'))` resolves to an array, so without `await` you get a promise and `.length` is `undefined`, and a `for...of` over it fails rather than iterating the report card's rows. ## Where await is and is not required | Expression | Without await you get | Usable? | |---|---|---| | `driver.get(url)` | a promise that the navigation finished | only for ordering | | `driver.findElement(By.id('x'))` | a promise that also proxies the element interface | chained actions work | | `driver.findElements(By.css('.grade-row'))` | a promise of an array | no - cannot iterate | | `element.getText()` | a promise of a string | no - not comparable | | `element.isDisplayed()` | a promise of a boolean | no - always truthy | The second row is the one exception people trade on: the client's `findElement` returns a promise that also implements the element interface, so `driver.findElement(By.id('publish')).click()` does work. It is a convenience, not a general rule - anything that yields **data** rather than another command must be awaited. ## Milliseconds, and the other JavaScript-only shapes - The explicit wait is a method on the driver, not a class you construct: `await driver.wait(until.elementLocated(By.id('term-average')), 10000)`. - That timeout is in **milliseconds**, where Java takes a `Duration`, Python and Ruby take seconds and C# takes a `TimeSpan`. Copying `10` from a Python sample gives a ten-millisecond wait. - Conditions come from the `until` module - `until.elementLocated`, `until.elementIsVisible`, `until.titleIs`, `until.stalenessOf` - as factory functions rather than static methods on a class. - Implicit timeouts are set as an object of milliseconds: `await driver.manage().setTimeouts({ implicit: 5000 })`. - Locator names shorten: `By.css('.grade-row')`, not Java's `By.cssSelector`. ## Reading a JavaScript sample after a Java one Set the two side by side and the differences are all in how a value is obtained, not in what is asked of the browser: - Java's `String text = element.getText();` becomes `const text = await element.getText();`. - Java's `if (element.isDisplayed())` becomes `if (await element.isDisplayed())`. - Java's `List<WebElement> rows = driver.findElements(...)` becomes `const rows = await driver.findElements(...)`. - Java's `new WebDriverWait(driver, Duration.ofSeconds(10)).until(cond)` becomes `await driver.wait(cond, 10000)`. The method names are nearly identical, which is exactly what makes this port dangerous: a Java engineer reading JavaScript sees familiar camelCase and stops noticing that every one of those calls has changed its return type from a value into a promise of a value. The `await` keyword is not decoration wrapped around a call that already worked - it is the part that turns a promise into the thing the next line expects to read. ## A safe skeleton 1. Put the whole scenario inside an `async` function so `await` is legal on every line. 2. Build the driver with `await new Builder().forBrowser('chrome').build()`. 3. Await every command, and read results into `const` bindings so a missing `await` shows up as a promise in a log line rather than as a branch that always passes. 4. Put `await driver.quit()` in a `finally`, so a failed report-card assertion still closes the browser. 5. When a wait behaves as if the element never appeared, check the units before checking the locator.

  • Does driver.findElement need an await before a chained click?
    Strictly, no. The client's `findElement` returns a promise that also implements the element interface, so `driver.findElement(By.id('publish')).click()` works and the click is queued behind the lookup. You still await the outer expression to know it finished, and any call that yields data - text, a boolean, an array - must be awaited.
  • What is the JavaScript spelling of an implicit timeout?
    `await driver.manage().setTimeouts({ implicit: 5000 })`, taking milliseconds in an options object. Java calls `driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5))`, Python `driver.implicitly_wait(5)`, and Ruby `driver.manage.timeouts.implicit_wait = 5`. The unit is the part to check when porting.
  • How do you guarantee the browser closes when a step throws?
    Wrap the scenario body in `try` and put `await driver.quit()` in the `finally`. Because a rejected promise propagates out of the awaited expression, an unhandled failure would otherwise skip the teardown and leave a browser process running for every failed report-card test.

A JavaScript Selenium call hands you a claim ticket rather than the item itself; awaiting is walking to the counter. A claim ticket is a real object either way, which is why an unawaited isDisplayed() reads as true even when the report card is hidden.

saying these in an interview costs you the question

  • Checks isDisplayed() without await and always gets true
  • Passes 10 to driver.wait expecting a ten-second timeout
  • Expects Selenium 4 to order JavaScript commands automatically
  • Iterates findElements() without awaiting the array first
  • Thinks await changes what the browser is asked to do
open as a page

In Selenium 4, what breaks when a Java wait helper for a report-card page is hand-ported to Python?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Four surfaces change: method names go snake_case, the single By object becomes two arguments (a tuple inside condition helpers), the Duration timeout becomes a bare number of seconds, and text is read as a property.

open as a page

In Selenium's Python client, why must a locator given to expected_conditions be wrapped in a tuple?

level: juniorimportance: should knowfreq 50%

basics

~20 s

Because those helpers take one locator argument, while find_element takes two. The pair goes in as a single value, and a single value holding two things is a tuple, which is why the call shows double parentheses.

open as a page

Why does Selenium's Python client write find_element(By.ID, "x") where Java writes findElement(By.id("x"))?

level: middleimportance: should knowfreq 55%

basics

~20 s

Java's By is a factory that returns one locator object holding both the strategy and the value. Python's By is a set of strategy constants, so the strategy and the value stay two separate arguments.

open as a page

How does Selenium's Ruby client spell a locator such as an id, compared with Java's By?

level: juniorimportance: nice to knowfreq 28%

basics

~10 s

Ruby passes a hash whose key names the strategy: find_element(id: 'term-average'). There is no locator object to build, unlike Java's By.id, and the two-positional form find_element(:id, 'term-average') also works.

open as a page