
Playwright Video Recording
FreeCapture browser interactions as video effortlessly.
Free · Opens the source repo
What Playwright Video Recording does
Playwright Video Recording is a powerful tool designed for developers and designers who need to create high-quality video recordings of browser interactions. This skill leverages Playwright's capabilities to record actions within a browser, making it ideal for generating demo videos, app walkthroughs, and UI flow presentations. With a straightforward setup process, users can quickly start capturing video footage of their applications in action, enhancing their ability to communicate features and workflows effectively.
The skill allows for various recording configurations, including customizable viewport sizes and video quality settings. Users can easily switch between different resolutions, such as 1080p for high-definition output or 720p for smaller file sizes. Additionally, the option to slow down actions during recording helps in creating more user-friendly videos that highlight specific interactions clearly. This flexibility makes it suitable for a wide range of applications, from product demonstrations to educational content.
To get started, users simply need to install Playwright and set up a basic recording script. The provided examples cover common scenarios, such as form submissions, multi-page navigation, and even scroll demonstrations. Each example is designed to be easily adaptable, allowing users to tailor their recordings to their specific needs. Furthermore, the ability to inject CSS for cursor visualization enhances the viewer's experience by providing clear visual cues during playback.
Overall, Playwright Video Recording is an essential tool for anyone looking to create professional-quality video content of web applications. Its ease of use and robust features make it a valuable addition to the toolkit of developers and designers alike.
When to use it
Use this skill when you need to capture and share browser interactions for demos, tutorials, or presentations.
When not to use it
This skill is not suitable for recording applications that require extensive audio capture or for scenarios where live streaming is needed.
What you can build with it
Creating Demo Videos
Use Playwright Video Recording to capture your application's features in action for marketing or training purposes.
UI Testing and Documentation
Record interactions during UI testing to create visual documentation that helps teams understand workflows.
Educational Content Production
Generate walkthrough videos for tutorials or online courses to visually guide users through complex processes.
How to install Playwright Video Recording
View source1. Install with the skills CLI
npx skills add calesthio/openmontage/playwright-recording --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by calesthioPlaywright Video Recording
Playwright can record browser interactions as video - perfect for demo footage in Remotion compositions.
Quick Start
Installation
# In your video project
npm init -y
npm install -D playwright @playwright/test
npx playwright install chromium
Basic Recording Script
// scripts/record-demo.ts
import { chromium } from 'playwright';
async function recordDemo() {
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: {
dir: './recordings',
size: { width: 1920, height: 1080 }
}
});
const page = await context.newPage();
// Your recording actions
await page.goto('https://example.com');
await page.waitForTimeout(2000);
await page.click('button.demo');
await page.waitForTimeout(3000);
// Close to save video
await context.close();
await browser.close();
console.log('Recording saved to ./recordings/');
}
recordDemo();
Run with:
npx ts-node scripts/record-demo.ts
# or
npx tsx scripts/record-demo.ts
Recording Configuration
Viewport Sizes
// Standard 1080p (recommended for Remotion)
viewport: { width: 1920, height: 1080 }
// 720p (smaller files)
viewport: { width: 1280, height: 720 }
// Square (social media)
viewport: { width: 1080, height: 1080 }
// Mobile
viewport: { width: 390, height: 844 } // iPhone 14
Video Quality Settings
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: {
dir: './recordings',
size: { width: 1920, height: 1080 } // Match viewport for crisp output
},
// Slow down for visibility
// Note: slowMo is on browser launch, not context
});
// For slow motion, launch browser with slowMo
const browser = await chromium.launch({
slowMo: 100 // 100ms delay between actions
});
Recording Patterns
Form Submission Demo
import { chromium } from 'playwright';
async function recordFormDemo() {
const browser = await chromium.launch({ slowMo: 50 });
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: { dir: './recordings', size: { width: 1920, height: 1080 } }
});
const page = await context.newPage();
await page.goto('https://myapp.com/form');
await page.waitForTimeout(1000);
// Type with realistic speed
await page.fill('#name', 'John Smith', { timeout: 5000 });
await page.waitForTimeout(500);
await page.fill('#email', 'john@example.com');
await page.waitForTimeout(500);
// Click submit
await page.click('button[type="submit"]');
// Wait for result
await page.waitForSelector('.success-message');
await page.waitForTimeout(2000);
await context.close();
await browser.close();
}
Multi-Page Navigation
async function recordNavDemo() {
const browser = await chromium.launch({ slowMo: 100 });
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: { dir: './recordings', size: { width: 1920, height: 1080 } }
});
const page = await context.newPage();
// Page 1
await page.goto('https://myapp.com');
await page.waitForTimeout(2000);
// Navigate to page 2
await page.click('nav a[href="/features"]');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(2000);
// Navigate to page 3
await page.click('nav a[href="/pricing"]');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(2000);
await context.close();
await browser.close();
}
Scroll Demo
async function recordScrollDemo() {
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: { dir: './recordings', size: { width: 1920, height: 1080 } }
});
const page = await context.newPage();
await page.goto('https://myapp.com/long-page');
await page.waitForTimeout(1000);
// Smooth scroll
await page.evaluate(async () => {
const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
for (let i = 0; i < 10; i++) {
window.scrollBy({ top: 200, behavior: 'smooth' });
await delay(300);
}
});
await page.waitForTimeout(1000);
await context.close();
await browser.close();
}
Login Flow
async function recordLoginDemo() {
const browser = await chromium.launch({ slowMo: 75 });
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: { dir: './recordings', size: { width: 1920, height: 1080 } }
});
const page = await context.newPage();
await page.goto('https://myapp.com/login');
await page.waitForTimeout(1000);
await page.fill('#email', 'demo@example.com');
await page.waitForTimeout(300);
await page.fill('#password', '••••••••');
await page.waitForTimeout(500);
await page.click('button[type="submit"]');
// Wait for dashboard
await page.waitForURL('**/dashboard');
await page.waitForTimeout(3000);
await context.close();
await browser.close();
}
Cursor Highlighting
Playwright doesn't show cursor by default. Add visual indicators:
CSS Cursor Highlight
// Inject cursor visualization
await page.addStyleTag({
content: `
* { cursor: none !important; }
.playwright-cursor {
position: fixed;
width: 24px;
height: 24px;
background: rgba(255, 100, 100, 0.5);
border: 2px solid rgba(255, 50, 50, 0.8);
border-radius: 50%;
pointer-events: none;
z-index: 999999;
transform: translate(-50%, -50%);
transition: transform 0.1s ease;
}
.playwright-cursor.clicking {
transform: translate(-50%, -50%) scale(0.8);
background: rgba(255, 50, 50, 0.8);
}
`
});
// Add cursor element
await page.evaluate(() => {
const cursor = document.createElement('div');
cursor.className = 'playwright-cursor';
document.body.appendChild(cursor);
document.addEventListener('mousemove', (e) => {
cursor.style.left = e.clientX + 'px';
cursor.style.top = e.clientY + 'px';
});
document.addEventListener('mousedown', () => cursor.classList.add('clicking'));
document.addEventListener('mouseup', () => cursor.classList.remove('clicking'));
});
Click Ripple Effect
// Add click ripple visualization
await page.addStyleTag({
content: `
.click-ripple {
position: fixed;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(234, 88, 12, 0.4);
pointer-events: none;
z-index: 999998;
transform: translate(-50%, -50%) scale(0);
animation: ripple 0.4s ease-out forwards;
}
@keyframes ripple {
to {
transform: translate(-50%, -50%) scale(2);
opacity: 0;
}
}
`
});
// Custom click function with ripple
async function clickWithRipple(page, selector) {
const element = await page.locator(selector);
const box = await element.boundingBox();
await page.evaluate(({ x, y }) => {
const ripple = document.createElement('div');
ripple.className = 'click-ripple';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
document.body.appendChild(ripple);
setTimeout(() => ripple.remove(), 400);
}, { x: box.x + box.width / 2, y: box.y + box.height / 2 });
await element.click();
}
Output for Remotion
Move Recording to public/demos/
import { chromium } from 'playwright';
import * as fs from 'fs';
import * as path from 'path';
async function recordForRemotion(outputName: string) {
const browser = await chromium.launch({ slowMo: 50 });
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
recordVideo: { dir: './temp-recordings', size: { width: 1920, height: 1080 } }
});
const page = await context.newPage();
// ... recording actions ...
await context.close();
// Get the video path
const video = page.video();
const videoPath = await video?.path();
if (videoPath) {
const destPath = `./public/demos/${outputName}.webm`;
fs.mkdirSync(path.dirname(destPath), { recursive: true });
fs.renameSync(videoPath, destPath);
console.log(`Recording saved to: ${destPath}`);
// Get duration for config
// Use ffprobe: ffprobe -v error -show_entries format=duration -of csv=p=0 file.webm
}
await browser.close();
}
Convert WebM to MP4
Playwright outputs WebM. Convert for better Remotion compatibility:
ffmpeg -i recording.webm -c:v libx264 -crf 20 -preset medium -movflags faststart public/demos/demo.mp4
Interactive Recording
For user-driven recordings where you manually perform actions:
// Inject ESC key listener to stop recording
async function injectStopListener(page: Page): Promise<void> {
await page.evaluate(() => {
if ((window as any).__escListenerAdded) return;
(window as any).__escListenerAdded = true;
(window as any).__stopRecording = false;
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.preventDefault();
(window as any).__stopRecording = true;
}
});
});
}
// Poll for stop signal - handle navigation errors gracefully
while (!stopped) {
try {
const shouldStop = await page.evaluate(() => (window as any).__stopRecording === true);
if (shouldStop) break;
} catch {
// Page navigating - continue recording
}
await new Promise(r => setTimeout(r, 200));
}
Key insight: page.evaluate() throws during navigation. Use try/catch and continue - don't treat errors as stop signals.
Window Scaling for Laptops
Record at full 1080p while showing a smaller window:
const scale = 0.75; // 75% window size
const context = await browser.newContext({
viewport: { width: 1920 * scale, height: 1080 * scale },
deviceScaleFactor: 1 / scale,
recordVideo: { dir: './recordings', size: { width: 1920, height: 1080 } },
});
Cookie Banner Dismissal
Comprehensive selector list for common consent platforms:
const COOKIE_SELECTORS = [
'#onetrust-accept-btn-handler', // OneTrust
'#CybotCookiebotDialogBodyButtonAccept', // Cookiebot
'.cc-btn.cc-dismiss', // Cookie Consent by Insites
'[class*="cookie"] button[class*="accept"]',
'[class*="consent"] button[class*="accept"]',
'button:has-text("Accept all")',
'button:has-text("Accept cookies")',
'button:has-text("Got it")',
];
async function dismissCookieBanners(page: Page): Promise<void> {
await page.waitForTimeout(500);
for (const selector of COOKIE_SELECTORS) {
try {
const btn = page.locator(selector).first();
if (await btn.isVisible({ timeout: 100 })) {
await btn.click({ timeout: 500 });
return;
}
} catch { /* try next */ }
}
}
Call after page.goto() and on page.on('load') for navigation.
Important: Injected Elements Appear in Video
Warning: Any DOM elements you inject (cursors, control panels, overlays) will be recorded. For UI-free recordings, use terminal-based controls only (Ctrl+C, max duration timer).
Tips for Good Demo Recordings
- Use slowMo - 50-100ms makes actions visible
- Add waitForTimeout - Pause between actions for comprehension
- Wait for animations - Use
waitForLoadState('networkidle') - Match Remotion dimensions - 1920x1080 at 30fps typical
- Test without recording first - Debug before final capture
- Clear browser state - Use fresh context for clean demos
- Dismiss cookie banners - Use comprehensive selector list above
- Re-inject on navigation - Cursor/listeners reset on page load
Feedback & Contributions
If this skill is missing information or could be improved:
- Missing a pattern? Describe what you needed
- Found an error? Let me know what's wrong
- Want to contribute? I can help you:
- Update this skill with improvements
- Create a PR to github.com/digitalsamba/claude-code-video-toolkit
Just say "improve this skill" and I'll guide you through updating .claude/skills/playwright-recording/SKILL.md.
Frequently asked questions about Playwright Video Recording
Similar skills
Spring Boot Testing
Master testing techniques for Spring Boot 4 applications.
GitHub Issues
Manage GitHub issues efficiently with MCP tools.
Geofeed Tuner
Optimize your IP geolocation feeds in CSV format.
Batch Files
Master Windows batch scripting for automation and task management.
Adobe Illustrator Scripting
Automate your Illustrator workflows with ExtendScript.
Plugin Structure
Create and organize Claude Code plugins effectively.
