Começar a usar a Headless Chrome

Texto longo, leia o resumo

O Chrome sem comando será disponibilizado no Chrome 59. É uma forma de executar o navegador Chrome em um ambiente headless. Executar o Chrome sem o Chrome. Ele traz todos os recursos modernos da plataforma da Web fornecidos pelo Chromium e o mecanismo de renderização Blink para a linha de comando.

Por que isso é útil?

Um navegador sem comando é uma ótima ferramenta para testes automatizados e ambientes de servidor em que você não precisa de um shell de interface visível. Por exemplo, você pode fazer alguns testes em uma página da Web real, criar um PDF dela ou apenas inspecionar como o navegador renderiza um URL.

Como iniciar sem comando (CLI)

A maneira mais fácil de começar a usar o modo headless é abrir o binário do Chrome na linha de comando. Se você tiver o Chrome 59 ou superior instalado, inicie-o com a sinalização --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 deve apontar para sua instalação do Chrome. A localização exata varia de acordo com a plataforma. Já no Mac, criei aliases convenientes para cada versão do Chrome que instalei.

Se você estiver no Canal Stable do Chrome e não conseguir instalar a versão Beta, recomendamos o uso de 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"

Faça o download do Chrome Canary aqui.

Recursos de linha de comando

Em alguns casos, pode não ser necessário criar um script de programação headless do Chrome. Há algumas sinalizações de linha de comando úteis para executar tarefas comuns.

Como imprimir o DOM

A flag --dump-dom imprime document.body.innerHTML em stdout:

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

Criar um PDF

A sinalização --print-to-pdf cria um PDF da página:

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

Como fazer capturas de tela

Para fazer uma captura de tela de uma página, use a sinalização --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/

A execução com --screenshot produzirá um arquivo chamado screenshot.png no diretório de trabalho atual. Se você procura capturas de tela de página inteira, as coisas são um pouco mais complicadas. Há uma ótima postagem do blog de David Schnurr que explica isso. Confira Como usar o headless Chrome como uma ferramenta automatizada de captura de tela .

Modo REPL (loop de leitura-avaliação-impressão)

A sinalização --repl é executada em um modo headless em que é possível avaliar expressões JS no navegador, diretamente da linha de comando:

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

Depurando o Chrome sem uma interface de usuário do navegador?

Quando você executa o Chrome com --remote-debugging-port=9222, ele inicia uma instância com o protocolo DevTools ativado. O protocolo é usado para se comunicar com o Chrome e direcionar a instância do navegador sem comando. Ele também é usado por ferramentas como Sublime, VS Code e Node para depurar aplicativos remotamente. #synergy

Como você não tem uma interface para ver a página, acesse http://localhost:9222 em outro navegador para verificar se tudo está funcionando. Será exibida uma lista de páginas inspecionáveis em que é possível clicar e ver o que está sendo renderizado:

Controle remoto do DevTools
Interface de depuração remota do DevTools

A partir daqui, você pode usar os recursos conhecidos do DevTools para inspecionar, depurar e ajustar a página normalmente. Se você estiver usando o Headless programaticamente, esta página também será uma ferramenta de depuração poderosa para ver todos os comandos brutos do protocolo do DevTools em toda a rede, comunicando-se com o navegador.

Como usar programaticamente (Node)

Animador de fantoches

O Puppeteer é uma biblioteca de Node desenvolvida pela equipe do Chrome. Ela fornece uma API de alto nível para controlar o Chrome headless (ou completo). É semelhante a outras bibliotecas de teste automatizadas, como Phantom e NightmareJS, mas funciona apenas com as versões mais recentes do Chrome.

Entre outras coisas, o Puppeteer pode ser usado para facilmente fazer capturas de tela, criar PDFs, navegar e buscar informações sobre elas. Recomendamos essa biblioteca se quiser automatizar rapidamente o teste do navegador. Ele oculta as complexidades do protocolo DevTools e cuida de tarefas redundantes, como a inicialização de uma instância de depuração do Chrome.

Instale:

npm i --save puppeteer

Exemplo: imprimir o user agent

const puppeteer = require('puppeteer');

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

Exemplo: fazer uma captura de tela da página

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

Consulte a documentação do Puppeteer (em inglês) para saber mais sobre a API completa.

Biblioteca CRI

chrome-remote-interface é uma biblioteca de nível inferior à API do Puppeteer. Ele é recomendado caso você queira ter uma abordagem mais próxima do padrão e usar o protocolo DevTools diretamente.

Iniciando o Chrome

O Chrome-remote-interface não inicia o Chrome para você. Portanto, você terá que cuidá-lo por conta própria.

Na seção da CLI, iniciamos o Chrome manualmente usando --headless --remote-debugging-port=9222. No entanto, para automatizar totalmente os testes, convém gerar o Chrome do seu aplicativo.

Uma maneira é usar 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) => {
  ...
});

Mas as coisas ficam complicadas se você quer uma solução portátil que funcione em várias plataformas. Veja o caminho codificado para o Chrome :(

Como usar o ChromeLauncher

O Lighthouse é uma ferramenta maravilhosa para testar a qualidade de apps da Web. Um módulo robusto para iniciar o Chrome foi desenvolvido no Lighthouse e agora é extraído para uso independente. O módulo NPM chrome-launcher vai encontrar onde o Chrome está instalado, configurar uma instância de depuração, iniciar o navegador e eliminá-lo quando o programa terminar. A melhor parte é que ele funciona em várias plataformas graças ao Node.

Por padrão, o chrome-launcher tentará iniciar o Chrome Canary (se ele estiver instalado), mas você pode mudar essa configuração para selecionar manualmente qual Chrome usar. Para usá-lo, primeiro instale pelo npm:

npm i --save chrome-launcher

Exemplo: como usar chrome-launcher para iniciar a versão headless

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

A execução desse script não faz muita coisa, mas uma instância do Chrome será acionada no gerenciador de tarefas que carregou about:blank. Não há interface de navegador. Não temos cabeça.

Para controlar o navegador, precisamos do protocolo DevTools!

Recuperar informações sobre a página

Vamos instalar a biblioteca:

npm i --save chrome-remote-interface
Exemplos

Exemplo: imprimir o 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']);
});

O resultado será algo como: HeadlessChrome/60.0.3082.0

Exemplo: verificar se o site tem um manifesto de app da 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.
});

})();

Exemplo: extraia o <title> da página usando as APIs 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.
});

})();

Como usar o Selenium, WebDriver e ChromeDriver

No momento, o Selenium abre uma instância completa do Chrome. Em outras palavras, é uma solução automatizada, mas não completamente headless. No entanto, o Selenium pode ser configurado para executar a versão headless do Chrome com um pouco de trabalho. Recomendo Executar o Selenium com o Headless Chrome se você quiser ter instruções completas sobre como configurar as coisas por conta própria, mas coloquei alguns exemplos abaixo para começar.

Como usar o ChromeDriver

O ChromeDriver 2.32 usa o Chrome 61 e funciona bem com a versão headless do Chrome.

Instalar:

npm i --save-dev selenium-webdriver chromedriver

Exemplo:

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

Como usar o WebDriverIO

A WebDriverIO é uma API de nível superior, além do Selenium WebDriver.

Instalar:

npm i --save-dev webdriverio chromedriver

Exemplo: filtrar recursos CSS em 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();

})();

Outros recursos

Aqui estão alguns recursos úteis para você começar:

Documentos

Ferramentas

  • chrome-remote-interface: módulo de nó que encapsula o protocolo DevTools
  • Lighthouse: ferramenta automatizada para testar a qualidade de apps da Web. Faz uso intenso do protocolo
  • chrome-launcher: módulo de nó para iniciar o Chrome, pronto para automação

Demonstrações

  • "The Headless Web": a ótima postagem de Paul Kinlan sobre o uso do Headless com o api.ai.

Perguntas frequentes

A flag --disable-gpu é necessária?

Só no Windows. Ele não é mais necessário em outras plataformas. A flag --disable-gpu é uma solução temporária para alguns bugs. Essa sinalização não será necessária em versões futuras do Chrome. Consulte crbug.com/737678 (link em inglês) para ver mais informações.

Ainda preciso do Xvfb?

Não. O Headless Chrome não usa uma janela, então um servidor de exibição como o Xvfb não é mais necessário. Você pode executar seus testes automatizados sem ele.

O que é Xvfb? O Xvfb é um servidor de exibição na memória para sistemas do tipo Unix que permite executar aplicativos gráficos (como o Chrome) sem uma tela física conectada. Muitas pessoas usam o Xvfb para executar versões anteriores do Chrome e fazer testes "headless".

Como faço para criar um contêiner do Docker que execute o Headless Chrome?

Confira o relatório Lighthouse-ci. Ele tem um exemplo de Dockerfile (em inglês) que usa node:8-slim como imagem de base e instala e executa o Lighthouse no App Engine Flex.

Posso usá-lo com Selenium / WebDriver / ChromeDriver?

Sim. Consulte Usar Selenium, WebDriver e ChromeDriver.

Qual é a relação com o PhantomJS?

A headless Chrome é semelhante a ferramentas como o PhantomJS. Ambas podem ser usadas para testes automatizados em um ambiente sem comando. A principal diferença entre as duas é que o Phantom usa uma versão mais antiga do WebKit como mecanismo de renderização, enquanto o Headless Chrome usa a versão mais recente do Blink.

No momento, o Phantom também oferece uma API de nível superior ao protocolo DevTools.

Onde informo bugs?

Informe bugs do Headless Chrome em crbug.com (link em inglês).

Registre bugs no protocolo do DevTools em github.com/ChromeDevTools/devtools-protocol (link em inglês).