In Selenium, which Java types may be passed to executeScript, and what types can its result be?
answer
- Only JSON-shaped things cross
- One numeric type on one side
- Elements travel by reference, not by copy
- The cast the compiler cannot check
- Long, never Integer
basics
~20 sYou may pass numbers, booleans, strings, WebElements, and lists or String-keyed maps of those; anything else throws IllegalArgumentException. Results come back as Boolean, Long, Double, String, List, Map, WebElement or null, and always need a cast.
solid answer
~40 sEverything crosses as JSON, so only JSON-shaped values plus element references can travel. On the way in, Selenium accepts a `Number`, `Boolean`, `String`, `WebElement`, an array or `Collection`, or a `Map` with `String` keys, converting nested structures recursively; a `Duration`, a `LocalDate` or your own DTO fails client-side with `IllegalArgumentException: Argument is of an illegal type`. On the way out the declared type is `Object`: an HTML element becomes a `WebElement`, a whole number becomes a `Long`, a decimal becomes a `Double`, arrays become `List<Object>`, plain objects become `Map<String, Object>`, and no `return` gives `null`. The classic bug is casting a count to `Integer` - JavaScript has one numeric type and the client always maps a non-decimal number to `Long`.
code
java · 17 linesJavascriptExecutor js = (JavascriptExecutor) driver;
WebElement summary = driver.findElement(By.id("return-summary"));
Long rows = (Long) js.executeScript(
"return arguments[0].querySelectorAll('tbody tr').length;", summary);
@SuppressWarnings("unchecked")
List<WebElement> flagged = (List<WebElement>) js.executeScript(
"return Array.from(arguments[0].querySelectorAll('tr.needs-review'));", summary);
@SuppressWarnings("unchecked")
Map<String, Object> facts = (Map<String, Object>) js.executeScript(
"return {rows: arguments[0].rows.length, caption: arguments[0].id};",
summary.findElement(By.tagName("table")));
long tableRows = (Long) facts.get("rows");
String firstFlagged = flagged.get(0).getText();go deeper
Recall that the result of a script is typed Object and always needs a cast, and that you can hand elements to a script and get elements back.
Explain the mapping in both directions and why it exists: only JSON-shaped values plus element references cross, which is where Long, Double, List and Map come from.
Show the practical judgment - batching several reads into one returned object, avoiding the Long-versus-Double ambiguity by returning a String, and keeping unchecked casts contained.
Own the boundary question: how much application knowledge should live inside injected script strings at all, given that nothing about them is type-checked or refactor-safe.
## The wire only carries JSON `executeScript` does not share memory with the browser. The client serializes your arguments to JSON, posts them to `/session/{id}/execute/sync` alongside the script text, and deserializes whatever comes back. Every rule about types on both sides falls out of that one fact: only values with a JSON shape - plus the one special case of an **element reference** - can cross. ## What you may pass in Arguments are converted client-side, before any HTTP request happens, by `WebElementToJsonConverter`. It accepts: - `null`, a `String`, a `Boolean` and any `Number`; - a `WebElement` - replaced by `{"element-6066-11e4-a52e-4f735466cecf": "<id>"}`, the W3C **web element identifier**; - a `ShadowRoot`, replaced by its own `shadow-6066-...` reference; - any array or `Collection`, converted recursively; - a `Map` whose **keys are Strings**, converted recursively. Anything else fails fast on your own thread with `IllegalArgumentException: Argument is of an illegal type: ...`. A `java.time.Duration`, a `LocalDate`, an enum or your own `TaxReturn` DTO is rejected before a single byte leaves the process - convert it to a string or a number yourself. Inside the script the values appear in the `arguments` array-like, in order: `arguments[0]` is the first extra value you passed. ## What you get back | The script returns | You receive in Java | |---|---| | an HTML element | `WebElement` | | a non-decimal number, e.g. `42` | `Long` | | a decimal number, e.g. `1.5` | `Double` | | `true` / `false` | `Boolean` | | an array | `List<Object>`, elements converted by these same rules | | a plain object | `Map<String, Object>` | | `null`, `undefined`, or no `return` at all | `null` | | anything else | `String` | Because the declared return type is `Object`, **every one of these needs a cast**, and the compiler cannot help you get it right. ## The Long trap The single most common failure on this API is: ```java // ClassCastException: java.lang.Long cannot be cast to java.lang.Integer int rows = (Integer) js.executeScript( "return document.querySelectorAll('#return-summary tbody tr').length;"); ``` JavaScript has one numeric type, and the client maps a whole number to `Long`, never to `Integer`. Cast to `Long`. The mirror-image trap is assuming `Double`: a value such as `getBoundingClientRect().top` comes back as `Double` only when it actually has a fractional part, and as `Long` when the browser reports a whole number. When you cannot be sure which you will get, cast to `Number` and call `intValue()` or `doubleValue()` on it. ## Elements make a full round trip An element passed in is looked up on the remote end by its reference, so it is the **same node** the driver already knows - not a copy: - If that node has been detached by a re-render of the tax-return summary, the remote end returns `stale element reference` rather than running your script. - If the reference belongs to a different browsing context than the one currently selected, it is not found there. Elements coming **out** are just as real. `return document.querySelectorAll('tr.needs-review')` yields a `List<Object>` of `WebElement`s bound to the session, and you can call `getText()` or `click()` on them exactly as if `findElement` had produced them. That makes a script a legitimate way to run a query the `By` strategies cannot express and hand the results back to normal Selenium code. ## Practical rules 1. **Return the smallest thing that answers the question.** `return arguments[0].textContent;` gives you a `String` you can assert on; returning the element and then calling `getText()` costs a second round trip. 2. **Batch a wide read into one object.** One script returning `{rows: n, total: t, flagged: f}` becomes a single `Map<String, Object>` and one HTTP call, instead of three scripts and three calls. 3. **Normalise numbers in the page.** If you need a decimal, do the arithmetic in JavaScript and return a `String`, then parse it in Java - that side-steps the `Long` and `Double` ambiguity entirely. 4. **Never assume a shape you did not produce.** `Map<String, Object>` from a script is unchecked; a `@SuppressWarnings("unchecked")` cast that is wrong fails later and further away than the script that caused it. The short version: arguments are numbers, booleans, strings, elements, lists and String-keyed maps; results are `Boolean`, `Long`, `Double`, `String`, `List`, `Map`, `WebElement` or `null`. Everything else is either an `IllegalArgumentException` on the way in or a `ClassCastException` on the way out.
- Why does casting a row count to Integer throw a ClassCastException?JavaScript has a single numeric type, and Selenium's client maps any non-decimal result to `Long`. `Integer` is never produced, so the cast fails at runtime even though the value is small. Cast to `Long`, or to `Number` and call `intValue()` when the script might return either a whole number or a decimal.
- What actually crosses the wire when you pass a WebElement into a script?Not the element - a JSON object holding the W3C web element identifier, `element-6066-11e4-a52e-4f735466cecf`, mapped to the reference the driver already issued. The remote end looks that reference up in the current browsing context, so the script receives the very same DOM node, and a detached one produces a stale element reference error instead.
- How would you return several values from one script?Return a plain object: `return {rows: n, total: t};` arrives as a `Map<String, Object>` with each value converted by the usual rules. That is one HTTP round trip instead of three, and it keeps the reads consistent with each other because they were taken at the same instant.
saying these in an interview costs you the question
- Casts a scripted count to Integer instead of Long
- Expects a returned element to arrive as an HTML string
- Thinks any Java object can be passed as a script argument
- Assumes a script with no return statement yields an empty String
- Believes returned elements must be re-found before use