SoftwareTestPilot
Automation TestingPublished: 8 min read

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.

Avinash K
Founder & QA Engineer at SoftwareTestPilot
Share:XLinkedInWhatsApp
Stable Selenium locator patterns for QA engineers
Stable Selenium locator patterns for QA engineers

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%+.

Frequently asked questions

1.What is the most reliable Selenium locator?
By.id followed by data-* attributes. Both are explicitly owned by developers and survive visual refactors.
2.Should I use CSS or XPath in Selenium?
Use CSS by default; use XPath only when you need text matching or axis traversal.
3.How do I handle dynamic ids?
Use contains() in XPath or [id^='prefix'] in CSS. Better: ask developers for a data-testid.
4.How do I test my locators before running the suite?
Paste HTML into the free selector generator at /tools/selector-generator — it validates locators against the DOM and shows matches instantly.