In a Selenium custom wait condition, how do you poll a page state that no element reflects?
answer
- Some signals never reach the DOM
- The driver is also a script executor
- The fragment is an anonymous function body
- No return statement means null every poll
- A returned zero still ends the wait
basics
~20 sCast the driver the wait hands you to JavascriptExecutor and return the result of executeScript from the condition. The fragment needs an explicit return statement, or every poll receives null and the wait just times out.
solid answer
~50 sRun a read-only script from inside the condition body: cast the driver the wait passes in, call `executeScript`, and turn its result into the poll's verdict. On a plant-nursery order form that lets you wait on a value nothing renders, such as a counter of in-flight re-pricing requests. Two things bite. First, Selenium runs the fragment as the body of an anonymous function and returns `null` unless the script contains an explicit `return`, so a fragment written as a bare expression makes every poll look not-ready and the wait times out. Second, the marshalled type matters: a number comes back as a `Long` and a string as a `String`, and both end the wait even when they are zero or empty. Compare in JavaScript so a `Boolean` comes back, and keep the script a pure read.
go deeper
Remember two rules: the script needs an explicit return, and the driver must be cast to JavascriptExecutor first. Practise writing a condition that returns a boolean comparison done in the script.
Explain the marshalling: booleans arrive as Boolean, whole numbers as Long, strings as String, and only null keeps the wait polling among those. Show why a count of zero ends it.
Weigh the coupling. Show when a scripted signal is the only honest way to observe readiness and when it ties the suite to an application internal that nobody has agreed to keep stable.
Own the contract question: if tests are going to poll application-exposed state, decide with the page's owners which signals are supported, named and stable rather than scraped from the bundle.
## When no element carries the signal Most custom conditions read the DOM: is this cell populated, is that button enabled. Sometimes the readiness signal is not in the DOM at all. On a **plant-nursery order form** the useful signals are often JavaScript-side: - the number of **in-flight requests** the page is tracking while it re-prices an order, - a **flag the application sets** on `window` once the delivery-week calendar has finished hydrating, - `document.readyState`, when you care that the document itself has finished loading, - the **scroll or layout state** of the order-lines table, which no attribute reflects, - a value the page keeps in a client-side store rather than rendering. For these the condition needs to run script and use its result as the poll's verdict. ## Wiring a script into the condition `JavascriptExecutor` is implemented by the driver, so inside the condition body you cast and call it. The sequence is: 1. **Cast the driver** the wait handed you: `((JavascriptExecutor) d)`. 2. **Call `executeScript`** with a fragment that ends in an explicit `return`. 3. **Compare the result** to what "ready" means, in Java, rather than trusting the raw value. 4. **Return `null`** while it is not ready, and the value you want otherwise. ```java Boolean priced = new WebDriverWait(driver, Duration.ofSeconds(10)) .until(d -> (Boolean) ((JavascriptExecutor) d) .executeScript("return window.nurseryOrder.pendingRequests === 0;")); ``` Step 2 is where beginners lose an afternoon. Selenium runs your fragment **as the body of an anonymous function**, and its documentation is explicit: unless the script returns a value, `null` is what comes back. Writing `"window.nurseryOrder.pendingRequests === 0"` without `return` therefore yields `null` on every poll, and the wait simply times out with no clue as to why. ## How the returned value meets the null-or-false rule `executeScript` returns `Object`, and the marshalling rules decide which Java type you get. That interacts directly with the wait's stop condition — the loop keeps polling only on `null` and on a `Boolean` carrying `false`: | Script returns | Java type you receive | Effect on the wait | |---|---|---| | `true` / `false` | `Boolean` | stops on true, keeps polling on false | | a non-decimal number | `Long` | **stops**, even when the number is `0` | | a decimal number | `Double` | stops | | a string | `String` | **stops**, even when it is empty | | an HTML element | `WebElement` | stops | | an array | `List<Object>` | **stops**, even when it is empty | | nothing, or `null` | `null` | keeps polling | The rows in bold are the traps. A script returning `pendingRequests` as a count looks like a natural condition, but `0` arrives as a `Long` and ends the wait on the first poll — the opposite of what was intended. Compare in **JavaScript** (`=== 0`) so a `Boolean` comes back, or compare in **Java** after the call, and never rely on a number or a string being treated as false. ## Keep the script a read The same idempotence rule that governs any custom condition governs the script inside it. The fragment runs once per poll, so it must observe and not change anything: - Reading `window`, `document` or an application-exposed value is fine. - Assigning to a page variable, dispatching an event, or calling an application function that submits the order is not. - Anything with a network side effect is doubly wrong, because the poll count decides how many times it happens. ## Failure modes to expect - **Times out with no explanation.** The commonest cause is the missing `return`; check that first. - **Ends immediately.** The script returned a number, a string or an array that is not the boolean you assumed. - **Throws out of `until`.** A `JavascriptException` from a bad fragment or a missing global is not in the wait's default ignore list, so it propagates and ends the wait rather than being retried. - **Depends on an application internal.** A flag on `window` is a contract with the application's authors; if they rename it, the wait breaks in a way that looks like a timing problem. Used carefully, a scripted read is the escape hatch that lets a condition observe state the page never renders. Used carelessly, it is a wait that either never waits or never finishes.
- Your scripted condition returns a count of pending requests and the wait ends at once. What went wrong?A non-decimal number marshals to a Java `Long`, and the wait stops on anything that is not `null` or a false `Boolean`. A count of `0` is therefore accepted as the answer on the first poll. Do the comparison in the script so a boolean comes back, or compare the returned value in Java and return `null` until it matches.
- What happens if the script throws inside the condition?A `JavascriptException` propagates straight out of `until` and ends the wait, because the default ignore list covers only `NotFoundException`. That is usually a bug worth surfacing: a missing global or a syntax error will not fix itself by polling, so failing loudly is better than a timeout that hides the real cause.
- What is the risk of waiting on a flag the application sets on window?It turns an implementation detail into a test contract. The wait is only as reliable as the application's promise to keep setting that flag, and a rename breaks the test in a way that looks like a timing failure. It is worth agreeing the signal with the people who own the page rather than discovering it by reading their bundle.
saying these in an interview costs you the question
- Writes a bare expression and expects it to be returned
- Assumes a numeric zero from a script keeps the wait polling
- Mutates page state inside the script the condition runs
- Expects a script error to be retried like a missing element
- Believes only DOM state can drive a Selenium wait