Playhead

CI & Docker

render exits 0 only when the verdict is publishable, so it drops into a pipeline as an end-to-end test that attaches video evidence to every run.

GitHub Actions

- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with: { node-version: 20, cache: npm }
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx -p playhead-cli playhead render specs/*.yaml -o out --junit out/junit.xml
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playhead-evidence
    path: |
      out/**/*.mp4
      out/**/verdict.json
      out/**/contact-sheet.png
      out/junit.xml

A red build now comes with a video of what broke. When the app itself breaks the flow (exit 1), Playhead writes a failure clip: the run up to the break, ending on a card naming it — which is exactly when video evidence is worth the most.

Machine-readable output

Docker

docker run --rm -v "$PWD:/work" -w /work --shm-size=1g \
  playhead render specs/examples/add-user.yaml -o out --json

The image builds on the Playwright base, so Chromium, its sandbox dependencies and system fonts are already there. Give it --shm-size=1g or Chromium will run out of shared memory mid-capture.

Determinism

Compose and verify never touch the network, so the same bundle and spec produce the same video. Pin that in CI:

playhead compose out/capture --expect-sha 9f2c…

For app-side determinism, freeze the world in the spec: environment: { timezone: UTC, fixedTime: "2026-01-01T12:00:00Z" } stops relative timestamps drifting between runs, and network.block keeps third-party noise out of the frame.