Grabación automatizada de videos de demostración (Playwright)
Modo (decidir PRIMERO)
Argumentos recibidos: $ARGUMENTS
| Argumentos | Modo | Plantilla |
|---|---|---|
contienen audio (o la solicitud pide voz / narración hablada) | Narrado — Voz TTS + subtítulos | record-demo-narrated-template.mjs + narrate.py |
| vacíos / cualquier otra cosa | Silencioso — solo subtítulos (predeterminado) | record-demo-template.mjs |
Modo narrado: idioma en (predeterminado) o pt (portugués brasileño); motor auto (predeterminado: inglés → kokoro offline; portugués → edge, con fallback a kokoro cuando está offline), o forzar kokoro / edge. Ejemplos: audio, audio pt, audio pt kokoro, audio en edge. También sigue el Modo narrado más abajo.
Escribe subtítulos y narración en el idioma elegido (en a menos que se haya dado pt o el usuario escriba en portugués).
Resumen
Un video de demostración es código, no una captura manual: un script de Playwright controla el navegador con grabación nativa, subtítulos superpuestos y resaltes visuales. Se puede volver a grabar cuando el producto cambie (npm run demo:record), con ritmo e historia controlados.
Principio fundamental: el video es un pitch, no una prueba E2E. Cada escena existe para demostrar un argumento — y el trabajo solo termina después de VERIFICAR el video generado fotograma a fotograma.
Cuándo usar / no usar
- ✅ POC/Demo de producto web para inversores, clientes, partes interesadas, onboarding
- ✅ Documentar un flujo completo que sigue evolucionando (regrabable)
- ✅ Con voz: modo narrado (TTS local/offline) — ver arriba
- ❌ Pruebas automatizadas (usar Playwright Test) · ❌ aplicaciones no web · ❌ capturar el sonido propio de la app (Playwright no graba audio del navegador)
Proceso
- Estado determinista primero. El video realiza acciones reales (inicios de sesión, clics, escrituras en BD). Asegura datos de semilla/demo idempotentes y cuentas dedicadas antes de grabar. Ejecuta el servidor en modo producción (
build && start) o calienta las rutas en desarrollo — la compilación diferida aparece como una pantalla blanca en el video. - Escribe el guion ANTES de codificar. Un arco de pitch de 4 actos: la visión (apertura) → cómo funciona (el diferenciador, por dentro) → la experiencia del usuario final → la prueba/resultado (cierra con la visión). Cada escena = 1 argumento + 1 subtítulo (titular fuerte + texto explicativo). Objetivo: 60–120s en total, 3–5s en cada pantalla clave.
- Adapta la plantilla — ayudas preparadas para subtítulos, resaltes, inicio de sesión, ritmo y conversión. Usa los colores de marca del proyecto para el subtítulo.
- Graba, y trata los fallos de selectores como un script roto (arréglalo, no lo rodees).
- VERIFICA el video — obligatorio. Duración y escenas:
Solo entrega después de inspeccionar los fotogramas. Si una escena falló, vuelve a grabar.ffprobe -v error -show_entries format=duration -of csv=p=0 demo.mp4 # captura 1 fotograma por escena y MIRA cada uno (¿subtítulo correcto? ¿pantalla correcta? ¿nada roto?) ffmpeg -y -ss <t> -i demo.mp4 -frames:v 1 -vf scale=800:-1 frame_<t>.png - Entrega
.mp4(funciona en PowerPoint/Slack/WhatsApp) además del.webm. Deja un script npm (demo:record) y agrega la salida a gitignore (es regenerable).
Modo narrado (audio)
Mismo proceso que arriba, con estas diferencias:
- Requisitos previos:
uvyffmpegen PATH (uv --version,ffmpeg -version). Copianarrate.pyen la MISMA carpeta que el script de grabación. La primera ejecución descarga el modelo Kokoro (~120 MB, almacenado en caché en~/.cache/recording-demo-videos). - El ritmo proviene del audio. Cada escena en
SCRIPTtienecaption(pantalla) +say(discurso, 1–2 oraciones, tono de pitch).await narrate(page, "id")muestra el subtítulo, coloca el discurso y espera a que termine — no uses unpause()fijo en escenas habladas. Hablar durante una interacción:narrate(page, id, { wait: false }); el siguientenarrate()espera al discurso anterior (sin superposición). - Idioma/voz mediante env:
DEMO_LANG=en|pt,DEMO_TTS=auto|kokoro|edge,DEMO_VOICE=<voz>(Kokoro:af_heart,am_michael,bf_emma,pf_dora,pm_alex; edge:en-US-AvaMultilingualNeural,pt-BR-FranciscaNeural…).kokoro= offline, Apache-2.0, predeterminado para inglés; su portugués es notablemente menos natural.edge= predeterminado para portugués, mejores voces, pero en línea a través de un endpoint no oficial de Microsoft — adecuado para demos internas; para uso comercial prefierekokoroo un TTS oficial. - La sincronización es automática: la plantilla abre con un destello magenta (
syncMarker), mide el retraso real de inicio del video y recorta el destello. ManténsyncMarkercomo la primera acción; si el registro dice "Sync marker not found", la voz estará desincronizada — verifica quesyncMarkersea la primera acción y que nada cubra la pantalla en el primer segundo, luego vuelve a grabar. - Salida:
demo.mp4(narrado — el entregable),demo.srt,timeline.json,demo.raw.webm(crudo, silencioso),audio/(caché: volver a grabar solo resintetiza las líneas cambiadas). - Verificación extra (obligatoria): además de los fotogramas, confirma que la voz aterrice en el lugar correcto:
ffprobe -v error -show_entries stream=codec_type,duration -of csv=p=0 demo.mp4 # debe tener video Y audio ffmpeg -i demo.mp4 -af silencedetect=n=-45dB:d=0.6 -f null - 2>&1 | grep silence_end # cada silence_end ≈ el atVideo de una escena en timeline.json (±0.2s); captura el fotograma en ese momento y verifica el subtítulo
Errores comunes (aprendidos por las malas)
| Error | Solución |
|---|---|
| El video obtiene un nombre de archivo aleatorio | Captura page.video() ANTES de cerrar; await video.path() DESPUÉS de context.close(); renombra |
| El subtítulo desaparece al navegar | Vuelve a inyectar el subtítulo después de cada goto (ayudante caption()), o addInitScript + sessionStorage |
| Video demasiado rápido para humanos | slowMo: 100–150 + pausas explícitas de 3–5s por escena + pressSequentially({ delay: 40–60 }) para escritura visible |
| mp4 no se abre en PowerPoint/reproductores | ffmpeg -c:v libx264 -pix_fmt yuv420p |
| Video más pequeño/recortado | recordVideo.size DEBE coincidir con viewport |
| Pantalla blanca/pendiente en el video | waitUntil: "networkidle" + waitForURL después de acciones; calienta rutas en desarrollo |
| El espectador no sabe dónde mirar | Resalte ámbar (sombra de caja) en el elemento antes de hacer clic, o un cursor falso inyectado |
| Volver a grabar duplica datos | Acciones idempotentes o una semilla que tolere reejecuciones (slugs con sufijo, upserts) |
| El inicio de sesión se filtra entre personas | context.clearCookies() entre cambios de usuario |
| (narrado) Voz desincronizada con la pantalla | El video de Playwright comienza 0.2–1.5s después de la página (varía por ejecución/versión) — usa el syncMarker de la plantilla, nunca un desplazamiento fijo |
| (narrado) Voz cortada o escenas apresuradas | pause() fijo en una escena hablada — reemplázalo con narrate(), que espera al discurso |
| (narrado) Acrónimo/marca mal pronunciado | Escríbelo fonéticamente en say (ej. "S Q L" o "sequel"); el subtítulo permanece correcto |
Errores comunes
- Saltarse la verificación de fotogramas ("se ejecutó sin errores" ≠ "el video está bien") — siempre mira.
- Subtítulos que describen la interfaz ("pantalla de inicio de sesión") en lugar de narrar el argumento ("el equipo inicia sesión — control de roles detallado").
- Grabar sin un guion escrito: obtienes una larga grabación de pantalla sin historia.
- Credenciales reales codificadas — usa cuentas demo/semilla, inyecta mediante env si son sensibles.