跳到正文
cccskills
免费在 GitHub 公开

playwright-recording

Record website interactions with Playwright to create demo clips, app walkthroughs, and footage for Remotion videos. The guidance covers screen dimensions, action timing, visible click effects, and converting recorded WebM files to MP4.

中文简介正在准备中。当前显示英语简介。

原文查看原文说明

Record browser interactions as video using Playwright. Use for capturing demo videos, app walkthroughs, and UI flows for Remotion videos. Triggers include recording a demo, capturing browser video, screen recording a website, or creating walkthrough footage.

集成工具Playwright
查看安装方法

适用场景

  • Recording app walkthroughs
  • Demonstrating form submission and login
  • Preparing browser footage for Remotion
  • Recording mobile-sized or square demos

包含的文件(2)

  • SKILL.md12.4 KB
  • reference.md8.0 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Playwright 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

  1. Use slowMo - 50-100ms makes actions visible
  2. Add waitForTimeout - Pause between actions for comprehension
  3. Wait for animations - Use waitForLoadState('networkidle')
  4. Match Remotion dimensions - 1920x1080 at 30fps typical
  5. Test without recording first - Debug before final capture
  6. Clear browser state - Use fresh context for clean demos
  7. Dismiss cookie banners - Use comprehensive selector list above
  8. 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:
    1. Update this skill with improvements
    2. 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.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Create 3D objects from text or images, choose catalog assets for repeated scenery, and inspect the results in Blender. The workflow checks geometry, materials, scale, and ground contact while recording asset sources and generation costs.

中文简介正在准备中。当前显示英语简介。

  • Create a unique 3D object from a text description
  • Reconstruct a 3D object from a concept image
  • Populate scenery with catalog vegetation and rocks
calesthio/OpenMontage7.1万2026年10月17日 更新

acestep

免费

Generate background music and songs with lyrics using ACE-Step 1.5, with controls for duration, tempo, and musical key. You can also restyle reference audio and separate vocals or individual instruments from mixed tracks.

中文简介正在准备中。当前显示英语简介。

  • Generate background music to match a video scene
  • Create a short jingle with supplied lyrics
  • Make a jazz arrangement of reference audio
calesthio/OpenMontage7.1万2026年10月17日 更新

agents

免费

Build voice assistants and conversational characters with ElevenLabs. Configure voices, language models, and external tools, then connect conversations to a website widget or use Twilio integration for outbound phone calls.

中文简介正在准备中。当前显示英语简介。

  • Creating a voice bot for customer support
  • Embedding a voice assistant on a website
  • Building an interactive voice character
calesthio/OpenMontage7.1万2026年10月17日 更新

Generate video clips from text descriptions or reference images through multiple API services. Compare available providers by cost and task needs, choose aspect ratios, track generation progress, and refine existing clips through conversational editing with Gemini Omni.

中文简介正在准备中。当前显示英语简介。

  • Generating clips from written scene descriptions
  • Animating a product photo
  • Creating vertical videos for social media
calesthio/OpenMontage7.1万2026年10月17日 更新

Create AI video clips from text or reference images, choosing among providers to match the task and budget. Supports vertical social videos, product photo animation, and conversational editing of existing clips with Gemini Omni Flash.

中文简介正在准备中。当前显示英语简介。

  • Create vertical video clips for social media
  • Animate product photos
  • Choose a video provider for a budget and visual style
calesthio/OpenMontage7.1万2026年10月17日 更新

Generate and edit images and videos through Atlas Cloud using one API key across multiple model families. Choose supported models for each task, download the results, and retain details about the model, inputs, and estimated cost.

中文简介正在准备中。当前显示英语简介。

  • Generate images and videos through one service
  • Edit product images using reference materials
  • Create videos from images, video, or audio references
calesthio/OpenMontage7.1万2026年10月17日 更新

查看 calesthio 的全部技能

报告此技能的问题