beginner20 min

Element Location: By Strategies and WebElement Behavior

The full menu of Selenium's By locator strategies, which ones actually hold up over time, and what a WebElement reference really represents.

What you'll learn

  • Choose the correct By locator strategy (id, css, xpath, and others) for a given situation
  • Rank locator strategies by real-world stability, and explain the reasoning
  • Explain why a WebElement reference can become invalid without the code visibly changing

Prerequisites

Explanation

Selenium's By class offers several distinct locator strategies: By.id("submit-button") (fastest, most direct — but only usable when a stable id attribute genuinely exists), By.name("email"), By.className("btn-primary"), By.tagName("button"), By.linkText("Sign in")/By.partialLinkText("Sign") (for <a> elements specifically, matched by their visible text), By.cssSelector("#login-form input[type='email']") (flexible, generally fast, familiar to anyone who's written CSS), and By.xpath("//button[contains(text(), 'Submit')]") (the most powerful and flexible — XPath can navigate up the DOM tree to a parent or ancestor, something CSS selectors cannot do at all, but that power comes with a real, honest cost: XPath expressions are frequently the hardest of any strategy to read, write correctly, and keep working as markup evolves).

Locator stability is a genuine, explicit spectrum worth reasoning about, similar in spirit to (though with different specific tools than) the locator-priority reasoning from browser-automation tools built around accessible-first locating: a stable id or a well-chosen data-testid-style attribute is the most resilient to unrelated changes; a CSS selector targeting a meaningful, purpose-specific class is next; and a deep, structural CSS selector or XPath expression describing exact DOM position (div > div:nth-child(3) > button, //div[3]/div[2]/button) is the least stable, breaking on almost any layout change unrelated to the element itself. Selenium doesn't have Playwright's built-in getByRole, but the same underlying principle — locate by something meaningful and stable, not by brittle structural position — applies just as strongly, and choosing a data-testid or a well-named class deliberately, specifically to support reliable test automation, is a common, worthwhile practice in real Selenium suites.

A WebElement returned by driver.findElement(...) is a live reference into the current DOM, not a snapshot of a value — and it can become stale (invalid) the moment the DOM it pointed into changes: a page navigation, a re-render triggered by JavaScript, or even the element being removed and an visually identical one added back in its place. Attempting to interact with a stale WebElement throws StaleElementReferenceException — a genuinely common, real error whose fix is almost never "wrap it in a try/catch and ignore it," but re-locating the element fresh (calling findElement again) after whatever DOM change occurred, since the old reference is permanently, unrecoverably invalid once the DOM it pointed to has changed underneath it.

Example

Modeling By-strategy stability ranking and WebElement staleness as data, mirroring the real reasoning behind both.

function locatorStabilityScore(strategy) {
  const scores = { id: 4, cssMeaningful: 3, cssStructural: 1, xpathStructural: 1 };
  return scores[strategy] ?? 2; // most other strategies land in a reasonable middle
}
console.log(locatorStabilityScore("id") > locatorStabilityScore("cssStructural")); // true

class FakeWebElement {
  constructor(domVersion) { this.domVersionAtCreation = domVersion; }
  isStale(currentDomVersion) { return this.domVersionAtCreation !== currentDomVersion; }
}

let domVersion = 1;
const element = new FakeWebElement(domVersion);
console.log(element.isStale(domVersion)); // false -- DOM hasn't changed since this reference was created

domVersion = 2; // simulates a re-render / navigation changing the DOM
console.log(element.isStale(domVersion)); // true -- this exact reference is now permanently invalid

Try it yourself

Create a NEW FakeWebElement after the DOM version changes, and confirm the fresh reference is not stale.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Write chooseByStrategy(elementDescription) modeling locator-strategy selection: return 'id' if elementDescription.hasStableId is true; else 'cssSelector' if elementDescription.hasMeaningfulClass is true; else 'xpath' if elementDescription.needsParentTraversal is true; else 'cssSelector' as a reasonable fallback.

Checks: prefers a stable id first · falls back to cssSelector for a meaningful class · chooses xpath specifically when parent traversal is needed

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write rankByStability(strategies) sorting an array of strategy strings ('id','cssMeaningful','cssStructural','xpathStructural') from MOST to LEAST stable using this lesson's scores (id:4, cssMeaningful:3, cssStructural:1, xpathStructural:1 -- unrecognized strategies score 2).

Checks: ranks strategies correctly by stability · does not mutate the input array

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Reaching for a deep structural XPath expression (`/html/body/div[2]/div[1]/button`) as a first choice -- this is the least stable possible locator, breaking on almost any unrelated layout change.
  • Storing a WebElement reference and reusing it across a page navigation or a significant DOM update -- the old reference becomes stale (invalid) the instant the DOM it pointed into changes, and interacting with it throws StaleElementReferenceException.
  • Catching StaleElementReferenceException and silently ignoring it, rather than re-locating the element fresh -- the old reference can never become valid again; the correct fix is always calling findElement again to get a current reference.

Knowledge check

Knowledge check

1. What can XPath do that a CSS selector fundamentally cannot?
2. What does it mean for a WebElement reference to become 'stale'?
3. What is the correct fix when code encounters StaleElementReferenceException?

Takeaway

Prefer stable, meaningful locators (id, a purpose-specific class or test attribute) over brittle structural CSS/XPath, reserving XPath specifically for when its unique upward-traversal capability is actually needed — and treat a WebElement as a live, DOM-state-bound reference that must be re-located, never reused, after the DOM it pointed into changes.

Summary

Selenium's By strategies (id, cssSelector, xpath, and others) trade off stability and power differently — id/meaningful-class locators are most resilient; structural CSS/XPath are least. XPath uniquely supports upward DOM traversal. A WebElement is a live DOM reference that becomes permanently stale after the underlying DOM changes, requiring a fresh findElement call, never a retry of the same reference.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.