Real renders, with the receipts they came with.
Every video on this page was produced by the CLI on an ordinary laptop, and every number beside it is read straight out of the artifacts that render wrote — compose-manifest.json and verify/verdict.json. Including the runs that did not pass, because that is the part of the product that actually matters.
Login, sort, product page, cart, checkout — on a store nobody here controls
Sauce Demo end to end: a real login, a native sort control, a product detail page, two add-to-carts, and a confirmed order. Filmed live on saucedemo.com with no fixtures.
- output
- 1:251920×1080 · 30fps · 5.5 MB
- capture
- 169 frames65.0s of source · viewport 1280×800
- steps
- 2325 camera keyframes · 8 click ripples
- narration
- 23 lineskokoro · voice heart · −16 LUFS
- verdict
- publishable13/13 actions pixel-verified
- video sha256
- 0b4ed39080fc7658770aac9a…playhead 0.4.0 · win32-x64
The spec that produced itsaucedemo-checkout.yaml
# Homepage flagship: Playhead capabilities on Sauce Demo.
# One cart item + taller viewport keep Finish framed (no bottom-edge ghost click).
playhead: 1
title: Product video from a spec, on a real store
subtitle: Playhead capabilities · filmed live on saucedemo.com
app:
url: https://www.saucedemo.com/
viewport: 1280x800
navigation: domcontentloaded
environment: { timezone: UTC, locale: en-US }
settle: { idleMs: 400, capMs: 5000 }
output:
kind: walkthrough
aspect: "16:9"
pacing: brisk
dwellScale: 0.65
camera: normal
audio:
narration: tts
provider: kokoro
voice: heart
rate: 118
sfx: true
endCard:
title: Filmed by Playhead
subtitle: Spec in · verified MP4 out
theme:
accent: "#6d7cff"
captions: bottom
scenes:
- id: signin
title: Real app, real login
steps:
- expect:
target: 'role=button[name="Login"]'
visible: true
caption: "YAML in · verified video out"
narration: "A YAML spec becomes a finished product video on a real public store."
focus: wide
- type:
target: 'placeholder=Username'
text: standard_user
caption: "Grounded locators"
narration: "Locators are grounded in the live DOM. Keystrokes are real."
focus: target
- type:
target: 'placeholder=Password'
text: secret_sauce
caption: "Real validation"
narration: "The app validates the input — nothing is painted onto frames."
focus: target
- click: 'role=button[name="Login"]'
caption: "Real navigation"
narration: "A real browser navigation, not a faked slide."
focus: target
shot: cut
- wait:
for: 'text="Products"'
state: visible
timeout: 20000
caption: "Structured wait"
narration: "Wait on the screen you need — never a guessed sleep."
focus: wide
- id: browse
title: Native controls
steps:
- select:
target: 'testid=product-sort-container'
value: Price (high to low)
caption: "Native select"
narration: "Playhead drives the real native select behind the styled menu."
focus: target
- click: 'text="Sauce Labs Backpack"'
caption: "Visible effect required"
narration: "Verification requires a visible change, not just a dispatched click."
focus: target
shot: cut
- id: product
title: Capture once
steps:
- wait:
for: 'testid=add-to-cart'
state: visible
timeout: 20000
caption: "Cross-page capture"
narration: "Capture is the only stage that touches the product."
focus: wide
- click: 'testid=add-to-cart'
caption: "Add to cart"
narration: "Re-compose later for new captions or voice — never re-open the store."
focus: target
- click: 'testid=shopping-cart-link'
caption: "Test id when unnamed"
narration: "No accessible name? Use the test id from playhead explore."
focus: target
shot: cut
- id: cart
title: Cart
steps:
- wait:
for: 'testid=checkout'
state: visible
timeout: 20000
caption: "App state on film"
narration: "Cart state is the app's — not an overlay."
focus: wide
- click: 'testid=checkout'
caption: "Checkout"
narration: "One capture, many cuts — docs, PR, or release notes."
focus: target
shot: cut
- id: details
title: Forms
steps:
- wait:
for: 'testid=firstName'
state: visible
timeout: 20000
caption: "A real form"
narration: "The app's validation still applies."
focus: wide
- type:
target: 'testid=firstName'
text: Priya
caption: "First name"
narration: "Real keystrokes into first name."
focus: target
- type:
target: 'testid=lastName'
text: Raman
caption: "Last name"
narration: "Last name."
focus: target
- type:
target: 'testid=postalCode'
text: "560001"
caption: "Postal code"
narration: "Postal code."
focus: target
- click: 'testid=continue'
caption: "Continue"
narration: "Totals are filmed from the app, never invented."
focus: target
shot: cut
- id: overview
title: Frame the click
steps:
# Wait on copy that proves the overview painted — Finish alone can resolve on a
# still-white shell and get filmed as a blank content region.
- wait:
for: 'text=Total:'
state: visible
timeout: 20000
caption: "Overview painted"
narration: "Overview is on screen with the store's own totals."
focus: wide
- wait:
for: 'testid=finish'
state: visible
timeout: 20000
caption: "Finish in view"
narration: "Finish is framed — target-in-frame is a hard check."
focus: 'testid=finish'
- hover: 'testid=finish'
caption: "Camera on the button"
narration: "Camera tracks measured geometry so the click is readable."
focus: target
- click: 'testid=finish'
caption: "Place the order"
narration: "Cursor-on-target confirms the hit in the delivered pixels."
focus: target
- id: done
title: Publishable verdict
steps:
- wait:
for: 'text="Thank you for your order!"'
state: visible
timeout: 25000
caption: "Confirmation"
narration: "Order confirmed on the live store."
focus: wide
- expect:
target: 'text="Thank you for your order!"'
visible: true
caption: "Verdict: publishable"
narration: "Ten checks on the delivered video. Exit zero only when publishable — product video as a build artifact."
focus: wide
The contact sheet verification wroteverify/contact-sheet.png

What each check assertedplayhead/checks@4
| encode | the file is a real, decodable H.264 video of the planned length |
| target-in-frame | every action target was fully inside the frame, and large enough to read |
| cursor-on-target | the rendered cursor was on the element at the moment of each click |
| action-effect | each action visibly changed the UI, compared frame to frame |
| output-pixels | the delivered pixels match the region the plan promised |
| frozen-blank | no frozen stretches and no blank content regions |
| captions | every caption fit its card without being shrunk or truncated |
| pacing | no step is too fast to follow |
| masks | masked regions are opaque in the output and absent from the log |
| audio | each spoken line is audible inside its own window at target loudness |
A product flow with a modal, a masked field and a toast
The pipeline's own demo app, built to exercise the hard parts: a dialog in the browser top layer, a redacted password, network latency and a success toast. Cursor and click now land on the same beat as the UI reaction.
- output
- 1:121920×1080 · 30fps · 3.1 MB
- capture
- 378 frames38.5s of source · viewport 1280×720
- steps
- 125 camera keyframes · 4 click ripples
- narration
- 12 lineskokoro · voice heart · −16 LUFS
- verdict
- publishable6/7 actions pixel-verified
- video sha256
- 51533ce2968922f23d20c193…playhead 0.4.0 · win32-x64
The spec that produced itacme-onboarding.yaml
# The product-flow example: a modal in the browser top layer, a redacted
# password that never reaches a frame, a masked environment banner, and a
# success toast — narrated, with the camera framing each action.
playhead: 1
title: Onboard a teammate in Acme Admin
subtitle: Account live in under a minute
app:
url: http://localhost:4173
viewport: 1280x720
environment: { timezone: UTC, locale: en-US, fixedTime: "2026-01-01T12:00:00Z" }
output:
kind: walkthrough
aspect: "16:9"
pacing: normal
dwellScale: 1.1
audio:
narration: tts
provider: kokoro
voice: heart
rate: 105
sfx: true
endCard:
title: Acme Admin
subtitle: Onboarding, filmed from the real app
theme:
accent: "#6d7cff"
captions: bottom
masking:
# Redacted in the DOM before any pixel is painted — the staging banner is
# never in a frame to begin with, so it cannot leak through a bad crop.
- target: 'testid=env-banner'
style: solid
scenes:
- id: dashboard
title: Where you start
steps:
- expect:
target: 'role=heading[name="Dashboard"]'
visible: true
caption: "The admin dashboard"
narration: "Here is Acme Admin, the real application, with the staging banner masked out before a single pixel is painted."
focus: wide
- id: users
title: Open Users
steps:
- click: 'role=link[name="Users"]'
caption: "Open Users"
narration: "We start where an administrator starts — the list of people who already have access."
focus: 'role=heading[name="Users"]'
- expect:
target: 'role=heading[name="Users"]'
visible: true
caption: "The team directory"
narration: "Verification checks that this heading really arrived, not merely that a click was dispatched."
- id: invite
title: Add a teammate
steps:
- click: 'role=button[name="Add user"]'
# The dialog fills most of the screen, so there is no close-up that can
# hold both the button and its result: say "wide" rather than making the
# planner discover that per frame and fall back.
focus: wide
shot: cut
caption: "Add user"
narration: "The invite form opens in a dialog, which the browser paints in its top layer above everything else."
- type:
target: 'label=Full name'
text: Jane Doe
caption: "Type the full name"
narration: "Every field is filled with real keystrokes into the real input."
- type:
target: 'label=Email'
text: jane@acme.dev
caption: "Enter the work email"
narration: "The work email — this is what the invitation will be sent to."
- type:
target: 'label=Temporary password'
text: "s3cret-Pass!"
mask: true
caption: "Set a temporary password"
narration: "The temporary password is masked at capture time. The real value exists only in the spec file, never in a frame and never in the event log."
- click: 'role=button[name="Create user"]'
caption: "Create the account"
narration: "One click, and the account is live."
focus: 'text="User added"'
- id: confirm
title: Confirm it worked
steps:
- expect:
target: 'text="User added"'
visible: true
caption: "Account created"
narration: "The toast confirms it, and the check for a visible effect confirms the toast."
# A toast is too small to fill a 16:9 close-up: zooming to it framed several seconds
# of empty panel and tripped the frozen-blank check. Hold the page instead.
focus: wide
- expect:
target: 'text="Jane Doe"'
visible: true
caption: "Jane is in the directory"
narration: "Jane is now in the directory — the outcome an administrator actually cares about."
focus: wide
- id: audit
title: The audit trail
steps:
- click: 'role=link[name="Activity"]'
caption: "Open Activity"
narration: "And because this is the real app, the action left a real audit trail."
focus: wide
- scroll: { by: { y: 220 } }
caption: "The new entry"
narration: "Change the wording of this walkthrough and you re-compose it in seconds — the application is never touched again."
focus: wide
The contact sheet verification wroteverify/contact-sheet.png

What each check assertedplayhead/checks@4
| encode | the file is a real, decodable H.264 video of the planned length |
| target-in-frame | every action target was fully inside the frame, and large enough to read |
| cursor-on-target | the rendered cursor was on the element at the moment of each click |
| action-effect | each action visibly changed the UI, compared frame to frame |
| output-pixels | the delivered pixels match the region the plan promised |
| frozen-blank | no frozen stretches and no blank content regions |
| captions | every caption fit its card without being shrunk or truncated |
| pacing | no step is too fast to follow |
| masks | masked regions are opaque in the output and absent from the log |
| audio | each spoken line is audible inside its own window at target loudness |
A $48,200 refund the UI called successful
A watchable bug report: numbered steps, a live console feed that leaves before the last slide, a closing report card, and bug-report.md. The toast says the money came back. The ledger and the stack say it did not.
- output
- 1:221920×1080 · 30fps · 3.7 MB
- capture
- 246 frames30.7s of source · viewport 1280×720
- steps
- 111 camera keyframes · 3 click ripples
- narration
- 11 lineskokoro · voice michael · −16 LUFS
- verdict
- publishable3/3 actions pixel-verified
- video sha256
- 4735d2b61e7f85d789efd76a…playhead 0.4.0 · win32-x64
The spec that produced ithelios-refund-bug.yaml
# Bug-repro: a refund that the UI calls successful while the API throws.
# The film is literal (no cinematic zoom). Console errors are painted on the
# output from capture/console.json so the ticket includes the stack, not just the toast.
playhead: 1
title: "Repro: refund reports success, charge stays captured"
subtitle: Helios billing · $48,200 Northwind seat expansion
app:
url: http://localhost:4173/helios.html
viewport: 1280x720
environment: { timezone: UTC, locale: en-US }
output:
kind: bug-repro
aspect: "16:9"
pacing: normal
dwellScale: 1.2
audio:
narration: tts
provider: kokoro
voice: michael
rate: 100
sfx: true
endCard:
title: Helios Ops
subtitle: Bug-repro · billing refund
theme:
accent: "#ef4444"
captions: bottom
scenes:
- id: open
title: Overview
steps:
- expect:
target: 'role=heading[name="Overview"]'
visible: true
caption: "Helios Ops"
narration: "Helios Ops, the payments console. Northwind Trading was charged forty-eight thousand two hundred dollars for a seat expansion."
focus: wide
- id: billing
title: Billing
steps:
- click: 'role=link[name="Billing"]'
caption: "Open Billing"
narration: "Billing is where the charge should be able to move to Refunded."
focus: 'role=heading[name="Recent charges"] >> nth=0'
- expect:
target: 'role=heading[name="Recent charges"] >> nth=0'
visible: true
caption: "Recent charges"
narration: "Northwind is still captured. The outstanding balance is forty-eight thousand two hundred."
focus: wide
- expect:
target: 'text="Northwind outstanding · $48,200" >> nth=0'
visible: true
caption: "Outstanding $48,200"
narration: "That figure is the ledger. After a real refund it has to drop to zero."
focus: wide
- id: refund
title: Issue refund
steps:
- click: 'role=button[name="Refund Northwind Trading"]'
caption: "Refund Northwind Trading"
narration: "Refund. The confirm dialog states the contract: status becomes Refunded, balance becomes zero."
focus: wide
shot: cut
- expect:
target: 'role=heading[name="Issue refund?"]'
visible: true
caption: "Confirm the refund"
narration: "Issue refund. This is the click a support engineer would make."
focus: wide
- click: 'role=button[name="Issue refund"]'
caption: "Issue refund"
narration: "The toast says the money came back. Watch the row — and watch the console."
focus: wide
- wait:
for: 'text=refund.failed'
state: visible
timeout: 8000
caption: "The webhook says failed"
narration: "The last webhook is refund.failed. idempotencyKey is undefined. The API threw a 500."
focus: wide
- id: lie
title: The UI lied
steps:
- expect:
target: 'text="Northwind outstanding · $48,200" >> nth=0'
visible: true
caption: "Balance unchanged"
narration: "The outstanding balance did not move. The charge is still captured."
focus: wide
- expect:
target: 'text="Captured"'
visible: true
caption: "Still captured"
narration: "That is the bug: a success toast on a thrown TypeError, forty-eight thousand dollars still on the ledger, and the stack in the console."
focus: wide
- wait: { ms: 1800 }
caption: "Hold on the evidence"
narration: "Attach this clip, the verdict, and console.json to the ticket. The recording is the report."
focus: wide
The contact sheet verification wroteverify/contact-sheet.png

bug-report.md — the pasteable write-up: URL, steps with timecodes, and the captured console.
What each check assertedplayhead/checks@4
| encode | the file is a real, decodable H.264 video of the planned length |
| target-in-frame | every action target was fully inside the frame, and large enough to read |
| cursor-on-target | the rendered cursor was on the element at the moment of each click |
| action-effect | each action visibly changed the UI, compared frame to frame |
| output-pixels | the delivered pixels match the region the plan promised |
| frozen-blank | no frozen stretches and no blank content regions |
| captions | every caption fit its card without being shrunk or truncated |
| pacing | no step is too fast to follow |
| masks | masked regions are opaque in the output and absent from the log |
| audio | each spoken line is audible inside its own window at target loudness |
Every one of these is two commands away.
The specs above are the complete input. Validate the locators against your own app, render, and read the verdict — no account, no API key, nothing leaves your machine.
npx playhead init
playhead validate specs/tour.yaml # every locator, against the live app
playhead render specs/tour.yaml -o out