web-dev-qa-db-fra.com

Détecter Chrome fonctionnant en mode sans tête à partir de JavaScript

Avec la sortie de Chrome 59, le mode "sans tête" est maintenant disponible dans les versions stables pour Linux et macOS (et bientôt aussi Windows avec Chrome 60). Cela nous permet d'exécuter une version complète de Chrome sans interface utilisateur visible, une grande capacité à avoir pour les tests automatisés. Voici des exemples.

chrome --headless --disable-gpu --dump-dom https://stackoverflow.com/

Dans mon lanceur de test JavaScript, j'aime enregistrer autant d'informations que possible sur le navigateur utilisé, pour aider à isoler les problèmes. Par exemple, j'enregistre de nombreuses propriétés de navigator, y compris les plugins de navigateur actuels:

JSON.stringify(Array.from(navigator.plugins).map(p => p.name))
["Chrome PDF Viewer","Widevine Content Decryption Module","Shockwave Flash","Native Client","Chrome PDF Viewer"]

Ma compréhension est que Chrome devrait se comporter de manière identique en mode sans tête, mais j'ai suffisamment d'expérience pour être sceptique quant à une nouvelle fonctionnalité qui peut modifier considérablement le pipeline de rendu.

Pour l'instant, je vais exécuter des tests dans les deux modes. Je voudrais que le testeur enregistre si le mode sans tête est utilisé. Je pourrais transmettre ces informations dans les configurations de test, mais je préfère avoir une solution JavaScript pure que je peux intégrer dans le lanceur de test lui-même. Cependant, je n'ai pas pu trouver d'interface de navigateur qui révèle si le mode sans tête est actif.

Existe-t-il un moyen de détecter si Chrome fonctionne en mode sans tête à partir de JavaScript?

51
Jeremy Banks

Tu peux vérifier navigator.webdriver propriété qui est:

La propriété en lecture seule webdriver de l'interface navigator indique si l'agent utilisateur est contrôlé par l'automatisation.

...

Le navigator.webdriver la propriété est vraie lorsque dans:

Chrome Le --enable-automation ou la --headless le drapeau est utilisé.
Firefox Le marionette.enabled préférence ou --marionette le drapeau est passé.

La recommandation W3C WebDriver la décrit comme suit:

navigator.webdriver Définit un moyen standard pour les agents utilisateurs coopérants d'informer le document qu'il est contrôlé par WebDriver, par exemple afin que d'autres chemins de code puissent être déclenchés pendant l'automatisation.

16
Justas

La chaîne d'agent utilisateur inclut HeadlessChrome au lieu de Chrome . C'est probablement le signal que vous êtes censé rechercher, vous pouvez donc utiliser:

/\bHeadlessChrome\//.test(navigator.userAgent)

D'autres signaux intéressants incluent:

  • Ça ressemble à window.chrome n'est pas défini lorsqu'il est sans tête.
  • [innerWidth, innerHeight] est [800, 600] (codé en dur dans headless_browser.cc), tandis que [outerWidth, outerHeight] est [0, 0] (ce qui ne devrait généralement pas se produire).
29
Josh Lee

Il suffit de lire cet article d'Antoine Vastel qui propose quelques pistes:

  • tester l'agent utilisateur avec /HeadlessChrome/.test(window.navigator.userAgent), mais cela est facilement usurpé
  • tester des plugins avec navigator.plugins.length == 0
  • tester des langues avec navigator.languages == ""
  • tester les informations du fournisseur et du moteur de rendu WebGL (voir l'article pour des détails intéressants)
  • tester les fonctionnalités prises en charge telles que détectées par Modernizr: il semble que les "hairlines" ne soient pas prises en charge (les hairlines hidpi/retina, qui sont des bordures CSS avec moins de 1px de largeur, pour être physiquement 1px sur les écrans hidpi). Le test est !Modernizr["hairline"].
  • tester la taille de l'espace réservé pour l'image manquante. Insérez une image avec une URL non valide et testez image.width == 0 && image.height == 0 dans image.onerror (ils ont trouvé que celui-ci était le plus robuste).

Je ne peux pas parler des motivations de Google ( est Headless Chrome uniquement pour faciliter les tests d'applications Web? Hmmm ... ), mais cela pourrait être considéré comme une liste de bogues qui pourraient être corrigés un jour, il faut donc se demander pendant combien de temps ces tests fonctionneront :)

8
Hugues M.

La meilleure solution que j'ai jusqu'à présent est ce hack. Je ne l'utiliserais pas dans le code prod, mais le ferais dans les tests.

Le bloqueur de fenêtres publicitaires intempestives de Chrome est généralement activé pour tous les sites Web, mais il est désactivé en mode sans tête. Nous pouvons utiliser la possibilité d'ouvrir des fenêtres contextuelles comme un proxy assez précis pour être en mode sans tête. L'implémentation est simple: essayez de open(...) une fenêtre, et vérifiez si nous obtenons null (indiquant qu'il a été bloqué) au lieu d'un objet Window. Si nous en ouvrons un, fermez-le le plus rapidement possible.

function canPopUp() {
  var w = open("");
  if (w !== null) {
    w.close();
    return true;
  } else {
    return false;
  }
}

var isHeadless = canPopUp;

Pour un exemple rapide, vous pouvez essayer ce qui suit avec et sans l'indicateur --headless:

chrome --headless --disable-gpu --dump-dom 'data:text/html,<!doctype html><body><script>document.body.innerHTML = `headless: ${open("") !== null}`;</script>'

5
Jeremy Banks

navigator.plugins devrait contenir un tableau de plugins présents dans le navigateur (comme Flash, ActiveX ou Java). Pour le navigateur headless, il sera nul).

Dans le cadre du contrôle de sécurité, il peut être utilisé alert, pour sans tête, il sera ignoré:

var start = Date.now();
alert('Press OK');
var elapse = Date.now() - start;
if (elapse < 15) {
    console.log("headless environment detected");
}

Plusieurs techniques de détection des navigateurs sans tête sont discutées dans la présentation OWASP AppSecUSA 2014 Hide & Seek du navigateur sans tête ( vidéo , slides ) par Sergey Shekyan et Bei Zhang.

4
FieryCat