Video evidence for PRs
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-attach | Record-everything | |
|---|---|---|
| Trigger | hook/step calls capture (often failure-gated) | framework records implicitly from config |
| Granularity | per-step / per-scenario / per-failure | per spec (coarse) |
| Green-run cost | ~zero if failure-gated | pays on every test unless pruned |
| Examples | cucumber-js this.attach; Serenity BDD; Playwright trace | Cypress 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
| Stack | Capture | Surfaced via |
|---|---|---|
| cucumber-js + Playwright kolu | recordVideo on newContext; page.screenshot(); optional context.tracing | this.attach → html formatter, or save .webm + upload |
| cucumber-js + Cypress | auto 1 video/feature; auto failure shot | screenshots auto-embedded; video = sidecar mp4 |
| WebdriverIO + Cucumber | wdio-video-reporter stitches frames via ffmpeg | auto-embeds into Allure |
| cucumber-jvm + Serenity BDD | auto screenshot-per-step | ”living documentation” HTML |
| Playwright Trace Viewer | context.tracing.start/stop → trace.zip | open in trace.playwright.dev; link from PR |
The kolu wiring — shipped in #1099
1 — recordVideo 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
- Per-page, not per-context, never per-step.
recordVideowrites one.webmper page; kolu opens one page per scenario → one file per scenario. There is no per-step video (screenshots can be per-step; video can’t). this.attachtakes an options object now —this.attach(buf, { mediaType, fileName }); the bare-string form is legacy-compat.KoluWorld extends Worldinheritsattach; it’s unavailable inBeforeAll/AfterAll.- Don’t base64-inline a
.webmintoreport.html(bloat) — externalize, or skip the report and upload the clip (what the delivery layer already does). - Comparison nits:
@wdio/video-reporter(scoped) doesn’t exist — it’s the unscopedwdio-video-reporter;playwright-videois abandoned-in-practice. Neither affects kolu.
The design — shipped as written in #1099
- Env-gate
recordVideo(+ optionalslowMo, + skip the animations-off init) insupport/hooks.tsunderKOLU_EVIDENCE. - Pick (or author) a scenario reusing existing steps and select it by
--name— no hand-driven clicks, no feature-file edit. - Grab the
.webminAfter; hand it to the unchanged ffmpeg → GIF/mp4 →evidence-assetsrelease → Pages-player flow (History). Capture source changes; delivery is identical. - 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.
| PR | What it shipped |
|---|---|
| nix-chrome-devtools-mcp#2 | Screencast 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: 
# ▶ HD: …/video-evidence/evidence.html?repo=juspay/kolu&v=<slug>.mp4
Follow-up
- Picking up a new MCP launcher requires a Claude restart (the MCP server is spawned at session start).