Começar a usar a Headless Chrome

Texto longo, leia o resumo

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

Por que isso é útil?

Um navegador sem cabeça é 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 executar alguns testes em uma página da Web real, criar um PDF dela ou apenas inspecionar como o navegador renderiza um URL.

Como iniciar o modo headless (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 mais recente instalado, inicie o Chrome com a 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.

O chrome precisa apontar para a instalação do Chrome. O local exato vai variar de acordo com a plataforma. Como estou usando um Mac, criei aliases convenientes para cada versão do Chrome que instalei.

Se você estiver no canal estável do Chrome e não conseguir usar a versão Beta, recomendamos usar 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 da linha de comando

Em alguns casos, talvez não seja necessário criar um script de forma programática para o Chrome Headless. Há algumas flags de linha de comando úteis para realizar tarefas comuns.

Como imprimir o DOM

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

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

Criar um PDF

A flag --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 flag --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 vai produzir um arquivo chamado screenshot.png no diretório de trabalho atual. Se você quer capturas de tela de página inteira, as coisas são um pouco mais complicadas. Há uma ótima postagem no blog de David Schnurr que pode ajudar. Confira Como usar o headless Chrome como uma ferramenta automatizada de captura de tela .

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

A flag --repl executa o modo Headless em um modo em que é possível avaliar expressões JS no navegador, diretamente na 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
$

Como depurar o Chrome sem uma interface 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 cabeça. É também o que ferramentas como Sublime, VS Code e Node usam para depurar remotamente um aplicativo. #synergy

Como você não tem a interface do navegador para ver a página, navegue até http://localhost:9222 em outro navegador para verificar se tudo está funcionando. Você vai encontrar uma lista de páginas inspecionáveis em que é possível clicar e conferir o que o Headless está renderizando:

DevTools Remote
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 de forma programática, essa página também é uma ferramenta de depuração poderosa para conferir todos os comandos brutos do protocolo do DevTools que passam pela rede, se comunicando com o navegador.

Como usar programaticamente (Node)

Animador de fantoches

O Puppeteer (em inglês) é uma biblioteca do Node desenvolvida pela equipe do Chrome. Ele fornece uma API de alto nível para controlar o Chrome (ou completo) sem cabeça. É 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 fazer capturas de tela, criar PDFs, navegar por páginas e buscar informações sobre elas. Recomendo a biblioteca se você quiser automatizar rapidamente os testes do navegador. Ele oculta as complexidades do protocolo do DevTools e cuida de tarefas redundantes, como iniciar 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();
})();

Confira a documentação do Puppeteer para saber mais sobre a API completa.

Biblioteca CRI

chrome-remote-interface é uma biblioteca de nível inferior à API do Puppeteer. Eu recomendo isso se você quiser estar próximo ao metal e usar o protocolo do DevTools diretamente.

Como iniciar o Chrome

O chrome-remote-interface não inicia o Chrome para você. Portanto, você precisa fazer isso 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. Olhe esse caminho codificado para o Chrome :(

Como usar o ChromeLauncher

O Lighthouse é uma ferramenta maravilhosa para testar a qualidade dos seus 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 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();
});

Executar esse script não faz muito, mas você verá uma instância do Chrome sendo iniciada no gerenciador de tarefas que carregou about:blank. Não haverá interface do navegador. Não temos cabeça.

Para controlar o navegador, precisamos do protocolo do DevTools.

Como 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 é algo como: HeadlessChrome/60.0.3082.0

Exemplo: verifique se o site tem um manifesto do 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 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, o WebDriver e o ChromeDriver

No momento, o Selenium abre uma instância completa do Chrome. Em outras palavras, é uma solução automatizada, mas não totalmente sem servidor. No entanto, o Selenium pode ser configurado para executar o Chrome sem cabeça com um pouco de trabalho. Recomendamos Como executar o Selenium com o Chrome sem cabeça se você quiser instruções completas sobre como configurar tudo por conta própria. No entanto, incluímos alguns exemplos abaixo para você começar.

Como usar o ChromeDriver

O ChromeDriver 2.32 usa o Chrome 61 e funciona bem com o Chrome headless.

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

O WebDriverIO é uma API de nível mais alto em cima do Selenium WebDriver.

Instalar:

npm i --save-dev webdriverio chromedriver

Exemplo: filtrar recursos de CSS no 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

Confira alguns recursos úteis para começar:

Documentos

Ferramentas

  • chrome-remote-interface: módulo de nó que envolve o protocolo do DevTools.
  • Lighthouse: ferramenta automatizada para testar a qualidade do app 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: ótimo post de Paul Kinlan sobre o uso de Headless com a api.ai.

Perguntas frequentes

Preciso da flag --disable-gpu?

Somente no Windows. Outras plataformas não precisam mais disso. A flag --disable-gpu é uma solução temporária para alguns bugs. Você não vai precisar dessa flag em versões futuras do Chrome. Consulte crbug.com/737678 (link em inglês) para ver mais informações.

Então, ainda preciso do Xvfb?

Não. O Chrome sem cabeça 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 em memória para sistemas do tipo Unix que permite executar aplicativos gráficos (como o Chrome) sem uma tela física anexada. 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 Chrome sem cabeça?

Confira o lighthouse-ci. Ele tem um exemplo de Dockerfile que usa node:8-slim como imagem de base e instala e executa o Lighthouse no App Engine Flex.

Posso usar isso com o Selenium / WebDriver / ChromeDriver?

Sim. Consulte Como usar o Selenium, o WebDriver e o ChromeDriver.

Qual é a relação com o PhantomJS?

O Chrome Headless é semelhante a ferramentas como o PhantomJS. Ambos podem ser usados para testes automatizados em um ambiente sem cabeça. A principal diferença entre os dois é que o Phantom usa uma versão mais antiga do WebKit como mecanismo de renderização, enquanto o Chrome Headless usa a versão mais recente do Blink.

No momento, o Phantom também oferece uma API de nível mais alto do que o protocolo DevTools.

Onde informo bugs?

Para bugs no Chrome Headless, envie-os em crbug.com.

Para bugs no protocolo do DevTools, registre-os em github.com/ChromeDevTools/devtools-protocol.