CI & Docker

Because render exits non-zero unless the verdict is publishable, it drops into a pipeline as an end-to-end test whose failure artifact is a video someone will actually watch.

The GitHub Action#

A composite action ships in the repository. It caches and installs Chromium, renders your specs, uploads the artifacts and comments on the pull request.

name: video
on: [pull_request]

jobs:
  walkthrough:
    runs-on: ubuntu-latest
    permissions:
      contents: write        # only needed for host-pages
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }

      - name: Start the app
        run: npm ci && npm run start &

      - uses: Cypher-aman/playhead@v0
        with:
          specs: specs/tour.yaml specs/onboarding.yaml
          out: playhead-out
          workers: 2
          host-pages: true
InputMeaning
specsRequired. Space-separated spec paths, or a glob expanded by an earlier shell step.
outOutput directory for bundles, videos and verdicts. Default playhead-out.
workersConcurrent renders when given several specs. Default 2.
playhead-versionThe playhead-cli version to run — an npm dist-tag or an exact version. Default latest; pin it for reproducible builds.
signing-keySets PLAYHEAD_SIGNING_KEY, so verdicts are HMAC-signed and attestable.
host-pagesDeploy the evidence page to gh-pages so the PR comment is a playable URL instead of an artifact zip. Requires Pages source = gh-pages.

Outputs: junit (the report path) and evidence-url (the playable page, when host-pages is on).

Failures that read well#

playhead render specs/*.yaml -o out --junit report.xml

JUnit output emits one testcase per verification check, so a failure names the rule it broke — cursor-on-target, captions, output-pixels — rather than reporting that the video job failed. Pair it with any test reporter action and the review UI shows exactly which craft rule regressed.

Treat exit 1 as a product bug, not a flaky test. Exit 1 means your app broke the flow, and Playhead still writes a failure clip that ends on the step that broke with the reason on screen. Upload it — it is usually a better bug report than the stack trace beside it. All exit codes →

Docker#

The bundled Dockerfile runs the whole pipeline on the Playwright base image, which already carries the browser and its system libraries:

docker build -t playhead .
docker run --rm -v "$PWD:/work" -w /work playhead \
  render specs/tour.yaml -o out

Point specs at host.docker.internal (or run the app in the same network) when the app under test lives outside the container.

Container prerequisites#

playhead doctor --ci

This adds the checks that only matter in a container: the narration provider, the browser sandbox, and /dev/shm — a small shm allocation is the classic cause of Chromium dying mid-capture. Run it as the first step of a new pipeline and you will skip an afternoon.

SymptomUsually
Chromium crashes partway through captureToo little /dev/shm. Give the container ~1GB.
Narration is silent in CI but works locallyThe Kokoro model was not installed in the image. Run npx playhead init --no-browser during the build, or accept silent output.
Fonts render as boxesInstall the font packages, or let doctor tell you which are missing.
Captures differ run to runSet app.environment (timezone, locale, fixedTime) and block third-party requests with app.network.block.
Exit 3 immediatelyAn infrastructure problem — ffmpeg, the browser or fonts. Not your spec.

Rendering a pool#

playhead render specs/*.yaml -o out -w 4

Several specs render concurrently with per-spec subdirectories under out. The process exits non-zero if any spec is not publishable.

Each render is single-threaded internally. Concurrency comes from rendering multiple specs at once, not from splitting one video across cores. Segment-parallel rendering is a designed escape hatch that is not built yet — so for one long spec, wall-clock time scales with output length.

Catching regressions#

playhead compare artifacts/baseline current/

Verdict drift, newly failing checks, coverage change and per-step timing deltas, exiting 2 when the walkthrough regressed. Keep the last green run as a baseline artifact and this turns “the demo feels slower” into a number.

Reporting into a tracker#

playhead render specs/repro.yaml --jira PROJ-482

Attaches the video or failure clip, the verdict, the contact sheet, the JUnit report and the console log to an existing issue, and posts a verdict comment — on success or on a flow failure. Credentials come from the environment; Playhead never creates issues.