đź§­ Selenium Selector Playbook

Static IDs, CSS mastery, and dynamic XPath patterns

Solid Static Hooks Beginner

The lowest maintenance selectors rely on attributes the UI team can guarantee. IDs, names, and data-test values should be your default.

Checklist

  • Reach for By.id before By.xpath.
  • Use By.name for forms when IDs are missing.
  • Create data-testid attributes in component libraries.
  • Avoid brittle selectors that depend on color, font, or parent order.
// SeleniumSelectorTest#seleniumStaticSelectors WebElement username = driver.findElement(By.id("static-username")); WebElement password = driver.findElement(By.name("static-password")); WebElement remember = driver.findElement(By.cssSelector("label.login-toggle input")); WebElement cta = driver.findElement(By.cssSelector("[data-test='primary-cta']")); username.sendKeys("architect@acme.dev"); password.sendKeys("TestAutomationFTW"); remember.click(); cta.click(); WebElement result = driver.findElement(By.id("cta-result")); assertEquals(result.getText().trim(), "CTA clicked via static selectors");

CSS Power Moves Intermediate

CSS selectors are fast and readable. Combine attribute matchers, parent scopes, and pseudo classes to avoid XPath unless necessary.

Selector Purpose Example
[data-row-id^='user-'] Prefix match for generated IDs. driver.findElement(By.cssSelector("[data-row-id^='user-']"))
li.feed-card:has(strong) CSS4 :has() supported via ChromeDriver. driver.findElement(By.cssSelector("li.feed-card:has(strong)"))
section.dynamic-feed button.promote-btn Scope by component to avoid duplicates. driver.findElements(By.cssSelector("section.dynamic-feed button.promote-btn"))
Chrome + Edge support :has(): Selenium 4 rides on Chromium DevTools, so modern CSS works out of the box. Use it when the DOM is otherwise uncooperative.

XPath Situations Advanced

XPath still shines when you need ancestry, order, or text functions not exposed in CSS. Keep expressions short, descriptive, and relative.

// SeleniumSelectorTest#seleniumDynamicSelectors driver.findElement(By.id("add-user")).click(); driver.findElement(By.id("add-user")).click(); WebElement pinnedCard = driver.findElement( By.xpath("//li[contains(@class,'feed-card')][.//strong[contains(text(),'Design System')]]") ); String promoteTarget = driver.findElements(By.cssSelector("button.promote-btn")).get(1).getText(); driver.findElements(By.cssSelector("button.promote-btn")).get(1).click(); String feedLog = driver.findElement(By.id("feed-log")).getText(); assertTrue(feedLog.startsWith("Promoted")); JavascriptExecutor js = (JavascriptExecutor) driver; String lastRow = (String) js.executeScript( "return document.querySelector('[data-row-id]:last-of-type').getAttribute('data-row-id');" ); assertTrue(lastRow.startsWith("user-"));
Guardrails:
  • Never start with //div[5]/div[2] - absolute paths break quickly.
  • Anchor XPath expressions with text or attributes.
  • Store XPath strings in constants so refactors touch one place.

Maintenance Toolkit Expert

  • Use data URLs for experimentation: the repository now includes SeleniumSelectorTest, which loads a sandbox DOM so you can iterate without hitting a real environment.
  • Wrap selectors in Page Objects: keep raw By definitions in one place and reuse across suites.
  • Log debug info: capture WebElement#getAttribute values whenever a selector fails so investigation is faster.
Pair with Playwright strategies: Comparing both frameworks helps teams choose the right tool per application. Jump to the Playwright guide for :has-text recipes.