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 /tmp or 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

StepWhat
1Confirm output dir with user (relative to CWD by default)
2Add recordVideo to Playwright context
3Run test — .webm lands in output dir
4Check ffmpeg in PATH; use ffmpeg-static if missing
5Convert .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)

CRFQualityUse for
18Near-losslessArchive
23Good (default)Tickets, Jira
28Smaller fileQuick shares

Common Mistakes

MistakeFix
Can't find the output fileAlways confirm output dir with user upfront — never silently use /tmp or hidden paths
MP4 silently overwrites previous recordingUse auto-increment pattern: name.mp4name_01.mp4name_02.mp4
Video file is 0 bytesMust call context.close() before browser.close() — video is flushed on context close
ffmpeg-static not foundRun npm install ffmpeg-static in the project root first, then the script picks it up
Video doesn't record in test runnerEnsure outputDir is set in config; video only saves when test completes
headless: true records blank video on some systemsUse headless: false for reliable recording