In a Selenium 4 suite, some executeAsyncScript calls fail with ScriptTimeoutException while others pass. How do you diagnose that?
answer
- Injected JavaScript has its own ceiling
- Read it back rather than assume
- Session state a fixture may have lowered
- Drivers may not start at the spec value
- The callback, not return, ends it
basics
~20 sRead the session's real script timeout with getScriptTimeout() first. It is session state an earlier test may have lowered, drivers differ from the specified 30 second default, and the exception only means the callback never fired.
solid answer
~40 sStart from the fact that the script timeout is session state, not a constant. Call `driver.manage().timeouts().getScriptTimeout()` at the failure point: a shared driver plus one fixture that called `scriptTimeout(Duration)` explains an apparently unrelated test failing later. Next check the driver's own starting value, because the W3C initial value is 30,000 ms but Selenium's conformance test records Chrome, Edge, Firefox and Safari defaulting to five seconds instead. Then check the script itself, since `ScriptTimeoutException` says only that the injected callback never arrived, which also happens when the callback is wired to an event that already fired. A navigation while an async script is pending errors the command out too. Treat host speed as the last hypothesis, not the first, and remember the same timeout also bounds synchronous `executeScript`.
code
java · 25 linesimport java.time.Duration;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.ScriptTimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class PlotMapTiles {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(10));
driver.get("https://garden.example.org/plots/map");
Object tiles = ((JavascriptExecutor) driver).executeAsyncScript(
"var done = arguments[arguments.length - 1];"
+ "window.plotMap.onTilesLoaded(function () {"
+ " done(document.querySelectorAll('.plot-tile').length);"
+ "});");
System.out.println("plot tiles rendered: " + tiles);
} catch (ScriptTimeoutException e) {
System.out.println("plot tiles never signalled completion");
} finally {
driver.quit();
}
}
}go deeper
Know that injected JavaScript has its own ceiling, set with scriptTimeout on manage().timeouts(), and that an async script must call the callback the driver appends as its last argument or it will time out.
Explain the mechanics: the ceiling is stored on the session, the W3C initial value is 30,000 ms, expiry returns the script timeout error which the Java client raises as ScriptTimeoutException, and the return value of an async script is discarded.
Show the diagnostic order. Read the effective ceiling back, account for drivers whose starting value differs from the specification, check that the callback can fire at all, then rule out a navigation during the script before you blame the machine.
Own the policy question of who may change session-wide timeouts and where, since a ceiling raised to silence one flaky helper is inherited by every test that shares the driver and hides the next fault as effectively as it hid the first.
## What the script timeout bounds A WebDriver session's **script timeout** is the ceiling on how long the remote end will wait for injected JavaScript before it gives up. It lives alongside the page load timeout and the implicit wait in the session's timeouts configuration, and it is set in Selenium 4 with `driver.manage().timeouts().scriptTimeout(Duration)` and read back with `getScriptTimeout()`. Two things about its scope surprise people: - Under the W3C WebDriver specification **both** *Execute Script* and *Execute Async Script* start a timer from the session's script timeout. It is not an async-only setting, even though async is where it usually bites. - It is **session state**. Whatever the last fixture set is what the next test gets, because the value is stored on the remote end and only cleared when the session ends. A synchronous script rarely reaches the ceiling, because it finishes as soon as its function body returns. An async script is different: it only finishes when the **callback that the driver appends as the last argument** is invoked. If that callback never fires, the command has no other way to end, and the timeout is the only thing that ends it. ## The default, and why it may not be the number you observe | Source | Script timeout it reports | Notes | |---|---|---| | W3C WebDriver specification | 30,000 ms | the specified initial value for a new session | | Selenium's own conformance test | 30,000 ms asserted | marked not-yet-implemented for Chrome, Edge, Firefox and Safari | | Those four drivers, per that note | 5,000 ms | the reason recorded on the test is "Default to 5s" | The practical rule: **do not assume the default**. `getScriptTimeout()` costs one round trip and tells you what this session is really using. A helper that behaves on one driver and fails on another, with nothing in the test changed, is very often this divergence rather than anything in the page. ## What expiry looks like When the ceiling is hit, the remote end returns the W3C `script timeout` error and the Java client raises `org.openqa.selenium.ScriptTimeoutException` - a distinct class from the `TimeoutException` that a page load timeout produces, which is itself useful diagnostic signal. The single most common cause is a script written as if it were synchronous: ```java executor.executeAsyncScript("return 1 + 2;"); ``` That throws `ScriptTimeoutException`, not `3`. The async form ignores the function's return value entirely; the first argument passed to the injected callback is the result. Selenium's own test suite pins this behaviour, and it pins the sibling case too: `window.setTimeout(function () {}, 0);` also times out, because scheduling a function that never calls the callback is still never calling the callback. ## Diagnosing a script timeout that appears on some runs only When a plot-map helper that polls for rendered `.plot-tile` elements fails intermittently, work through the possibilities in this order: 1. **Read the effective ceiling.** Call `getScriptTimeout()` at the failure point. A shared driver plus a fixture that lowered the timeout for one test explains an "unrelated" test failing later. 2. **Check the driver's own default.** If nothing in the suite sets the timeout, the session may be running on a five-second ceiling rather than the specified thirty. 3. **Check whether the callback can fire at all.** A callback wired to an event that has already fired before the script was injected never fires; the timeout is the symptom, not the fault. 4. **Check for a navigation during the script.** If the page navigates while an async script is pending, the command errors out instead of completing - Selenium has an explicit test for exactly that case. 5. **Check the machine, last.** A genuinely slower host only matters once the first four are ruled out, and raising the ceiling to hide any of the first three buries the real fault. ## Keeping the number honest across a suite - Set the script timeout **once, at session setup**, so every test starts from the same known ceiling rather than inheriting whatever the previous test left. - If a single test needs a longer ceiling, set it and **restore it**, or accept that the change is now session-wide. - Treat a `ScriptTimeoutException` as a claim about *your script's completion signal*, not about the page being slow. The exception says the callback did not arrive; it says nothing about why. - Keep the async injection point narrow. The smaller the script between injection and callback, the smaller the set of reasons it can fail to signal.
- Does the script timeout bound synchronous executeScript as well, or only the async form?Both. The W3C algorithms for Execute Script and Execute Async Script each start a timer from the session's script timeout and return the script timeout error if the promise is still pending when it fires. Synchronous scripts rarely reach the ceiling because they end when the function body returns, but the ceiling is there.
- Why does executeAsyncScript with the body "return 1 + 2;" time out instead of returning 3?The async form ignores the function's return value; the result is the first argument handed to the callback the driver appends as the script's last argument. A body that only returns never invokes that callback, so nothing ends the command and the script timeout is what finally fires. Selenium's own test suite pins this.
- What happens if the page navigates while an async script is still pending?The command errors out rather than completing. Selenium has a dedicated test asserting that a page load detected while waiting on an async script produces a WebDriverException, so a navigation racing your readiness script is a distinct failure from the callback simply being slow.
saying these in an interview costs you the question
- Raises the script timeout instead of finding why the callback never fired
- Assumes every driver starts at the specification's 30 second value
- Thinks the script timeout is per call rather than session state
- Believes an async script's return value is what the client receives
- Confuses ScriptTimeoutException with the page load TimeoutException