skip to content

Why must a Selenium LoadableComponent's isLoaded() signal failure by throwing an Error?

level: middleimportance: should knowfreq 38%

answer

  1. The catch clause decides the control flow
  2. The check returns void, not boolean
  3. Error and Exception are separate branches
  4. findElement throws on the wrong branch
  5. AssertionError is the intended throwable

basics

~20 s

Because get() drives the load path with catch (Error e). Only a subclass of Error routes to load(); a boolean return is ignored by the signature, and an ordinary exception escapes get() so the screen is never loaded.

solid answer

~40 s

`LoadableComponent.get()` is written as `try { isLoaded(); return this; } catch (Error e) { load(); }`, so the **type** of what you throw is the control flow. `isLoaded()` is declared `protected abstract void isLoaded() throws Error` - it returns nothing, so a boolean has nowhere to go, and only a `Throwable` on the `Error` branch of the hierarchy is caught. `AssertionError` is the natural choice, which is why the convention is to fail the check with an assertion. The trap is a check that calls `driver.findElement(...)` directly: `NoSuchElementException` extends `RuntimeException`, so it sails past `catch (Error e)`, `load()` never runs, and the screen is never navigated to. It also means a harness that wraps calls in `catch (Exception e)` will not catch a load failure at all.

code

java · 14 lines
java
@Override
protected void isLoaded() throws Error {
  if (driver.findElements(By.id("hive-log-table")).isEmpty()) {
    throw new AssertionError("Hive log table missing at " + driver.getCurrentUrl());
  }
  try {
    String apiary = driver.findElement(By.cssSelector("[data-apiary-name]")).getText();
    if (!"Meadow Row".equals(apiary)) {
      throw new AssertionError("Wrong apiary in the hive log header: " + apiary);
    }
  } catch (NoSuchElementException e) {
    throw new AssertionError("Hive log header not rendered", e);
  }
}

go deeper

for a junior

Remember the rule of thumb: signal not-ready by throwing, usually an AssertionError with a useful message, and never by returning a value. The method returns void, so there is no value to return.

for a middle

Explain that get() catches Error specifically, and that Error and Exception are sibling branches of Throwable. Be ready to say what happens when a WebDriver exception escapes the check.

for a senior

Show how this interacts with the harness: reporting hooks and retry wrappers that catch Exception go blind, and readiness checks must be written so no WebDriver exception can leak out of them.

for a principal

Weigh the design itself. Using Error as a control-flow channel is unusual, catches too much, and differs from the .NET port's boolean check - decide whether your codebase adopts it or writes a narrower guard of its own.

## The signature that dictates the shape Selenium's `LoadableComponent<T>` declares its readiness check as: ```java protected abstract void isLoaded() throws Error; ``` Two facts are baked into that one line. - The return type is **`void`**. There is no boolean for `get()` to inspect, so "return false when the screen is missing" is not an option the API offers. - The declared throwable is **`Error`**, and `get()` catches exactly that: ```java public T get() { try { isLoaded(); return (T) this; } catch (Error e) { load(); } isLoaded(); return (T) this; } ``` The **type of the throwable is the control flow**. Throwing puts the component on the load path; returning normally puts it on the already-loaded path. Nothing else is consulted. ## Why an Error rather than a boolean - A boolean carries **one bit**. An `AssertionError` carries a message, so the hive-log check can fail with `"Hive log table not rendered at " + driver.getCurrentUrl()` and the report names the screen, the condition and the URL that was actually open. - A check may assert **several things** - the table is present, the queen-status column is populated, the apiary name in the header matches. With a throwing check, the first failed assertion stops the method and names itself; with a boolean, all of them collapse into `false`. - `AssertionError` is a subclass of `Error`, so the assertion style teams already write in their tests drops straight into `isLoaded()` with no adapter. ## Error versus Exception, and the trap between them `Error` and `Exception` are the two direct subclasses of `java.lang.Throwable`; neither is a subtype of the other. `catch (Error e)` therefore catches nothing on the `Exception` side, and Selenium's own WebDriver failures are all on that side: `NoSuchElementException` extends `NotFoundException`, which extends `WebDriverException`, which extends `RuntimeException`. So a readiness check written like this is silently broken: ```java @Override protected void isLoaded() throws Error { driver.findElement(By.id("hive-log-table")); // wrong: throws an Exception } ``` When the hive log is not open, `findElement` throws `NoSuchElementException`, which is **not** an `Error`. It escapes the `try` in `get()`, `load()` is never reached, and the test fails at the very first check with a lookup error - having never navigated anywhere. | What `isLoaded()` does | Caught by `get()`? | Is `load()` called? | What the caller sees | |---|---|---|---| | Returns normally | not applicable | no | `get()` returns the component at once | | Throws `AssertionError` | yes | yes | normal path: load, then re-check | | Throws a custom subclass of `Error` | yes | yes | same as above | | Throws `NoSuchElementException` | **no** | **no** | the exception escapes the first check | | Throws any other `RuntimeException` | **no** | **no** | the exception escapes the first check | ## Writing a check that cannot leak an exception 1. Prefer the plural lookup: `driver.findElements(By.id("hive-log-table")).isEmpty()` returns an empty list instead of throwing, so you decide what to raise. 2. Raise an `AssertionError` (or your own `Error` subclass) with a message naming the screen and the condition that failed. 3. If you must call a throwing API, wrap it: catch the `WebDriverException` and rethrow it as an `AssertionError` with the original as the cause, so the load path still triggers and the cause survives. 4. Keep the check side-effect free. It runs on **every** `get()`, including the already-loaded fast path, so it should not click, type or navigate. You also cannot widen the signature: an override may not declare a checked exception the parent does not, and `isLoaded()` declares only `Error`. Whatever a check throws is therefore unchecked - which makes the `Error`/`RuntimeException` distinction the only thing that decides its fate. ## Consequences for the test harness - **`catch (Exception e)` will not see a load failure.** Selenium's own javadoc warns to expect an `Error` rather than an `Exception` from these components. Harness code that funnels everything through a broad exception handler - screenshot-on-failure glue, a retry wrapper, a reporting hook - has to catch `Error` or `Throwable` to observe the failure at all. - **The failure that surfaces is yours.** Neither `get()` nor the base class invents a message; whatever the second `isLoaded()` threw is what the caller receives. - **The .NET port made the opposite choice.** Its `LoadableComponent<T>` spells the readiness check `EvaluateLoadedStatus()` returning a `bool`, with a separate `UnableToLoadMessage`. That is a genuine difference between the two ports, so an answer that describes the throwing contract should say it is the Java shape.

  • What actually happens if isLoaded() throws NoSuchElementException on a screen that is not open?
    `get()` catches only `Error`, and `NoSuchElementException` extends `RuntimeException`, so it is not caught. The exception propagates out of `get()` before `load()` is reached, the browser never navigates, and the test reports a lookup failure that looks like a bad locator rather than a screen that was never opened.
  • Does throwing an Error from isLoaded() risk masking a real JVM error such as OutOfMemoryError?
    In principle yes - `catch (Error e)` in `get()` is broad, so an `OutOfMemoryError` or `StackOverflowError` raised inside the check would be swallowed and trigger a `load()` attempt. In practice the check is a short DOM query, but it is a fair criticism of the design and a good senior-level observation.
  • How should a failure-screenshot hook be written around a LoadableComponent?
    It has to catch `Error` or `Throwable`, not `Exception`. A hook written as `catch (Exception e)` sees nothing when a screen fails to load, because the failure arrives as an `AssertionError`. Catching `Throwable`, capturing the evidence, and rethrowing preserves both the artefact and the original failure.

saying these in an interview costs you the question

  • Returns false from isLoaded() instead of throwing anything
  • Calls findElement in isLoaded() and expects load() to still run
  • Thinks catch (Exception e) around get() catches a load failure
  • Believes any throwable from isLoaded() sends get() to load()
  • Declares a checked exception on the isLoaded() override