44Selenium WebDriver
What you will master here
- WebDriver architecture: W3C protocol, browser drivers, JSON-wire → W3C shift
- What changed in Selenium 4 (W3C only, relative locators, CDP, new window API)
- Locators including Selenium 4 relative locators (above/below/near)
- Waits: implicit vs explicit (WebDriverWait + ExpectedConditions) vs fluent
- Dropdowns, alerts, frames, windows, Actions class
- POM, PageFactory, Selenium Grid 4, staleness, Selenium vs Playwright
44.1 WebDriver architecture
Selenium WebDriver is a client–server protocol. Your test (client binding) sends commands as HTTP requests to a browser driver (chromedriver, geckodriver), which translates them into native browser automation and returns JSON.
Test code → [HTTP + JSON] → Browser driver → [native API] → Browser (Java / Python / C# / JS) (chromedriver, geckodriver) (Chrome / Firefox)
Each command = one HTTP round trip. Historically used Selenium's own JSON Wire Protocol; now superseded by the W3C WebDriver standard. Same shape (HTTP+JSON) but standardised endpoints and capability negotiation across browsers.
44.2 What changed in Selenium 4
| Area | Selenium 3 | Selenium 4 |
|---|---|---|
| Protocol | JSON Wire + W3C dialect | W3C only |
| Locators | Standard By only | Adds relative locators (above/below/near) |
| DevTools | None | CDP access via getDevTools() |
| Windows/tabs | Manual handles | newWindow(WindowType.TAB) |
| Shadow DOM | Workarounds | getShadowRoot() |
| BiDi (experimental) | — | WebDriver BiDi — closer to Playwright's bidirectional events |
44.3 Setting up
// Maven dependency
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.27.0</version>
</dependency>
// Driver — Selenium Manager (built-in, no manual chromedriver download)
WebDriver driver = new ChromeDriver();
driver.get("https://example.com");
driver.quit();
44.4 Locators
// Standard
driver.findElement(By.id("user"));
driver.findElement(By.name("email"));
driver.findElement(By.className("btn-primary"));
driver.findElement(By.tagName("button"));
driver.findElement(By.linkText("Sign in"));
driver.findElement(By.partialLinkText("Sign"));
driver.findElement(By.cssSelector("input[type=email]"));
driver.findElement(By.xpath("//button[normalize-space()='Submit']"));
// Selenium 4 relative locators
import static org.openqa.selenium.support.locators.RelativeLocator.with;
By passwordField = By.id("password");
WebElement label = driver.findElement(with(By.tagName("label")).above(passwordField));
WebElement nearby = driver.findElement(with(By.tagName("button")).near(passwordField, 100));
44.5 Waits — the most-asked Selenium topic
Implicit wait
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.findElement(By.id("x")); // polls up to 10 s if not found
- Set once, applies to all findElement calls
- Polls only for presence, NOT visibility/clickability
- DON'T mix with explicit waits — non-deterministic timing
Explicit wait
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement el = wait.until(ExpectedConditions.elementToBeClickable(By.id("x")));
- Per condition; powerful + readable
- Use ExpectedConditions: presenceOfElementLocated, visibilityOf, elementToBeClickable, textToBe, invisibilityOf, alertIsPresent
// Fluent wait — custom polling + ignored exceptions
Wait<WebDriver> fluent = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(30))
.pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class);
WebElement el = fluent.until(d -> d.findElement(By.id("x")));
44.6 Common interactions
// Click, type
driver.findElement(By.id("user")).sendKeys("alice");
driver.findElement(By.cssSelector("button.primary")).click();
// Dropdown
Select select = new Select(driver.findElement(By.id("country")));
select.selectByVisibleText("India");
select.selectByValue("IN");
select.selectByIndex(2);
// Alerts
Alert alert = driver.switchTo().alert();
String msg = alert.getText();
alert.accept(); // alert.dismiss(); alert.sendKeys("...");
// Frames
driver.switchTo().frame("frame-name");
// ... do stuff ...
driver.switchTo().defaultContent();
// Windows / tabs
String original = driver.getWindowHandle();
driver.switchTo().newWindow(WindowType.TAB);
driver.get("https://other.com");
driver.switchTo().window(original);
for (String h : driver.getWindowHandles()) { /* iterate */ }
// Actions class — chained gestures
new Actions(driver)
.moveToElement(menu).pause(Duration.ofMillis(200))
.click(menuItem).keyDown(Keys.SHIFT).click(other).keyUp(Keys.SHIFT).perform();
// JavaScript executor
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].scrollIntoView(true);", el);
String title = (String) js.executeScript("return document.title;");
44.7 Page Object Model (Selenium-style)
public class LoginPage {
private final WebDriver driver;
private final By emailField = By.id("email");
private final By passwordField = By.id("password");
private final By submitBtn = By.cssSelector("button[type=submit]");
public LoginPage(WebDriver driver) { this.driver = driver; }
public DashboardPage signIn(String email, String pw) {
driver.findElement(emailField).sendKeys(email);
driver.findElement(passwordField).sendKeys(pw);
driver.findElement(submitBtn).click();
return new DashboardPage(driver);
}
}
// PageFactory variant
public class LoginPageFactory {
@FindBy(id = "email") private WebElement email;
@FindBy(id = "password") private WebElement password;
@CacheLookup @FindBy(css = "button[type=submit]") private WebElement submit;
public LoginPageFactory(WebDriver driver) {
PageFactory.initElements(driver, this);
}
}
@CacheLookup makes it worse (caches the WebElement). Many teams now skip PageFactory and use plain By locators + findElement on each call.44.8 Selenium Grid 4
Distributed Selenium execution. Hub-node or standalone modes.
# Start a hub java -jar selenium-server.jar hub # Register a node java -jar selenium-server.jar node --hub http://hub:4444 # Or single command for dev java -jar selenium-server.jar standalone
// Connect to remote
WebDriver driver = new RemoteWebDriver(
new URL("http://grid:4444"),
new ChromeOptions());
Selenium Grid 4 added containerised Docker support, observability via OpenTelemetry, and a Studio UI.
44.9 Staleness — the Selenium-only pain
WebElement is a handle to a found node. When the DOM re-renders, the handle becomes stale → StaleElementReferenceException. Mitigations:
- Don't cache WebElements long; re-find before each interaction
- Catch + retry pattern in helpers
- Use explicit waits that re-find each poll
- Avoid
@CacheLookup
44.10 Selenium vs Playwright — interview summary
| Concern | Selenium | Playwright |
|---|---|---|
| Protocol | HTTP + W3C WebDriver | WebSocket + CDP |
| Per-command latency | Higher (HTTP round trip) | Lower (multiplexed WS) |
| Auto-wait | Manual / explicit waits | Built-in actionability loop |
| Locator stability | WebElement can go stale | Locator re-resolves every action |
| Multi-tab / context | Manual window handles | BrowserContext + Page native |
| Network mocking | BiDi or proxy | Built-in page.route |
| Trace viewer | None built-in | First-class |
| Language reach | Everything (Java leader) | JS/TS leader, Python/Java/.NET |
| Ecosystem age | Mature, huge | Newer but rapidly growing |
Module 43 — Selenium Q&A
What's the WebDriver protocol?
Implicit vs explicit wait — when which?
What is FluentWait?
new FluentWait(driver).withTimeout(...).pollingEvery(...).ignoring(NoSuchElementException.class).until(d -> ...).What is StaleElementReferenceException?
What's new in Selenium 4?
newWindow(WindowType.TAB), getShadowRoot(), and the BiDi protocol (closer to Playwright's bidirectional model).What are relative locators?
with(By.tagName("input")).above(passwordField). Useful when there's no good attribute but a clear visual relationship. Brittle if the layout reflows.How do you handle dropdowns?
<select>, wrap in Select: new Select(driver.findElement(...)).selectByVisibleText("X"). For custom dropdowns (divs), click trigger then click option.How do you switch to an alert?
Alert alert = driver.switchTo().alert(); alert.accept(); or dismiss() or sendKeys() for prompts. Wrap with alertIsPresent() wait if timing is uncertain.How do you switch frames and back?
driver.switchTo().frame(name|index|element), do work, then driver.switchTo().defaultContent() (back to top) or parentFrame() (one level up).What is the Actions class?
new Actions(driver).moveTo(el).keyDown(Keys.SHIFT).click().perform(). Required for true mouse interactions like context-menu and drag-and-drop.What is PageFactory and what's its risk?
@FindBy annotations + PageFactory.initElements() to lazy-init WebElement fields. Risk: fields go stale when DOM re-renders. @CacheLookup caches and makes staleness inevitable. Modern Selenium code often skips PageFactory.How do you set up Selenium Grid 4?
selenium-server.jar standalone for dev, or hub-node mode for distributed: java -jar selenium-server.jar hub + node --hub http://hub:4444. Tests connect via new RemoteWebDriver(hubUrl, capabilities).What is Selenium Manager?
Selenium or Playwright for a new project?
How do you debug a "element not interactable" in Selenium?
wait.until(ExpectedConditions.elementToBeClickable(...)). Scroll into view first: js.executeScript("arguments[0].scrollIntoView(true)", el). Check for overlays (modal, sticky header) intercepting clicks.