Exportar a Puppeteer: de la grabación al script
Los formatos de exportación, qué código genera cada uno, las cinco cosas que hay que arreglar a mano, y cómo convertir un script generado en algo mantenible.
Una grabación exportada es código real, ejecutable fuera del navegador, que reproduce el flujo sin ninguna intervención. Es el puente entre la exploración manual y la automatización, y su valor depende casi por completo de lo que hagas después de exportar: el código generado funciona y no es mantenible, y convertirlo en algo que sobreviva a seis meses de cambios exige cinco intervenciones concretas.
- Elegir el formato de exportación adecuado a lo que quieres hacer.
- Leer el código generado y entender su estructura.
- Aplicar las cinco correcciones que lo hacen mantenible.
- Integrar el resultado en una batería de pruebas.
Los formatos
Formato de datos estructurados. La grabación tal cual, en un formato que otras herramientas pueden leer y reproducir. Es el más portable y el que conviene guardar como fuente de verdad si vas a seguir editando la grabación en el panel.
Script de reproducción. Un script mínimo que carga la grabación y la ejecuta con una librería de reproducción. Es la exportación más fiel y la que menos código genera.
Script de automatización completo. Genera código con todas las acciones expresadas explícitamente. Es el más útil para convertir en una prueba real, porque es código legible que puedes editar.
Script de automatización con análisis de rendimiento. El mismo, más la instrumentación que ejecuta una auditoría durante el flujo. Es el punto de partida para automatizar auditorías de recorridos completos.
Y el panel admite extensiones que añaden formatos propios, con lo que se puede exportar al marco de pruebas que use tu equipo.
La exportación es unidireccional. Si editas el script generado, esos cambios no vuelven al panel, y si vuelves a exportar la grabación pierdes tus ediciones. La forma sana de trabajar es tratar la exportación como una generación inicial: se exporta una vez, se convierte en código mantenido a mano, y a partir de ahí el panel solo se usa para grabar flujos nuevos.
Qué genera y qué le falta
El código generado tiene una estructura previsible: arranca el navegador, abre una página, y ejecuta la secuencia de acciones con esperas por selector.
Lo que le falta, sistemáticamente, son cinco cosas.
Uno: estructura. Todo es una secuencia plana de acciones sin funciones, sin nombres y sin reutilización. Diez grabaciones exportadas producen diez ficheros con el mismo bloque de inicio de sesión repetido.
Dos: aserciones reales. La grabación comprueba que los elementos existen, no que la aplicación haga lo correcto. Un script que recorre el flujo sin verificar nada solo detecta errores catastróficos.
Tres: manejo de fallos útil. Cuando algo falla, el error dice que un selector no apareció, y no da ninguna pista de en qué estado estaba la aplicación.
Cuatro: independencia del estado. El script asume que la aplicación está en el estado en que estaba al grabar. Si el usuario de prueba tiene ya un elemento en el carrito, o si un diálogo de bienvenida aparece la primera vez, falla.
Cinco: control del entorno. Sin fijar tiempo, azar ni red, cualquier prueba que compare algo es inestable.
El script mantenible
Estas cinco correcciones convierten el código generado en algo que sobrevive. El resultado es más largo y es el que sigue funcionando dentro de un año.
// Script derivado de una grabacion, convertido en prueba mantenible
import puppeteer from 'puppeteer';
// 1. ESTRUCTURA: acciones reutilizables con nombres del dominio
const acciones = {
async iniciarSesion(pagina, usuario, clave) {
await pagina.goto('https://ejemplo.com/entrar', { waitUntil: 'domcontentloaded' });
await pagina.locator('[data-test="usuario"]').fill(usuario);
await pagina.locator('[data-test="clave"]').fill(clave);
await pagina.locator('[data-test="entrar"]').click();
await pagina.locator('[data-test="panel-principal"]').wait();
},
async buscarProducto(pagina, termino) {
await pagina.locator('[data-test="busqueda"]').fill(termino);
await pagina.keyboard.press('Enter');
await pagina.locator('[data-test="resultados"][data-estado="listo"]').wait();
},
async anadirAlCarrito(pagina, indice = 0) {
const botones = await pagina.$$('[data-test="anadir-al-carrito"]');
if (!botones[indice]) throw new Error('No hay producto en la posicion ' + indice);
await botones[indice].click();
await pagina.locator('[data-test="panel-carrito"]').wait();
}
};
// 2. ASERCIONES sobre el comportamiento, no sobre la existencia
async function afirmar(pagina, descripcion, comprobacion) {
const ok = await comprobacion();
if (!ok) {
// 3. DIAGNOSTICO cuando falla: captura, HTML y errores de consola
await pagina.screenshot({ path: `fallo-${Date.now()}.png`, fullPage: true });
const html = await pagina.content();
const errores = await pagina.evaluate(() => globalThis.__errores ?? []);
console.error('FALLO:', descripcion);
console.error('Errores de consola registrados:', errores);
console.error('HTML guardado, longitud:', html.length);
throw new Error('Asercion fallida: ' + descripcion);
}
console.log('OK:', descripcion);
}
async function ejecutar() {
const navegador = await puppeteer.launch({ headless: true });
const pagina = await navegador.newPage();
// 5. CONTROL DEL ENTORNO: tiempo, azar y errores, antes de que la pagina ejecute nada
await pagina.evaluateOnNewDocument(() => {
const AHORA = new Date('2026-08-06T09:00:00Z').getTime();
const FechaOriginal = Date;
globalThis.Date = class extends FechaOriginal {
constructor(...a) { return a.length ? new FechaOriginal(...a) : new FechaOriginal(AHORA); }
static now() { return AHORA; }
};
let semilla = 42;
Math.random = () => { semilla = (semilla * 1103515245 + 12345) % 2147483648; return semilla / 2147483648; };
globalThis.__errores = [];
addEventListener('error', e => globalThis.__errores.push(e.message));
addEventListener('unhandledrejection', e => globalThis.__errores.push(String(e.reason)));
});
// Interceptar la red para que la prueba no dependa del backend
await pagina.setRequestInterception(true);
pagina.on('request', (peticion) => {
if (peticion.url().includes('/api/buscar')) {
return peticion.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, nombre: 'Camiseta azul', precio: 19.9 }])
});
}
peticion.continue();
});
try {
// 4. ESTADO CONOCIDO: partir siempre del mismo punto
await pagina.goto('https://ejemplo.com/');
await pagina.evaluate(() => { localStorage.clear(); sessionStorage.clear(); });
await acciones.iniciarSesion(pagina, 'prueba@ejemplo.com', 'clave-de-prueba');
await acciones.buscarProducto(pagina, 'camiseta');
await afirmar(pagina, 'la busqueda devuelve al menos un resultado',
async () => (await pagina.$$('[data-test="producto"]')).length > 0);
await acciones.anadirAlCarrito(pagina);
await afirmar(pagina, 'el carrito muestra exactamente un elemento',
async () => await pagina.$eval('[data-test="lista-carrito"]', el => el.children.length === 1));
await afirmar(pagina, 'el total no es cero',
async () => await pagina.$eval('[data-test="total-carrito"]', el => !/^0[,.]00/.test(el.textContent.trim())));
await afirmar(pagina, 'no se han registrado errores de consola',
async () => (await pagina.evaluate(() => globalThis.__errores.length)) === 0);
console.log('Flujo completado sin incidencias.');
} finally {
await navegador.close();
}
}
await ejecutar();
Las tres aserciones intermedias son la diferencia entre una prueba y un paseo. Comprueban comportamiento —cuántos elementos hay en el carrito, que el total no sea cero— y no la existencia de nodos. Y la última, la de errores de consola, es la que más veces detecta problemas reales con menos esfuerzo: una excepción no capturada durante el flujo la caza siempre.
Cuándo la grabadora es el camino y cuándo no
Es el camino para crear rápido una prueba de un flujo que ya existe, para reproducir un bug de forma determinista, y para dar un punto de partida a alguien que no ha escrito automatización nunca.
No es el camino cuando la interacción es compleja —arrastrar, gestos, lienzo, contenido dentro de marcos anidados—, cuando la prueba necesita lógica —bucles, condiciones, datos generados— o cuando ya tienes una batería consolidada con sus convenciones, porque el código generado no las seguirá.
En ese último caso, la grabadora sigue siendo útil como herramienta de descubrimiento de selectores: se graba, se miran los selectores que eligió, y se escriben a mano en el estilo del proyecto.
La automatización de interfaz tiene un problema de calibración que decide su destino en un proyecto, y merece la pena enunciarlo porque explica por qué tantas baterías de pruebas acaban desactivadas. Una prueba tiene valor si su fallo es informativo, es decir, si cuando falla eso significa que hay un problema real. Las pruebas generadas de una grabación sin más trabajo tienden a los dos extremos malos a la vez, que es lo desconcertante. Por un lado fallan demasiado, por motivos que no son bugs: un selector estructural que cambió con un rediseño inocuo, una espera que no era suficiente en un servidor de integración más lento, una fecha que apareció distinta, un tercero que tardó más de lo normal. Cada uno de esos fallos consume tiempo de alguien y no encuentra nada, y después de tres o cuatro seguidos el equipo aprende a ignorar el color rojo, que es el momento en que la batería deja de servir para nada aunque siga ejecutándose. Y por otro lado no fallan cuando deberían, porque solo comprueban que unos elementos existen: la aplicación puede estar mostrando datos incorrectos, el total puede ser cero, la petición puede haber fallado en silencio, y la prueba pasa contenta porque encontró todos sus selectores. Las cinco correcciones de esta lección atacan los dos extremos a la vez: la estabilidad de selectores, el control del entorno y las esperas por condición reducen los falsos fallos; las aserciones sobre comportamiento y la comprobación de errores de consola reducen los falsos aciertos. La regla que resume el trabajo, y que conviene aplicar a cada prueba que escribas: si no puedes describir en una frase qué bug detectaría esta prueba, no la escribas. Y su complemento: si una prueba falla por algo que no es un bug, arréglala inmediatamente o bórrala, porque una prueba poco fiable es peor que ninguna prueba: consume tiempo y además enseña al equipo a desconfiar de la señal.