Q23 of 37 · Selenium

How do you handle dynamic web elements with changing IDs?

SeleniumMidseleniumdynamic-elementslocatorsstability

Short answer

Short answer: Use stable attributes the framework doesn't generate — `data-test`, role, name, or text — instead of generated ids. If you must match a generated id, use partial matches (`[id^=user-row-]`, `contains(@id,'row-')`) anchored on the stable prefix.

Detail

Dynamic ids are most often a side-effect of UI frameworks: React's useId, MUI's mui-12345, Angular's cdk-overlay-2. The id changes between renders or releases — locators that hard-code it break.

The right answer is to stop relying on the id:

  1. Ask devs for data-test attributes. The single best move. [data-test=user-row-42] beats every other locator strategy and is the de-facto industry standard.

  2. Use stable user-facing attributes. Forms have name. Buttons have visible text. Roles (role=dialog, role=button) are stable across visual refactors.

  3. Use scoped, structural CSS:

By.cssSelector("[data-section=users] tr td:nth-child(2)")

When you can't get devs to add hooks:

// Starts-with — anchor on the stable prefix
By.cssSelector("[id^='user-row-']")

// Contains — partial match anywhere in the id
By.xpath("//div[contains(@id,'row-')]")

// Matches — full regex (xpath 2.0 only, niche)
By.xpath("//div[matches(@id, 'row-\\d+')]")

Anti-patterns to avoid:

  • Indexed selectors ((//tr)[3]) — break the moment a row is added.
  • Generated class names from CSS-in-JS (.css-1q2w3e4) — change every build.
  • getAttribute("id") then string-matching — pushes fragility from locator into code.

A cultural note: when you find yourself writing contains(@id, ...) patterns repeatedly, that's a signal the app needs test hooks. Push back rather than building elaborate workarounds.

// EXAMPLE

// ❌ Fragile — generated id changes each render
By.id("user-row-mui-12345")

// ✅ Anchor on the stable prefix
By.cssSelector("[id^='user-row-']")

// ✅ Better — use the framework-agnostic test hook
By.cssSelector("[data-test='user-row-42']")

// ✅ When CSS isn't enough — relative xpath via attribute,
//    not position
By.xpath("//div[contains(@class,'user-row') and @data-id='42']")

// WHAT INTERVIEWERS LOOK FOR

Naming data-test attributes as the cultural fix, comfort with starts-with/contains CSS and xpath patterns, and recognising indexed locators as the anti-pattern.

// COMMON PITFALL

Building ever-more-complex relative xpath to dodge generated ids when a five-minute conversation with devs about adding data-test attributes would solve it permanently.