Skip to content

Locator resolution

Every target you write in a test ("Sign in button", "email field", "user avatar") is plain English. At run time, the locator engine turns that into a Playwright selector that picks one specific DOM element. This page explains how.

For each step that has a target, the executor:

  1. Reads the elementType hint if present. button, link, input, checkbox, radio, select, text — these narrow what role of element to look for. The parser already normalizes common synonyms (btnbutton, dropdownselect, etc. — see YAML schema element types).

  2. Tries the role-based locator first. elementType: "button" with target: "Sign in" becomes Playwright’s page.get_by_role( "button", name="Sign in"). That’s the gold standard — it matches what a screen reader would say, ignoring DOM-layer noise.

  3. Falls back to a CSS or get_by_text match when role-based matching can’t find the element. For an input field, the engine looks at common shapes: <input>, <textarea>, [role="textbox"], [role="searchbox"].

  4. Last resort — fuzzy text search. If the literal text doesn’t match exactly, the engine tries case-insensitive contains, then partial substring matches. This is what makes “Sign in button” match <button>Sign In</button> (case mismatch) or even <button> Sign In Now </button> (extra whitespace + suffix).

The fuzzy fallback is intentionally generous. Tests written by non-engineers describe what they see, not what’s in the DOM; the engine bridges the gap.

elementTypeWhat it matchesFallback if no role hit
button<button>, role="button"Clickable <div> / <a> containing the text
link<a>, role="link"Any anchor-like element
input<input>, <textarea>, role="textbox", role="searchbox"Visible inputs near a matching label
checkbox<input type="checkbox">, role="checkbox"Compound locator: label + nearby checkbox
radio<input type="radio">, role="radio"Same pattern as checkbox
select<select>, role="combobox"Native and ARIA-combobox patterns
textAny element whose text matchesHeadings (h1-h6), paragraphs, labels

The exact resolution lives in backend/app/services/execution/locator_builder.py and contextual_locator.py — those are the source of truth if you ever need to dig deeper.

Traditional tests pin to CSS selectors (.btn-primary.large) or data-test-ids ([data-testid="submit"]). Both break the moment someone touches the DOM or the markup conventions.

Marriska’s locators pin to what the user sees: a button labeled “Sign in” stays “Sign in” across CSS refactors, design-system swaps, and even framework migrations. As long as the visible label hasn’t changed, the test still finds the element.

The cost: when labels change, tests do break. That’s a feature — “Submit” → “Pay now” is a behavioural change worth re-confirming the test for.