К основному содержанию
скилл CX Codex

playwright-recording — скилл для Claude Code: запись браузера в видео

Скилл записывает действия в браузере как видео через Playwright — для демо, обзоров интерфейса и футажа под Remotion.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add digitalsamba/claude-code-video-toolkit --skill playwright-recording -a claude-code
Открыть на GitHub

Что делает playwright-recording

Скилл даёт агенту готовые шаблоны скриптов на TypeScript: запуск Chromium, создание контекста с recordVideo, переходы по страницам, заполнение форм, плавный скролл и логин. Видео сохраняется в папку ./recordings, а размер кадра задаётся в viewport и в size — для Remotion советуют 1920×1080.

Отдельный блок — визуализация курсора. Playwright по умолчанию курсор не рисует, поэтому в скрипт добавляют CSS-кружок и обработчики mousemove, mousedown, mouseup, чтобы клики было видно на записи. Есть и эффект расходящегося круга при нажатии.

Перед запуском нужен Node.js и установленный Chromium: npm install -D playwright @playwright/test, затем npx playwright install chromium. Скрипты запускаются через npx ts-node или npx tsx. Замедление действий задаётся в chromium.launch({ slowMo: 100 }), а не в контексте — если поставить его не туда, движения на видео останутся резкими.

Какие задачи решает

  • записать демо интерфейса приложения в видеофайл
  • снять проход по нескольким страницам сайта для обзора
  • показать заполнение формы с реалистичной скоростью печати
  • сделать плавный скролл длинной страницы для футажа
  • подсветить курсор и клики, которых не видно на записи

Примеры запросов агенту

  • запиши демо-видео прохода по страницам приложения через playwright-recording
  • сделай скрипт записи формы с slowMo 50 и сохрани видео в ./recordings
  • добавь в запись подсветку курсора и эффект клика

Как установить playwright-recording

1Через Skills CLI (Vercel)

npx skills add digitalsamba/claude-code-video-toolkit --skill playwright-recording -a claude-code

2Вручную: положить файлы

~/.claude/skills/playwright-recording/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/playwright-recording/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Из того же набора8

ffmpeg Готовые команды FFmpeg для конвертации, сжатия, обрезки видео и подготовки ассетов к проектам на Remotion. CX Codex +1 11 700установок elevenlabs Набор инструкций для генерации озвучки, звуковых эффектов и клонирования голоса через ElevenLabs API. Пригодится при монтаже видео или записи подкастов. CX Codex +1 1 200установок moviepy Скилл для сборки видео на Python: накладывает читаемый текст на ролики LTX-2 и SadTalker, склеивает клипы в одном build.py. CX Codex +1 1 000установок qwen-edit Паттерны промптов и настройки параметров для редактирования изображений через Qwen-Image. Пригодится, когда нужно сохранить лицо на фото или переодеть персонажа. CX Codex +1 964установок runpod Скилл для запуска открытых ИИ-моделей на облачных GPU через RunPod serverless. Пригодится, когда нужно обработать видео или картинку, а своей видеокарты не хватает. CX Codex +1 865установок acestep Скилл для генерации музыки и работы с аудио: фоновые треки, вокал, каверы, извлечение стемов. Пригодится, когда для видео нужен саундтрек или джингл. CX Codex +1 841установок ideogram4 Генерирует изображения с читаемым текстом и точной палитрой через Ideogram 4. Пригодится для обложек и плашек с надписями. CX Codex +1 471установок

Похожие

playwright-generate-test официальный github Создаёт Playwright-тест по сценарию через Playwright MCP. Подходит, когда нужно пройти сценарий в браузере перед генерацией кода. 18 162установок recording-browser-flow-as-test spencerpauly Записывает действия в браузере через MCP и превращает их в E2E-тест Playwright с надёжными селекторами accessibility tree. CX Codex +1 406установок invisible_playwright_mcp feder-cr MCP-сервер, который даёт ассистенту настоящий Firefox: ходит по страницам, кликает, печатает и читает их по инструкции на обычном языке. CX Codex +1 ★ 877звёзд репо playwright-explore-website официальный github Исследует сайт через Playwright MCP, фиксирует пользовательские сценарии и предлагает тест-кейсы. Подходит для первичной проверки интерфейса. 14 792установок playwright-dev microsoft Объясняет, как разрабатывать Playwright: добавлять API, MCP-инструменты, команды CLI и управлять зависимостями. CX Codex +1 3 700установок playwright-in-sandbox customware-ai Скилл для проверки интерфейса в браузере через Playwright и написания устойчивых E2E-тестов. Пригодится, когда после правок UI нужны скриншоты, а не только состояние DOM. CX Codex +1 508установок

Рядом в теме «Браузер и парсинг»

Всё по теме «Браузер и парсинг» →

Вопросы о playwright-recording

Как установить playwright-recording в Claude?

Выполните npx skills add digitalsamba/claude-code-video-toolkit --skill playwright-recording -a claude-code. Скилл появится в ~/.claude/skills/playwright-recording/SKILL.md.

Что делает playwright-recording?

Даёт шаблоны скриптов Playwright, которые записывают действия в браузере как видео: переходы, заполнение форм, скролл, логин. Записи складываются в ./recordings и подходят как футаж для Remotion.

Что нужно для работы скилла?

Node.js и Chromium: npm install -D playwright @playwright/test, затем npx playwright install chromium. Скрипты запускаются через npx ts-node или npx tsx.

Исходный текст

SKILL.md · MIT
---
name: playwright-recording
description: 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 Video Recording

Playwright can record browser interactions as video - perfect for demo footage in Remotion compositions.

## Quick Start

### Installation

```bash
# In your video project
npm init -y
npm install -D playwright @playwright/test
npx playwright install chromium
```

### Basic Recording Script

```typescript
// 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:
```bash
npx ts-node scripts/record-demo.ts
# or
npx tsx scripts/record-demo.ts
```

## Recording Configuration

### Viewport Sizes

```typescript
// 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

```typescript
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

```typescript
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', '[email protected]');
  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

```typescript
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

```typescript
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

```typescript
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', '[email protected]');
  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

```typescript
// 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

```typescript
// 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/

```typescript
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:

```bash
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:

```typescript
// 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:

```typescript
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:

```typescript
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`.

© digitalsamba · MIT · digitalsamba/claude-code-video-toolkit

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой