Volver al Centro de conocimiento
Despliegue

Capturas fiables en navegadores headless

Flujo práctico para capturas headless fiables con viewport de perfil, señales de disponibilidad, captura nativa completa, revisión del final y memoria para contenedores.

BotBrowser Team

Documentación

Prefieres la documentación del producto mantenida?

Este artículo tiene una página equivalente en el centro de documentación. Usa los docs para el flujo canónico, las flags actuales y la referencia duradera.

BotBrowser permite repetir capturas en contextos de navegador controlados, pero no certifica igualdad visual entre hosts, pantallas, fuentes o estados de la aplicación. Usa este flujo para probar una ruta propia y registrar el límite que realmente revisaste.

Resumen

Mantén el viewport del perfil, espera marcadores de disponibilidad y final visibles, usa la captura nativa completa, revisa la parte inferior y reserva memoria para la imagen aprobada más larga. BotBrowser ofrece capturas repetibles, no equivalencia visual universal.

Contenido

Empiece por el viewport del perfil

Una captura solo resulta útil cuando representa la página que el navegador debía renderizar. En una sesión basada en un perfil, el viewport es el primer elemento del acuerdo de renderizado. Controla los saltos de línea, los puntos de cambio responsive, la navegación fija, el tamaño de las imágenes y la cantidad de contenido visible antes de desplazarse. No es un valor temporal que el script pueda cambiar para facilitar una captura.

Un fallo habitual consiste en dejar que la biblioteca de automatización elija un viewport cómodo. El contexto puede ser más estrecho que el perfil seleccionado y la página se reorganiza antes de crear la imagen. Otro fallo consiste en poner la altura del viewport a la altura del documento para conseguir una captura completa. Eso cambia el diseño responsive y hace que el resultado ya no represente la sesión del navegador.

Cuando el perfil debe proporcionar las dimensiones, deje el viewport del contexto sin configurar. En Puppeteer use defaultViewport: null. En Playwright cree el contexto sin sobrescribir viewport. Si el producto necesita otro tamaño, incluya esa decisión en el perfil y en la especificación de captura, y manténgala igual en las ejecuciones comparables.

const browser = await chromium.launch({
  executablePath: process.env.BROWSER_BINARY,
  args: [`--bot-profile=${process.env.BROWSER_PROFILE}`],
});

const context = await browser.newContext();
const page = await context.newPage();

El ejemplo no contiene una configuración de viewport a propósito. Esa omisión es la parte importante. No use una función de resize para hacer coincidir la altura del documento. Un documento largo debe seguir siendo largo dentro del viewport del perfil.

Un flujo de captura pasa del viewport y las señales de disponibilidad a la captura nativa y la revisión.

Qué prueba la captura y qué informa la propia página

Una captura es evidencia sobre píxeles, no una afirmación sobre los valores que calculó la página. Conviene mantener ambos lados juntos para que cada pregunta de revisión llegue al artefacto correcto.

SeñalQué muestra la capturaQué informa la misma páginaPor qué importa la diferencia
Ancho del viewportEl ancho con el que se dibujó el diseñowindow.innerWidth y window.visualViewport.widthUna captura tomada tras un resize sigue pareciendo válida, mientras el ancho informado revela qué punto de cambio se aplicó.
Tamaño de pantallaNada sobre la pantalla emuladascreen.width y screen.heightLa pantalla del perfil puede diferir del viewport del contexto, y solo los valores informados lo exponen.
Factor de píxelesLas dimensiones en píxeles de la salidawindow.devicePixelRatioUna imagen de 3840 de ancho puede venir de un viewport de 1920 con factor 2 o de uno de 3840 con factor 1.
Familia de fuentes cargadaLas formas de los glifos, después del hechodocument.fonts.status y document.fonts.check()Los glifos de reserva se parecen bastante en una imagen pequeña, pero el estado informado nombra la familia usada.
Estado de la animaciónUn instante congeladoelement.getAnimations() y el tiempo de transiciónDos imágenes de la misma página pueden diferir solo porque la captura cayó en otro fotograma.
Geometría del elementoLos píxeles donde se pintó un elementoelement.getBoundingClientRect()Un pie recortado se ve correcto en la parte alta de la imagen; el rectángulo muestra la posición real.

Use la tabla de atributos para las preguntas que una imagen no responde. Si alguien pregunta qué punto de cambio, qué fuente o qué fotograma, la respuesta sale de un valor calculado leído en la misma sesión, no de una segunda mirada a la imagen.

Defina el estado listo con señales de la página

Que termine la navegación no significa que la imagen esté lista. La página puede haber terminado su carga inicial mientras la aplicación coloca tarjetas, carga un gráfico, selecciona el idioma o cambia las fuentes de reserva por las definitivas. Una captura tomada entre esos pasos es una imagen válida de un estado que el lector no debía recibir.

Defina un pequeño acuerdo de disponibilidad para cada ruta. Un marcador creado por la página suele ser más fiable que una espera fija, porque la página puede crearlo cuando está presente el contenido necesario. Puede ser un elemento con un atributo estable, un estado visible o un componente de ruta que solo aparece después del último cambio de diseño. El acuerdo debe describir el contenido visible y no depender de un evento privado del navegador.

document.readyState llega a 'complete' cuando ya se disparó el evento load, lo que no dice nada sobre la última pasada de diseño. Una región que marca aria-busy="true" mientras se actualiza y retira el atributo al terminar ofrece un anclaje estándar, y document.fonts.status informa 'loading' o 'loaded' para el conjunto de fuentes.

Un acuerdo útil suele comprobar lo siguiente:

  • El contenedor principal existe y está visible.
  • Los datos de la vista solicitada ya se han renderizado.
  • Han terminado los cambios de diseño posteriores a la llegada de los datos.
  • Las fuentes de títulos, etiquetas y texto están listas.
  • Las imágenes de la zona solicitada han cargado o han llegado a un estado de reserva intencionado.
  • Una captura completa tiene un marcador claro de final.

El marcador final es especialmente útil en páginas progresivas. Que aparezca la primera tarjeta no dice nada sobre la mitad inferior de un informe. Esperar el marcador de final proporciona un límite estable. Si la ruta no tiene un final natural, defina un límite de captura deliberado en lugar de tratar un documento que crece sin límite como si estuviera completo.

Orden de espera práctico

El siguiente patrón combina un marcador de ruta, la disponibilidad de fuentes y el estado de las imágenes. No duerme durante un intervalo arbitrario. Cambie los selectores para cada página y haga que los marcadores formen parte del acuerdo de la ruta, no de un selector genérico usado en todo el sitio.

async function waitForCaptureReady(page) {
  await page.waitForLoadState('domcontentloaded');
  await page.locator('[data-capture-ready]').waitFor({ state: 'visible' });
  await page.locator('[data-capture-end]').waitFor({ state: 'visible' });

  await page.evaluate(async () => {
    await document.fonts.ready;
    await Promise.all(
      [...document.images].map(image =>
        image.complete
          ? undefined
          : new Promise(resolve => {
              image.addEventListener('load', resolve, { once: true });
              image.addEventListener('error', resolve, { once: true });
            })
      )
    );
  });
}

La rama de imágenes trata una miniatura opcional que no llega como una reserva terminada, para que un recurso secundario no bloquee el trabajo. Si todas las imágenes son necesarias, haga que el marcador de la página espere a los recursos obligatorios y registre una falta clara cuando uno no esté disponible.

La ausencia de actividad de red puede servir como señal auxiliar, pero no debe ser la única. Las actualizaciones en vivo, las conexiones persistentes y las consultas periódicas pueden mantener ocupada una página cuando el contenido visible ya está listo. Por otro lado, un renderizado del cliente puede terminar sin otra petición. El acuerdo de la página debe expresar el estado que el lector necesita ver.

Evite una pausa fija después de la navegación. En una página rápida solo añade espera y en una página lenta todavía puede capturar demasiado pronto. También oculta la razón por la que la página no estaba disponible. Un marcador estable deja un punto que se puede revisar cuando cambia la ruta.

Capture la página completa sin estirar el viewport

Para una imagen del viewport, use la opción de captura normal. Para una imagen del documento, use la opción nativa de página completa de la biblioteca de automatización. Esa opción conserva el viewport del perfil y llega al contenido situado debajo de la zona visible.

await waitForCaptureReady(page);

await page.screenshot({
  path: outputPath,
  fullPage: true,
  type: 'png',
});

No establezca la altura del viewport a la altura del documento antes de esta llamada. Ese atajo cambia el diseño responsive, puede impedir que el desplazamiento active contenido diferido y crea una imagen que ya no representa el perfil elegido. También puede producir una superficie mayor que la capacidad del sistema gráfico o de la memoria del contenedor.

Una secuencia de captura determinista

El orden importa más que cualquier espera aislada. Ejecute estos pasos en secuencia, y cada uno responde una pregunta que el anterior deja abierta.

  1. Lance con el perfil y sin sobrescribir el viewport, para que defaultViewport: null en Puppeteer o un contexto intacto en Playwright informe las dimensiones del perfil.
  2. Navegue con waitUntil: 'domcontentloaded' y deténgase ahí. El evento load dice que el analizador terminó, no que el diseño sea definitivo.
  3. Espere la marca de disponibilidad de la página ([data-capture-ready]) para que se haya ejecutado el diseño que depende de datos. Sin ella, una ejecución rápida captura un contenedor vacío.
  4. Espere la marca final de la página ([data-capture-end]) para que el contenido progresivo esté completo. Sin ella, un informe largo pierde su mitad inferior.
  5. Espere document.fonts.ready para que el texto se dibuje con el paquete de fuentes del perfil y no con un sustituto cuyas métricas nunca coincidirán.
  6. Resuelva cada img del rango de captura en load o error, para que una imagen tardía no desplace el diseño entre dos ejecuciones.
  7. Prepare las secciones diferidas con desplazamiento normal, vuelva arriba y repita los pasos 3 y 4. Desplazarse carga contenido; no sustituye a la captura nativa.
  8. Capture con fullPage: true y type: 'png' desde el viewport sin cambios, para que el límite de la imagen coincida con el diseño medido.
  9. Registre window.innerWidth, window.devicePixelRatio y el estado de fuentes resuelto junto al archivo, para que una comparación posterior nombre el ajuste que cambió.

Escriba el archivo con un nombre temporal en el volumen de salida y muévalo a su lugar cuando la escritura se cierre, para que un trabajo cancelado no reemplace la imagen aprobada anterior con una parcial.

Si hay contenido diferido, puede prepararlo con desplazamiento normal antes de la captura nativa. Desplazarse sirve para pedir a la página que cargue el rango que se quiere conservar. No sustituye a la captura completa. Vuelva a la posición superior, confirme de nuevo el acuerdo de disponibilidad y deje que la opción nativa recopile el documento.

El desplazamiento infinito necesita un límite explícito. Decida si la imagen termina en el conjunto cargado, en una sección con nombre o en un estado final de la ruta. No permita que el trabajo se desplace indefinidamente mientras la página añade elementos. Un informe con límites definidos es más fácil de revisar, almacenar y comparar, y su altura no depende del momento exacto de la captura.

Elementos fijos y diseño responsive

Las cabeceras adhesivas, paneles de consentimiento, botones de chat y barras flotantes pueden cubrir contenido en una imagen completa. Si no forman parte del informe, la ruta de captura debe ofrecer una presentación de impresión o archivo. Una hoja de estilos pequeña y específica para la captura suele ser más clara que intentar borrar elementos después de crear la imagen. Mantenga el viewport del perfil sin cambios.

Use el mismo viewport en ejecuciones comparables. Cambiar el ancho cambia los saltos de línea y la posición de las tarjetas aunque los datos sean iguales. Cambiar el factor de píxeles del dispositivo también cambia las dimensiones de salida y la memoria necesaria durante la creación. Registre estos valores como metadatos de la captura, no como decisiones accidentales del script.

Revise la parte inferior de cada imagen larga

El final de una imagen larga merece una comprobación propia. La parte superior puede verse bien mientras el pie, la última fila, los bordes o el fondo se cortan. También puede quedar un marcador de carga en la parte inferior. Abrir siempre la imagen desde la esquina superior hace que estos problemas pasen desapercibidos.

Añada un marcador final estable a la página e inclúyalo en la muestra de revisión. Puede ser el título del pie, la última sección del informe o una zona de finalización que tenga sentido para el lector. Debe aparecer en la imagen final, no ser una marca oculta de la ejecución del navegador.

Revise el extremo inferior de tres maneras:

  1. Abra una vista reducida y confirme que el documento llega al final previsto.
  2. Abra la última parte a una escala legible y revise la última fila, el pie, los bordes y el relleno del fondo. Una captura de solo esa franja en Playwright es page.screenshot({ clip: { x: 0, y: 800, width: 1280, height: 400 } }).
  3. Compare las dimensiones y el tamaño del archivo con el registro de captura. Un cambio repentino puede indicar un movimiento de diseño, una sección ausente o un recurso que no llegó. identify -format '%wx%h %b' shot.png imprime los dos valores que debe contener el registro.

Las comprobaciones automáticas deben centrarse en el acuerdo de la página y en el archivo generado. Confirme que el marcador final estaba visible antes de capturar, que el archivo existe y que la imagen se puede decodificar. Conserve unas pocas páginas largas representativas para revisión visual. La comparación de píxeles es útil en un conjunto controlado, pero conviene acompañarla con una revisión legible: las fuentes, el renderizado de la plataforma y los cambios legítimos de contenido pueden modificar píxeles concretos.

No use la altura bruta del documento como única condición de éxito. La altura puede existir antes de que se pinte el contenido y puede seguir creciendo después de crear la imagen. El marcador final y la revisión del archivo responden mejor a la pregunta que importa: ¿llegó el contenido previsto a la imagen final?

Reserve memoria compartida para imágenes altas en contenedores

El tamaño del contenedor forma parte de la configuración del servidor; la configuración del servidor sin interfaz reúne los flags y el camino de pantalla que la acompañan. Una imagen larga consume más que el tamaño final del PNG o JPEG en disco. El navegador necesita espacio de trabajo mientras compone la página, la capa de automatización necesita espacio mientras recibe el resultado y el codificador lo necesita mientras escribe el archivo. Un perfil de alta densidad de píxeles aumenta la superficie en ambas direcciones. Un montaje pequeño de memoria compartida puede fallar solo con la página más larga y parecer un problema ocasional.

Reserve memoria compartida según el trabajo real. Considere el viewport del perfil, el factor de píxeles, el documento más largo aprobado, el formato de imagen y las capturas que puedan coexistir en el contenedor. Deje espacio para el arranque del navegador y el renderizado habitual. Establezca de forma explícita el tamaño de /dev/shm con el runtime del contenedor o con el ajuste equivalente de Compose, en lugar de aceptar el valor pequeño predeterminado.

docker run --rm \
  --shm-size="${BROWSER_SHM_SIZE}" \
  -e BROWSER_PROFILE=/run/profiles/capture.enc \
  screenshot-worker

El valor debe pertenecer a la configuración de despliegue porque depende del conjunto de páginas aprobado. No copie la configuración de una prueba de página corta a un trabajo de documentos largos. Cuando cambie el conjunto, vuelva a capturar la página representativa más exigente y revise tanto el resultado como los recursos del contenedor.

También importan otros recursos. El directorio de salida debe admitir la imagen completa y su escritura temporal, el montaje debe tener la propiedad que usa el worker y el almacenamiento del perfil debe estar separado de los artefactos finales. Una escritura fallida no debe reemplazar la imagen aprobada anterior con un archivo parcial. Escriba primero con un nombre temporal en el mismo volumen, cierre el archivo y después use la operación atómica normal del sistema de archivos.

Si la configuración Linux aprobada usa Xvfb, su superficie debe cubrir el viewport del perfil y utilizar la configuración de color validada para el despliegue. El modo headless nativo puede no necesitar Xvfb. No añada una pantalla virtual solo porque la imagen sea alta. Elija un camino compatible, pruébelo con las páginas objetivo y manténgalo igual en desarrollo y producción.

Elija el formato según el uso

PNG es una opción segura para texto, diagramas, interfaces y regresiones visuales. Conserva los bordes definidos y evita artefactos cerca de las etiquetas pequeñas. JPEG puede ser apropiado para vistas previas con muchas fotografías cuando importa más el tamaño del archivo que la precisión de los bordes. Incluya el formato en la especificación para que otro trabajo no cambie el criterio de revisión.

page.screenshot() de Playwright acepta type: 'png' o type: 'jpeg', y omitBackground: true mantiene el fondo transparente solo en PNG. Puppeteer también ofrece type: 'webp' junto a quality entre 0 y 100, útil cuando el consumidor puede decodificar WebP.

La salida de alta densidad de píxeles ayuda cuando el lector ampliará un informe o cuando la imagen se conservará como archivo. También aumenta memoria, transferencia y almacenamiento. El perfil seleccionado debe proporcionar el factor de píxeles; no lo sobrescriba en el contexto solo para conseguir una imagen más nítida. Si se necesita una vista previa pequeña, créela después de conservar la captura aprobada.

Use nombres estables que registren la ruta, la familia del perfil, el modo, el formato y la fecha, sin incluir datos privados de la página. Conserve el artefacto original cuando genere una variante. Así se puede distinguir un cambio de renderizado de un cambio de miniatura en una revisión posterior.

Un patrón de producción pequeño

La función de captura debe mostrar el orden sin ambigüedad: abrir una página basada en el perfil, navegar, esperar el acuerdo de la página, usar la opción nativa de página completa cuando corresponda y cerrar la página si la captura falla.

async function capture(page, url, outputPath, fullPage = false) {
  await page.goto(url, { waitUntil: 'domcontentloaded' });
  await waitForCaptureReady(page);
  await page.screenshot({
    path: outputPath,
    fullPage,
    type: 'png',
  });
}

Si varias rutas comparten la misma política de sesión aprobada, cree el navegador fuera de esta función. Mantenga clara la propiedad de cada página, ciérrela cuando el artefacto esté completo y entregue el error junto con la ruta y el modo. Una captura fallida debe ser visible para el ejecutor, no sustituirse en silencio por un archivo antiguo.

En un lote, cada página debe pasar por las mismas comprobaciones de disponibilidad y artefacto. No cree una vía rápida que omita el marcador final para las páginas que vienen después. La primera página suele verse bien; una página lenta revela memoria insuficiente, fuentes tardías o un documento que sigue creciendo. Un acuerdo común facilita interpretar esas diferencias.

Síntomas y orden de revisión

La imagen está vacía o casi vacía. Confirme que la navegación llegó a la ruta prevista, que apareció el marcador de listo y que el camino de pantalla elegido está disponible. Revise la salida del navegador para saber si la página se detuvo antes del marcador, en vez de añadir otra pausa. La captura es una sola ida y vuelta CDP: Page.captureScreenshot devuelve la imagen completa en una cadena base64, así que una transferencia interrumpida no deja un archivo parcial que comparar.

La imagen termina antes de la última sección. Confirme el marcador final, prepare el contenido diferido con desplazamiento normal y compruebe que el desplazamiento infinito tiene un límite. Después revise el extremo inferior del archivo.

El texto cambia entre capturas. Confirme el mismo viewport del perfil, factor de píxeles, fuentes, esquema de color y datos. Espere a que las fuentes estén listas y revise los cambios de diseño antes de capturar.

El contenedor falla solo con páginas largas. Revise juntos el montaje de memoria compartida, el espacio temporal, el volumen de salida y la superficie de pantalla. Una página corta no demuestra que quepa la imagen más larga. Ajuste el recurso, repita la captura representativa y guarde el ajuste con el registro del trabajo. --disable-dev-shm-usage mueve los archivos de memoria compartida del navegador a /tmp; cambia dónde aparece la presión, no si caben el viewport del perfil y la superficie de píxeles.

Un control flotante cubre el informe. Use la presentación de archivo de la ruta o capture un elemento de contenido significativo. No edite los píxeles después y confirme que la presentación conserva el viewport del perfil.

Aparece un marcador de carga. Haga que el marcador de listo espere al recurso necesario o declare la reserva como intencionada. El estado de red por sí solo no indica si el lector puede aceptar un marcador.

Dos tamaños de archivo desde una misma página

La misma ruta, el mismo perfil y el mismo script producen un PNG de unos 218 KB en una ejecución y de unos 402 KB en la siguiente. Nada falló y ambas imágenes abren. Aísle la causa capa por capa en lugar de compararlas a ojo.

  1. Compare primero las dimensiones en píxeles con identify -format '%wx%h %b' shot.png. Dimensiones idénticas con distinto tamaño de archivo apuntan a contenido, texto o compresión; dimensiones distintas apuntan al viewport.
  2. Descarte el viewport. Lea window.innerWidth y window.devicePixelRatio en cada ejecución. Un factor que pasó de 2 a 1 reduce la superficie de píxeles a la mitad en ambos sentidos y cambia el tamaño mucho más que cualquier cambio de contenido.
  3. Descarte la carga de fuentes. Lea document.fonts.status y el resultado de document.fonts.check('16px "Your Heading Family"') antes de capturar. Una ejecución capturada con status === 'loading' pintó glifos sustitutos, que comprimen distinto y desplazan las alturas de línea.
  4. Descarte la animación. Llame a document.getAnimations() y compruebe que una transición larga o un indicador tienen playState === 'finished'. Un fotograma capturado a mitad de transición cambia el suavizado y el relleno de fondo, así que el codificador emite un flujo distinto.
  5. Solo entonces compare el contenido. Compare la región de texto entre los dos archivos; si las palabras difieren, cambiaron los datos y ninguna captura es incorrecta.
  6. Guarde la corrección en el acuerdo de disponibilidad: espere la familia de fuentes y el marcador final, y desactive o espere la animación no esencial de la ruta de captura.

Localice la capa antes de cambiar el script. Una diferencia de tamaño solo dice que la salida cambió, no cuál de estas tres entradas se movió. Dos valores medidos por ejecución, el viewport informado y el estado de fuentes, los separan en una sola pasada.

Revise antes de convertir la captura en referencia

Mantenga una muestra pequeña y representativa para la revisión del despliegue. Incluya una página corta, un informe largo, una página con fuentes web, una página con medios diferidos y un diseño responsive que use el perfil seleccionado. La intención no es reunir todas las rutas, sino cubrir los estados que cambian el límite de la imagen o la demanda de recursos; la validación de versiones del navegador guarda esa muestra junto a la compilación a la que pertenece.

Para cada ruta registre el perfil, el origen del viewport, el modo, el formato, el camino de pantalla, la política de memoria del contenedor, el marcador de listo y el marcador final. Revise la primera pantalla y la parte inferior. Confirme que el artefacto se puede decodificar, tiene la orientación esperada y se guarda en el lugar previsto. Cuando un cambio sea intencionado, actualice también el registro de captura.

Una captura fiable es un flujo de renderizado, no el último clic. El perfil aporta un viewport estable, la página aporta un estado listo con significado, la opción nativa de página completa alcanza el contenido inferior sin cambiar el viewport y la revisión presta atención a la parte que más fácilmente se pierde. La memoria compartida y el almacenamiento completan el acuerdo para documentos largos y de alta densidad de píxeles. Así las imágenes siguen siendo útiles para revisión visual, archivo y controles de calidad autorizados en distintos entornos.

BotBrowser soporta capturas repetibles en contextos controlados; no garantiza igualdad visual entre hosts, pantallas, fuentes o estados de la aplicación.

Conclusión práctica

Trata la captura como un contrato de renderizado: fija viewport e inputs, espera disponibilidad visible, captura de forma nativa, revisa el borde inferior y conserva una ruta de reserva. Registra lo observado por BotBrowser sin prometer que todos los hosts, pantallas, fuentes o estados coincidirán.

Fuentes

Para seguir leyendo, consulta validación de versiones del navegador y eventos WebDriver BiDi.

#Screenshot#headless#despliegue#Reliability#Playwright

Este artículo forma parte de la guía: Despliegue y versión del navegador: mapa operativo

Lleva BotBrowser de la investigación a producción

Usa estas guías para entender el modelo y después avanzar hacia validación multiplataforma, contextos aislados y despliegue de navegador preparado para escalar.