Volver al Centro de conocimiento
Primeros pasos

Selenium sin ventana: ChromeOptions y qué cambia

Chrome sin ventana en Selenium difiere de una sesión con ventana en campos que la página lee. Las ChromeOptions que importan, el tamaño por defecto y por qué se aísla mejor.

BotBrowser Team

Documentación

Quieres la documentación estructurada de Primeros pasos?

Este artículo forma parte de la biblioteca editorial. Para pasos de configuración, material de referencia y actualizaciones continuas, entra en la sección de docs.

Ejecutar Selenium sin ventana es un argumento en ChromeOptions y un navegador distinto por debajo, y la distancia entre esos dos hechos es donde vive casi toda la confusión. BotBrowser proporciona un perfil declarado que mantiene coherentes entre sí las superficies que una página puede leer en cada arranque, y no cambia lo que Selenium le pidió a Chrome; las opciones que pasas deciden el modo y el perfil decide qué informa ese modo. Una ejecución local de tres sesiones muestra qué campos se mueven al cambiar el modo y cuáles no.

Resumen

  • --headless=new es lo que muestra la documentación actual de Selenium para Chrome, y ejecuta el navegador completo sin ventana.
  • La implementación anterior es un binario aparte, chrome-headless-shell, no otro valor de la marca.
  • ChromeOptions es donde vive el modo: la misma cadena --headless=new llega a cada binding por el método de argumentos de ese binding, como muestra el bloque siguiente.
  • Sin ventana y con ventana coincidieron en ventana gráfica, relación de píxeles del dispositivo y núcleos lógicos en la ejecución registrada.
  • Difirieron en el token del agente de usuario, el número de complementos en la shell y el objeto chrome en la shell.
  • Una sesión sin ventana es más fácil de aislar que una con ventana cuando esos campos se contradicen.
  • Registra el binario, el token y el número de complementos en cada ejecución; la marca por sí sola no dice qué navegador corrió.

Contenido

Dos paneles. El primero muestra la llamada ChromeOptions de Selenium que activa el modo sin ventana y los tres argumentos que cambian el comportamiento. El segundo enumera los campos que una página lee en sesiones sin ventana y con ventana, y marca cuáles coincidieron en una ejecución registrada y cuáles difirieron.

Dónde se define el modo

Selenium nunca elige un modo sin ventana por sí mismo; entrega argumentos a Chrome. La documentación actual de Selenium muestra --headless=new en su ejemplo de Chrome, y la misma cadena aparece en cada enlace de lenguaje porque el argumento se pasa sin cambios.

Java        options.addArguments("--headless=new");
Python      options.add_argument("--headless=new")
C#          options.AddArgument("--headless=new");
JS          options.addArguments('--headless=new')

La implementación anterior no es otro valor de esa marca. Es un ejecutable distinto, chrome-headless-shell, que Chrome for Testing publica por separado, y Selenium llega a él apuntando el binario del navegador a esa ruta en lugar de cambiar el argumento. Esa distinción explica una clase de informes en los que dos equipos pasan opciones idénticas y observan navegadores diferentes.

La comparación completa de las dos implementaciones, incluido lo que cada una informa para los complementos y el objeto chrome, está en Chrome headless nuevo frente al modo antiguo; la parte específica de Selenium es solo cómo se selecciona el binario.

same options + chrome binary            -> unified headless
same options + chrome-headless-shell    -> old implementation

Selenium 4 exige que la versión mayor de ChromeDriver coincida con la versión mayor de Chrome, y la documentación lo dice de forma directa. Una discrepancia falla al crear la sesión, que es el fallo bueno; el malo es un driver que coincide pero un binario que no, porque entonces la sesión arranca e informa lo que informe ese binario.

De dónde salen ese binario y su perfil es una decisión aparte con su propia lista de comprobación, y integración de perfiles de navegador en Selenium cubre las preguntas de propiedad de la sesión que una marca sin ventana no toca.

Las ChromeOptions que cambian el comportamiento

Tres opciones más allá del modo cambian lo que ve la página, y cada una evita un fallo concreto.

--headless=new                 full browser, no window
--window-size=1920,1080        without it, the window is 800x600 or the driver default
--disable-gpu                  older advice; check whether the target needs GPU paths

El tamaño de ventana es la opción que más a menudo explica una diferencia visual entre una ejecución local y CI. Una sesión sin ventana y sin tamaño explícito arranca con un valor por defecto pequeño, y una página que se maqueta de otra forma por debajo de un punto de ruptura renderiza un documento distinto en lugar del mismo documento en un marco menor. Fijar el tamaño de forma explícita hace que la comparación tenga sentido.

--disable-gpu es un consejo heredado de la época en que el modo sin ventana usaba un rasterizador por software por defecto. En una máquina con GPU utilizable puede cambiar qué ruta de renderizado se ejecuta, así que copiarlo a una suite nueva sin comprobarlo es una forma de introducir una diferencia en lugar de eliminarla.

option                     what it changes                     check before adopting
--headless=new             window visibility, not the engine   yes, it is the mode
--window-size              layout breakpoints                  set it to the product viewport
--disable-gpu              rendering path                      only if the host has no GPU

Dos opciones más aparecen en casi todo fragmento copiado y ninguna hace falta para el modo sin ventana. --no-sandbox desactiva una frontera de seguridad y es un recurso para contenedores, no un ajuste del modo; --disable-dev-shm-usage rodea un /dev/shm pequeño en algunos contenedores. Ambas cambian el entorno en el que corre la página, y ambas pertenecen a la configuración del host y no a un archivo de prueba que también corre en local.

Una cuarta opción merece nombre porque no es un argumento de Chrome: el directorio de perfil. Selenium pasa --user-data-dir como cualquier otro argumento, y cuando falta, el driver crea un perfil temporal que se descarta al terminar la sesión. Eso está bien para una prueba que parte de un estado limpio y mal para una que espera que sobreviva un inicio de sesión o una extensión, y el fallo es silencioso porque un perfil nuevo parece un navegador que funciona hasta la primera aserción que depende del estado guardado.

options.addArguments("--user-data-dir=/tmp/profile-42")   -> state survives the session
no --user-data-dir                                        -> temporary profile, discarded

El orden de los argumentos no importa a Chrome, pero el orden de la configuración sí importa a quien lee. Fija el binario, luego el modo, luego la ventana, luego el perfil, luego los recursos del host. Un fragmento que los mezcla en otro orden no es incorrecto, y es mucho más difícil de revisar cuando una sesión se comporta de forma inesperada.

Ejecutar la misma prueba en local y en CI

El problema más común de Selenium sin ventana no es un problema del modo; es una diferencia entre la máquina que escribió la prueba y la que la ejecuta. Tres propiedades del host cambian el documento que renderiza una página, y ninguna la fija ChromeOptions.

property        local value              CI value                 effect
fonts           full system set          minimal or none          glyphs render as boxes
GPU             hardware path            software rasteriser      subtle anti-aliasing change
display         real screen              none, or a virtual one   window metrics differ

Las fuentes son lo primero que conviene comprobar cuando una ejecución sin ventana produce una imagen que casi está bien. Un contenedor sin fuentes CJK renderiza esos caracteres como cajas notdef, y el fallo es idéntico con ventana en el mismo contenedor, que es la pista: si con ventana falla igual, el modo no es la variable.

La ruta de GPU es la segunda. Una sesión sin ventana en una máquina sin GPU utilizable llega a un rasterizador por software, y la salida compuesta puede diferir de una sesión con ventana en la misma máquina en unos pocos valores de subpíxel. Un límite de comparación ajustado en el portátil de desarrollo informará diferencias que vienen del host y no del código bajo prueba.

La pantalla es la tercera, y es la que primero encuentran los usuarios de Selenium en un servidor. Un Chrome con ventana necesita un servidor gráfico; uno sin ventana no. Esa asimetría es toda la razón por la que se usa el modo sin ventana en CI, y también significa que una suite que pasa sin ventana y falla con ventana suele tener un problema de pantalla y no de renderizado.

Una cuarta propiedad del host es la hora. Un contenedor arranca con el reloj y la zona horaria de su host, y una página que formatea una fecha renderiza una cadena distinta en una sesión con zona UTC que en otra con la zona del desarrollador. Esa diferencia aparece en una comparación visual como una región de texto cambiada y parece un problema de fuentes hasta que se lee la cadena.

host property     how it shows up in a diff
fonts             boxes, wrong glyph shapes, changed text width
GPU               subpixel differences across the whole image
display           window metrics, outerWidth
time zone         a different date or time string in one region

La zona horaria merece una frase en el informe de ejecución porque eliminarla es barato: imprime Intl.DateTimeFormat().resolvedOptions().timeZone y navigator.language en ambas sesiones, y una discrepancia se explica sola.

passes headless, fails headed  -> check the display server first
fails both the same way        -> host property (fonts, GPU, libraries)
fails only headless            -> mode or binary

Registrar esas tres propiedades en el informe de ejecución es lo que hace utilizable la tabla anterior después de los hechos. Una captura sin lista de fuentes y sin descripción de GPU es la foto de una máquina desconocida.

Cuando el HTML cambia

Una sesión sin ventana que devuelve un marcado distinto al de una con ventana es un problema distinto de una que devuelve píxeles distintos, y el diagnóstico es más corto. La página ejecutó otro JavaScript o recibió otros valores, y ambas cosas son visibles en el DOM.

1. dump document.documentElement.outerHTML in both modes
2. diff the two files, ignoring timestamps and generated ids
3. for each difference, check whether a page script branches on a value you record

El tercer paso es donde terminan casi todos los casos. Una página que ramifica según navigator.userAgent producirá un marcado distinto porque el token difiere, y eso es esperado y no un defecto. Una página que ramifica según navigator.plugins.length producirá un marcado distinto solo cuando cambió el binario, que es otra conclusión con otra solución.

branch on userAgent       -> differs between headless and headed, expected
branch on plugins.length  -> differs only when the binary is the shell
branch on innerWidth      -> differs only when the window option is missing

Ese mapa convierte una diferencia en una decisión. También explica por qué una suite que pasa en local y falla en CI puede fallar por un valor que nadie anotó: la rama existe en la página, y los campos registrados son los que dicen qué rama tomó.

Los modos de fallo que conviene nombrar

Cuatro fallos explican la mayoría de los informes de Selenium sin ventana que resultan no ser sobre el modo, y cada uno tiene una firma visible antes de depurar nada.

symptom                                  actual cause
element not visible, headed works        window size below the breakpoint
session creation fails immediately       driver and browser major mismatch
blank screenshot, text renders as boxes  missing fonts in the container
stale element after navigation           the element handle was captured before the load

El primero es el caso del tamaño de ventana ya descrito: el elemento existe, la página lo maquetó fuera del área visible, y una sesión sin ventana con una ventana por defecto pequeña lo informa como no visible. El segundo es una discrepancia de versiones y no tiene nada que ver con el modo, pero aparece en las mismas búsquedas porque ambos fallos surgen en la primera ejecución en un entorno nuevo.

El tercero es el caso de las fuentes, y es el que produce una captura que parece rota en lugar de vacía. El cuarto es un problema de espera y no de renderizado, y esperas de Selenium y pruebas estables es donde se describen las condiciones de una espera estable.

check before debugging the page:
  1. window size at or above the product viewport
  2. driver major equals browser major
  3. container font set matches the developer machine
  4. the element handle was obtained after the navigation settled

Recorrer esa lista en orden cuesta unos minutos y elimina las cuatro explicaciones más comunes. Un fallo que sobrevive a las cuatro merece depuración a nivel de página, y en ese punto los campos registrados en las secciones anteriores dicen qué modo y qué binario lo produjeron.

Qué lee una página de forma distinta

Una ejecución local registró los mismos campos en tres sesiones en el mismo host, con compilaciones estándar de Chromium y un perfil desechable por sesión. Los valores son los que imprimió esa ejecución, no una afirmación sobre todas las compilaciones.

unified headless  HeadlessChrome/131.0.0.0      plugins=5  chrome=object
headless shell    HeadlessChrome/131.0.6778.33  plugins=0  chrome=undefined
headed            Chrome/131.0.0.0              plugins=5  chrome=object

webdriver=true in all three (Selenium sets it)

El token es el campo que una página lee primero, y es el que separa los modos con más claridad: las dos variantes sin ventana informan HeadlessChrome y la que tiene ventana informa Chrome. Es una señal deliberada y documentada, no un accidente, y por eso una suite que verifica el token tiene que decidir qué está verificando.

navigator.userAgent    HeadlessChrome in headless, Chrome in headed
navigator.plugins      0 on the shell, 5 on unified and headed
window.chrome          undefined on the shell, object on the others

El número de complementos y el objeto chrome son los dos campos que separan la shell de las otras dos, y no el modo sin ventana del modo con ventana. Eso importa a quien depura una diferencia entre una ejecución local y CI: si el número de complementos cambió, cambió el binario, no el modo.

Los valores por defecto de ventana y pantalla

Las mediciones que sorprendieron en la ejecución registrada son las que no se movieron. La ventana gráfica, el tamaño de pantalla y la relación de píxeles del dispositivo fueron idénticos en las tres sesiones, y solo outerWidth difirió, en dos píxeles en la sesión con ventana por el marco de la ventana.

field              unified   shell   headed
innerWidth         1280      1280    1280
innerHeight        720       720     720
screen.width       1280      1280    1280
outerWidth         1280      1280    1282
devicePixelRatio   1         1       1

La consecuencia práctica es que una comparación de capturas entre estos modos no está probando el tamaño de ventana. Si dos ejecuciones sin ventana de la misma página producen imágenes distintas, la diferencia es animación, carga diferida o una fuente, y cambiar el modo no lo arreglará.

same mode, two runs differ  -> animation, timing or fonts
different modes, same size  -> mode or binary, not the viewport

Esa distinción merece constar en el informe de prueba, porque una diferencia visual sin causa asociada invita a la solución equivocada. Registrar los cuatro campos de la ventana gráfica junto a la imagen convierte «la captura cambió» en «la captura cambió mientras la ventana gráfica no».

Por qué el modo sin ventana es más fácil de aislar

Una sesión sin ventana no es sospechosa por sí misma, pero es más fácil de aislar cuando sus campos se contradicen. La ejecución registrada es un caso limpio: el token decía modo sin ventana y el resto del navegador parecía normal. Una sesión modificada puede ser lo contrario, con el token diciendo una cosa y la lista de complementos o el objeto chrome diciendo otra.

coherent headless    -> token says headless, everything else consistent
patched headless     -> token rewritten, plugin list or chrome object not

Por eso la comparación útil es entre campos y no contra un valor esperado único. Una página que lee solo navigator.userAgent puede quedar satisfecha con una reescritura; una página que lee el token, el número de complementos y el objeto chrome a la vez comprueba si el navegador es coherente por dentro. Qué cambia un núcleo de navegador frente a sobrescrituras inyectadas es el mismo argumento desde el otro lado.

Selenium añade una señal propia: navigator.webdriver vale true porque lo fija el driver, y eso también es cierto con ventana. Por lo tanto no es evidencia del modo sin ventana, y una prueba que lo trate como tal producirá un hallazgo sobre el driver y no sobre el modo.

La misma lógica se aplica al aviso de automatización. Chrome muestra una barra de software de prueba automatizada en algunas versiones cuando el driver lo inicia, y esa barra es un elemento de interfaz y no un valor visible para la página, así que una sesión sin ventana no la muestra y una con ventana puede mostrarla. Una comparación de capturas que incluya el marco del navegador diferirá entre los dos modos por una razón que no tiene nada que ver con la página.

page-level fields     token, plugins, chrome object, viewport, time zone
window-level chrome   infobar, window frame, tab strip

Capturar la página y no la ventana es lo que elimina la segunda fila de la comparación. La propia API de capturas de Selenium captura la ventana gráfica, que es el valor por defecto correcto por esta razón, y una captura de ventana completa desde el escritorio y no desde el driver reintroduce el problema.

Hay un campo más que conviene registrar para quien ejecuta la misma suite contra más de una compilación del navegador. El token HeadlessChrome lleva una versión, y la versión de ese token es la compilación que produjo la sesión. Una suite que actualiza Chrome entre ejecuciones y registra solo el modo sin ventana ha descartado el campo que habría explicado un cambio de renderizado, y la solución es una línea más en el informe y no un cambio en la prueba.

recorded token:  HeadlessChrome/131.0.0.0    -> build 131, headless
recorded token:  HeadlessChrome/131.0.6778.33 -> shell build, patch differs

Esa línea es también la forma más rápida de responder a la pregunta que inició esta comparación. Dos ejecuciones con las mismas opciones y tokens distintos están ejecutando binarios distintos, y ninguna cantidad de ajuste de opciones las hará coincidir.

Dos ejecuciones con el mismo token y comportamiento distinto son el caso difícil, y casi siempre son una diferencia de host y no de modo. El token dice la compilación; las fuentes, la ruta de GPU y la zona horaria dicen la máquina; y las cuatro comprobaciones anteriores son las que separan ambos casos.

Conclusión práctica

Fija el modo con --headless=new, fija el tamaño de ventana al de la ventana gráfica del producto y registra la ruta del binario junto al token del agente de usuario. Esos tres hechos separan la shell del modo sin ventana unificado y a ambos del modo con ventana, y lo hacen desde el lado de la página, donde una prueba puede verificarlos sin leer el código de arranque.

Cuando una ejecución sin ventana difiere de una con ventana, revisa los campos en este orden: token, número de complementos, objeto chrome, ventana gráfica, relación de píxeles del dispositivo. Los tres primeros son propiedades del modo y del binario; los dos últimos pertenecen al host y a las opciones. Una diferencia en el primer grupo es una decisión de arranque; una diferencia en el segundo es de configuración.

La afirmación que conviene evitar es que el modo sin ventana no deja nada que leer. No se sostiene, y la ejecución registrada muestra exactamente dónde es visible el modo. Lo que se sostiene es más estrecho y más útil: una sesión sin ventana coherente es un navegador completo sin ventana, y los campos que la describen se pueden leer y registrar como cualquier otro.

Fuentes

#Selenium#Chrome Sin Ventana#ChromeOptions#Chromedriver#Automatización

Este artículo forma parte de la guía: Primeros pasos con automatización de navegador: mapa inicial

Ejecuta la misma comprobación en BotBrowser

Carga un perfil, mantén dentro del navegador las señales que acabas de leer y confirma el resultado en tu propia máquina antes del despliegue.