Selenium WebDriver

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.

Architecture contrastWebDriver pays an HTTP round trip per command and can only poll for state. That single fact explains why per-command latency is higher than Playwright's WebSocket+CDP and why every wait must be written explicitly.

44.2 What changed in Selenium 4

AreaSelenium 3Selenium 4
ProtocolJSON Wire + W3C dialectW3C only
LocatorsStandard By onlyAdds relative locators (above/below/near)
DevToolsNoneCDP access via getDevTools()
Windows/tabsManual handlesnewWindow(WindowType.TAB)
Shadow DOMWorkaroundsgetShadowRoot()
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")));
The classic interview trapMixing implicit + explicit waits causes unpredictable timing — implicit polls inside the explicit wait. Pick one (explicit preferred); don't mix.

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);
  }
}
PageFactory caveatThe proxy fields go stale on re-renders → StaleElementReferenceException. @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:

44.10 Selenium vs Playwright — interview summary

ConcernSeleniumPlaywright
ProtocolHTTP + W3C WebDriverWebSocket + CDP
Per-command latencyHigher (HTTP round trip)Lower (multiplexed WS)
Auto-waitManual / explicit waitsBuilt-in actionability loop
Locator stabilityWebElement can go staleLocator re-resolves every action
Multi-tab / contextManual window handlesBrowserContext + Page native
Network mockingBiDi or proxyBuilt-in page.route
Trace viewerNone built-inFirst-class
Language reachEverything (Java leader)JS/TS leader, Python/Java/.NET
Ecosystem ageMature, hugeNewer but rapidly growing

Module 43 — Selenium Q&A

What's the WebDriver protocol?
Client–server HTTP/JSON protocol. The W3C-standardised version replaced Selenium's older JSON Wire Protocol. Each command from the test binding is a separate HTTP request to a browser driver, which translates to native browser APIs.
Implicit vs explicit wait — when which?
Implicit applies globally to all findElement calls — only checks presence. Explicit waits a specific condition (visible, clickable, text matches) on a specific locator. Always prefer explicit; never mix the two (timing becomes unpredictable).
What is FluentWait?
Customisable explicit wait — choose polling interval and which exceptions to ignore mid-wait. Useful for non-standard conditions: new FluentWait(driver).withTimeout(...).pollingEvery(...).ignoring(NoSuchElementException.class).until(d -> ...).
What is StaleElementReferenceException?
Thrown when you act on a WebElement whose underlying DOM node has been re-rendered/replaced. Fix: re-find the element before each interaction, or catch+retry. Don't cache WebElements across actions.
What's new in Selenium 4?
W3C-only protocol (no JSON Wire), relative locators (above/below/near), CDP access via getDevTools(), newWindow(WindowType.TAB), getShadowRoot(), and the BiDi protocol (closer to Playwright's bidirectional model).
What are relative locators?
Selenium 4 feature for finding elements by spatial relationship: 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?
For native <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?
Builder for chained input gestures — hover, drag, key combos, double-click. 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?
A pattern using @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?
Run 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?
Built into Selenium 4.6+: automatically downloads + manages browser driver binaries. No more manual chromedriver downloads or third-party WebDriverManager dependency.
Selenium or Playwright for a new project?
For greenfield JS/TS web automation, Playwright wins on speed, auto-wait, trace viewer, network mocking. For Java enterprise shops with existing TestNG/REST Assured/Allure infra, Selenium 4 + WebDriver BiDi is solid. Pick based on language/team familiarity, not raw capability.
How do you debug a "element not interactable" in Selenium?
Wait for clickable (not just present): wait.until(ExpectedConditions.elementToBeClickable(...)). Scroll into view first: js.executeScript("arguments[0].scrollIntoView(true)", el). Check for overlays (modal, sticky header) intercepting clicks.