How to Write Stable Selenium Locators That Don't Break
The 7 rules for writing Selenium locators that survive UI changes. Real examples, anti-patterns to avoid, and a free selector generator that emits Page Object classes.

2026-07-17 · By Avinash K
Selenium suites don't fail because Selenium is broken — they fail because locators broke overnight when someone renamed a class or reordered a div. Here are the seven rules senior SDETs enforce in code review.
1. Prefer id, then data-* attributes
An id is the fastest and most stable locator. If none exists, ask developers to add a data-testid. Both survive Tailwind refactors, i18n renames, and component library upgrades.
driver.findElement(By.id("submit-btn")).click();
driver.findElement(By.cssSelector("[data-testid='pricing-pro']")).click();2. Never write absolute XPath
Any XPath that starts with /html/ is a bug waiting to ship. Refactor to relative XPath or CSS the moment you see one in a PR.
// BAD
driver.findElement(By.xpath("/html/body/div[2]/div[1]/form/button"));
// GOOD
driver.findElement(By.xpath("//form[@id='login']//button[normalize-space()='Sign in']"));3. Scope locators inside containers
When a page has repeated patterns (table rows, cards, list items), find the parent first and search within it. This avoids brittle nth-child selectors and gives clearer failure messages.
WebElement row = driver.findElement(By.xpath("//tr[td[normalize-space()='Acme Corp']]"));
row.findElement(By.cssSelector("button.delete")).click();4. Use Selenium 4 relative locators for edge cases
Selenium 4 shipped above(), below(), toLeftOf(), toRightOf(), and near(). Great for visually-defined elements without stable attributes.
WebElement password = driver.findElement(with(By.tagName("input")).below(emailField));5. Wrap everything in a Page Object
Never let a raw By.xpath(...) live inside a test method. When the DOM changes you want to update one file, not fifty tests. The free selector generator emits a ready-to-commit Page Object in Java, TypeScript, Python, or C#.
6. Add explicit waits, delete Thread.sleep
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.elementToBeClickable(By.id("submit-btn")));7. Review locators like you review production code
Add a "locator quality" checklist to your PR template: prefer id/data-testid, no absolute XPath, no Thread.sleep, no raw selectors inside tests, POM wrapping. Enforcing this for a single quarter typically cuts flake rate by 60%+.