VLearnVibium

Selector Best Practices in Vibium

Selector best practices in Vibium: prefer semantic finds (role, text, label, testid) over brittle CSS, scope your locators, and lean on auto-waiting.

By Pramod Dutta··4 min read·Verified with Vibium 26.2
▶ Animated overview · made with Remotion

The best selectors in Vibium are semantic ones — role, text, label, placeholder, and testid — because they match elements the way a user (or screen reader) perceives them rather than how the markup happens to be nested today. Vibium's find() accepts a CSS string, a Playwright-style XPath, or semantic keyword arguments, and you can combine them. Semantic finds resist redesigns: a button still has role="button" and the text "Submit" even after a CSS refactor renames .btn-primary-v2. Vibium also resolves the shortest matching text node via its pickBest() heuristic, so find(role="button", text="Submit") prefers the real button over a paragraph that merely mentions "Submit." Because Vibium auto-waits for elements to become actionable before acting, you do not need to harden selectors with manual retries. This guide covers the order you should reach for selectors, how to scope them, and the anti-patterns that cause flake.

Why are CSS selectors brittle?

CSS and XPath selectors break because they encode implementation details, not intent. A selector like div.card > div:nth-child(3) > button.btn.btn-sm depends on the exact DOM tree, the class names your build tool generates, and the position of an element among its siblings. Any of those can change in a routine UI tweak, and your automation silently fails.

Vibium still supports CSS — it is the right tool for genuinely stable hooks like #login or [data-testid="checkout"]. The problem is positional and style-coupled selectors. As a rule: if a designer changing a color or reordering a list would break your selector, the selector is too brittle.

What selectors should I prefer in Vibium?

Reach for selectors in this order of durability:

  1. testid — an explicit data-testid your team controls. The most stable hook because it exists only for automation.
  2. role + text — accessible role plus visible label. Matches user intent and doubles as an accessibility signal.
  3. label / placeholder — for form fields, target the visible label or placeholder text.
  4. Stable CSS IDs / data attributes#email, [data-cy="..."].
  5. CSS classes / XPath — last resort, only when nothing above exists.

Here is the same login field expressed from most to least durable:

from vibium import browser_sync as browser
 
vibe = browser.launch()
vibe.go("https://practice.example.com/login")
 
# Best: semantic, intent-based
vibe.find(testid="email").type("user@example.com")
vibe.find(label="Email address").type("user@example.com")
vibe.find(role="button", text="Sign in").click()
 
# Acceptable: stable hooks
vibe.find("#email").type("user@example.com")
 
# Avoid: positional / style-coupled
vibe.find("form > div:nth-child(2) input.field-lg").type("user@example.com")
 
vibe.quit()

See the full find element reference for every supported parameter, and type text for input handling.

How do I write resilient selectors for repeated structures?

Scope your finds to a container when a page repeats the same structure — product cards, table rows, list items. Without scoping, find("button") is ambiguous across dozens of identical "Add to cart" buttons. Vibium's find() supports combining a CSS selector scope with semantic parameters, so you can say "the Add to cart button inside this card."

# Find all product cards, then act within one
cards = vibe.findAll(".product-card")
print(f"Found {len(cards)} cards")
 
# Scope a semantic find to a specific container
vibe.find(selector=".product-card.featured", role="button", text="Add to cart").click()

This pattern keeps each selector unambiguous without resorting to fragile nth-child chains. For a full walkthrough of looping over results, see paginate results.

Do I still need waits with good selectors?

No — Vibium auto-waits, so a correct selector plus a normal action is usually all you need. Before every click, type, or fill, Vibium polls until the target is actionable: visible, stable (its box stops moving), receiving events (not covered by an overlay), and enabled. The default timeout is 30 seconds, and you can tune it per action.

# Vibium waits up to 30s for this to become clickable — no manual sleep
vibe.find(role="button", text="Submit").click()
 
# Tighten the timeout when you expect a fast page
vibe.find("#submit").click(timeout=5000)

This is why brittle selectors, not missing sleeps, are the usual root cause of flake in Vibium. If your selector is unambiguous and stable, actionability handles the timing. To go deeper on the mechanics, read how actionability works.

A quick checklist

  • Prefer testid, then role+text, then label/placeholder, then stable IDs.
  • Never select by generated class names or nth-child position.
  • Scope finds to a container for repeated UI.
  • Let auto-waiting handle timing; don't paper over bad selectors with sleeps.
  • Treat a role/label selector as free accessibility coverage.

Durable selectors are the single biggest lever for stable automation. Pair them with Vibium's auto-waiting and most flake disappears. Next, see how to write flake-free Vibium tests and the find element command reference.

Frequently asked questions

What is the best selector strategy in Vibium?

Prefer Vibium's semantic finds — role, text, label, placeholder, and testid — over raw CSS or XPath. They match elements the way users and screen readers perceive them, so they survive markup changes. Reserve CSS selectors for stable IDs and data attributes.

Does Vibium support XPath selectors?

Yes. Vibium accepts an xpath parameter in find(), alongside CSS strings and semantic kwargs like role and text. XPath is supported but discouraged for most cases because deep positional paths break easily; a data-testid or role match is usually more durable.

How do I avoid flaky selectors in Vibium?

Target stable hooks (data-testid, role, label text) instead of generated class names or nth-child positions. Vibium auto-waits for actionability, so you rarely need manual sleeps. Scope finds to a container when pages repeat structures like cards or table rows.

Vibium is created by Jason Huggins. This is an independent tutorial — see the official Vibium site and GitHub repo for canonical docs.

Related guides