Locators

A small grammar, deliberately biased toward how a person describes a control rather than where it sits in the DOM. Get these right and your spec survives a redesign.

The forms#

FormMatches
role=button[name="Save"]An ARIA role with an accessible name. The default and the most durable.
label=EmailA form control by its <label>, aria-label or aria-labelledby.
text="Total revenue"Any element by its visible text.
placeholder=Search…An input by its placeholder.
testid=row-3A data-testid, data-test-id or data-test attribute.
css=.pipeline > .card:first-childA raw CSS selector — the escape hatch.
testid covers the three common spellings. data-testid, data-test-id and data-test all resolve. Cypress-style data-cy still needs css=[data-cy="row-3"].

Exact, substring, or regex#

The quoting is the matching rule, and it is easy to miss:

Written asMeans
text="Save changes"Exact — quoted, whitespace-normalised, whole string.
text=SaveSubstring — bare, case-insensitive. Matches “Save changes” too.
text=/^Save/iRegex — delimited by slashes, with flags.

The same applies to a role’s name: role=button[name="Save"] is exact, role=button[name=Save] is a substring match.

When more than one element matches#

role=button[name="Delete"] >> nth=2      # zero-based

Prefer narrowing before counting: a nearby unique container plus a CSS chain usually reads better in six months than an index into a list whose order may change.

Iframes#

frame=#checkout >> role=button[name="Pay"]

Chain frame=SELECTOR >> to descend into a frame. Same-origin frames are pierced one level deep. Cross-origin iframes cannot be explored, and therefore cannot be addressed — Playhead is honest about that rather than half-working.

Shadow DOM#

Open shadow roots are pierced by the same engines capture uses, so a component-library app is as addressable as a plain one. Closed shadow roots are invisible to explore and to capture, which means some apps genuinely cannot be filmed.

Explore and capture must agree. Discovery reads the page from inside it while capture resolves locators through Playwright’s engines. Accessible names are computed the way the ARIA spec does, excluding aria-hidden subtrees — so a decorative icon inside a link never becomes part of its name ("Contacts", not "☰Contacts"). A locator that explore prints but capture cannot resolve is a bug in Playhead, not in your spec. Please report it.

Choosing well#

Do not write these by hand#

playhead explore prints every addressable element on the page as a ready-to-paste locator, ranked for stability, and playhead validate checks every locator in a spec against the live DOM with suggestions for the ones that miss. Authoring workflow →