playwright-record-mp4
Use when running Playwright browser automation and needing to produce an MP4 video for tickets, bug reports, or evidence. Covers video recording config, ffmpeg detection, npx fallback install, and .webm to MP4 conversion.
Playwright Record to MP4
Overview
Configure Playwright to record .webm video, run the test/script, then detect ffmpeg (install via npx if missing) and convert to MP4 with good compression.
Output Directory — Always Confirm Before Writing
Before generating any script, ask the user:
"Should the video output directory be relative to your current working directory, or do you want an absolute path?"
- Default suggestion:
./videos/<test-name>/(relative to CWD where the skill is invoked) - Never silently use
/tmpor any absolute path — the user may not find the file - If the user specifies a path like
/tmp/foo, confirm: "I'll write to/tmp/foo— is that correct?"
Output File Naming — Auto-increment if Exists
When writing the MP4, never overwrite an existing file. Use this pattern:
import { existsSync } from 'fs';
import { resolve } from 'path';
let mp4 = resolve(OUTPUT_DIR, `${NAME}.mp4`);
if (existsSync(mp4)) {
let i = 1;
do { mp4 = resolve(OUTPUT_DIR, `${NAME}_${String(i).padStart(2, '0')}.mp4`); i++; }
while (existsSync(mp4));
}
// result: name.mp4 → name_01.mp4 → name_02.mp4 ...
Quick Reference
| Step | What |
|---|---|
| 1 | Confirm output dir with user (relative to CWD by default) |
| 2 | Add recordVideo to Playwright context |
| 3 | Run test — .webm lands in output dir |
| 4 | Check ffmpeg in PATH; use ffmpeg-static if missing |
| 5 | Convert .webm → .mp4 (H.264, CRF 23), auto-increment name if exists |
Step 1 — Playwright Video Config
Script (one-off recording):
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
recordVideo: { dir: 'videos/', size: { width: 1280, height: 720 } },
ignoreHTTPSErrors: true,
});
const page = await context.newPage();
// --- your steps here ---
await context.close(); // must close context to flush video to disk
await browser.close();
Test runner (playwright.config.ts):
export default {
use: {
video: 'on', // or 'retain-on-failure'
ignoreHTTPSErrors: true,
},
outputDir: 'test-results/',
};
Step 2 — Check ffmpeg & Convert (Shell)
Use this script after the test completes. It auto-installs via npm if ffmpeg is not on PATH.
#!/usr/bin/env bash
# Usage: ./convert.sh videos/myrecording.webm
INPUT="$1"
OUTPUT="${INPUT%.webm}.mp4"
if command -v ffmpeg &>/dev/null; then
FFMPEG="ffmpeg"
else
echo "ffmpeg not found — installing ffmpeg-static via npm..."
npm install --silent ffmpeg-static
FFMPEG=$(node -e "process.stdout.write(require('ffmpeg-static'))")
fi
"$FFMPEG" -i "$INPUT" \
-c:v libx264 \
-crf 23 \
-preset fast \
-c:a aac \
"$OUTPUT" \
&& echo "Done: $OUTPUT"
Save as ~/.claude/skills/playwright-record-mp4/convert.sh and chmod +x it.
Step 3 — One-liner (if ffmpeg already installed)
ffmpeg -i test-results/video.webm -c:v libx264 -crf 23 -preset fast output.mp4
After Conversion — Always Report the Output Location
After the MP4 is created, output this to the user:
✓ Converted: <absolute-path-to-mp4> (<size>)
You can see all .webm and .mp4 files in this directory:
file://<absolute-path-to-output-dir>
🤖 This session used ~N,000 tokens and saved you ~M minutes vs doing it manually.
- Use an absolute path so the
file://link is clickable in the terminal/IDE - List both
.webm(raw Playwright output) and.mp4(converted) so the user knows both exist - If the terminal supports it, the
file://URI makes the folder openable with one click
Token/time-saved calculation (Claude reports this, not the script):
- Estimate N = approximate total tokens used in this session (visible in your context counter, or estimate ~3,000 for a simple recording session)
- M = round(N / 1000 × 3) — rough heuristic: 1,000 tokens ≈ 3 min of manual work
- Example: 6,000 tokens → "saved you ~18 minutes"
Example full output:
✓ Converted: /Users/jane/project/videos/test13/demo_server_test.mp4 (639K)
You can see all .webm and .mp4 files in this directory:
file:///Users/jane/project/videos/test13/
🤖 This session used ~6,000 tokens and saved you ~18 minutes vs doing it manually.
In the Node.js script, emit this after conversion:
const absDir = resolve(OUTPUT_DIR);
const absMP4 = resolve(mp4);
console.log(`\n✓ Converted: ${absMP4} (${size})`);
console.log(`You can see all .webm and .mp4 files in this directory:`);
console.log(` file://${absDir}`);
// Claude adds the token/time-saved line above the script output
CRF Guide (quality vs size)
| CRF | Quality | Use for |
|---|---|---|
| 18 | Near-lossless | Archive |
| 23 | Good (default) | Tickets, Jira |
| 28 | Smaller file | Quick shares |
Common Mistakes
| Mistake | Fix |
|---|---|
| Can't find the output file | Always confirm output dir with user upfront — never silently use /tmp or hidden paths |
| MP4 silently overwrites previous recording | Use auto-increment pattern: name.mp4 → name_01.mp4 → name_02.mp4 |
| Video file is 0 bytes | Must call context.close() before browser.close() — video is flushed on context close |
ffmpeg-static not found | Run npm install ffmpeg-static in the project root first, then the script picks it up |
| Video doesn't record in test runner | Ensure outputDir is set in config; video only saves when test completes |
headless: true records blank video on some systems | Use headless: false for reliable recording |