Selenium JavaScriptExecutor (With Examples)
Two lines puzzle almost every Selenium beginner:
WebDriver driver = new ChromeDriver();
and:
(JavascriptExecutor) driver
Both involve Java typecasting, and understanding why they work explains how Selenium is designed.
This guide covers upcasting and downcasting with a runnable demo, then the two interfaces you cast to most — TakesScreenshot and JavascriptExecutor — including return values and when JavaScript actions are and aren't a good idea.
Upcasting and Downcasting
| Upcasting | Downcasting | |
|---|---|---|
| Direction | Child → parent, or class → interface | Parent → child |
| Syntax | Implicit: Animal a = new Dog(); |
Explicit: Dog d = (Dog) a; |
| Can fail? | No | Yes — ClassCastException if the object isn't really a child |
| What you can call | Only methods available through the reference type | Child-specific methods become available |
Java Example
Animal a = new Dog(); // upcasting — implicit
a.sound(); // Animal sound
Dog d = (Dog) a; // downcasting — works because the object IS a Dog
d.bark(); // Dog barking
Dog bad = (Dog) new Animal(); // ClassCastException
if (a instanceof Dog dog) {
dog.bark();
}
The output comes from running the demo.
The important rule to remember is:
A cast does not change the object. It only changes which methods the compiler lets you access through the reference.
The cast succeeds at runtime only when the actual object has the required type.
Upcasting in Selenium: WebDriver driver = new ChromeDriver()
In Selenium:
WebDriveris an interface.ChromeDriveris a class that implementsWebDriverthrough Selenium's driver hierarchy.- Assigning a
ChromeDriverobject to aWebDriverreference is upcasting. - Upcasting is implicit.
- Test code written against
WebDrivercan work with different browser implementations.
For example:
WebDriver driver = new ChromeDriver();
You could also use:
WebDriver driver = new FirefoxDriver();
or:
WebDriver driver = new EdgeDriver();
This is runtime polymorphism in action.
For more information, see OOP Concepts in Your Framework.
Why Cast to Other Interfaces?
The WebDriver interface deliberately contains core browser functionality such as:
get()findElement()findElements()quit()close()
Additional capabilities are exposed through other Selenium interfaces.
For example, a browser driver can implement interfaces such as:
ChromeDriver implements WebDriver, JavascriptExecutor, TakesScreenshot, HasCapabilities, …
When your variable is declared as:
WebDriver driver = new ChromeDriver();
the reference exposes the methods available through WebDriver.
If you need functionality from another interface, you can cast the reference:
JavascriptExecutor js = (JavascriptExecutor) driver;
or:
TakesScreenshot screenshot = (TakesScreenshot) driver;
Strictly speaking, this is a cast from one interface type to another rather than a traditional parent-to-child downcast.
It works because the underlying object implements both interfaces.
If an object does not implement the target interface, the cast can result in ClassCastException.
TakesScreenshot
Selenium's TakesScreenshot interface provides screenshot functionality.
File src =
((TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE);
Path target =
Paths.get("target", "screenshots", "home.png");
Files.createDirectories(target.getParent());
Files.copy(
src.toPath(),
target,
StandardCopyOption.REPLACE_EXISTING
);
You can also return a Base64 screenshot:
String base64 =
((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BASE64);
Base64 screenshots can be useful when embedding screenshots into HTML reports.
Taking an Element Screenshot
Selenium can also capture an individual element:
File element =
driver.findElement(By.id("summary"))
.getScreenshotAs(OutputType.FILE);
Use a relative, project-based path rather than a machine-specific path such as:
C:\Screenshots
A project-relative path is much easier to use across different development machines and CI agents.
For automatic screenshots after failures, see TestNG Interview Questions.
JavascriptExecutor
JavascriptExecutor allows Selenium tests to execute JavaScript in the current browser context.
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript(
"window.scrollTo(0, document.body.scrollHeight);"
);
The interface provides two important methods:
executeScript()— executes JavaScript synchronously.executeAsyncScript()— executes asynchronous JavaScript and waits for the callback.
The script executes in the current page or frame context.
Passing Elements In: arguments[0]
You can pass Selenium objects into JavaScript.
For example:
WebElement footer =
driver.findElement(By.id("footer"));
js.executeScript(
"arguments[0].scrollIntoView({block: 'center'});",
footer
);
js.executeScript(
"arguments[0].style.border = '3px solid red';",
footer
);
Values supplied after the JavaScript string become:
arguments[0]
arguments[1]
arguments[2]
and so on.
For example:
arguments[0].scrollIntoView({block: 'center'});
The block: 'center' option moves the element toward the middle of the viewport, which can help prevent sticky headers from covering the element.
Getting Values Back ⭐
JavaScript can return values to Java.
String title =
(String) js.executeScript(
"return document.title;"
);
Long scrollY =
(Long) js.executeScript(
"return window.pageYOffset;"
);
Boolean loaded =
"complete".equals(
js.executeScript(
"return document.readyState;"
)
);
WebElement el =
(WebElement) js.executeScript(
"return document.querySelector('#cart');"
);
String hidden =
(String) js.executeScript(
"return arguments[0].textContent;",
footer
);
The important point is the use of return:
return document.title;
Without return, the Java code does not receive the JavaScript value.
Common JavaScript Return Types
JavaScript values are mapped into Java/Selenium representations.
Common examples include:
| JavaScript Value | Typical Java Representation |
|---|---|
| String | String |
| Whole number | Long |
| Decimal number | Double |
| Boolean | Boolean |
| DOM element | WebElement |
| JavaScript array | List |
| JavaScript object | Map |
Therefore, the Java side may need to cast the returned value.
Scrolling
JavaScript can be used for several scrolling scenarios.
| Goal | JavaScript |
|---|---|
| Bottom of the page | window.scrollTo(0, document.body.scrollHeight) |
| Down by 500 px | window.scrollBy(0, 500) |
| To an element | arguments[0].scrollIntoView({block: 'center'}) |
| Horizontally | window.scrollBy(500, 0) |
| Inside a scrollable div | arguments[0].scrollTop = arguments[0].scrollHeight |
Scroll to the Bottom
js.executeScript(
"window.scrollTo(0, document.body.scrollHeight);"
);
Scroll by a Specific Amount
js.executeScript(
"window.scrollBy(0, 500);"
);
Scroll to an Element
WebElement element =
driver.findElement(By.id("footer"));
js.executeScript(
"arguments[0].scrollIntoView({block: 'center'});",
element
);
scrollTo() moves to an absolute position, while scrollBy() moves relative to the current position.
Selenium 4 also provides native scrolling through the Actions API, including methods such as scrollToElement() and scrollByAmount().
JavaScript Click — A Fallback With a Cost
A JavaScript click can be executed like this:
js.executeScript(
"arguments[0].click();",
element
);
However, JavaScript clicking is different from performing a normal Selenium click.
A JavaScript click directly triggers the element's click handler.
It can bypass conditions that would prevent a real user from clicking the element, such as:
- An overlay covering the element
- An element being outside the viewport
- A hidden element
- A spinner blocking the page
- An element that appears disabled
- Other UI interaction problems
This means a test can pass with JavaScript while the actual user interaction is still broken.
What to Try Before JavaScript Click
Before using a JavaScript click, investigate the actual reason for the failure.
For example:
- Wait for the element to become clickable.
- Wait for an overlay or spinner to disappear.
- Scroll the element into view.
- Close a banner covering the element.
- Verify that the element is enabled.
- Check whether another element is intercepting the click.
See Element Not Clickable at Point.
JavaScript clicks are better reserved for setup or special situations where the actual browser interaction is not the behaviour being tested.
If you use one, document why it is necessary.
STAR Example
Situation
A Submit button in an automated workflow intermittently failed to click because a dynamically loaded footer banner was covering the button.
Task
The objective was to make the checkout automation reliable without hiding a genuine UI problem.
Action
I investigated the page layout and found that the banner was overlapping the button at certain viewport positions.
Instead of immediately replacing the normal Selenium click with JavaScript, I first scrolled the button into the centre of the viewport:
js.executeScript(
"arguments[0].scrollIntoView({block: 'center'});",
element
);
I then waited for the obstructing element to disappear before using the normal Selenium click().
The UI overlap was also treated as a product issue because a real user could experience the same problem on smaller screens.
Result
The automation became more stable while still preserving the ability of the test to detect the underlying UI problem.
The important lesson was that JavaScript should not automatically be used to bypass an interaction failure. The cause of the failure should be investigated first.
See how Selenium commands travel from the test to the browser in the Selenium WebDriver Visualizer.
For additional practice, see Actions & JavaScriptExecutor Exercises.
From Real Projects
In my automation roles I created, modified and enhanced Selenium WebDriver scripts in Java, organised with POM classes and a business library, and ran them with TestNG on Chrome and Firefox. Exercises like these build exactly the muscle memory you need when a real application — for me, Canolog's sales, inventory and service screens — throws a new kind of element at you. Cast the driver to JavascriptExecutor only where you really need it.
📚 Official documentation: MDN: JavaScript Guide · Selenium documentation
FAQs
What is upcasting in Selenium?
Upcasting means assigning an object of a child implementation to a parent or interface reference.
For example:
WebDriver driver = new ChromeDriver();
ChromeDriver is the actual object, while WebDriver is the reference type.
The conversion is implicit.
Why do we cast the driver to JavascriptExecutor?
executeScript() belongs to the JavascriptExecutor interface rather than the WebDriver interface.
Therefore, when you have:
WebDriver driver = new ChromeDriver();
you can access the JavaScript functionality by casting:
JavascriptExecutor js =
(JavascriptExecutor) driver;
The cast works because the underlying driver object implements JavascriptExecutor.
What is downcasting and when does it fail?
Downcasting converts a reference into a more specific type.
For example:
Animal animal = new Dog();
Dog dog = (Dog) animal;
This works because the actual object is a Dog.
But:
Animal animal = new Animal();
Dog dog = (Dog) animal;
can result in:
ClassCastException
The reason is that the actual object is not a Dog.
You can check safely with instanceof:
if (animal instanceof Dog dog) {
dog.bark();
}
How do you get a value back from executeScript?
Use the JavaScript return statement:
String title =
(String) js.executeScript(
"return document.title;"
);
Depending on the JavaScript value, you may receive:
StringLongDoubleBooleanWebElementListMap
Should I use JavaScript click?
Use it carefully and preferably as a fallback.
A JavaScript click can bypass normal user interaction conditions and can hide problems such as:
- Overlapping elements
- Incorrect waits
- Hidden elements
- Loading overlays
- Broken UI behaviour
Investigate the real cause of the click failure first.
How do you take a screenshot?
Cast the driver to TakesScreenshot:
File screenshot =
((TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE);
You can then copy the file into a project-specific screenshots directory.
What is the difference between executeScript() and executeAsyncScript()?
executeScript() runs JavaScript synchronously.
executeAsyncScript() is designed for asynchronous JavaScript and waits for the callback supplied to the script to indicate completion.