QA Interview Hub · Knowledge base · Quizzes

Selenium + Java

Locators and waits

Locators: By, CSS versus XPath, relative locators

An element is found with driver.findElement(By...). The By strategies: id, name, className, tagName, linkText, partialLinkText, cssSelector, xpath.

Priority order for stable tests: a dedicated test attribute (data-testid) or a stable id → a meaningful CSS by role and structure → XPath only when CSS is not enough. Avoid long chains from the root (/html/body/div[3]/div[2]/button) and generated classes (.css-1x2y3z): they break with any markup change.

CSSXPath
Readabilityshorter, familiar to frontend developerswordier
Search by textnoyes: //button[normalize-space()='Sign in']
Move to parent/ancestorlimited: :has() selects a parent by its child (in all modern browsers)yes: .., ancestor::
Speedthe difference is unnoticeable in modern browsers— the same

Speed is not an argument: choose by convenience and stability.

driver.findElement(By.cssSelector("[data-testid='login-submit']")).click();
driver.findElement(By.xpath("//tr[td[text()='Ivan']]//button[@title='Delete']")).click();

findElement throws NoSuchElementException if the element is absent, while findElements returns an empty list. That is handy for checking absence.

Relative locators (Selenium 4) find an element by position relative to another: above, below, toLeftOf, toRightOf, near (within 50 pixels).

WebElement password = driver.findElement(
    RelativeLocator.with(By.tagName("input")).below(By.id("email")));

They help when an element has no reliable attributes, but relying on layout is more fragile than relying on an attribute, so this is a fallback. For shadow DOM there is element.getShadowRoot(), then a search inside the root.

Waits: implicit, explicit, fluent

A modern page changes asynchronously, so a test must wait for a state, not for time. Thread.sleep(5000) is the worst option: either too long (slow tests) or too short (flakes).

Selenium has three mechanisms:

  • Implicit wait is a global element lookup timeout for the whole session (0 by default): driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2)). It only waits for presence in the DOM, not visibility or clickability.
  • Explicit wait (WebDriverWait) waits for a specific condition, polling the page until the condition holds or the timeout expires (then TimeoutException).
  • FluentWait is the same with a configurable polling interval and exceptions to ignore.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement button = wait.until(ExpectedConditions.elementToBeClickable(By.id("pay")));
button.click();

Wait<WebDriver> fluent = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(15))
    .pollingEvery(Duration.ofMillis(300))
    .ignoring(NoSuchElementException.class, StaleElementReferenceException.class);

The condition can be an ExpectedConditions method (visibilityOfElementLocated, elementToBeClickable, invisibilityOfElementLocated, textToBe, urlContains) or a lambda d -> ....

Rule: the Selenium documentation explicitly says not to mix implicit and explicit waits: the total time becomes unpredictable (for example up to the sum of both timeouts). Choose explicit waits for controlled conditions and keep implicit at 0.

Separate from element waits there are the page load timeout (pageLoad) and the script timeout (script).

StaleElementReferenceException and other common interaction exceptions

A WebElement is not the element itself but a reference to it in the current DOM. If the page re-renders that node (a list refresh, an SPA transition, innerHTML), the reference goes stale and any action throws StaleElementReferenceException.

How to deal with it:

  • do not keep a WebElement for long; keep the By and locate the element before acting;
  • wait for a stable state: ExpectedConditions.refreshed(...), stalenessOf(old) before looking up the new one, or a FluentWait that ignores this exception;
  • do not swallow the exception in try/catch with an arbitrary sleep: that hides the cause.

Other common exceptions:

  • NoSuchElementException: the element is absent (not yet appeared, another iframe, a wrong locator);
  • ElementClickInterceptedException: another element took the click (a spinner, a modal, a sticky header); wait for the overlay to disappear (invisibilityOfElementLocated) or scroll the element into view;
  • ElementNotInteractableException: the element is in the DOM but cannot be used (hidden, zero size);
  • TimeoutException: an explicit wait did not get its condition;
  • SessionNotCreatedException: the driver and browser are incompatible or the browser did not start;
  • NoSuchSessionException: the session is already closed (quit()) or the browser crashed.

A JavascriptExecutor click (executeScript("arguments[0].click()", el)) bypasses interception but also hides the real problem: a user could not have made that click. It is a last resort with an explanation in code.