Selenium Waits & Expected Conditions

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Most flaky Selenium tests are synchronisation bugs: the script sends the next command before the application is ready for it. This page explains the three ways Selenium waits (implicit, explicit and fluent), why you must not mix the first two, the Expected Conditions that describe common things worth waiting for, and the support features that help you observe and protect a driver (listeners that fire on every command, and ThreadGuard). It closes with the troubleshooting pages of the official documentation: the common errors, the "unable to locate driver" error, and logging. It was written against Selenium 4.51.0 (released 9 October 2026) and checked on 25 October 2026. The Java (JUnit 5.14.4, Java 17) and Python (pytest) examples were run against the Bookshelf application with headless Chrome 155 on macOS; Bookshelf was started with BOOKSHELF_REVIEW_DELAY_MS=1000 so that saving a review takes about one second and the waits have something real to wait for. C#, Ruby and JavaScript snippets are shown where the official documentation shows them; they were not run here because the examples were run in Java and Python only.

For the idea behind waiting (why a script races the page) read How Browser Automation Works; for how Playwright and Cypress avoid explicit waits, see Playwright Locators, Actions & Auto-Waiting and Cypress Core Concepts. Locators, element commands and frames are in Locating & Interacting; timeouts, page load strategies and driver options are in Drivers, Browsers & Options; the general strategy for reliable tests is in Flaky Tests & Reliability.

Why a script needs to wait

Every navigation command (get, back, refresh) waits for the document readyState that the page load strategy asks for (the default is complete) before the driver returns. That only covers the assets named in the HTML. Scripts that run afterwards add elements, change visibility or replace content, and a single-page application does most of its work after load. Selenium can interact with an element only when it is both present in the DOM and displayed, so a command that runs too early fails with NoSuchElementException or ElementNotInteractableException.

Bookshelf has a realistic case. After a signed-in reader submits a review, the new row appears at once with data-state="pending", and about a second later, when the server answers, it becomes data-state="saved". A test that looks for the saved row straight after the click races the server. The first example shows the failure, and every later example fixes it with a different kind of wait.

The sign-in and review steps are shared by all examples, so they live in a small helper. Imports are omitted.

final class Bookshelf {
  static final String BASE_URL = System.getenv().getOrDefault("BASE_URL", "http://localhost:3914");
  static final String READER_PASSWORD =
      System.getenv().getOrDefault("BOOKSHELF_READER_PASSWORD", "reader-local-only");

  static final By SAVED_REVIEW = By.cssSelector("[data-testid=review][data-state=saved]");
  static final By SUBMIT = By.cssSelector("[data-testid=review-submit]");

  static ChromeOptions headless() {
    ChromeOptions options = new ChromeOptions();
    options.addArguments("--headless=new");
    return options;
  }

  /** Signs in as the reader, opens a book and submits a review; the app saves it after a delay. */
  static void submitReview(WebDriver driver, String text) {
    driver.get(BASE_URL + "/signin");
    driver.findElement(By.id("username")).sendKeys("reader");
    driver.findElement(By.id("password")).sendKeys(READER_PASSWORD);
    driver.findElement(By.cssSelector("[data-testid=signin-submit]")).click();
    new WebDriverWait(driver, Duration.ofSeconds(5)).until(ExpectedConditions.urlContains("/account"));
    driver.get(BASE_URL + "/books/3");
    new Select(driver.findElement(By.id("rating"))).selectByValue("4");
    driver.findElement(By.id("text")).sendKeys(text);
    driver.findElement(SUBMIT).click();
  }
}

The Python equivalent is a pytest fixture in conftest.py that does the same steps and returns a submit function. The reader password comes from BOOKSHELF_READER_PASSWORD, never from the test file.

@pytest.fixture
def submit_review(driver):
    """Signs in, opens a book and submits a review; the app saves it after a delay."""
    def submit(text="Saved a moment later, after the delay."):
        driver.get(f"{BASE_URL}/signin")
        driver.find_element(By.ID, "username").send_keys("reader")
        driver.find_element(By.ID, "password").send_keys(READER_PASSWORD)
        driver.find_element(By.CSS_SELECTOR, "[data-testid=signin-submit]").click()
        WebDriverWait(driver, 5).until(EC.url_contains("/account"))
        driver.get(f"{BASE_URL}/books/3")
        Select(driver.find_element(By.ID, "rating")).select_by_value("4")
        driver.find_element(By.ID, "text").send_keys(text)
        driver.find_element(By.CSS_SELECTOR, "[data-testid=review-submit]").click()

    return submit

The failure, and why sleeping is not the answer

@Test
void noWaitFails() {
  Bookshelf.submitReview(driver, "Saved a moment later, after the delay.");
  // The review is only "saved" after the server answers (about one second), so this fails.
  assertThrows(org.openqa.selenium.NoSuchElementException.class,
      () -> driver.findElement(Bookshelf.SAVED_REVIEW));
}
def test_no_wait_fails(driver, submit_review):
    submit_review()
    # The review is only "saved" after the server answers (about one second).
    with pytest.raises(NoSuchElementException):
        driver.find_element(*SAVED_REVIEW)

Both tests pass because they assert the failure. The usual first fix is a fixed sleep. It is the wrong one: too short and the test is still flaky, too long and it is added in many places and the suite becomes slow. A sleep also never reports what it was waiting for. Selenium provides two mechanisms that are better, and one of them, the explicit wait, is the one to use.

Implicit waits

An implicit wait is a session-wide setting: while it is non-zero, every element lookup that finds nothing keeps retrying until the element appears or the time runs out, and only then raises NoSuchElementException. The default is 0 seconds, so by default a missing element fails immediately. The wait ends as soon as the element is found, so a large value does not slow down the tests that do not need it. It applies to the whole session, to findElement and findElements alike, and it is part of the W3C timeouts object next to the page load timeout (300 seconds by default) and the script timeout (30 seconds by default).

@Test
void implicitWait() {
  driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
  Bookshelf.submitReview(driver, "Saved a moment later, after the delay.");
  WebElement saved = driver.findElement(Bookshelf.SAVED_REVIEW); // blocks until the element exists
  assertTrue(saved.getText().contains("Saved a moment later"));
}
def test_implicit_wait(driver, submit_review):
    driver.implicitly_wait(5)
    submit_review()
    saved = driver.find_element(*SAVED_REVIEW)  # blocks until the element exists
    assert "Saved a moment later" in saved.text

The same setting in the other bindings, as shown by the official documentation (these snippets were not run here):

Binding Implicit wait of two seconds

C#

driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2);

Ruby

driver.manage.timeouts.implicit_wait = 2

JavaScript

await driver.manage().setTimeouts({ implicit: 2000 });

The value can also be requested when the session is created, with the timeouts capability of the browser options. Both checked here:

ChromeOptions options = new ChromeOptions();
options.setImplicitWaitTimeout(Duration.ofSeconds(5));   // also setPageLoadTimeout, setScriptTimeout
options = webdriver.ChromeOptions()
options.timeouts = {"implicit": 5000}                    # milliseconds; also "pageLoad" and "script"
driver = webdriver.Chrome(options=options)
print(driver.timeouts.implicit_wait, driver.timeouts.page_load, driver.timeouts.script)   # 5.0 300.0 30.0

Implicit waits are convenient, but they are blunt. They only wait for presence, never for visibility, enabled state, text or a count, they cannot be tuned per call, and they interact badly with explicit waits (next section).

Do not mix implicit and explicit waits

The official documentation is blunt about it: mixing the two can cause unpredictable wait times, for example an implicit wait of ten seconds and an explicit wait of fifteen could time out after twenty. The reason is that an explicit wait polls by calling findElement, and with an implicit wait set, every one of those calls can itself block for the implicit timeout. The explicit wait only checks its own deadline between polls, so a poll that started before the deadline runs to the end of the implicit wait.

This was measured against Bookshelf: an implicit wait of 6 seconds and an explicit wait of 2 seconds waiting for an element that never appears.

@Test
void mixingImplicitAndExplicitWaits() {
  driver.get(Bookshelf.BASE_URL + "/books/3");
  driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(6));
  WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(2));
  long start = System.nanoTime();
  assertThrows(TimeoutException.class,
      () -> wait.until(ExpectedConditions.presenceOfElementLocated(By.id("does-not-exist"))));
  long seconds = Duration.ofNanos(System.nanoTime() - start).toSeconds();
  System.out.println("MIXED WAIT: explicit 2 s + implicit 6 s gave up after about " + seconds + " s");
  assertTrue(seconds >= 6, "the implicit wait stretched the explicit one");
}
def test_mixing_implicit_and_explicit_waits(driver, base_url):
    driver.get(f"{base_url}/books/3")
    driver.implicitly_wait(6)
    start = time.monotonic()
    with pytest.raises(TimeoutException):
        WebDriverWait(driver, 2).until(EC.presence_of_element_located((By.ID, "does-not-exist")))
    elapsed = time.monotonic() - start
    print(f"MIXED WAIT: explicit 2 s + implicit 6 s gave up after {elapsed:.1f} s")
    assert elapsed >= 6
MIXED WAIT: explicit 2 s + implicit 6 s gave up after about 6 s     (Java)
MIXED WAIT: explicit 2 s + implicit 6 s gave up after 6.0 s         (Python)

The explicit wait asked for two seconds and took six. With several polls or nested conditions the overshoot multiplies. The rule that follows: pick explicit waits and leave the implicit wait at 0. If a codebase already sets an implicit wait in a base class, remove it, or set it back to zero around the explicit wait with driver.manage().timeouts().implicitlyWait(Duration.ZERO).

Explicit waits

An explicit wait is a loop in your code that polls a condition until it returns something other than false or null, or until a timeout, when it raises TimeoutException. It is the right tool because it names the condition for each place that needs one. By default the wait also tolerates a missing element: Java’s WebDriverWait ignores NotFoundException and Python’s WebDriverWait ignores NoSuchElementException, so a condition that looks the element up keeps polling until it exists. The default polling interval is 500 milliseconds in both.

The condition can be an Expected Condition (next sections) or a function of the driver. Java also accepts any java.util.function.Function, so a lambda works; Python accepts any callable.

@Test
void explicitWaitWithExpectedConditions() {
  Bookshelf.submitReview(driver, "Saved a moment later, after the delay.");
  WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
  WebElement saved = wait.until(ExpectedConditions.visibilityOfElementLocated(Bookshelf.SAVED_REVIEW));
  assertEquals("saved", saved.getDomAttribute("data-state"));
}

@Test
void explicitWaitWithLambda() {
  Bookshelf.submitReview(driver, "Saved a moment later, after the delay.");
  Wait<WebDriver> wait = new WebDriverWait(driver, Duration.ofSeconds(5));
  WebElement saved = wait.until(d -> d.findElement(Bookshelf.SAVED_REVIEW));
  assertTrue(saved.isDisplayed());
}
def test_explicit_wait_with_expected_condition(driver, submit_review):
    submit_review()
    saved = WebDriverWait(driver, timeout=5).until(EC.visibility_of_element_located(SAVED_REVIEW))
    assert saved.get_dom_attribute("data-state") == "saved"


def test_explicit_wait_with_lambda(driver, submit_review):
    submit_review()
    saved = WebDriverWait(driver, timeout=5).until(lambda d: d.find_element(*SAVED_REVIEW))
    assert saved.is_displayed()

until returns whatever the condition returned (the element, a list, a boolean), so the result of the wait is usually the object the next line needs. Bindings differ in spelling only. The official documentation shows:

Binding Explicit wait for a revealed element (not run here)

C#

new WebDriverWait(driver, TimeSpan.FromSeconds(2)).Until(d => revealed.Displayed);

Ruby

Selenium::WebDriver::Wait.new.until { revealed.displayed? }

JavaScript

await driver.wait(until.elementIsVisible(revealed), 2000);

NET stopped providing Expected Conditions in Selenium 4; there you write the condition as a lambda. Ruby uses blocks and

needs no condition classes. JavaScript has its own until module, whose conditions return promises.

Timeouts and failure messages

When the timeout expires the wait raises TimeoutException. Its message starts with the condition that failed and, for Expected Conditions, the underlying error, which is usually enough to see what went wrong:

@Test
void timeoutMessage() {
  driver.get(Bookshelf.BASE_URL + "/books/3");
  WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(1));
  TimeoutException e = assertThrows(TimeoutException.class,
      () -> wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("does-not-exist"))));
  System.out.println("TIMEOUT MESSAGE: " + e.getMessage().lines().findFirst().orElse(""));
}
def test_timeout_message(driver, base_url):
    driver.get(f"{base_url}/books/3")
    with pytest.raises(TimeoutException) as info:
        WebDriverWait(driver, 1).until(EC.visibility_of_element_located((By.ID, "does-not-exist")), message="no such box")
    print("TIMEOUT MESSAGE:", str(info.value).splitlines()[0])
TIMEOUT MESSAGE: Expected condition failed: waiting for visibility of element found by By.id: does-not-exist, but... org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"#does\-not\-exist"}.
TIMEOUT MESSAGE: Message: no such box

Pass a message (message= in Python, withMessage in Java) to say in domain terms what was awaited; the Python example shows that a custom message replaces the default one.

Fluent waits and customisation

The wait class can be customised in four ways: how often the condition is evaluated (the polling interval), which exceptions are swallowed while polling, the total timeout, and the timeout message. Java exposes them on FluentWait (WebDriverWait is a FluentWait<WebDriver> with sensible defaults), Python as keyword arguments of WebDriverWait, C# as properties of WebDriverWait, and Ruby as keyword arguments of Wait.new.

@Test
void fluentWaitWithPollingAndIgnoredExceptions() {
  Bookshelf.submitReview(driver, "Saved a moment later, after the delay.");
  Wait<WebDriver> wait = new FluentWait<>(driver)
      .withTimeout(Duration.ofSeconds(5))
      .pollingEvery(Duration.ofMillis(200))
      .ignoring(org.openqa.selenium.NoSuchElementException.class)
      .withMessage("the review was not saved");
  String author = wait.until(d -> d.findElement(Bookshelf.SAVED_REVIEW)
      .findElement(By.cssSelector("[data-testid=review-author]")).getText());
  assertEquals("reader", author);
}
def test_wait_with_polling_and_ignored_exceptions(driver, submit_review):
    submit_review()
    wait = WebDriverWait(
        driver,
        timeout=5,
        poll_frequency=0.2,
        ignored_exceptions=[NoSuchElementException, StaleElementReferenceException],
    )
    author = wait.until(
        lambda d: d.find_element(*SAVED_REVIEW).find_element(By.CSS_SELECTOR, "[data-testid=review-author]").text
    )
    assert author == "reader"

Both ran against Bookshelf and passed. The options in the official documentation, with the other bindings, are:

Binding Timeout Polling interval Ignored exceptions

Java (FluentWait)

withTimeout(Duration)

pollingEvery(Duration)

ignoring(Class), ignoreAll(Collection)

Python (WebDriverWait)

timeout=

poll_frequency=

ignored_exceptions=

C# (WebDriverWait)

constructor argument or Timeout

PollingInterval

IgnoreExceptionTypes(typeof(...))

Ruby (Wait.new)

timeout:

interval:

ignore: (list of error classes)

The C# and Ruby forms in the table come from the official documentation and were not run here. The official page also uses the idea of retrying an action: if "element not interactable" is retried by default, ignore ElementNotInteractableException and put the action inside the condition, making sure the function returns true when it succeeded. That turns a wait into a retry loop, which is useful for an element that exists but is still being animated into place; a wait on elementToBeClickable is usually clearer.

Expected Conditions

Expected Conditions are ready-made conditions for explicit waits. Some take a locator, so the lookup is repeated on every poll and a stale element is not a problem; others take an element you already hold, which saves a lookup but fails with a stale element if the DOM is rebuilt. Prefer the locator forms for anything dynamic.

Java has them in ExpectedConditions (class org.openqa.selenium.support.ui), Python in the expected_conditions module (imported by convention as EC), JavaScript in the until module of selenium-webdriver. .NET dropped them in Selenium 4 to minimise maintenance, and Ruby does not need them. The Java and Python catalogues of 4.51.0, as read from the released packages, are:

What is awaited Java ExpectedConditions Python EC

Page title and URL

titleIs, titleContains, urlToBe, urlContains, urlMatches

title_is, title_contains, url_to_be, url_contains, url_matches, url_changes

Element present

presenceOfElementLocated, presenceOfAllElementsLocatedBy, presenceOfNestedElementLocatedBy, presenceOfNestedElementsLocatedBy

presence_of_element_located, presence_of_all_elements_located

Element visible

visibilityOfElementLocated, visibilityOf, visibilityOfAllElements, visibilityOfAllElementsLocatedBy, visibilityOfNestedElementsLocatedBy

visibility_of_element_located, visibility_of, visibility_of_all_elements_located, visibility_of_any_elements_located

Element hidden or gone

invisibilityOfElementLocated, invisibilityOf, invisibilityOfAllElements, invisibilityOfElementWithText, stalenessOf

invisibility_of_element_located, invisibility_of_element, staleness_of

Element clickable

elementToBeClickable (locator or element)

element_to_be_clickable (locator or element)

Text

textToBe, textToBePresentInElement, textToBePresentInElementLocated, textToBePresentInElementValue, textMatches

text_to_be_present_in_element, text_to_be_present_in_element_value, text_to_be_present_in_element_attribute

Attributes and properties

attributeToBe, attributeContains, attributeToBeNotEmpty, domAttributeToBe, domPropertyToBe

element_attribute_to_include

Selection state

elementToBeSelected, elementSelectionStateToBe

element_to_be_selected, element_located_to_be_selected, element_selection_state_to_be, element_located_selection_state_to_be

Number of elements

numberOfElementsToBe, numberOfElementsToBeMoreThan, numberOfElementsToBeLessThan

(use a lambda that counts find_elements)

Frames, alerts and windows

frameToBeAvailableAndSwitchToIt, alertIsPresent, numberOfWindowsToBe

frame_to_be_available_and_switch_to_it, alert_is_present, number_of_windows_to_be, new_window_is_opened

JavaScript

javaScriptThrowsNoExceptions, jsReturnsValue

(use execute_script in a lambda)

Combinators and retry

not, and, or, refreshed

all_of, any_of, none_of

Notes on the ones that cause most trouble:

  • visibilityOf... waits for the element to be displayed; presenceOf... only that it exists in the DOM. After an action that reveals something, wait for visibility. Presence is enough when you only read an attribute.

  • elementToBeClickable is visible and enabled. It does not prove that nothing covers the element, so an overlay or a moving element can still cause ElementClickInterceptedException.

  • stalenessOf is true once an old element reference is detached, which is the reliable signal that a page reloaded or that a list was rebuilt. refreshed wraps a condition and retries it when it throws StaleElementReferenceException.

  • alertIsPresent returns the alert, so wait.until(alertIsPresent()).accept() works. Alerts are covered in Locating & Interacting.

  • Python’s all_of, any_of and none_of combine conditions; Java’s and, or and not return a Boolean.

The Python example below combines several of them against Bookshelf; it ran and passed.

def test_any_of_all_of_and_staleness(driver, base_url):
    driver.get(f"{base_url}/books?q=quiet")
    first = driver.find_element(By.CSS_SELECTOR, "[data-testid=book-card]")
    wait = WebDriverWait(driver, 5)
    wait.until(EC.all_of(EC.title_contains("Bookshelf"), EC.visibility_of(first)))
    driver.refresh()
    assert wait.until(EC.staleness_of(first))
    with pytest.raises(StaleElementReferenceException):
        first.text
    wait.until(EC.any_of(EC.url_contains("quiet"), EC.title_is("never")))

The Java counterpart checks staleness and refreshed:

@Test
void stalenessAndRefreshed() {
  driver.get(Bookshelf.BASE_URL + "/books?q=quiet");
  WebElement firstCard = driver.findElement(By.cssSelector("[data-testid=book-card]"));
  driver.navigate().refresh();
  WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
  assertTrue(wait.until(ExpectedConditions.stalenessOf(firstCard)));
  assertThrows(StaleElementReferenceException.class, firstCard::getText);
  WebElement again = wait.until(ExpectedConditions.refreshed(
      ExpectedConditions.visibilityOfElementLocated(By.cssSelector("[data-testid=book-card]"))));
  assertTrue(again.isDisplayed());
}

Writing your own condition

A condition is just a function of the driver that returns a non-null, non-false value when satisfied. Write one when the page needs a check that no built-in condition covers, such as "at least one review is saved" or "the count of cards equals what the API says". In Java an ExpectedCondition<T> is a functional interface, so a lambda is enough:

@Test
void customExpectedCondition() {
  Bookshelf.submitReview(driver, "Saved a moment later, after the delay.");
  // An ExpectedCondition is a java.util.function.Function<WebDriver, T>: a lambda is enough.
  ExpectedCondition<Integer> savedReviewCount = d -> {
    var saved = d.findElements(Bookshelf.SAVED_REVIEW);
    return saved.isEmpty() ? null : saved.size();
  };
  Integer count = new WebDriverWait(driver, Duration.ofSeconds(5)).until(savedReviewCount);
  assertEquals(1, count);
  // Any JDK Function works too, without Guava on the type.
  Function<WebDriver, WebElement> asFunction = ExpectedConditions.presenceOfElementLocated(Bookshelf.SAVED_REVIEW);
  assertNotNull(asFunction.apply(driver));
}

In Python the same is a lambda or a small class with a __call__ method that takes the driver. Return False or None to keep waiting, and something truthy when done.

Java 4.51: ExpectedCondition no longer implements Guava’s Function

Before Selenium 4.51, ExpectedCondition<T> extended two interfaces: com.google.common.base.Function and java.util.function.Function. From 4.51 it extends only the JDK one:

public interface ExpectedCondition<T extends @Nullable Object>
    extends java.util.function.Function<WebDriver, T> {}

The change is part of an effort, announced on 21 September 2026, to remove Guava from the Java client bindings. It is a breaking change only for code that types a variable, field or parameter as Guava’s Function and assigns an ExpectedCondition to it. Passing the result of an ExpectedConditions factory straight to until, or implementing the interface with a lambda, is unaffected. Compiling the following against 4.51.0 confirms it (Guava is on the classpath, so the Guava type resolves; the assignment is what fails):

// Before: compiled with 4.50 and earlier, fails with 4.51
com.google.common.base.Function<WebDriver, WebElement> condition =
    ExpectedConditions.elementToBeClickable(By.id("submit"));

// After: a drop-in replacement, the apply method is identical
java.util.function.Function<WebDriver, WebElement> condition =
    ExpectedConditions.elementToBeClickable(By.id("submit"));
Check.java:7: error: incompatible types: ExpectedCondition<WebElement> cannot be converted to Function<WebDriver,WebElement>
        ExpectedConditions.elementToBeClickable(By.id("submit"));

To find affected code run grep -rn "com.google.common.base.Function" --include="*.java" . and replace the type with java.util.function.Function, with ExpectedCondition<WebElement>, or let the compiler infer it with var. The ExpectedCondition interface in the 4.51.0 jar was inspected with javap and extends only java.util.function.Function<WebDriver, T>. One more fact from the same check: running mvn dependency:tree on a project that depends on selenium-java 4.51.0 still lists Guava 33.7.2 as a transitive dependency of selenium-api, so the library has not yet vanished from the classpath. Only this public type changed; do not rely on Guava being present or absent through Selenium, and declare your own dependency if your own code uses Guava.

Listeners and event firing

Listeners run custom code whenever specific Selenium commands are sent, which is handy for logging, screenshots on failure or tracing a flaky test. The official page for command listeners has no examples at the time of writing, so this section is based on the API of the released packages, which was run in Java and Python.

Java: WebDriverListener and EventFiringDecorator

Implement WebDriverListener, an interface made of default methods named beforeX and afterX for every driver, element, navigation, alert, window and options command, plus beforeAnyCall, afterAnyCall and onError. Override only what you need, then wrap the driver with EventFiringDecorator:

public static class RecordingListener implements WebDriverListener {
  final List<String> events = new ArrayList<>();

  @Override public void beforeGet(WebDriver driver, String url) { events.add("before get " + url); }
  @Override public void afterGet(WebDriver driver, String url) { events.add("after get"); }
  @Override public void beforeClick(WebElement element) { events.add("before click " + element.getDomAttribute("data-testid")); }
  @Override public void afterSendKeys(WebElement element, CharSequence... keys) { events.add("sent keys to " + element.getDomAttribute("id")); }
  @Override public void onError(Object target, Method method, Object[] args, InvocationTargetException e) {
    events.add("error in " + method.getName() + ": " + e.getTargetException().getClass().getSimpleName());
  }
}

@Test
void eventFiringDecorator() {
  RecordingListener listener = new RecordingListener();
  WebDriver driver = new EventFiringDecorator<WebDriver>(listener).decorate(Bookshelf.newDriver());
  try {
    driver.get(Bookshelf.BASE_URL + "/books");
    driver.findElement(By.cssSelector("[data-testid=search-input]")).sendKeys("quiet");
    driver.findElement(By.cssSelector("[data-testid=search-submit]")).click();
    assertThrows(NoSuchElementException.class, () -> driver.findElement(By.id("missing")));
    listener.events.forEach(e -> System.out.println("EVENT: " + e));
    assertTrue(listener.events.contains("before click search-submit"));
  } finally {
    driver.quit();
  }
}
EVENT: before get http://localhost:3914/books
EVENT: after get
EVENT: sent keys to q
EVENT: before click search-submit
EVENT: error in findElement: NoSuchElementException

Two details from running it. First, the listener class has to be public: with a package-private nested class the decorator silently did not call the before and after methods, and only onError fired. Second, findElement returns decorated elements, so listeners on click and sendKeys fire for elements found through the decorated driver, but not for elements you obtained from the undecorated one. The decorator replaces the older EventFiringWebDriver and WebDriverEventListener, which are not in the 4.51.0 selenium-support jar. Several listeners can be passed to the constructor, and EventFiringDecorator can be extended to decorate other driver types.

Python: EventFiringWebDriver and AbstractEventListener

Python keeps EventFiringWebDriver and AbstractEventListener (module selenium.webdriver.support). Subclass the listener and override the hooks you need: before_navigate_to, after_navigate_to, before_navigate_back, after_navigate_back, before_navigate_forward, after_navigate_forward, before_find, after_find, before_click, after_click, before_change_value_of, after_change_value_of, before_execute_script, after_execute_script, before_close, after_close, before_quit, after_quit and on_exception.

class RecordingListener(AbstractEventListener):
    def __init__(self):
        self.events = []

    def before_navigate_to(self, url, driver):
        self.events.append(f"before navigate {url}")

    def after_navigate_to(self, url, driver):
        self.events.append("after navigate")

    def before_click(self, element, driver):
        self.events.append(f"before click {element.get_dom_attribute('data-testid')}")

    def after_change_value_of(self, element, driver):
        self.events.append(f"changed value of {element.get_dom_attribute('id')}")

    def on_exception(self, exception, driver):
        self.events.append(f"error: {type(exception).__name__}")


def test_event_firing_webdriver(driver, base_url):
    listener = RecordingListener()
    wrapped = EventFiringWebDriver(driver, listener)
    wrapped.get(f"{base_url}/books")
    wrapped.find_element(By.CSS_SELECTOR, "[data-testid=search-input]").send_keys("quiet")
    wrapped.find_element(By.CSS_SELECTOR, "[data-testid=search-submit]").click()
    with pytest.raises(NoSuchElementException):
        wrapped.find_element(By.ID, "missing")
    assert "before click search-submit" in listener.events
EVENT: before navigate http://localhost:3914/books
EVENT: after navigate
EVENT: changed value of q
EVENT: before click search-submit
EVENT: error: NoSuchElementException

The event-firing and listener wrappers for C#, Ruby and JavaScript were not run; consult the API documentation of each binding for its listener types, since the official page for command listeners shows no code for them.

ThreadGuard

ThreadGuard exists only in the Java binding. It wraps a driver and checks that every call comes from the thread that created it, raising a WebDriverException otherwise. WebDriver instances are not thread-safe, and misuse in parallel runs produces mysterious failures that are hard to diagnose; the guard turns them into a clear error at the first bad call.

@Test
void threadGuardRejectsAnotherThread() throws Exception {
  WebDriver driver = ThreadGuard.protect(Bookshelf.newDriver());
  try {
    AtomicReference<Throwable> thrown = new AtomicReference<>();
    Thread other = new Thread(() -> {
      try {
        driver.get(Bookshelf.BASE_URL);
      } catch (Throwable t) {
        thrown.set(t);
      }
    });
    other.start();
    other.join();
    assertInstanceOf(WebDriverException.class, thrown.get());
    driver.get(Bookshelf.BASE_URL); // the creating thread still works
  } finally {
    driver.quit();
  }
}
Thread safety error; this instance of WebDriver was constructed on thread main (id 1) and is being accessed by thread Thread-46 (id 228)
This is not permitted and *will* cause undefined behaviour

ThreadGuard does not replace the need to give each parallel test its own driver, normally with a ThreadLocal or a per-test fixture. With JUnit 5 parallel execution, create the driver in @BeforeEach and quit it in @AfterEach, as the examples on this page do, and wrap it with ThreadGuard.protect if you want the safety net.

Troubleshooting

The official troubleshooting section starts with the observation that the most common Selenium error is poor synchronisation. If you are not sure that a failure is a timing problem, hard-code a long sleep where it fails: if the failure disappears, an explicit wait will fix it (then remove the sleep). Many reports are caused by the browser drivers rather than Selenium itself, which you can rule out by running the command in more than one browser. For questions, see the support options of the project; for a bug in Selenium, file a bug report on GitHub. The "Upgrade to Selenium 4" page of this section is covered in Selenium IDE & Legacy.

Understanding common errors

The W3C specification defines the errors a driver can return, and each binding maps them to its own class. Kotlin uses the Java classes. The mapping in the official documentation is:

W3C error Java Python C# Ruby JavaScript

detached shadow root

DetachedShadowRootException

DetachedShadowRootException

DetachedShadowRootException

DetachedShadowRootError

DetachedShadowRootError

element click intercepted

ElementClickInterceptedException

ElementClickInterceptedException

ElementClickInterceptedException

ElementClickInterceptedError

ElementClickInterceptedError

element not interactable

ElementNotInteractableException

ElementNotInteractableException

ElementNotInteractableException

ElementNotInteractableError

ElementNotInteractableError

insecure certificate

InsecureCertificateException

InsecureCertificateException

InsecureCertificateException

InsecureCertificateError

InsecureCertificateError

invalid argument

InvalidArgumentException

InvalidArgumentException

WebDriverArgumentException

InvalidArgumentError

InvalidArgumentError

invalid cookie domain

InvalidCookieDomainException

InvalidCookieDomainException

InvalidCookieDomainException

InvalidCookieDomainError

InvalidCookieDomainError

invalid element state

InvalidElementStateException

InvalidElementStateException

InvalidElementStateException

InvalidElementStateError

InvalidElementStateError

invalid selector

InvalidSelectorException

InvalidSelectorException

InvalidSelectorException

InvalidSelectorError

InvalidSelectorError

invalid session id

NoSuchSessionException

InvalidSessionIdException

WebDriverException

InvalidSessionIdError

NoSuchSessionError

javascript error

JavascriptException

JavascriptException

JavaScriptException

JavascriptError

JavascriptError

move target out of bounds

MoveTargetOutOfBoundsException

MoveTargetOutOfBoundsException

MoveTargetOutOfBoundsException

MoveTargetOutOfBoundsError

MoveTargetOutOfBoundsError

no such alert

NoAlertPresentException

NoAlertPresentException

NoAlertPresentException

NoSuchAlertError

NoSuchAlertError

no such cookie

NoSuchCookieException

NoSuchCookieException

NoSuchCookieException

NoSuchCookieError

NoSuchCookieError

no such element

NoSuchElementException

NoSuchElementException

NoSuchElementException

NoSuchElementError

NoSuchElementError

no such frame

NoSuchFrameException

NoSuchFrameException

NoSuchFrameException

NoSuchFrameError

NoSuchFrameError

no such shadow root

NoSuchShadowRootException

NoSuchShadowRootException

NoSuchShadowRootException

NoSuchShadowRootError

NoSuchShadowRootError

no such window

NoSuchWindowException

NoSuchWindowException

NoSuchWindowException

NoSuchWindowError

NoSuchWindowError

script timeout

ScriptTimeoutException

TimeoutException

WebDriverTimeoutException

ScriptTimeoutError

ScriptTimeoutError

session not created

SessionNotCreatedException

SessionNotCreatedException

InvalidOperationException

SessionNotCreatedError

SessionNotCreatedError

stale element reference

StaleElementReferenceException

StaleElementReferenceException

StaleElementReferenceException

StaleElementReferenceError

StaleElementReferenceError

timeout

TimeoutException

TimeoutException

WebDriverTimeoutException

TimeoutError

TimeoutError

unable to capture screen

ScreenshotException

ScreenshotException

InvalidOperationException

UnableToCaptureScreenError

UnableToCaptureScreenError

unable to set cookie

UnableToSetCookieException

UnableToSetCookieException

UnableToSetCookieException

UnableToSetCookieError

UnableToSetCookieError

unexpected alert open

UnhandledAlertException

UnexpectedAlertPresentException

UnhandledAlertException

UnexpectedAlertOpenError

UnexpectedAlertOpenError

unknown command

UnsupportedCommandException

WebDriverException

NotImplementedException

UnknownCommandError

UnknownCommandError

unknown error

WebDriverException

WebDriverException

UnknownErrorException

UnknownError

WebDriverError

unknown method

UnsupportedCommandException

WebDriverException

UnknownMethodException

UnknownMethodError

UnknownMethodError

unsupported operation

UnsupportedCommandException

WebDriverException

UnsupportedOperationException

UnsupportedOperationError

UnsupportedOperationError

All of these inherit from the base exception of their binding (WebDriverException in Java, Python and C#, WebDriverError in Ruby and JavaScript), except the C# InvalidOperationException and NotImplementedException, which are .NET system exceptions. Java’s NoSuchElementException, StaleElementReferenceException and other element errors live in org.openqa.selenium; note that java.util.NoSuchElementException is a different class, so import the right one (the examples above spell out org.openqa.selenium.NoSuchElementException where both could be imported). The causes and fixes, grouped by the likelihood of meeting them:

Error Likely cause What to do

NoSuchElementException

Wrong place (an earlier step failed), wrong time (the element is not in the DOM yet), or the locator changed.

Check that the page is the expected one, use a proper explicit wait, and re-check the locator in the browser’s developer tools.

StaleElementReferenceException

The element was found earlier but is no longer in the current DOM: the page refreshed or re-rendered, you navigated, or you switched window or frame.

Locate the element again each time it is used, or wrap it in an object that stores the locator and relocates on a stale error. If the context changed, switch back to the right window or frame; if the page changed, navigate back and locate again. Use stalenessOf and refreshed for waiting.

ElementClickInterceptedException

Another element would receive the click: a sticky header, a modal or an animation.

Wait with elementToBeClickable, scroll the element into view with a script or Actions.moveToElement, and wait for animations to stop.

ElementNotInteractableException

The action is not supported by the element (sendKeys on a label), the locator matched a different element, or the element is hidden or off-screen.

Use the right action, make the locator unique, scroll into view and wait until the element is interactable.

InvalidSelectorException

A malformed CSS or XPath expression, or an XPath given to a CSS strategy.

Validate the selector in the browser’s developer tools.

TimeoutException

An explicit wait expired (the usual cause, raised by the binding rather than the driver), or the page took longer than the page load timeout (300 seconds by default).

Check that the awaited condition can happen, raise the timeout only if the application is legitimately slow, or use a different page load strategy.

SessionNotCreatedException

Browser and driver versions do not match, macOS privacy settings block the driver, or the driver binary is missing or not executable.

Match the driver to the browser (Selenium Manager does this for you), allow the driver in System Settings, and make the binary executable with chmod +x.

InvalidSessionIdException (Java: NoSuchSessionException)

The session was deleted by quit(), or the last window was closed with close().

Look for quit() and close() calls that run too early.

NoSuchWindowException

The window or tab was closed or the handle is wrong.

After closing a window, switch to another one; list the window handles to check.

NoSuchFrameException

The frame has not loaded yet, the locator or index is wrong, or nested frames were not entered one at a time.

Wait with frameToBeAvailableAndSwitchToIt and switch through each parent.

NoAlertPresentException, UnhandledAlertException

The dialog has not opened, was already closed, or is an HTML modal; or an unexpected dialog blocks the command.

Wait with alertIsPresent, treat HTML modals as normal elements, and set the unhandledPromptBehavior capability for unexpected dialogs.

InsecureCertificateException

A self-signed, expired or mismatched TLS certificate in a test environment.

Set acceptInsecureCerts to true in the options if you trust the site, or fix the certificate.

InvalidArgumentException

A wrong type or malformed value: a relative URL passed to get, an unknown capability value, or an upload path that does not exist (on the machine running the browser, unless a local file detector is used).

Read the message; it normally names the argument.

InvalidCookieDomainException, UnableToSetCookieException, NoSuchCookieException

A cookie added before navigating to its domain, with invalid values, or secure on a non-HTTPS page; or a cookie that does not exist for the page.

Navigate to the domain first, check the values and the scheme.

InvalidElementStateException

The element is disabled or read-only, for example when clearing it.

Wait for the application to enable it.

JavascriptException, ScriptTimeoutException

A script failed in the browser, or an asynchronous script never called its callback within the script timeout (30 seconds by default).

Debug the script in the developer tools, call the callback on every path, and prefer WebDriver commands to scripts.

MoveTargetOutOfBoundsException

A pointer action’s offset puts it outside the viewport.

Check the offsets, scroll first and use a bigger window.

DetachedShadowRootException, NoSuchShadowRootException

The shadow root’s host was removed or replaced; or the element is not a shadow host or the root is not attached yet.

Find the host again and take its shadow root again; wait until it is attached.

ScreenshotException

The page or element is too large for the browser to capture.

Capture the viewport or a smaller element.

UnsupportedCommandException, WebDriverException

The command is not in the W3C specification, or not implemented by that driver or its version; or an unspecific driver error such as a crashed browser.

Update the browser and driver, read the full message and turn on logging.

ElementNotVisibleException is a legacy name that is not part of the W3C specification; current drivers return ElementNotInteractableException instead, and only the Python binding still defines the old class for backward compatibility. The page also links an interactive Selenium Wait Code Generator for building explicit wait snippets.

Five of these errors were reproduced against Bookshelf in Java and Python (both ran and passed):

@Test
void commonErrors() {
  driver.get(Bookshelf.BASE_URL + "/books/3");
  // NoSuchElementException: wrong place or wrong time.
  assertThrows(NoSuchElementException.class, () -> driver.findElement(By.id("nope")));
  // InvalidSelectorException: an XPath expression given to a CSS strategy.
  assertThrows(InvalidSelectorException.class, () -> driver.findElement(By.cssSelector("//h1")));
  // ElementNotInteractableException: the sign-in error message is in the DOM but
  // hidden until a sign-in fails.
  driver.get(Bookshelf.BASE_URL + "/signin");
  WebElement hidden = driver.findElement(By.cssSelector("[data-testid=signin-error]"));
  assertThrows(ElementNotInteractableException.class, () -> hidden.sendKeys("text"));
  // StaleElementReferenceException: the page changed after the element was found.
  WebElement username = driver.findElement(By.id("username"));
  driver.navigate().refresh();
  assertThrows(StaleElementReferenceException.class, username::getText);
  // TimeoutException is raised by the wait, not by the driver.
  assertThrows(TimeoutException.class, () -> new WebDriverWait(driver, Duration.ofMillis(500))
      .until(ExpectedConditions.visibilityOfElementLocated(By.id("nope"))));
}
def test_common_errors(driver, base_url):
    driver.get(f"{base_url}/books/3")
    with pytest.raises(NoSuchElementException):
        driver.find_element(By.ID, "nope")
    with pytest.raises(InvalidSelectorException):
        driver.find_element(By.CSS_SELECTOR, "//h1")
    driver.get(f"{base_url}/signin")
    hidden = driver.find_element(By.CSS_SELECTOR, "[data-testid=signin-error]")
    with pytest.raises(ElementNotInteractableException):
        hidden.send_keys("text")

The remaining errors in the tables (click intercepted, session not created, shadow root, certificate and so on) are described from the official documentation and were not reproduced here.

Unable to locate driver

Historically the most common first-run error. It shows as one of these messages, depending on the binding and browser: the path to the driver executable must be set by the webdriver.chrome.driver system property; the executable chromedriver needs to be available in the path; the file geckodriver does not exist; or "Unable to locate the chromedriver executable". It means Selenium needed a driver to talk to the browser and none of the ways it looks for one succeeded. The solutions, in order of preference:

  1. Use a current Selenium. Since Selenium 4.6, Selenium Manager downloads the right driver (and, since 4.11, the browser) automatically, so no setup is needed. If you get the error with a recent version, turn on logging (below) and file a bug report with the log. Selenium Manager and its configuration are described in Getting Started.

  2. Put the driver on the PATH. Download the driver manually, place it in a directory already on PATH or add its directory (export PATH=$PATH:/path/to/driver in ~/.bash_profile or ~/.zshenv, or setx PATH on Windows), and check with chromedriver --version. This changes the location without changing code and works across machines.

  3. Give the path in a Service object. Create the driver’s service class and set the executable path. It needs no environment variables but makes the code less portable.

  4. Use a third-party manager, only if you cannot use Selenium Manager (an old Selenium, or a feature it lacks): WebDriverManager for Java, WebDriver Manager for Python, a WebDriver Manager package for .NET, and the webdrivers gem for Ruby.

ChromeDriverService service = new ChromeDriverService.Builder()
    .usingDriverExecutable(new File("/path/to/chromedriver"))
    .build();
WebDriver driver = new ChromeDriver(service, new ChromeOptions());
service = webdriver.ChromeService(executable_path="/path/to/chromedriver")
driver = webdriver.Chrome(service=service)

These two snippets are the documented way to set the driver path and were not run here, because the Selenium Manager download is the setup used by every run on this page. The driver for each browser is maintained by the browser vendor: Chromium and Chrome by Google, Firefox (geckodriver) by Mozilla, Edge by Microsoft, Internet Explorer by the Selenium project and Safari by Apple (built into macOS High Sierra and newer). The Opera driver is no longer supported.

Logging Selenium commands

Turning logging on is often the quickest way to see what Selenium sent and received. Each binding uses its platform’s standard logging, so the names and levels differ:

Binding Logger and levels (default) Default output Mapping of Selenium messages

Java

java.util.logging, per class: SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST (INFO)

System.err; add a Handler such as FileHandler for a file

Actionable items and deprecations: WARNING. Useful information: INFO. Debugging: FINE, FINER and FINEST.

Python

logging, per module (selenium): CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET (WARNING)

sys.stderr; add a StreamHandler or FileHandler

WARNING for deprecations, INFO, DEBUG.

C#

static Log class in OpenQA.Selenium.Internal.Logging: Error, Warn, Info, Debug, Trace, None (Warn)

Console.Error; add a FileLogHandler

Warn, Info, Debug.

Ruby

Selenium::WebDriver.logger: debug, info, warn, error, fatal (info)

standard output; set logger.output for a file

warn for deprecations, info, one level (debug) for details.

JavaScript

selenium-webdriver/lib/logging, logger webdriver: OFF, SEVERE, WARNING, INFO, DEBUG, FINE, FINER, FINEST, ALL (OFF)

none until logging.installConsoleHandler(); no file output

INFO and below.

Selenium’s policy is to mark an API deprecated for three releases and then remove it, and deprecations are logged as warnings, so a clean log at the warning level is a cheap way to see what will break on upgrade. Java logging has two traps. Setting the level on the root logger is not enough; you must also set the level of the root handlers (default INFO), and you usually want to raise detail only for the classes you care about, such as RemoteWebDriver (every command and every HTTP request) and SeleniumManager. The official documentation also points to the separate Selenium Logger project for an easier setup in Java.

@Test
void loggingToAFile() throws Exception {
  Path output = Files.createTempFile("selenium", ".log");
  Logger root = Logger.getLogger("");
  Level previous = root.getLevel();
  root.setLevel(Level.FINE);
  for (Handler h : root.getHandlers()) h.setLevel(Level.FINE);
  Handler handler = new FileHandler(output.toString());
  handler.setFormatter(new java.util.logging.SimpleFormatter());
  root.addHandler(handler);
  Logger.getLogger(RemoteWebDriver.class.getName()).setLevel(Level.FINEST);
  try {
    driver.get(Bookshelf.BASE_URL);
    Logger.getLogger("demo").info("this is useful information");
    Logger.getLogger("demo").fine("this is detailed debug information");
  } finally {
    root.removeHandler(handler);
    handler.close();
    root.setLevel(previous);
    for (Handler h : root.getHandlers()) h.setLevel(Level.INFO);
  }
  assertTrue(Files.readString(output).contains("this is detailed debug information"));
}
Oct 25, 2026 9:44:00 PM org.openqa.selenium.remote.RemoteWebDriver log
FINE: Executing: get [d4977c173d8ebcc765e667bd34802b5c, get {url=http://localhost:3914}]
FINE: Executing request: (POST) /session/d4977c173d8ebcc765e667bd34802b5c/url {
Oct 25, 2026 9:44:01 PM org.openqa.selenium.remote.RemoteWebDriver log
INFO: this is useful information
FINE: this is detailed debug information

The log language follows the JVM locale: on the host used here it printed Spanish level names until the test was run with -Duser.language=en. The file had 181 lines for one get. In Python the logger is the selenium package logger, and sub-packages can have their own level:

def test_logging_to_a_file(driver, base_url, tmp_path):
    log_path = tmp_path / "selenium.log"
    logger = logging.getLogger("selenium")
    logger.setLevel(logging.DEBUG)
    handler = logging.FileHandler(log_path)
    handler.setFormatter(logging.Formatter("%(levelname)s %(name)s: %(message)s"))
    logger.addHandler(handler)
    logging.getLogger("selenium.webdriver.remote").setLevel(logging.DEBUG)
    try:
        driver.get(base_url)
        logger.info("this is useful information")
        logger.debug("this is detailed debug information")
    finally:
        logger.removeHandler(handler)
        handler.close()
    lines = log_path.read_text().splitlines()
    assert any("this is detailed debug information" in line for line in lines)
DEBUG selenium.webdriver.remote.remote_connection: POST http://localhost:64299/session/d15d1016eade3072f6f8fe3cc0e523c5/url {'url': 'http://localhost:...
DEBUG selenium.webdriver.remote.remote_connection: Remote response: status=200 | data={"value":null} | headers=HTTPHeaderDict({'Content-Length': '14', ...
DEBUG selenium.webdriver.remote.remote_connection: Finished Request
INFO selenium: this is useful information
DEBUG selenium: this is detailed debug information

With pytest there is one more trap: pytest captures logging and hides it unless the test fails. To see logs from passing tests run pytest -s -p no:logging, which stops the console capture and pytest’s own logging plugin, and then call logging.basicConfig(level=logging.WARN) yourself. The examples above log to a file instead, so they need none of that.

Filtering works at the same granularity as the logger: per class in Java and C# (Log.SetLevel(typeof(RemoteWebDriver), LogEventLevel.Debug)), per module in Python, and in Ruby by message id: every Selenium message has an id, and logger.ignore(:jwp_caps, :logger_info) turns off deprecation notices while logger.allow(%i[selenium_manager example_id]) opts in. The C#, Ruby and JavaScript logging snippets (Log.Handlers.Add(new FileLogHandler(path)), Selenium::WebDriver.logger.level = :debug, logging.getLogger('webdriver').setLevel(logging.Level.INFO)) come from the official documentation and were not run here.

Choosing a wait strategy

  • Use explicit waits for everything that is not instantly present, name the condition, and keep the implicit wait at 0.

  • Wait for a user-visible state change, such as data-state="saved", not for elapsed time. Where the application has no such signal, add one (a data-state attribute or a live region) rather than adding sleeps.

  • Prefer Expected Conditions with locators over conditions on stored elements; they survive re-rendering.

  • Give long waits a message that says what was awaited, and keep waits in page objects so tests read as intentions (see Test Design & Page Objects).

  • Keep one driver per thread, and use ThreadGuard in Java if tests run in parallel.

  • A wait cannot fix a test that depends on state left by another; see Flaky Tests & Reliability.

  • Asynchronous BiDi events (network, console, script) can replace polling for some conditions; see WebDriver BiDi.

What changed recently

Version Change

Selenium 4.0

EventFiringDecorator and WebDriverListener replace the older Java event-firing classes, which are not in the 4.51 packages. .NET drops Expected Conditions.

Selenium 4.6

Selenium Manager downloads the matching driver automatically, which makes the "unable to locate driver" error rare.

Selenium 4.10

The Ruby logger gains the level, output, ignore and allow API shown above.

Selenium 4.11

Selenium Manager also downloads the browser when it is missing.

Selenium 4.51 (9 October 2026)

Java ExpectedCondition stops implementing Guava’s Function and extends only java.util.function.Function; Guava is removed from the client bindings piece by piece, although selenium-api 4.51.0 still pulls it in transitively.

The common errors page was extended on 10 October 2026 to document all the W3C WebDriver errors; the table above reflects that revision.