FAQ & troubleshooting

The questions that come up most, and the limitations worth knowing before you plan around the tool.

Authoring#

A locator will not resolve, but I can see the element#

Run playhead explore <url> and copy the line verbatim. The usual causes, in order of frequency:

It resolved during explore but failed during render#

That is a bug in Playhead, not in your spec — discovery and execution are required to agree about what exists. Please tell us with the locator and the page.

testid does not match my attribute#

testid= matches data-testid, data-test-id and data-test. For data-cy, use css=[data-cy="row-3"].

My step has the right locator but the wrong element#

Several elements match. Narrow with a container and a CSS chain if you can, or disambiguate with >> nth=2 (zero-based) if you cannot.

Capture#

The capture bundle has far fewer frames than I expected#

That is normal. Frames are captured when the page paints, so an idle screen emits nothing and the bundle treats the gap as a hold — a 47-second capture might be 192 frames. Compose reconstructs smooth 30fps output from it.

“no source frame within 150ms after the action”#

Playhead could not land a frame at that action beat, usually because the machine was too busy to take a screenshot in time. Consequences show up in verification: action-effect warns that it had no frame to compare, and cursor-on-target may fail because it cannot confirm the cursor at the beat.

PLAYHEAD_CAPTURE=screenshot playhead render specs/tour.yaml -o out

That forces a paced screenshot loop instead of the paint-driven source: denser frames, more CPU. Closing other work during a render helps as much as any flag.

The capture films my login page#

The saved session expired. Re-run playhead login, and add app.assertLoggedIn so the next stale session aborts the capture instead of producing a confident, useless video.

app.dismiss is on by default and handles most of them. For the rest, add a setup: step with optional: true, or mask the element.

The page never finishes loading#

Ad-heavy pages may never fire load. Set app.navigation: domcontentloaded, and block the third-party noise with app.network.block.

Verification#

cursor-on-target failed#

Either the click genuinely landed elsewhere, or there was no frame near the beat to confirm the cursor sprite on. Check for capture-gap warnings in the same run first — the two failures travel together.

output-pixels failed with a correlation near 0.5#

A near miss on a small subject that changed the moment it was acted on. It is usually an authoring signal rather than a renderer bug: give the camera a more substantial subject. There is a worked example of exactly this on the verification page.

captions reported an overflow#

Shorten caption and move the detail into narration, which has no size limit. Then re-compose — no re-capture needed.

frozen-blank found frozen footage#

A long stretch with no new frames spanning an action beat. Same root cause as a capture gap; the paced-capture escape hatch above is the usual fix.

Everything passed but coverage is low#

Weaker evidence than it looks, and the verdict says so deliberately. Low coverage almost always means capture gaps.

Output#

There is no narration in the video#

Check output.audio.narration: tts is set, then run playhead doctor — the Kokoro package may not be installed. The audio check will also skip with a message telling you exactly this.

The voice sounds absurdly fast#

rate means words-per-minute for say but playback speed ×100 for Kokoro. The schema rejects a say-style value on Kokoro for this reason; sensible Kokoro values are 80–140.

The video is much longer than the capture#

Expected with narration: pacing grows so every spoken line finishes inside its step. Shorten the lines, or set pacing: brisk and a lower dwellScale.

Every shot is wide and the camera never moves#

Look for “camera planner exhausted its attempt budget” in the log. Afocus: hint somewhere cannot be satisfied — usually a payoff element that never fits alongside its action. Say focus: wide explicitly on that step.

Environment#

ffmpeg or Chromium is missing#

playhead doctor names the exact fix. ffmpeg and ffprobe ship with the package, so a failure there is a broken install rather than a missing system dependency; Chromium comes from npx playwright install chromium.

Chromium dies partway through a container render#

Almost always a small /dev/shm. Give the container about 1GB, and run playhead doctor --ci.

Known limitations#

  • Closed shadow roots cannot be filmed. They are invisible to explore and to capture, so some apps genuinely cannot be automated. Open shadow roots are fine, bounded at 400 elements and 400 roots so component-heavy pages stay fast.
  • Cross-origin iframes are unexplorable. Same-origin frames are pierced one level deep.
  • record observes clicks, changes and Enter. Hovers, drags and keyboard shortcuts are not recorded yet — write those steps by hand, or author over MCP.
  • Rendering is single-threaded per video. Concurrency comes from rendering several specs at once; segment-parallel rendering is designed but not built.
  • One theme. There is no theme.name, because a knob that is accepted and ignored is a lie. It returns when a theme registry does.

Still stuck?#

Tell us what you are trying to film. Bug reports that include the spec and verify/verdict.json get answered first.