Careeroza
Stable Locators and Smart Waits
Locator Stability Ladder
Prefer locators that developers have no reason to change. Agree with the developers on data-testid attributes for important elements.
<button id="add-to-cart" data-testid="add-to-cart" class="btn btn-primary mt-2 css-1x8k2">
Add to Cart
</button>| Locator | Stability |
|---|---|
By.cssSelector("[data-testid='add-to-cart']") | best: exists only for tests |
By.id("add-to-cart") | very good |
By.cssSelector("button.btn-primary") | medium: styling classes change |
By.xpath("//button[text()='Add to Cart']") | medium: text changes, translations |
By.xpath("/html/body/div[3]/div[2]/button") | worst: breaks on any layout change |
.css-1x8k2 | never: generated class names change every build |
Wait for the Right Condition
// Bad: fixed sleep, slow and still flaky
Thread.sleep(3000);
driver.findElement(By.id("checkout")).click();
// Good: wait for the spinner to go away, then for the button to be clickable
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".loading-spinner")));
wait.until(ExpectedConditions.elementToBeClickable(By.id("checkout"))).click();
// Good: wait for the data, not just the element
wait.until(ExpectedConditions.textToBe(By.id("cart-count"), "2"));Tip: Wait for what the user waits for: the spinner gone, the count updated, the URL changed (urlContains("/orders/")). Waiting only for "element present" is the cause of many flaky clicks.
Handling Stale Elements
// The product list re-renders after sorting; old WebElement references become stale.
public List<String> productNamesAfterSort(String option) {
new Select(driver.findElement(By.id("sort"))).selectByVisibleText(option);
wait.until(ExpectedConditions.stalenessOf(driver.findElement(By.cssSelector(".product-card"))));
return driver.findElements(By.cssSelector(".product-card .name")) // find again
.stream().map(WebElement::getText).toList();
}