Erste Schritte mit Headless Chrome

Eric Bidelman

Kurzfassung

Headless Chrome wird in Chrome 59 eingeführt. Damit lässt sich der Chrome-Browser in einer monitorlosen Umgebung ausführen. Chrome ohne Chrome auszuführen. Damit werden alle modernen Webplattformfunktionen, die von Chromium und der Blink-Rendering-Engine bereitgestellt werden, über die Befehlszeile bereitgestellt.

Warum ist das nützlich?

Ein monitorloser Browser ist ein hervorragendes Tool für automatisierte Tests und Serverumgebungen, in denen Sie keine sichtbare UI-Shell benötigen. Sie können beispielsweise Tests für eine echte Webseite durchführen, eine PDF-Datei davon erstellen oder einfach prüfen, wie der Browser eine URL rendert.

Monitorlos starten (CLI)

Am einfachsten starten Sie den monitorlosen Modus, indem Sie die Chrome-Binärdatei über die Befehlszeile öffnen. Wenn Sie Chrome 59 oder höher installiert haben, starten Sie Chrome mit dem Flag --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 sollte auf Ihre Installation von Chrome verweisen. Der genaue Standort variiert je nach Plattform. Auf dem Mac habe ich für jede installierte Chrome-Version einen Alias erstellt.

Wenn Sie die stabile Version von Chrome verwenden und die Betaversion nicht nutzen können, empfehlen wir die Verwendung von 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"

Hier können Sie Chrome Canary herunterladen.

Befehlszeilenfunktionen

In einigen Fällen ist ein programmatisches Scripting von Headless Chrome nicht erforderlich. Für häufige Aufgaben stehen einige nützliche Befehlszeilen-Flags zur Verfügung.

DOM drucken

Das Flag --dump-dom gibt document.body.innerHTML in stdout aus:

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

PDF erstellen

Mit dem Flag --print-to-pdf wird eine PDF-Datei der Seite erstellt:

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

Screenshots erstellen

Verwenden Sie das Flag --screenshot, um einen Screenshot einer Seite zu erstellen:

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/

Bei Ausführung mit --screenshot wird im aktuellen Arbeitsverzeichnis eine Datei mit dem Namen screenshot.png erstellt. Wenn Sie Screenshots der ganzen Seite benötigen, ist die Sache ein bisschen komplizierter. Es gibt einen tollen Blogbeitrag von David Schnurr, über den Sie informiert wurden. Weitere Informationen finden Sie unter Headless Chrome als automatisiertes Screenshot-Tool verwenden .

REPL-Modus (Read-Eval-Print-Schleife)

Das Flag --repl wird „headless“ in einem Modus ausgeführt, in dem Sie JS-Ausdrücke im Browser direkt über die Befehlszeile auswerten können:

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

Fehlerbehebung in Chrome ohne Browser-Benutzeroberfläche?

Wenn Sie Chrome mit --remote-debugging-port=9222 ausführen, wird eine Instanz mit aktiviertem DevTools-Protokoll gestartet. Das Protokoll wird für die Kommunikation mit Chrome und die Steuerung der monitorlosen Browserinstanz verwendet. Es wird auch von Tools wie Sublime, VS Code und Node verwendet, um das Debugging einer Anwendung per Fernzugriff auszuführen. #synergy

Da Sie keine Browser-UI haben, um die Seite anzusehen, rufen Sie http://localhost:9222 in einem anderen Browser auf und prüfen Sie, ob alles funktioniert. Sie sehen eine Liste von Inspectable-Seiten, auf die Sie klicken können, um zu sehen, was „Headless“ rendert:

Fernbedienung für Entwicklertools
Entwicklertools-UI für Remote-Debugging

Von hier aus können Sie die bekannten Entwicklertools verwenden, um die Seite wie gewohnt zu untersuchen, zu debuggen und zu optimieren. Wenn Sie Headless programmatisch verwenden, ist diese Seite auch ein leistungsstarkes Debugging-Tool, mit dem Sie alle unformatierten Befehle des DevTools-Protokolls über die Leitung übertragen und mit dem Browser kommunizieren können.

Programmatisch verwenden (Node)

Puppenspieler

Puppeteer ist eine vom Chrome-Team entwickelte Knotenbibliothek. Es bietet eine allgemeine API zur Steuerung der monitorlosen oder vollständigen Chrome-Version. Es ähnelt anderen automatisierten Testbibliotheken wie Phantom und NightmareJS, funktioniert jedoch nur mit den neuesten Versionen von Chrome.

Mit Puppeteer können Sie unter anderem ganz einfach Screenshots erstellen, PDFs erstellen, durch Seiten navigieren und Informationen zu diesen Seiten abrufen. Ich empfehle die Bibliothek, wenn Sie Browsertests schnell automatisieren möchten. Er verbirgt die Komplexität des DevTools-Protokolls und übernimmt redundante Aufgaben wie das Starten einer Debug-Instanz von Chrome.

Installieren:

npm i --save puppeteer

Beispiel – User-Agent ausgeben

const puppeteer = require('puppeteer');

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

Beispiel: Screenshot der Seite erstellen

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();
})();

Sehen Sie sich die Puppeteer-Dokumentation an, um mehr über die vollständige API zu erfahren.

CRI-Bibliothek

chrome-remote-interface ist eine untergeordnete Bibliothek als die API von Puppeteer. Ich empfehle es, wenn Sie nah am Metal arbeiten und das DevTools-Protokoll direkt verwenden möchten.

Chrome wird gestartet

Chrome wird nicht von "chrome-remote-interface" gestartet, in diesem Fall müssen Sie das selbst erledigen.

Im Befehlszeilenabschnitt haben wir mit --headless --remote-debugging-port=9222 Chrome manuell gestartet. Um Tests jedoch vollständig zu automatisieren, empfiehlt es sich, Chrome aus Ihrer Anwendung zu generieren.

Eine Möglichkeit ist die Verwendung von 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) => {
  ...
});

Wenn Sie jedoch eine portable Lösung benötigen, die auf mehreren Plattformen funktioniert, wird es komplizierter. Werfen Sie einfach einen Blick auf diesen hartcodierten Pfad zu Chrome.

ChromeLauncher verwenden

Lighthouse ist ein hervorragendes Tool zum Testen der Qualität Ihrer Webanwendungen. In Lighthouse wurde ein robustes Modul zum Starten von Chrome entwickelt, das jetzt für die eigenständige Verwendung extrahiert wird. Das NPM-Modul chrome-launcher ermittelt, wo Chrome installiert ist, richtet eine Debug-Instanz ein, startet den Browser und beendet ihn nach Abschluss des Programms. Das Beste daran ist, dass es dank Node plattformübergreifend funktioniert.

chrome-launcher versucht standardmäßig, Chrome Canary zu starten (sofern installiert). Sie können dies aber ändern, um manuell auszuwählen, welche Chrome-Version verwendet werden soll. Installieren Sie zuerst von npm aus, um es zu verwenden:

npm i --save chrome-launcher

Beispiel: Headless mit chrome-launcher starten

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();
});

Das Ausführen dieses Skripts führt nicht viel dazu, aber Sie sollten sehen, dass im Task-Manager eine Instanz von Chrome gestartet wird, die about:blank geladen hat. Denken Sie daran, dass es keine Browser-UI gibt. Wir sind ohne Kopf.

Um den Browser zu steuern, benötigen wir das Entwicklertools-Protokoll.

Informationen über die Seite abrufen

Installieren wir nun die Bibliothek:

npm i --save chrome-remote-interface
Beispiele

Beispiel – User-Agent ausgeben

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

...

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

Ergebnisse in etwa: HeadlessChrome/60.0.3082.0

Beispiel: Prüfen Sie, ob die Website ein Web-App-Manifest hat.

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.
});

})();

Beispiel: Extrahieren Sie die <title> der Seite mithilfe von DOM APIs.

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.
});

})();

Selenium, WebDriver und ChromeDriver verwenden

Derzeit öffnet Selenium eine vollständige Instanz von Chrome. Es handelt sich also um eine automatisierte Lösung, aber nicht komplett ohne Kopf. Selenium kann jedoch mit ein wenig Aufwand für die monitorlose Ausführung von Chrome konfiguriert werden. Wenn Sie eine vollständige Anleitung für die Einrichtung benötigen, empfehlen wir Ihnen, Selenium mit Headless Chrome auszuführen. Im Folgenden finden Sie jedoch einige Beispiele für den Einstieg.

ChromeDriver verwenden

ChromeDriver 2.32 verwendet Chrome 61 und funktioniert gut mit der monitorlosen Chrome-Version.

Installieren:

npm i --save-dev selenium-webdriver chromedriver

Beispiel:

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();

WebDriverIO verwenden

WebDriverIO ist eine API auf höherer Ebene über Selenium WebDriver.

Installieren:

npm i --save-dev webdriverio chromedriver

Beispiel: CSS-Funktionen auf chromestatus.com filtern

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();

})();

Weitere Ressourcen

Hier sind einige nützliche Ressourcen für den Einstieg:

Dokumentation

Tools

  • chrome-remote-interface – Knotenmodul, das das DevTools-Protokoll umschließt
  • Lighthouse: automatisiertes Tool zum Testen der Qualität von Webanwendungen; nutzt stark das Protokoll
  • chrome-Launcher – Knotenmodul zum Starten von Chrome, bereit für die Automatisierung

Demos

  • The Headless Web – Paul Kinlans großartiger Blogpost zur Verwendung von Headless mit api.ai.

Häufig gestellte Fragen

Benötige ich das Flag --disable-gpu?

Nur unter Windows. Auf anderen Plattformen ist sie nicht mehr erforderlich. Das Flag --disable-gpu ist eine temporäre Problemumgehung für einige Programmfehler. Sie werden dieses Flag in zukünftigen Chrome-Versionen nicht mehr benötigen. Weitere Informationen finden Sie unter crbug.com/737678.

Ich brauche also immer noch Xvfb?

Nein. Bei Headless Chrome wird kein Fenster verwendet, sodass ein Anzeigeserver wie Xvfb nicht mehr benötigt wird. Sie können Ihre automatisierten Tests auch ohne diese Funktion ausführen.

Was ist Xvfb? Xvfb ist ein speicherinterner Anzeigeserver für Unix-ähnliche Systeme, mit dem Sie grafische Anwendungen wie Chrome ohne angeschlossenen physischen Bildschirm ausführen können. Viele Nutzer verwenden Xvfb für ältere Versionen von Chrome, um "monitorlose" Tests durchzuführen.

Wie erstelle ich einen Docker-Container, auf dem die monitorlose Chrome-Version ausgeführt wird?

Sehen Sie sich lighthouse-ci an. Es gibt ein Beispiel-Dockerfile, das node:8-slim als Basis-Image verwendet und Lighthouse auf App Engine Flex installiert und ausführt.

Kann ich dies mit Selenium / WebDriver / ChromeDriver verwenden?

Ja. Weitere Informationen finden Sie unter Selenium, WebDriver und ChromeDriver verwenden.

Was hat das mit PhantomJS zu tun?

Headless Chrome ist vergleichbar mit Tools wie PhantomJS. Beide können für automatisierte Tests in einer monitorlosen Umgebung verwendet werden. Der Hauptunterschied zwischen den beiden besteht darin, dass Phantom eine ältere Version von WebKit als Rendering-Engine verwendet, während Headless Chrome die neueste Version von Blink nutzt.

Derzeit bietet Phantom auch eine API auf höherer Ebene als das DevTools-Protokoll.

Wo kann ich Fehler melden?

Melden Sie Fehler bei Headless Chrome unter crbug.com.

Fehler im DevTools-Protokoll können Sie unter github.com/ChromeDevTools/devtools-protocol einreichen.