Videos demo con bueno-demo
bueno-demo graba un recorrido guionizado por una página web y lo renderiza en un clip estilo Screen Studio. Hay dos pasos: captura que maneja una ventana Chromium real y la graba, y renderizado que añade la cámara, el cursor y los títulos.
Configuración, una vez por máquina
npm i -D bueno-demo # o ejecuta todo con npx bueno-demo
npx playwright install chromium
brew install ffmpeg # requerido; añade imagemagick para pósteres WebP
El ciclo
- Escribe un script, un archivo JSON (ver más abajo). Mantén una idea por clip, de 10 a 15 segundos.
- Captura:
npx bueno-demo capture demos/<nombre>.json out/<nombre> - Corte opcional de una carga lenta:
npx bueno-demo cut out/<nombre> <desde-s> <hasta-s> - Renderiza:
npx bueno-demo render out/<nombre> out/<nombre>/<nombre> --gif - Revísalo antes de usarlo. Crea una hoja de contactos y verifica cada fotograma:
ffmpeg -i out/<nombre>/<nombre>.mp4 -vf "fps=1,scale=640:-1,tile=3x4" -frames:v 1 sheet.pngSi aparece un error o datos privados en cámara, corrige el script o córtalo, luego vuelve a renderizar.
Scripts
{
"url": "http://localhost:3000/ajustes",
"viewport": [1440, 900],
"storage": "auth.json",
"keep": "document.querySelectorAll('[data-user-email]').forEach(e => e.textContent = '[email protected]')",
"steps": [
{ "scroll": "[data-testid=facturacion]" },
{ "click": "[data-testid=plan-toggle]", "after": 1200 },
{ "type": "[data-testid=nombre-equipo]", "text": "Northwind", "clear": true },
{ "hover": "[data-testid=guardar]", "zoom": true },
{ "click": "[data-testid=guardar]", "zoom": false },
{ "wait": 800 }
]
}
Pasos, cada uno opcionalmente con after (ms para pausar después):
click: desliza hacia el elemento y haz clic.zoom: falseomite el zoom de cámara; úsalo en un clic que navegue.type: haz clic, luego escribetext(delayms por tecla, por defecto 55;clear: truereemplaza el valor).fill: haz clic, luego establecetextde una vez, para inputs que la escritura no puede manejar (selectores de fecha).hover: desliza hacia allí.zoom: truetambién apunta la cámara.upload: desliza hacia una zona de destino y adjuntafile(relativo al script) alinput.press: una tecla, como"Escape".scroll: desplaza suavemente el elemento hasta que sea visible.wait: pausa, en ms.eval: ejecuta JS en la página, en cámara.
Opciones:
- Iniciar sesión:
storageapunta a un archivo de estado de almacenamiento de Playwright (guarda uno connpx playwright codegen --save-storage=auth.json <url>). O"cookiesFrom": "agent-browser"toma prestada una sesión de agent-browser con sesión iniciada. Las cookies nunca se imprimen. - Privacidad:
keepes JS que se ejecuta antes de la grabación y cada 250 ms durante ella, para reemplazar nombres reales y correos electrónicos por sustitutos, de modo que una re-renderización nunca los recupere.hideañade selectores CSS para ocultar (los overlays de desarrollo de Next.js y Vercel ya están ocultos;"hide": falselos muestra). - Fuera de cámara:
setupes JS que se ejecuta una vez antes de la grabación (para cerrar un banner, por ejemplo). - Sensación:
travelMs(tiempo de recorrido del cursor, por defecto 700),settle(espera después de cargar, por defecto 1500),tail(pausa al final, por defecto 1200),cursorStart([x, y]),reducedMotion: true. - Carga:
waitUntil(por defectonetworkidle). Una página que transmite video o hace polling nunca estará inactiva, por lo que la captura se agota; establece"load"y deja quesettlecubra el resto. - Otro servidor:
--base http://localhost:3001en la captura graba la misma ruta en otro lugar.
Selectores: prefiere data-testid o estructura (#title, input[name=amount]) sobre texto visible, para que los cambios de copia no rompan el script.
Banderas de renderizado
--zoom 1.6qué tan lejos acerca un clic (1.3 cuando importan dos cosas a la vez).--speed 0.9reproduce un 10% más lento;--speed 2.5para un montaje rápido.--end 9.8corta en ese segundo;--hold 1.2congela el último fotograma.--matte '#fdfaf1'enmarca la página como una ventana redondeada sobre ese color (por defecto es de borde a borde).--title 'Moviendo un evento' --kicker 'Eventos'añade un título de apertura;--end-capañade una tarjeta de cierre.--brand brand.jsonestablece el aspecto del título y la tarjeta:ink,paper,accent,displayFont/monoFont({ "family", "file", "weight" }),logo,footer,kicker. Las rutas son relativas al archivo de marca.--fitrenderiza con la forma propia de la página (vertical para un viewport de teléfono).--giftambién escribe un GIF de 960px y 15fps.
Reglas
- Nunca grabes datos reales de clientes. Usa cuentas de prueba y un hook
keeppara cualquier cosa personal; revisa la hoja de contactos antes de compartir. - Cortos y de un solo propósito. Una función por clip; recorta las esperas con
cut. - Aloja el video fuera del repositorio. Sube el script (y un póster si tu documentación lo necesita); coloca los MP4 y GIFs en tu almacenamiento o CDN.