Premiers pas avec Headless Chrome

Eric Bidelman

.

Résumé

Headless Chrome est disponible dans Chrome 59. Il permet d'exécuter le navigateur Chrome dans un environnement sans interface graphique. En bref, exécuter Chrome sans Chrome ! Il intègre toutes les fonctionnalités modernes de la plate-forme Web fournies par Chromium et le moteur de rendu Blink dans la ligne de commande.

En quoi est-ce utile ?

Un navigateur sans interface graphique est un excellent outil pour les tests automatisés et les environnements de serveur dans lesquels vous n'avez pas besoin d'une interface utilisateur visible. Par exemple, vous pouvez exécuter des tests sur une page Web réelle, en créer un PDF ou simplement inspecter la façon dont le navigateur affiche une URL.

Démarrer le mode sans interface graphique (CLI)

Le moyen le plus simple de commencer à utiliser le mode sans interface graphique consiste à ouvrir le binaire Chrome à partir de la ligne de commande. Si Chrome 59 ou une version ultérieure est installé, démarrez Chrome avec l'indicateur --headless:

chrome \
--headless \                   # Runs Chrome in headless mode.
--disable-gpu \                # Temporarily needed if running on Windows.
--remote-debugging-port=9222 \
https://www.chromestatus.com   # URL to open. Defaults to about:blank.

chrome doit renvoyer vers votre installation de Chrome. L'emplacement exact varie d'une plate-forme à l'autre. Comme je suis sur Mac, j'ai créé des alias pratiques pour chaque version de Chrome que j'ai installée.

Si vous utilisez la version stable de Chrome et que vous ne pouvez pas obtenir la version bêta, nous vous recommandons d'utiliser chrome-canary:

alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
alias chrome-canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"
alias chromium="/Applications/Chromium.app/Contents/MacOS/Chromium"

Pour télécharger Chrome Canary, cliquez ici.

Fonctionnalités de la ligne de commande

Dans certains cas, vous n'aurez peut-être pas besoin d'exécuter un script par programmation pour Chrome sans interface graphique. Certains indicateurs de ligne de commande utiles permettent d'effectuer des tâches courantes.

Imprimer le DOM

L'indicateur --dump-dom imprime document.body.innerHTML sur stdout:

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

Créer un PDF

L'indicateur --print-to-pdf crée un fichier PDF de la page:

chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/

Effectuer des captures d'écran

Pour effectuer une capture d'écran d'une page, utilisez l'indicateur --screenshot:

chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/

# Size of a standard letterhead.
chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://www.chromestatus.com/

# Nexus 5x
chrome --headless --disable-gpu --screenshot --window-size=412,732 https://www.chromestatus.com/

L'exécution avec --screenshot génère un fichier nommé screenshot.png dans le répertoire de travail actuel. Si vous recherchez des captures d'écran en pleine page, c'est un peu plus compliqué. Il y a un excellent article de blog de David Schnurr qui vous aide. Consultez Utiliser Chrome sans interface graphique comme outil de capture d'écran automatique .

Mode REPL (boucle read-eval-print)

L'option --repl s'exécute sans interface graphique dans un mode qui vous permet d'évaluer les expressions JS dans le navigateur, directement à partir de la ligne de commande:

$ chrome --headless --disable-gpu --repl --crash-dumps-dir=./tmp https://www.chromestatus.com/
[0608/112805.245285:INFO:headless_shell.cc(278)] Type a Javascript expression to evaluate or "quit" to exit.
>>> location.href
{"result":{"type":"string","value":"https://www.chromestatus.com/features"}}
>>> quit
$

Déboguer Chrome sans interface utilisateur du navigateur ?

Lorsque vous exécutez Chrome avec --remote-debugging-port=9222, une instance démarre avec le protocole DevTools activé. Ce protocole permet de communiquer avec Chrome et de piloter l'instance du navigateur sans interface graphique. Ce sont également les outils tels que Sublime, VS Code et Node qui permettent de déboguer à distance une application. #synergy

Comme vous ne disposez pas de l'interface utilisateur du navigateur pour voir la page, accédez à http://localhost:9222 dans un autre navigateur pour vérifier que tout fonctionne. Une liste de pages Inspectable s'affiche. Vous pouvez cliquer dessus et voir ce qui s'affiche sans interface graphique:

Télécommande DevTools
Interface utilisateur de débogage à distance des outils de développement

À partir de là, vous pouvez utiliser les fonctionnalités des outils de développement que vous connaissez déjà pour inspecter, déboguer et modifier la page comme vous le feriez normalement. Si vous utilisez la programmation sans interface graphique, cette page est également un outil de débogage puissant. Elle vous permet d'afficher toutes les commandes brutes du protocole DevTools diffusées sur le réseau, communiquant avec le navigateur.

Utilisation programmatique (nœud)

Marionnettiste

Puppeteer est une bibliothèque de nœuds développée par l'équipe Chrome. Elle fournit une API de haut niveau pour contrôler Chrome sans interface graphique (ou la version complète). Elle est semblable à d'autres bibliothèques de tests automatisés telles que Phantom et NightmareJS, mais ne fonctionne qu'avec les dernières versions de Chrome.

Puppeteer permet, entre autres, de faire facilement des captures d'écran, de créer des PDF, de naviguer sur les pages et d'extraire des informations sur ces pages. Je vous recommande la bibliothèque si vous souhaitez automatiser rapidement les tests des navigateurs. Elle élimine les complexités du protocole DevTools et se charge des tâches redondantes telles que le lancement d'une instance de débogage de Chrome.

Pour l'installer, procédez comme suit:

npm i --save puppeteer

Exemple : imprimez le user-agent

const puppeteer = require('puppeteer');

(async() => {
  const browser = await puppeteer.launch();
  console.log(await browser.version());
  await browser.close();
})();

Exemple : Faire une capture d'écran de la page

const puppeteer = require('puppeteer');

(async() => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.chromestatus.com', {waitUntil: 'networkidle2'});
  await page.pdf({path: 'page.pdf', format: 'A4'});

  await browser.close();
})();

Consultez la documentation de Puppeteer pour en savoir plus sur l'API complète.

Bibliothèque CRI

chrome-remote-interface est une bibliothèque de niveau inférieur à l'API de Puppeteer. Je vous le recommande si vous souhaitez être proche du métal et utiliser directement le protocole DevTools.

Lancement de Chrome

chrome-remote-interface ne lance pas Chrome pour vous. Vous devez donc vous en occuper vous-même.

Dans la section CLI, nous avons démarré Chrome manuellement à l'aide de --headless --remote-debugging-port=9222. Toutefois, pour automatiser entièrement les tests, vous souhaiterez probablement générer Chrome à partir de votre application.

Vous pouvez, par exemple, utiliser child_process:

const execFile = require('child_process').execFile;

function launchHeadlessChrome(url, callback) {
  // Assuming MacOSx.
  const CHROME = '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome';
  execFile(CHROME, ['--headless', '--disable-gpu', '--remote-debugging-port=9222', url], callback);
}

launchHeadlessChrome('https://www.chromestatus.com', (err, stdout, stderr) => {
  ...
});

Toutefois, les choses se compliquent si vous souhaitez une solution portable compatible avec plusieurs plates-formes. Regardez ce chemin d'accès codé en dur vers Chrome :(

Utilisation de ChromeLauncher

Lighthouse est un outil formidable qui vous permet de tester la qualité de vos applications Web. Un module robuste de lancement de Chrome a été développé dans Lighthouse. Il est désormais extrait pour une utilisation autonome. Le module NPM chrome-launcher va trouver l'emplacement d'installation de Chrome, configurer une instance de débogage, lancer le navigateur et le fermer lorsque votre programme est terminé. Le meilleur dans tout cela, c'est que cet outil fonctionne sur plusieurs plates-formes grâce à Node.

Par défaut, chrome-launcher tente de lancer Chrome Canary (s'il est installé), mais vous pouvez le modifier pour sélectionner manuellement Chrome à utiliser. Pour l'utiliser, commencez par l'installer à partir de npm:

npm i --save chrome-launcher

Exemple : Utilisation de chrome-launcher pour lancer sans interface graphique

const chromeLauncher = require('chrome-launcher');

// Optional: set logging level of launcher to see its output.
// Install it using: npm i --save lighthouse-logger
// const log = require('lighthouse-logger');
// log.setLevel('info');

/**
 * Launches a debugging instance of Chrome.
 * @param {boolean=} headless True (default) launches Chrome in headless mode.
 *     False launches a full version of Chrome.
 * @return {Promise<ChromeLauncher>}
 */
function launchChrome(headless=true) {
  return chromeLauncher.launch({
    // port: 9222, // Uncomment to force a specific port of your choice.
    chromeFlags: [
      '--window-size=412,732',
      '--disable-gpu',
      headless ? '--headless' : ''
    ]
  });
}

launchChrome().then(chrome => {
  console.log(`Chrome debuggable on port: ${chrome.port}`);
  ...
  // chrome.kill();
});

L'exécution de ce script ne fait pas grand-chose, mais vous devriez voir une instance de Chrome se déclencher dans le gestionnaire de tâches qui a chargé about:blank. N'oubliez pas qu'il n'y aura pas d'interface utilisateur de navigateur. Sans interface graphique.

Pour contrôler le navigateur, nous avons besoin du protocole des outils de développement.

Récupérer des informations sur la page

Installons la bibliothèque:

npm i --save chrome-remote-interface
Exemples

Exemple : imprimez le user-agent

const CDP = require('chrome-remote-interface');

...

launchChrome().then(async chrome => {
  const version = await CDP.Version({port: chrome.port});
  console.log(version['User-Agent']);
});

Cela devrait ressembler à ceci: HeadlessChrome/60.0.3082.0

Exemple : Vérifier si le site comporte un fichier manifeste d'application Web

const CDP = require('chrome-remote-interface');

...

(async function() {

const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});

// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page} = protocol;
await Page.enable();

Page.navigate({url: 'https://www.chromestatus.com/'});

// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
  const manifest = await Page.getAppManifest();

  if (manifest.url) {
    console.log('Manifest: ' + manifest.url);
    console.log(manifest.data);
  } else {
    console.log('Site has no app manifest');
  }

  protocol.close();
  chrome.kill(); // Kill Chrome.
});

})();

Exemple : Extrayez l'élément <title> de la page à l'aide des API DOM.

const CDP = require('chrome-remote-interface');

...

(async function() {

const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});

// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page, Runtime} = protocol;
await Promise.all([Page.enable(), Runtime.enable()]);

Page.navigate({url: 'https://www.chromestatus.com/'});

// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
  const js = "document.querySelector('title').textContent";
  // Evaluate the JS expression in the page.
  const result = await Runtime.evaluate({expression: js});

  console.log('Title of page: ' + result.result.value);

  protocol.close();
  chrome.kill(); // Kill Chrome.
});

})();

Avec Selenium, WebDriver et ChromeDriver

Pour le moment, Selenium ouvre une instance complète de Chrome. En d'autres termes, il s'agit d'une solution automatisée mais pas complètement sans interface graphique. Cependant, Selenium peut être configuré pour exécuter Chrome sans interface graphique avec un peu d'effort. Je vous recommande d'exécuter Selenium avec Headless Chrome si vous souhaitez obtenir des instructions complètes sur la configuration vous-même. Toutefois, j'ai ajouté quelques exemples ci-dessous pour vous aider à démarrer.

Utiliser ChromeDriver

ChromeDriver 2.32 utilise Chrome 61 et fonctionne bien avec Chrome sans interface graphique.

Installer :

npm i --save-dev selenium-webdriver chromedriver

Exemple :

const fs = require('fs');
const webdriver = require('selenium-webdriver');
const chromedriver = require('chromedriver');

const chromeCapabilities = webdriver.Capabilities.chrome();
chromeCapabilities.set('chromeOptions', {args: ['--headless']});

const driver = new webdriver.Builder()
  .forBrowser('chrome')
  .withCapabilities(chromeCapabilities)
  .build();

// Navigate to google.com, enter a search.
driver.get('https://www.google.com/');
driver.findElement({name: 'q'}).sendKeys('webdriver');
driver.findElement({name: 'btnG'}).click();
driver.wait(webdriver.until.titleIs('webdriver - Google Search'), 1000);

// Take screenshot of results page. Save to disk.
driver.takeScreenshot().then(base64png => {
  fs.writeFileSync('screenshot.png', new Buffer(base64png, 'base64'));
});

driver.quit();

Utiliser WebDriverIO

WebDriverIO est une API de niveau supérieur par rapport à Selenium WebDriver.

Installer :

npm i --save-dev webdriverio chromedriver

Exemple: Filtrer les fonctionnalités CSS sur chromestatus.com

const webdriverio = require('webdriverio');
const chromedriver = require('chromedriver');

const PORT = 9515;

chromedriver.start([
  '--url-base=wd/hub',
  `--port=${PORT}`,
  '--verbose'
]);

(async () => {

const opts = {
  port: PORT,
  desiredCapabilities: {
    browserName: 'chrome',
    chromeOptions: {args: ['--headless']}
  }
};

const browser = webdriverio.remote(opts).init();

await browser.url('https://www.chromestatus.com/features');

const title = await browser.getTitle();
console.log(`Title: ${title}`);

await browser.waitForText('.num-features', 3000);
let numFeatures = await browser.getText('.num-features');
console.log(`Chrome has ${numFeatures} total features`);

await browser.setValue('input[type="search"]', 'CSS');
console.log('Filtering features...');
await browser.pause(1000);

numFeatures = await browser.getText('.num-features');
console.log(`Chrome has ${numFeatures} CSS features`);

const buffer = await browser.saveScreenshot('screenshot.png');
console.log('Saved screenshot...');

chromedriver.stop();
browser.end();

})();

Autres ressources

Voici quelques ressources utiles pour commencer:

Documentation

Outils

  • chrome-remote-interface : module de nœud qui encapsule le protocole DevTools
  • Lighthouse : outil automatisé permettant de tester la qualité des applications Web. Ce protocole utilise de manière intensive le protocole.
  • chrome-launcher : module de nœud pour le lancement de Chrome, prêt pour l'automatisation

Démonstrations

  • The Headless Web : l'excellent article de blog de Paul Kinlan sur l'utilisation de Headless avec api.ai.

Questions fréquentes

Ai-je besoin de l'indicateur --disable-gpu ?

Uniquement sous Windows. Les autres plates-formes n'en ont plus besoin. L'indicateur --disable-gpu est une solution temporaire pour quelques bugs. Vous n'aurez plus besoin de cet indicateur dans les futures versions de Chrome. Pour plus d'informations, consultez la page crbug.com/737678.

J'ai encore besoin de Xvfb ?

Non. Chrome sans interface graphique n'utilise pas de fenêtre. Un serveur d'affichage comme Xvfb n'est donc plus nécessaire. Vous pouvez exécuter vos tests automatisés sans cela.

Qu'est-ce que Xvfb ? Xvfb est un serveur d'affichage en mémoire pour les systèmes de type Unix qui vous permet d'exécuter des applications graphiques (comme Chrome) sans écran physique associé. De nombreuses personnes utilisent Xvfb pour exécuter des versions antérieures de Chrome afin d'effectuer des tests "sans interface graphique".

Comment créer un conteneur Docker qui exécute Headless Chrome ?

Découvrez lighthouse-ci. Elle contient un exemple de Dockerfile qui utilise node:8-slim comme image de base, et qui installe et exécute Lighthouse sur l'environnement flexible App Engine.

Puis-je l'utiliser avec Selenium / WebDriver / ChromeDriver ?

Oui. Consultez Utiliser Selenium, WebDriver et ChromeDriver.

Quel est le rapport avec PhantomJS ?

Chrome sans interface graphique est semblable à des outils tels que PhantomJS. Les deux peuvent être utilisés pour des tests automatisés dans un environnement sans interface graphique. La principale différence entre les deux est que Phantom utilise une ancienne version de WebKit comme moteur de rendu, tandis que Headless Chrome utilise la dernière version de Blink.

À l'heure actuelle, Phantom fournit également une API de niveau supérieur au protocole DevTools.

Où puis-je signaler des bugs ?

Si vous rencontrez des bugs dans Chrome sans interface graphique, signalez-les sur crbug.com.

Signalez les bugs dans le protocole des outils de développement à l'adresse github.com/ChromeDevTools/devtools-protocol.