Writing a spec

The promise is locators correct by construction. Every authoring path exists to keep that promise: the locator in your spec came from an element that really exists, and was checked against the live DOM before you kept it.

Never type a locator by hand#

A hand-written locator is a guess about an accessible name, and guesses are what make recorded demos rot. All three paths below read the real page first.

1 · Record yourself#

The best default. A browser opens, you drive your app the way a user would, and you press Enter in the terminal when you are done.

playhead record http://localhost:3000 -o demo.yaml

Each interaction becomes a step whose locator is chosen from the element you actually touched, ranked best-first for stability, and validated on the spot. Options: --title, --kind, --viewport and --storage-state.

What record observes today: clicks, form changes and Enter. Hovers, drags and keyboard shortcuts are not recorded yet — add those steps to the YAML afterwards, or use MCP authoring, which can act on anything.

2 · Let a coding agent write it#

Playhead has no model and needs no API key of its own. After npx playhead init, your editor’s agent drives Playhead over MCP: it opens a session, reads a grounded catalog of the current screen, acts one step at a time, and commits the spec it walked.

> Film creating a new deal, with narration, from localhost:3000

The rule the skill enforces: every locator passed to playhead_act must be copied verbatim from the catalog. The agent is the author; Playhead is the grounded compiler that refuses invented names. The MCP tool surface →

3 · Write it yourself, from a catalog#

For full control, print what the page can address and paste the lines you want.

playhead explore http://localhost:3000
# Acme Admin — http://localhost:3000/
# 42 interactable elements (stability: ● high · ◐ medium · ○ low)

## nav
  ● [link]   role=link[name="Deals"]
  ● [link]   role=link[name="Users"]

## dialog
  ◐ [input]  label=Deal name
  ● [button] role=button[name="Create deal"]

## focus targets (use as focus: on a step)
  ● testid=pipeline-board

Only the current screen is listed, so explore again after a navigation. The stability marks are advice: ● is a stable accessible name, ○ means the locator leans on something likely to change.

Explore the page render will actually meet. explore --spec tour.yaml inherits the spec’s session, environment and network rules and runs its setup: steps first, so the catalog describes the screen behind your consent dialog or login wall — not a cold anonymous load of it.

Scaffold from a URL#

playhead init specs/tour.yaml --url http://localhost:3000

A commented starter spec, pre-filled with locators that resolve on that page.

Then validate, always#

One browser pass, every locator in the spec, all failures reported together with suggestions:

playhead validate specs/tour.yaml
✗ search/3  expect role=searchbox[name="Search Wikipedia"]
    no matching element on the current screen
    did you mean: role=combobox[name="Search Wikipedia"]

✗ 1 error(s), 0 warning(s) across 11 locator check(s)

That is a real failure from the spec behind this site’s hero video: Wikipedia swaps its search input for a combobox once the box takes focus. validate caught it in seconds; capturing it would have cost a full render to find out.

Validation also covers framing and anchoring — a focus: target that cannot hold its subject, or a caption with nowhere to sit, is reported here rather than discovered in the finished video.

Behind a login#

playhead login https://app.example.com     # a real browser opens; you sign in
  ✓ session → playhead-session.json

Point the spec at it and every command reuses that session. Playhead never sees the credentials; only the resulting storage state is written.

app:
  url: https://app.example.com/deals
  storageState: playhead-session.json

House style for specs#

DoWhy
Group steps into scenes with an id and a titleScenes are the unit of pacing and of --from-scene iteration, and titles become on-screen chapters.
Write a caption for anything a viewer must readCaptions are numbered, laid out and overflow-checked at compose time.
Prefer expect over waitAn assertion documents intent and makes the verifier prove the outcome arrived.
Use focus: sparinglyThe planner already frames the target. Reach for focus when the payoff is somewhere else on screen.
Mark optional steps optional: trueCookie banners and first-run tips that may not appear stop being a coin flip.
Keep secrets in mask: true fieldsThe value stays in the spec file; the frames and the event log only ever see bullets.

Full spec reference →