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.
The pipeline
Section titled “The pipeline”For each step that has a target, the executor:
-
Reads the
elementTypehint if present.button,link,input,checkbox,radio,select,text— these narrow what role of element to look for. The parser already normalizes common synonyms (btn→button,dropdown→select, etc. — see YAML schema element types). -
Tries the role-based locator first.
elementType: "button"withtarget: "Sign in"becomes Playwright’spage.get_by_role( "button", name="Sign in"). That’s the gold standard — it matches what a screen reader would say, ignoring DOM-layer noise. -
Falls back to a CSS or
get_by_textmatch when role-based matching can’t find the element. For aninputfield, the engine looks at common shapes:<input>,<textarea>,[role="textbox"],[role="searchbox"]. -
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.
Element types in detail
Section titled “Element types in detail”elementType | What it matches | Fallback 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 |
text | Any element whose text matches | Headings (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.
Why this matters for test stability
Section titled “Why this matters for test stability”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.
See also
Section titled “See also”- Step actions reference — every action and how it uses
target - Test YAML schema — the full set of fields a step can have
- How Marriska works — the broader pipeline this fits inside