Selenium WebDriver is the de‑facto standard for browser automation on the JVM, but the gap between a script that technically works and one that is reliable, debuggable, and fast is filled with small techniques you mostly learn the hard way. Below are six of them. Each tip explains the underlying problem, gives working Java code, and points to the official documentation so you can dig deeper.
All of these assume a recent Selenium 4 (or newer) setup. If you are still pulling the dependency by hand, the canonical artifact lives on Maven Central as org.seleniumhq.selenium:selenium-java, and the project home — with downloads, the changelog, and the full API reference — is selenium.dev. The Java‑specific documentation starts at selenium.dev/documentation.
1. Highlight the element you are about to touch
When a test misbehaves, the first question is usually "did WebDriver actually find the element I think it found?" A quick way to answer that visually is to draw a border around the element before interacting with it. Because WebDriver exposes the browser's JavaScript engine through the JavascriptExecutor interface, you can inject a temporary inline style:
public void highlightElement(WebElement element) {
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript(
"arguments[0].setAttribute(arguments[1], arguments[2]);",
element,
"style",
"border: 2px solid yellow; color: yellow; font-weight: bold;"
);
}
The script receives the element as arguments[0] and overwrites its style attribute. It is most useful during local debugging; in a headless CI run there is nothing to look at, so you would normally guard the call behind a debug flag. If you want the highlight to disappear afterward, capture the original style value first and restore it once the interaction is done.
2. Check whether a piece of text is present on the page
Older Selenium RC offered a convenient isTextPresent helper. WebDriver dropped it, and testers migrating from RC tend to miss it — but it takes only a couple of lines to reproduce. The page's full serialized HTML is available through WebDriver.getPageSource(), so a substring check does the job:
public boolean isTextPresent(String text) {
return driver.getPageSource().contains(text);
}
Two caveats worth knowing. First, getPageSource() returns the source, which may include text inside attributes, scripts, or hidden nodes — so a match does not guarantee the text is visible. Second, on dynamic pages the text might not have rendered yet; if you need to wait for it to appear, prefer the explicit‑wait condition ExpectedConditions.textToBePresentInElementLocated over scraping the whole page.
3. Never hard‑code a sleep while waiting for an element
A common antipattern is pausing the thread for a fixed duration and hoping the element shows up in time:
Thread.sleep(1000 * 3 * 60); // waits the FULL three minutes, always
driver.findElement(By.id("username"));
The problem is that Thread.sleep always blocks for the entire interval, even if the element appeared after one second. Multiply that waste across a suite of hundreds of tests and your run time balloons. Worse, if you set the value too low the test becomes flaky.
The right tool is an explicit wait, which polls the page and continues the moment a condition becomes true (or fails after a maximum timeout). In Selenium 4 the timeout is expressed as a java.time.Duration rather than a bare number of seconds:
import java.time.Duration;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
WebElement element = new WebDriverWait(driver, Duration.ofMinutes(3))
.until(ExpectedConditions.presenceOfElementLocated(By.id("username")));
This waits up to three minutes but returns as soon as the element is in the DOM. For more control over the polling interval and which exceptions to ignore, look at FluentWait. The official guide to all of this is the Waiting Strategies page, which also explains why you should avoid mixing implicit and explicit waits — combining them can produce unpredictable, additive timeouts.
4. When the native click doesn't fire, fall back to JavaScript
Occasionally element.click() does nothing, throws, or never returns control — typically because the element is overlapped, animating, or detached and re‑attached by a framework. As a workaround you can dispatch the click directly through the DOM:
public void javascriptClick(WebElement element) {
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].click();", element);
}
Treat this as a fallback rather than a default. A native WebDriver click simulates a real user — it scrolls the element into view, respects pointer events, and fails loudly when the element genuinely isn't interactable. A JavaScript click bypasses all of that, which means it can "succeed" against an element a real user could never reach, hiding a genuine bug. Reach for it only after you have confirmed the element really should be clickable (often by adding an elementToBeClickable wait first).
5. Suppress the file‑download dialog in Firefox
WebDriver cannot drive native OS dialogs, and Firefox's "Save / Open file?" prompt is a classic example. The way around it is to configure the browser not to ask in the first place, by setting download‑related preferences. In Selenium 4 these live on FirefoxOptions via addPreference, and you launch the driver with those options:
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
FirefoxOptions options = new FirefoxOptions();
options.addPreference("browser.download.dir", "D:\\downloads");
options.addPreference("browser.download.folderList", 2); // 2 = use the custom dir above
options.addPreference("browser.helperApps.neverAsk.saveToDisk",
"application/zip,application/octet-stream");
options.addPreference("browser.download.manager.showWhenStarting", false);
WebDriver driver = new FirefoxDriver(options);
What each preference does, briefly: folderList = 2 tells Firefox to use the directory you specify; browser.download.dir is that directory; neverAsk.saveToDisk is a comma‑separated list of MIME types Firefox will save silently instead of prompting for; and disabling showWhenStarting keeps the download manager from popping up. The trickiest part is getting the MIME type right — it must match the Content-Type the server actually sends, so inspect the response if downloads still prompt.
(If you prefer to keep an existing FirefoxProfile, you can set the same preferences on it and attach it with options.setProfile(profile).)
6. Override the Firefox user agent
Sometimes you want to see how a site responds to a mobile browser without provisioning a real device. Because most servers branch on the User-Agent request header, spoofing that header is often enough to get the mobile layout. You can do it with the general.useragent.override preference:
FirefoxOptions options = new FirefoxOptions();
options.addPreference("general.useragent.override",
"Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0");
WebDriver driver = new FirefoxDriver(options);
Keep the limits in mind: this changes only the header string. It does not alter the viewport size, touch‑event support, device‑pixel ratio, or any of the other signals a responsive site may use, so it is no substitute for genuine device or emulator testing. For a current, realistic value, copy a real UA string rather than the dated example above — the structure of the header is documented by MDN under User-Agent.
Further reading
- Selenium project & downloads: https://www.selenium.dev/
- WebDriver documentation: https://www.selenium.dev/documentation/webdriver/
- Waiting strategies (implicit, explicit, fluent): https://www.selenium.dev/documentation/webdriver/waits/
- Full Java API reference: https://www.selenium.dev/selenium/docs/api/java/
- Maven artifact: https://central.sonatype.com/artifact/org.seleniumhq.selenium/selenium-java
None of these tricks is a substitute for well‑structured locators and proper synchronization, but together they cover the rough edges you hit most often: seeing what your script touched, waiting without wasting time, getting past stubborn clicks and native dialogs, and shaping the browser's identity for the scenario under test.