Locators
Intent first: start with what users see
Section titled “Intent first: start with what users see”In OrbitTest, the default way to target an element is the text a user would use to find it. No selector syntax at all:
await orbit.click("Login");await orbit.type("Email", "user@example.com");await orbit.hover("Menu");For type(), OrbitTest resolves the field by its label, placeholder, name attribute, or accessible text, in other words, by all the clues a user has. This keeps tests aligned with behavior: a refactor that changes class names cannot break them, but a real change to the user-facing label will, and should.
The five explicit locator types
Section titled “The five explicit locator types”Sometimes text is ambiguous: three “Edit” buttons on one screen, or an icon button with no text at all. That is when you reach for an explicit locator:
| Locator | Example | Best for |
|---|---|---|
| Role | orbit.getByRole(“button”, “Login”) | Accessible elements; survives markup changes; also tests accessibility. |
| Attribute | orbit.getByAttribute(“data-testid”, “submit”) | Stable hooks the team controls, like data-testid. |
| CSS | orbit.css(“#login-btn”) | When you know the markup and need precision. |
| XPath | orbit.xpath(“//button[text()=‘Login’]“) | Structural queries CSS cannot express. |
| Object literal | { css: “#login” } or { role: “button”, name: “Login” } | Building locators dynamically in helpers. |
await orbit.click(orbit.getByRole("button", "Login"));await orbit.type(orbit.getByAttribute("name", "email"), "user@example.com");await orbit.click(orbit.css("#login"));await orbit.waitFor(orbit.xpath("//h1[contains(text(), 'Dashboard')]"));Explicit locators in action.
Choosing well: a practical order
Section titled “Choosing well: a practical order”When plain text is not enough, work down this list and stop at the first one that fits:
- Role + name (
getByRole("button", "Submit")): closest to user intent, and doubles as a basic accessibility check. - A test ID (
getByAttribute("data-testid", "submit-form")): completely stable, because the team owns the attribute. Worth adding to the product code when a screen is automation-heavy. - A semantic attribute that already exists:
name,aria-label,title. - CSS, scoped as tightly as possible.
- XPath, as the last resort.
The locators to avoid are the ones tied to layout: div > div:nth-child(3) > span, generated class names like .css-1x2y3z, or anything that encodes how deep an element happens to sit in the DOM today. These fail during harmless refactors and train your team to ignore red builds.
Multiple matches and ambiguity
Section titled “Multiple matches and ambiguity”If a text target matches several elements, make the locator more specific rather than hoping the first match is right. Role narrowing usually resolves it:
// Ambiguous: "Delete" appears on a button and in a headingawait orbit.click("Delete");
// Specific: only the buttonawait orbit.click(orbit.getByRole("button", "Delete"));When you need all the matches, not one, use orbit.all(); that workflow has its own page: Working with Elements.
Frequently asked questions
Section titled “Frequently asked questions”Are text matches case-sensitive?
Section titled “Are text matches case-sensitive?”Match the visible text as the user sees it. If you are unsure what the page really renders, read it back with orbit.text() or orbit.visibleText() and assert on that.
Should I ask developers to add data-testid attributes?
Section titled “Should I ask developers to add data-testid attributes?”For screens with heavy automation, yes, it is a small change that buys total selector stability. But try role and label locators first; if those work, you need nothing from the markup.
Do explicit locators work everywhere text does?
Section titled “Do explicit locators work everywhere text does?”Yes. Anything that accepts a text target (click, type, waitFor, exists, text readers) also accepts any locator object.
Related pages
Section titled “Related pages”- Working with Elements — Clicking, typing, reading text with three text readers, waiting for state, and iterating collections with orbit.all().
- Frames & Shadow DOM — Scope tests into iframes and open or closed shadow roots with orbit.frame() and orbit.shadow(), including nested paths.
- Test Basics — How to structure readable end-to-end tests: arrange-act-assert, the orbit object, per-test options, and strong assertions.
- Visual Automation — Test canvas, WebGL, and pixel-rendered UIs with coordinate actions, snapshots, pixel assertions, and color search.