Começar a usar a Headless Chrome

Texto longo, leia o resumo

A Headless Chrome está disponível no Chrome 59. É uma forma de executar o navegador Chrome em um ambiente headless. Essencialmente, executar o Chrome sem o Chrome! Ele leva 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 headless é 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, convém executar alguns testes em uma página da Web real, criar um PDF dela ou simplesmente inspecionar como o navegador renderiza um URL.

Como iniciar o 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ê tem o Chrome 59 ou mais recente 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 a instalação do Chrome. O local exato varia de acordo com a plataforma. Como estou no Mac, criei aliases convenientes para cada versão do Chrome que instalei.

Se você está no Canal Stable do Chrome e não consegue fazer o download da 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 de linha de comando

Em alguns casos, pode não ser necessário criar um script de maneira programática para o Headless Chrome. Existem algumas sinalizações de linha de comando úteis para executar tarefas comuns.

Como imprimir o DOM

A sinalização --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, é um pouco mais complicado. Tem uma ótima postagem de blog de David Schnurr para você. Confira Como usar a versão headless do Chrome como uma ferramenta automatizada de captura de tela .

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

A sinalização --repl é executada sem comando 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
$

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 acionar a instância headless do navegador. É também o que ferramentas como Sublime, VS Code e Node usam para depurar um aplicativo remotamente. #synergy

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

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

Agora, 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, esta página também é uma ferramenta de depuração poderosa para ver todos os comandos brutos do protocolo do DevTools passando pela rede, comunicando-se com o navegador.

Como usar programaticamente (nó)

Animador de fantoches

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

Entre outras coisas, o Puppeteer pode ser usado para fazer capturas de tela, criar PDFs, navegar em páginas e buscar informações sobre elas com facilidade. Eu 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-o:

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.

A biblioteca CRI

chrome-remote-interface é uma biblioteca de nível inferior do que a API da Puppeteer. Recomendo isso se você quiser ficar próximo do metal e usar o protocolo DevTools diretamente.

Iniciando o Chrome

O chrome-remote-interface não inicia o Chrome, então você terá que cuidar disso 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, é provável que você queira gerar o Chrome no seu aplicativo.

Uma delas é 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 o trabalho fica complicado se você quer uma solução portátil que funcione em várias plataformas. Basta dar uma olhada no caminho codificado para o Chrome :(

Usar o ChromeLauncher

O Lighthouse é uma ferramenta maravilhosa para testar a qualidade dos seus apps da Web. Um módulo robusto para lançar 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 estiver concluído. 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 estiver instalado), mas é possível alterar essa opção para selecionar manualmente qual Chrome usar. Para usá-lo, primeiro instale a partir do 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();
});

A execução desse script não tem muita utilidade, mas uma instância do Chrome será acionada no gerenciador de tarefas que carregou about:blank. Lembre-se, não haverá interface do navegador. Sem cabeça.

Para controlar o navegador, precisamos do protocolo 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']);
});

Resulta em algo como: HeadlessChrome/60.0.3082.0

Exemplo: verifique 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 APIs do 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 completamente headless. No entanto, o Selenium pode ser configurado para executar a versão headless do Chrome. Recomendamos Executar o Selenium com o Headless Chrome se quiser instruções completas sobre como configurar o recurso, mas confira alguns exemplos abaixo para você começar.

Usando 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

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 une o protocolo DevTools
  • Lighthouse: é uma ferramenta automatizada para testar a qualidade de apps da Web. Usa o protocolo de maneira intensa.
  • chrome-launcher: módulo de nó para iniciar o Chrome, pronto para automação

Demonstrações

  • The Headless Web: excelente postagem do blog de Paul Kinlan sobre como usar o Headless com api.ai.

Perguntas frequentes

Preciso da sinalização --disable-gpu?

Somente no Windows. Outras plataformas não precisam mais dele. A sinalização --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 para mais informações.

Ainda preciso da Xvfb?

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

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 para realizar testes "headless".

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

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

Posso usar esse recurso com o Selenium / WebDriver / ChromeDriver?

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

Como isso está relacionado ao PhantomJS?

O Headless Chrome é semelhante a ferramentas como o PhantomJS. Ambos podem ser usados para testes automatizados em um ambiente headless. 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 Headless Chrome usa a versão mais recente do Blink.

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

Onde informo bugs?

No caso de bugs no Headless Chrome, registre-os em crbug.com.

Para bugs no protocolo DevTools, registre-os em github.com/ChromeDevTools/devtools-protocol (link em inglês).