← the Atlas

Video evidence for PRs

reference · evergreen ·

Screencast capture + a shared Pages player — the substrate the evidence skill and per-release demos build on. Capture now rides the existing Cucumber harness.

Let /do attach video to a PR — not just screenshots — for changes about motion (animations, transitions, multi-step interactions). The delivery layer (GIF inline + a shared GitHub-Pages player, hosted on each project’s evidence-assets release) already shipped — see History below. The question this page answered: where to drive capture from. For kolu: the e2e Cucumber harness, not a hand-rolled script — shipped in #1099 .

Capture source — reuse the Cucumber harness

Capture has been driven two ways: the chrome-devtools MCP screencast, and a bespoke Playwright capture.mjs on an ephemeral pu box. Both hand-drive the UI, re-implementing clicks the e2e step library already owns.

The ecosystem does exactly this — two models

One dominant, framework-agnostic path: capture media in a hook, push it into the run via the runner’s attach API (this.attach in cucumber-js), let a formatter render it. The only real variation is when capture fires:

Explicit-attachRecord-everything
Triggerhook/step calls capture (often failure-gated)framework records implicitly from config
Granularityper-step / per-scenario / per-failureper spec (coarse)
Green-run cost~zero if failure-gatedpays on every test unless pruned
Examplescucumber-js this.attach; Serenity BDD; Playwright traceCypress video:true; wdio-video-reporter

Even Cypress retreated from record-everything: video is off by default since Cypress 13 (Aug 2023), citing CI cost. For evidence of a known, deliberate change (kolu’s case), explicit-attach of one scenario is the right model.

Per-ecosystem, condensed

StackCaptureSurfaced via
cucumber-js + Playwright kolurecordVideo on newContext; page.screenshot(); optional context.tracingthis.attach → html formatter, or save .webm + upload
cucumber-js + Cypressauto 1 video/feature; auto failure shotscreenshots auto-embedded; video = sidecar mp4
WebdriverIO + Cucumberwdio-video-reporter stitches frames via ffmpegauto-embeds into Allure
cucumber-jvm + Serenity BDDauto screenshot-per-step”living documentation” HTML
Playwright Trace Viewercontext.tracing.start/stoptrace.zipopen in trace.playwright.dev; link from PR

The kolu wiring — shipped in #1099

1recordVideo is env-gated on the existing context (it is a context option, verified — not a launch() option):

// support/hooks.ts — inside newScenarioPage(), extending the existing newContext call
const context = await browser.newContext({
  viewport, baseURL, ignoreHTTPSErrors: true,
  permissions: ["clipboard-write", "clipboard-read"],
  ...(rawVideoDir // set only under KOLU_EVIDENCE
    ? { recordVideo: { dir: rawVideoDir, size: EVIDENCE_VIEWPORT } } // 1280×720
    : {}),                                     // normal runs pay nothing
});

2 — the file is finalized only on context.close() (verified). Grab the handle before closing, read after:

// support/hooks.ts — extending the existing After hook
const video = this.page?.video();             // handle BEFORE close
if (this.context) await this.context.close();  // flushes the .webm
if (video && process.env.KOLU_EVIDENCE) {
  const webm = await video.path();            // valid only post-close
  // leave on disk → feed the SAME ffmpeg → GIF/mp4 → evidence-assets flow (History)
}

3 — run one scenario, reusing the step library:

KOLU_EVIDENCE=1 just test-quick features/code-tab.feature --name "Editing an HTML file refreshes the iframe preview live"

Corrections the verification pass forced

The design — shipped as written in #1099

  1. Env-gate recordVideo (+ optional slowMo, + skip the animations-off init) in support/hooks.ts under KOLU_EVIDENCE.
  2. Pick (or author) a scenario reusing existing steps and select it by --name — no hand-driven clicks, no feature-file edit.
  3. Grab the .webm in After; hand it to the unchanged ffmpeg → GIF/mp4 → evidence-assets release → Pages-player flow (History). Capture source changes; delivery is identical.
  4. Point the harness at the pu-served packaged binary via the existing KOLU_SERVER=<url> support (it already accepts a running-server URL) — the one piece of genuinely new plumbing.

Trace Viewer (context.tracing) is the richer-but-not-inline alternative for deep-debug artifacts; it can’t render in a PR, so it complements an inline GIF rather than replacing it.

History — what shipped

The delivery + embedding layer is solved and durable, and the capture source now rides the Cucumber harness ( #1099 ) — the design above is the shipped design.

PRWhat it shipped
nix-chrome-devtools-mcp#2Screencast capability: the launcher runs nix shell …#ffmpeg and passes --experimentalScreencast=true, exposing the MCP’s screencast_start/stop. No host install.
#1033 The original “video evidence” feature: MCP-screencast capture + the embedding solution + the 3-repo split + the .agency/do.md video procedure + this plan.
#1037 Moved capture off-machine: PR evidence runs on an ephemeral pu box via a bespoke Playwright capture.mjs; extracted the reusable pu + evidence skills. This is the capture path #1099 replaced.
#1080 Let Codex load the pu + evidence skills.
#1099 Capture moved onto the Cucumber harness: env-gated KOLU_EVIDENCE recordVideo in support/hooks.ts, scenario selected by --name, retiring capture.mjs for kolu.
#1213 Built on that path for the kolu.dev demo: KOLU_X11CAP, a separate marketing-grade x11grab screencast mode (screencast/engine.ts).

The durable constraint: GitHub video embedding

# the delivery half of the /do evidence flow — unchanged regardless of capture source
ffmpeg -i clip.webm -vf "fps=12,scale=900:-1:flags=lanczos" -loop 0 clip.gif
gh release upload evidence-assets clip.gif clip.mp4 --clobber          # this project's release
# comment: ![](…/evidence-assets/<slug>.gif)
#          ▶ HD: …/video-evidence/evidence.html?repo=juspay/kolu&v=<slug>.mp4

Follow-up