Retour au Centre de connaissances
Démarrage

Selenium sans fenêtre : ChromeOptions et écarts

Chrome sans fenêtre sous Selenium diffère d’une session fenêtrée sur des champs qu’une page lit. Les ChromeOptions utiles, la taille par défaut et pourquoi le mode se repère mieux.

BotBrowser Team

Documentation

Vous voulez la documentation structurée pour Démarrage ?

Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.

Lancer Selenium sans fenêtre tient à un argument dans ChromeOptions et à un autre navigateur en dessous, et l’écart entre ces deux faits concentre l’essentiel de la confusion. BotBrowser fournit un profil déclaré qui maintient cohérentes entre elles les surfaces qu’une page peut lire à chaque lancement, et ne change pas ce que Selenium a demandé à Chrome ; les options transmises décident du mode et le profil décide de ce que ce mode rapporte. Une exécution locale de trois sessions montre quels champs bougent quand le mode change et lesquels restent stables.

Résumé

  • --headless=new est ce que la documentation Selenium actuelle montre pour Chrome, et il exécute le navigateur complet sans fenêtre.
  • L’implémentation précédente est un binaire distinct, chrome-headless-shell, et non une autre valeur du drapeau.
  • ChromeOptions est l’endroit où vit le mode : la même chaîne --headless=new atteint chaque liaison par la méthode d’arguments de cette liaison, comme le montre le bloc ci-dessous.
  • Sans fenêtre et avec fenêtre concordaient sur la zone d’affichage, la densité de pixels et le nombre de cœurs logiques dans l’exécution enregistrée.
  • Ils différaient sur le jeton d’agent utilisateur, le nombre de plugins dans la shell et l’objet chrome dans la shell.
  • Une session sans fenêtre se repère plus facilement qu’une session fenêtrée quand ces champs se contredisent.
  • Notez le binaire, le jeton et le nombre de plugins à chaque exécution ; le drapeau seul ne dit pas quel navigateur a tourné.

Sommaire

Deux panneaux. Le premier montre l’appel ChromeOptions de Selenium qui active le mode sans fenêtre et les trois arguments qui changent le comportement. Le second énumère les champs qu’une page lit en session sans fenêtre et fenêtrée, en marquant ceux qui concordaient lors d’une exécution enregistrée et ceux qui différaient.

Où le mode est défini

Selenium ne choisit jamais lui-même un mode sans fenêtre ; il transmet des arguments à Chrome. La documentation Selenium actuelle montre --headless=new dans son exemple Chrome, et la même chaîne apparaît dans chaque liaison de langage parce que l’argument est transmis sans modification.

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

L’implémentation précédente n’est pas une autre valeur de ce drapeau. C’est un exécutable distinct, chrome-headless-shell, que Chrome for Testing publie séparément, et Selenium l’atteint en pointant le binaire du navigateur vers ce chemin plutôt qu’en changeant l’argument. Cette distinction explique une catégorie de rapports où deux équipes passent des options identiques et observent des navigateurs différents.

La comparaison complète des deux implémentations, y compris ce que chacune rapporte pour les plugins et l’objet chrome, se trouve dans Chrome headless nouveau contre ancien mode ; la partie propre à Selenium ne concerne que la sélection du binaire.

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

Selenium 4 exige que la version majeure de ChromeDriver corresponde à celle de Chrome, et la documentation le dit explicitement. Une divergence échoue à la création de session, ce qui est l’échec utile ; le mauvais cas est un pilote qui correspond mais un binaire qui ne correspond pas, car la session démarre alors et rapporte ce que ce binaire rapporte.

L’origine de ce binaire et de son profil relève d’une décision distincte avec sa propre liste de vérification, et intégration de profils de navigateur dans Selenium traite les questions de propriété de session qu’un drapeau sans fenêtre ne touche pas.

Les ChromeOptions qui changent le comportement

Trois options au-delà du mode changent ce que la page voit, et chacune évite une défaillance précise.

--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

La taille de fenêtre est celle qui explique le plus souvent un écart visuel entre une exécution locale et la CI. Une session sans fenêtre sans taille explicite démarre sur une valeur par défaut réduite, et une page qui se met en page autrement sous un seuil rend un document différent plutôt que le même document dans un cadre plus petit. Fixer la taille explicitement rend la comparaison significative.

--disable-gpu est un conseil hérité de l’époque où le mode sans fenêtre utilisait un rasteriseur logiciel par défaut. Sur une machine dotée d’un GPU exploitable, il peut changer la voie de rendu exécutée ; le recopier dans une nouvelle suite sans vérification introduit donc un écart au lieu d’en supprimer un.

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

Deux autres options apparaissent dans presque tous les extraits recopiés et aucune n’est nécessaire au mode sans fenêtre. --no-sandbox désactive une frontière de sécurité et relève du conteneur, pas du mode ; --disable-dev-shm-usage contourne un /dev/shm trop petit dans certains conteneurs. Toutes deux changent l’environnement dans lequel la page s’exécute, et toutes deux appartiennent à la configuration de l’hôte plutôt qu’à un fichier de test qui tourne aussi en local.

Une quatrième option mérite d’être nommée parce que ce n’est pas un argument Chrome : le répertoire de profil. Selenium passe --user-data-dir comme n’importe quel argument, et en son absence le pilote crée un profil temporaire jeté à la fin de la session. C’est correct pour un test qui part d’un état propre et faux pour un test qui attend la survie d’une connexion ou d’une extension, et l’échec est silencieux car un profil neuf ressemble à un navigateur qui fonctionne jusqu’à la première assertion qui dépend d’un état stocké.

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

L’ordre des arguments n’importe pas à Chrome, mais l’ordre de la configuration compte pour qui lit. Fixez le binaire, puis le mode, puis la fenêtre, puis le profil, puis les contournements propres à l’hôte. Un extrait qui les mélange autrement n’est pas faux, et il est bien plus difficile à relire quand une session se comporte de façon inattendue.

Exécuter le même test en local et en CI

Le problème Selenium sans fenêtre le plus courant n’est pas un problème de mode ; c’est un écart entre la machine qui a écrit le test et celle qui l’exécute. Trois propriétés de l’hôte changent le document rendu par une page, et aucune n’est fixée par 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

Les polices sont la première chose à vérifier quand une exécution sans fenêtre produit une image presque correcte. Un conteneur sans polices CJK rend ces caractères en carrés notdef, et l’échec est identique en mode fenêtré sur le même conteneur, ce qui est l’indice : si le mode fenêtré échoue de la même façon, le mode n’est pas la variable.

La voie GPU vient ensuite. Une session sans fenêtre sur une machine sans GPU exploitable atteint un rasteriseur logiciel, et la sortie composée peut différer d’une session fenêtrée sur la même machine de quelques valeurs de sous-pixel. Une tolérance de comparaison réglée sur le portable du développeur signalera donc des écarts venus de l’hôte et non du code testé.

L’affichage est la troisième, et c’est celle que les utilisateurs de Selenium rencontrent d’abord sur un serveur. Un Chrome fenêtré a besoin d’un serveur d’affichage ; une session sans fenêtre n’en a pas besoin. Cette asymétrie est toute la raison d’employer le mode sans fenêtre en CI, et elle signifie aussi qu’une suite qui passe sans fenêtre et échoue avec fenêtre a généralement un problème d’affichage plutôt que de rendu.

Une quatrième propriété de l’hôte est l’heure. Un conteneur démarre avec l’horloge et le fuseau de son hôte, et une page qui formate une date rend une chaîne différente dans une session en UTC et dans une session au fuseau du développeur. Cet écart apparaît dans un diff visuel comme une région de texte modifiée et ressemble à un problème de police jusqu’à la lecture de la chaîne.

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

Le fuseau mérite une phrase dans le rapport d’exécution car l’éliminer coûte peu : imprimez Intl.DateTimeFormat().resolvedOptions().timeZone et navigator.language dans les deux sessions, et une divergence s’explique d’elle-même.

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

Consigner ces trois propriétés dans le rapport d’exécution rend le tableau précédent utilisable après coup. Une capture sans liste de polices ni description de GPU est la photo d’une machine inconnue.

Quand le HTML lui-même diffère

Une session sans fenêtre qui renvoie un balisage différent d’une session fenêtrée pose un problème distinct d’une session qui renvoie des pixels différents, et le diagnostic est plus court. La page a exécuté un autre JavaScript ou reçu d’autres valeurs, et les deux se voient dans le 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

La troisième étape clôt la plupart des cas. Une page qui branche sur navigator.userAgent produira un balisage différent parce que le jeton diffère, et c’est attendu plutôt qu’un défaut. Une page qui branche sur navigator.plugins.length ne produira un balisage différent que si le binaire a changé, ce qui est une autre conclusion avec une autre correction.

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

Cette correspondance transforme un diff en décision. Elle explique aussi pourquoi une suite qui passe en local et échoue en CI peut échouer sur une valeur que personne n’a notée : la branche existe dans la page, et les champs consignés indiquent laquelle a été prise.

Les modes de défaillance à nommer

Quatre défaillances expliquent la majorité des rapports Selenium sans fenêtre qui ne concernent pas le mode, et chacune a une signature visible avant tout débogage.

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

La première est le cas de la taille de fenêtre déjà décrit : l’élément existe, la page l’a placé hors de la zone visible, et une session sans fenêtre avec une fenêtre par défaut réduite le signale comme invisible. La seconde est une divergence de versions sans rapport avec le mode, mais elle apparaît dans les mêmes recherches parce que les deux échecs surviennent au premier lancement dans un environnement neuf.

La troisième est le cas des polices, et c’est celle qui produit une capture qui semble cassée plutôt que vide. La quatrième est un problème d’attente et non de rendu, et attentes Selenium et tests stables expose les conditions d’une attente stable.

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

Parcourir cette liste dans l’ordre prend quelques minutes et élimine les quatre explications les plus courantes. Un échec qui survit aux quatre mérite un débogage au niveau de la page, et à ce stade les champs consignés dans les sections précédentes indiquent quel mode et quel binaire l’ont produit.

Ce qu’une page lit différemment

Une exécution locale a consigné les mêmes champs dans trois sessions sur le même hôte, avec des compilations Chromium standard et un profil jetable par session. Les valeurs sont celles imprimées par cette exécution, pas une affirmation sur toutes les compilations.

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)

Le jeton est le champ qu’une page lit en premier, et celui qui sépare le plus nettement les modes : les deux variantes sans fenêtre rapportent HeadlessChrome et la session fenêtrée rapporte Chrome. C’est un signal délibéré et documenté, pas un accident, et c’est pourquoi une suite qui vérifie le jeton doit décider ce qu’elle vérifie.

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

Le nombre de plugins et l’objet chrome sont les deux champs qui séparent la shell des deux autres, et non le mode sans fenêtre du mode fenêtré. Cela compte pour qui débogue un écart entre une exécution locale et la CI : si le nombre de plugins a bougé, le binaire a changé, pas le mode.

Les valeurs de fenêtre et d’écran par défaut

Les mesures qui ont surpris dans l’exécution enregistrée sont celles qui n’ont pas bougé. La zone d’affichage, la taille d’écran et la densité de pixels étaient identiques dans les trois sessions, et seul outerWidth différait, de deux pixels dans la session fenêtrée à cause du cadre de fenêtre.

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 conséquence pratique est qu’une comparaison de captures entre ces modes ne teste pas la taille de fenêtre. Si deux exécutions sans fenêtre de la même page produisent des images différentes, l’écart vient d’une animation, d’un chargement différé ou d’une police, et changer de mode n’y remédiera pas.

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

Cette distinction mérite d’entrer dans le rapport de test, car un diff visuel sans cause associée appelle la mauvaise correction. Consigner les quatre champs de la zone d’affichage à côté de l’image transforme « la capture a changé » en « la capture a changé alors que la zone d’affichage n’a pas changé ».

Pourquoi le mode sans fenêtre est plus facile à repérer

Une session sans fenêtre n’est pas suspecte en soi, mais elle se repère plus facilement quand ses champs se contredisent. L’exécution enregistrée est un cas net : le jeton annonçait le mode sans fenêtre et le reste du navigateur semblait ordinaire. Une session modifiée peut être l’inverse, avec un jeton qui dit une chose et la liste de plugins ou l’objet chrome qui en dit une autre.

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

C’est pourquoi la comparaison utile porte sur les champs entre eux plutôt que sur une valeur attendue unique. Une page qui lit navigator.userAgent seul peut être satisfaite par une réécriture ; une page qui lit ensemble le jeton, le nombre de plugins et l’objet chrome vérifie si le navigateur est cohérent en interne. Ce qu’un noyau de navigateur change face à des surcharges injectées est le même raisonnement pris par l’autre bout.

Selenium ajoute un signal qui lui est propre : navigator.webdriver vaut true parce que le pilote le fixe, et c’est vrai aussi en mode fenêtré. Ce n’est donc pas une preuve du mode sans fenêtre, et un test qui le traite comme tel produira une conclusion sur le pilote plutôt que sur le mode.

La même logique vaut pour la barre d’automatisation. Chrome affiche une barre d’information de logiciel de test automatisé dans certaines versions quand le pilote le lance, et cette barre est un élément d’interface et non une valeur visible pour la page : une session sans fenêtre ne l’affiche pas et une session fenêtrée peut l’afficher. Une comparaison de captures qui inclut le cadre du navigateur différera donc entre les deux modes pour une raison sans rapport avec la page.

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

Capturer la page plutôt que la fenêtre retire la seconde ligne de la comparaison. L’API de capture de Selenium capture la zone d’affichage, qui est la valeur par défaut correcte pour cette raison, et une capture de fenêtre entière prise depuis le bureau plutôt que depuis le pilote réintroduit le problème.

Un champ supplémentaire mérite d’être consigné pour qui exécute la même suite contre plusieurs compilations du navigateur. Le jeton HeadlessChrome porte une version, et la version de ce jeton est la compilation qui a produit la session. Une suite qui met Chrome à jour entre deux exécutions et ne consigne que le mode sans fenêtre a jeté le champ qui aurait expliqué un changement de rendu, et la correction tient à une ligne de plus dans le rapport plutôt qu’à une modification du test.

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

Cette ligne est aussi le moyen le plus rapide de répondre à la question qui a lancé la comparaison. Deux exécutions avec les mêmes options et des jetons différents font tourner des binaires différents, et aucun réglage d’option ne les fera concorder.

Deux exécutions avec le même jeton et un comportement différent forment le cas difficile, et elles relèvent presque toujours d’un écart d’hôte plutôt que de mode. Le jeton dit la compilation ; les polices, la voie GPU et le fuseau disent la machine ; et les quatre vérifications ci-dessus séparent les deux.

Conclusion pratique

Fixez le mode avec --headless=new, fixez la taille de fenêtre sur la zone d’affichage du produit et consignez le chemin du binaire à côté du jeton d’agent utilisateur. Ces trois faits séparent la shell du mode sans fenêtre unifié et l’un comme l’autre du mode fenêtré, et ils le font côté page, où un test peut les vérifier sans lire le code de lancement.

Quand une exécution sans fenêtre diffère d’une session fenêtrée, vérifiez les champs dans cet ordre : jeton, nombre de plugins, objet chrome, zone d’affichage, densité de pixels. Les trois premiers sont des propriétés de mode et de binaire ; les deux derniers appartiennent à l’hôte et aux options. Un écart dans le premier groupe est une décision de lancement ; un écart dans le second relève de la configuration.

L’affirmation à éviter est que le mode sans fenêtre ne laisse rien à lire. Elle ne tient pas, et l’exécution enregistrée montre exactement où le mode est visible. Ce qui tient est plus étroit et plus utile : une session sans fenêtre cohérente est un navigateur complet sans fenêtre, et les champs qui la décrivent se lisent et se consignent comme les autres.

Sources

#Selenium#Chrome Sans Fenêtre#ChromeOptions#Chromedriver#Automatisation

Cet article fait partie du guide : Débuter avec l'automatisation du navigateur : carte initiale

Exécutez la même vérification dans BotBrowser

Chargez un profil, gardez dans le navigateur les signaux que vous venez de lire, puis confirmez le résultat sur votre propre machine avant le déploiement.