Skills Agentes

Playwright Recording

Graba interacciones de navegador en vídeo con Playwright. Úsalo para capturar demos, recorridos de aplicación y flujos de interfaz para vídeos de Remotion.

Estrellas
49.5k

en todo el repo

Actividad
36

0–100, la ruta de este skill

Actualizado
hace 4 meses

último commit aquí

Commits
0

últimos 90 días

Contexto
3.2k tok

65 tok en reposo

Paquete
2 archivos

20 KB

Instalar

Funciona con cualquier agente que lea SKILL.md

npx -y skills add calesthio/OpenMontage --skill playwright-recording --agent claude-code

Se instala solo en este repositorio.

Este skill makes network requests.

Qué hace

  • Graba interacciones de navegador en vídeo con Playwright
  • Trae patrones listos: envío de formulario, navegación multipágina, demo de scroll y flujo de login
  • Añade resaltado del cursor con CSS y efecto de onda al hacer clic
  • Prepara la salida para Remotion: mueve la grabación a `public/demos/` y convierte WebM a MP4
  • Cubre tamaños de viewport, calidad de vídeo, escalado de ventana en portátiles y cierre de banners de cookies

Úsalo cuando

  • Grabar una demo o un recorrido por una aplicación
  • Capturar vídeo de navegador o grabar la pantalla de una web
  • Crear metraje de walkthrough para vídeos de Remotion

No lo uses cuando

    Qué lo activa

    Di cualquiera de estas frases y el agente debería cargar este skill.

    • Graba una demo de este flujo en la web
    • Captura en vídeo el proceso de registro
    • Necesito metraje del recorrido por la app

    SKILL.md

    En inglés

    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.

    Reproducido de calesthio/OpenMontage bajo licencia AGPL-3.0. Leer esta página en markdown.

    Archivos

    2 archivos en el paquete. Solo se lee SKILL.md al activarse — las referencias se cargan si el skill decide que las necesita.

    Antes de instalar

    Necesita Playwright instalado en el proyecto de vídeo; ojo con los elementos inyectados para resaltar el cursor, porque también salen en el vídeo.

    Necesita en el PATH:ffmpegnpmnpx

    Detalles

    Creador
    calesthio
    Categoría
    Testing y QA
    Licencia
    AGPL-3.0
    Recursos incluidos
    Incluye scripts o referencias
    Código fuente
    Ver SKILL.md

    Etiquetas

    Más de calesthio/OpenMontage

    Este repo incluye 89 skills. Si instalas uno, normalmente ya tienes los demás.

    Comfyui

    49.5k

    Úsalo al trabajar con workflows de ComfyUI en OpenMontage: comfyui_image/video/music, workflows propios, selección de output_node, modelos que faltan, LoRAs, poca VRAM e importación de workflows de la comunidad.

    Costo de contexto al activarse
    1.9k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 9 días
    diseno ui

    Genera vídeo cinematográfico de 4-30 s con ByteDance Seedance 2.5 por fal.ai, Volcengine Ark, Runway o ComfyUI. Cubre el contrato de prompt 2.5, cortes duros, locks de continuidad y voz.

    Costo de contexto al activarse
    3.2k tok
    Tamaño del paquete
    2 archivos
    Última actualización
    hace 4 días
    diseno ui

    Genera narración expresiva y multilingüe con fish.audio (modelos S1 / S2) y reutiliza voces clonadas mediante reference_id.

    Costo de contexto al activarse
    1.4k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 9 días
    diseno ui

    Genera y edita conversacionalmente vídeos cortos con Google Gemini Omni Flash: itera con ediciones en lenguaje natural, clips de 3-10s a 720p con audio y texto en pantalla, e imágenes de referencia por etiquetas.

    Costo de contexto al activarse
    2.1k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 9 días
    Permisos
    diseno ui

    Genera vídeo con MiniMax H3 (Hailuo 3.0) por la API oficial v2, fal.ai, Runway, nodos partner de ComfyUI o pesos abiertos locales. Clips de 4-15s a 2K con animación de primer/último fotograma.

    Costo de contexto al activarse
    582 tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 9 días
    diseno ui

    Genera, reconstruye, inspecciona y enruta activos 3D de producción para mundos de OpenMontage con Atlas Cloud, fal.ai, catálogos con licencia y Blender.

    Costo de contexto al activarse
    1.2k tok
    Tamaño del paquete
    2 archivos
    Última actualización
    hace 9 días
    diseno ui

    Skills relacionados

    Revisa animación de personaje local con comprobaciones de esquema, previsualizaciones en navegador con Playwright, muestreo de fotogramas y verificación final con FFmpeg/ffprobe.

    Costo de contexto al activarse
    389 tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 3 meses
    testing qa

    Genera, reconstruye, inspecciona y enruta activos 3D de producción para mundos de OpenMontage con Atlas Cloud, fal.ai, catálogos con licencia y Blender.

    Costo de contexto al activarse
    1.2k tok
    Tamaño del paquete
    2 archivos
    Última actualización
    hace 9 días
    diseno ui

    Acestep

    49.5k

    Generación musical con ACE-Step 1.5: música de fondo, pistas con voz, versiones y extracción de stems para producción de vídeo.

    Costo de contexto al activarse
    2.3k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 4 meses
    diseno ui