Aan de slag met Headless Chrome

TL; DR

Headless Chrome wordt geleverd in Chrome 59. Het is een manier om de Chrome-browser in een headless-omgeving te gebruiken. Kortom, Chrome gebruiken zonder chroom! Het brengt alle moderne webplatformfuncties van Chromium en de Blink-rendering-engine naar de opdrachtregel.

Waarom is dat nuttig?

Een headless browser is een geweldig hulpmiddel voor geautomatiseerd testen en serveromgevingen waar u geen zichtbare UI-shell nodig heeft. U wilt bijvoorbeeld enkele tests uitvoeren op een echte webpagina, er een PDF van maken of gewoon inspecteren hoe de browser een URL weergeeft.

Headless starten (CLI)

De eenvoudigste manier om aan de slag te gaan met de headless-modus is door het binaire bestand van Chrome te openen vanaf de opdrachtregel. Als u Chrome 59+ heeft geïnstalleerd, start u Chrome met de vlag --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 moet verwijzen naar uw installatie van Chrome. De exacte locatie varieert van platform tot platform. Omdat ik een Mac gebruik, heb ik handige aliassen gemaakt voor elke versie van Chrome die ik heb geïnstalleerd.

Als je het stabiele kanaal van Chrome gebruikt en de bèta niet kunt krijgen, raad ik je aan chrome-canary te gebruiken:

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"

Download Chrome Canary hier .

Commandoregelfuncties

In sommige gevallen hoeft u Headless Chrome mogelijk niet programmatisch te scripten . Er zijn enkele handige opdrachtregelvlaggen om algemene taken uit te voeren.

Het DOM afdrukken

De vlag --dump-dom drukt document.body.innerHTML af naar stdout:

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

Maak een PDF

De vlag --print-to-pdf maakt een PDF van de pagina:

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

Schermafbeeldingen maken

Om een ​​screenshot van een pagina te maken, gebruik je de vlag --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/

Uitvoeren met --screenshot zal een bestand produceren met de naam screenshot.png in de huidige werkmap. Als u schermafbeeldingen van een volledige pagina zoekt, zijn de zaken iets ingewikkelder. Er is een geweldige blogpost van David Schnurr waarin je alles te weten komt. Bekijk Headless Chrome gebruiken als een geautomatiseerde screenshot-tool .

REPL-modus (lees-eval-print-lus)

De vlag --repl wordt Headless uitgevoerd in een modus waarin u JS-expressies in de browser kunt evalueren, rechtstreeks vanaf de opdrachtregel:

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

Foutopsporing in Chrome zonder browser-UI?

Wanneer u Chrome uitvoert met --remote-debugging-port=9222 , wordt een exemplaar gestart waarbij het DevTools-protocol is ingeschakeld. Het protocol wordt gebruikt om met Chrome te communiceren en de headless browserinstantie aan te sturen. Het is ook wat tools als Sublime, VS Code en Node gebruiken voor het op afstand debuggen van een applicatie. #synergie

Omdat u geen browserinterface heeft om de pagina te bekijken, navigeert u naar http://localhost:9222 in een andere browser om te controleren of alles werkt. U ziet een lijst met inspecteerbare pagina's waar u doorheen kunt klikken en kunt zien wat Headless weergeeft:

DevTools op afstand
DevTools gebruikersinterface voor foutopsporing op afstand

Vanaf hier kunt u de bekende DevTools-functies gebruiken om de pagina te inspecteren, fouten op te sporen en aan te passen zoals u normaal zou doen. Als u Headless programmatisch gebruikt, is deze pagina ook een krachtig foutopsporingshulpmiddel om alle onbewerkte DevTools-protocolopdrachten over de draad te zien gaan en met de browser te communiceren.

Programmatisch gebruiken (knooppunt)

Poppenspeler

Puppeteer is een Node-bibliotheek ontwikkeld door het Chrome-team. Het biedt een API op hoog niveau om headless (of volledig) Chrome te beheren. Het is vergelijkbaar met andere geautomatiseerde testbibliotheken zoals Phantom en NightmareJS, maar werkt alleen met de nieuwste versies van Chrome.

Puppeteer kan onder andere worden gebruikt om eenvoudig schermafbeeldingen te maken, PDF's te maken, door pagina's te navigeren en informatie over die pagina's op te halen. Ik raad de bibliotheek aan als je het testen van browsers snel wilt automatiseren. Het verbergt de complexiteit van het DevTools-protocol en zorgt voor redundante taken zoals het starten van een foutopsporingsinstantie van Chrome.

Installeer het:

npm i --save puppeteer

Voorbeeld : druk de user-agent af

const puppeteer = require('puppeteer');

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

Voorbeeld : een screenshot van de pagina maken

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

Bekijk de documentatie van Puppeteer voor meer informatie over de volledige API.

De CRI-bibliotheek

chrome-remote-interface is een bibliotheek op een lager niveau dan de API van Puppeteer. Ik raad het aan als je dicht bij het metaal wilt zijn en het DevTools-protocol rechtstreeks wilt gebruiken.

Chrome starten

chrome-remote-interface start Chrome niet voor je, dus daar moet je zelf voor zorgen.

In de CLI-sectie hebben we Chrome handmatig gestart met --headless --remote-debugging-port=9222 . Om tests volledig te automatiseren, wilt u waarschijnlijk Chrome uit uw applicatie halen.

Eén manier is om child_process te gebruiken:

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

Maar het wordt lastig als je een draagbare oplossing wilt die op meerdere platforms werkt. Kijk maar eens naar dat hardgecodeerde pad naar Chrome :(

ChromeLauncher gebruiken

Lighthouse is een geweldig hulpmiddel om de kwaliteit van uw webapps te testen. Binnen Lighthouse is een robuuste module ontwikkeld voor het starten van Chrome, die nu wordt uitgepakt voor zelfstandig gebruik. De chrome-launcher NPM-module zoekt uit waar Chrome is geïnstalleerd, stelt een foutopsporingsinstantie in, start de browser en sluit deze af wanneer uw programma klaar is. Het beste deel is dat het platformonafhankelijk werkt dankzij Node!

Standaard probeert chrome-launcher Chrome Canary te starten (als het is geïnstalleerd), maar u kunt dit wijzigen om handmatig te selecteren welk Chrome u wilt gebruiken. Om het te gebruiken, installeert u eerst vanaf npm:

npm i --save chrome-launcher

Voorbeeld - chrome-launcher gebruiken om Headless te 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();
});

Het uitvoeren van dit script doet niet veel, maar je zou een exemplaar van Chrome moeten zien opstarten in Taakbeheer dat about:blank laadt. Houd er rekening mee dat er geen browser-UI zal zijn. We hebben geen hoofd.

Om de browser te besturen, hebben we het DevTools-protocol nodig!

Informatie over de pagina ophalen

Laten we de bibliotheek installeren:

npm i --save chrome-remote-interface
Voorbeelden

Voorbeeld : druk de user-agent af

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

...

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

Resultaten in zoiets als: HeadlessChrome/60.0.3082.0

Voorbeeld : controleer of de site een webapp-manifest heeft

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

})();

Voorbeeld : extraheer de <title> van de pagina met behulp van DOM API's.

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 en ChromeDriver gebruiken

Op dit moment opent Selenium een ​​volledig exemplaar van Chrome. Met andere woorden: het is een geautomatiseerde oplossing, maar niet volledig zonder hoofd. Selenium kan echter met een beetje werk worden geconfigureerd om Chrome zonder hoofd te gebruiken. Ik raad aan Selenium uit te voeren met Headless Chrome als je de volledige instructies wilt over hoe je dingen zelf kunt instellen, maar ik heb hieronder enkele voorbeelden gegeven om je op weg te helpen.

ChromeDriver gebruiken

ChromeDriver 2.32 gebruikt Chrome 61 en werkt goed met headless Chrome.

Installeren:

npm i --save-dev selenium-webdriver chromedriver

Voorbeeld:

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

Gebruik WebDriverIO

WebDriverIO is een API van een hoger niveau bovenop Selenium WebDriver.

Installeren:

npm i --save-dev webdriverio chromedriver

Voorbeeld: filter CSS-functies op 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();

})();

Verdere bronnen

Hier zijn enkele nuttige bronnen om u op weg te helpen:

Documenten

Hulpmiddelen

  • chrome-remote-interface - knooppuntmodule die het DevTools-protocol omhult
  • Lighthouse - geautomatiseerde tool voor het testen van de kwaliteit van webapps; maakt intensief gebruik van het protocol
  • chrome-launcher - knooppuntmodule voor het starten van Chrome, klaar voor automatisering

Demo's

  • " The Headless Web " - Paul Kinlan's geweldige blogpost over het gebruik van Headless met api.ai.

FAQ

Heb ik de vlag --disable-gpu nodig?

Alleen op Windows. Andere platforms hebben dit niet langer nodig. De vlag --disable-gpu is een tijdelijke oplossing voor een aantal bugs. U heeft deze vlag niet meer nodig in toekomstige versies van Chrome. Zie crbug.com/737678 voor meer informatie.

Dus ik heb nog steeds Xvfb nodig?

Nee. Headless Chrome gebruikt geen venster, dus een weergaveserver zoals Xvfb is niet langer nodig. U kunt uw geautomatiseerde tests met plezier zonder dit uitvoeren.

Wat is Xvfb? Xvfb is een in-memory weergaveserver voor Unix-achtige systemen waarmee u grafische applicaties (zoals Chrome) kunt uitvoeren zonder een aangesloten fysiek beeldscherm. Veel mensen gebruiken Xvfb om eerdere versies van Chrome uit te voeren om "headless" tests uit te voeren.

Hoe maak ik een Docker-container waarop Headless Chrome draait?

Kijk eens naar vuurtoren-ci . Het heeft een voorbeeld Dockerfile dat node:8-slim als basisimage gebruikt, Lighthouse installeert en uitvoert op App Engine Flex.

Kan ik dit gebruiken met Selenium/WebDriver/ChromeDriver ?

Ja. Zie Selenium, WebDriver en ChromeDriver gebruiken .

Hoe is dit gerelateerd aan PhantomJS?

Headless Chrome is vergelijkbaar met tools als PhantomJS . Beide kunnen worden gebruikt voor geautomatiseerd testen in een headless-omgeving. Het belangrijkste verschil tussen de twee is dat Phantom een ​​oudere versie van WebKit als rendering-engine gebruikt, terwijl Headless Chrome de nieuwste versie van Blink gebruikt.

Op dit moment biedt Phantom ook een API van een hoger niveau dan het DevTools-protocol .

Waar kan ik bugs melden?

Voor bugs tegen Headless Chrome kunt u deze indienen op crbug.com .

Voor bugs in het DevTools-protocol kunt u deze archiveren op github.com/ChromeDevTools/devtools-protocol .