Shell da versão headless do Chrome

O shell da versão headless do Chrome foi lançado no Chrome 59. É uma maneira 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.

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, talvez você queira 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.

chrome precisa apontar para a instalação do Chrome. O local exato varia de plataforma para plataforma. Como estou no Mac, criei aliases convenientes para cada versão do Chrome que tenho instalada.

Se você estiver no canal estável do Chrome e não conseguir o Beta, use 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.

Recursos da linha de comando

Em alguns casos, talvez você não precise programar a versão headless do Chrome. Há algumas flags úteis da linha de comando para realizar tarefas comuns.

Como imprimir o DOM

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

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


### Create a PDF

The `--print-to-pdf` flag creates a PDF of the page:

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

Como fazer capturas de tela

Para capturar 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 produzirá um arquivo chamado screenshot.png no diretório de trabalho atual. Se você estiver procurando capturas de tela de página inteira, as coisas serão um pouco mais complicadas. Há uma ótima postagem no blog de David Schnurr que aborda esse assunto. Confira Como usar o Chrome headless como uma ferramenta automatizada de captura de tela (link em inglês).

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

A flag --repl executa o modo headless em um modo em que você pode 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 acionar a instância do navegador headless. É 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, acesse http://localhost:9222 em outro navegador para verificar se tudo está funcionando. Você verá uma lista de páginas inspecionáveis em que pode clicar e ver o que o modo 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 como faria normalmente. Se você estiver usando o modo headless de forma programática, essa página também será uma ferramenta de depuração poderosa para ver todos os comandos brutos do protocolo DevTools que estão sendo transmitidos pela rede, comunicando-se com o navegador.

Como usar de forma programática (Node)

Animador de fantoches

O Puppeteer é uma biblioteca de nós desenvolvida pela equipe do Chrome. Ele fornece uma API de alto nível para controlar o Chrome headless (ou completo). É semelhante a outras bibliotecas de testes 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 pelas 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 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 à API do Puppeteer. Recomendo se você quiser estar perto do metal e usar o protocolo DevTools diretamente.

Como iniciar o Chrome

A chrome-remote-interface não inicia o Chrome para você. Portanto, você terá que cuidar disso.

Na seção da CLI, iniciamos o Chrome manualmente usando --headless --remote-debugging-port=9222. No entanto, para automatizar totalmente os testes, provavelmente você vai querer gerar o Chrome no 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ê quiser uma solução portátil que funcione em várias plataformas. Basta observar o 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 encerrá-lo quando o programa terminar. A melhor parte é que ele funciona em multiplataforma graças ao Node!

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

npm i --save chrome-launcher

Exemplo : usar chrome-launcher para iniciar o modo 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 muito, mas você verá uma instância do Chrome sendo iniciada no gerenciador de tarefas que carregou about:blank. Lembre-se de que não haverá interface do navegador. Estamos no modo headless.

Para controlar o navegador, precisamos do protocolo DevTools.

Como recuperar informações sobre a página

Para 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 : verificar 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 : extrair 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 completamente headless. No entanto, o Selenium pode ser configurado para executar o Chrome headless com um pouco de trabalho. Recomendo executar o Selenium com a versão headless do Chrome se você quiser as instruções completas sobre como configurar as coisas, mas incluí alguns exemplos abaixo para você começar.

Como usar o ChromeDriver

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

WebDriverIO é uma API de nível superior no 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();

})();

Fazer o download do Chrome headless antigo como chrome-headless-shell

A partir da versão 112 do Chrome, o novo modo headless do Chrome (--headless=new) está disponível. Esse modo permite que os desenvolvedores executem o Chrome em vez de um binário separado em um ambiente autônomo sem interface visível, o que é útil para casos de uso de teste e automação.

Há casos de uso distintos para o shell headless antigo e o novo modo headless:

  1. O shell headless antigo é um wrapper leve em torno do módulo //content do Chromium e, portanto, tem muito menos dependências. Especificamente, ele não exige X11/Wayland, D-Bus e, de certa forma, tem um desempenho melhor do que o navegador Chrome completo. Isso o torna adequado para casos de uso, como captura de tela automatizada ou raspagem da Web.
  2. O novo modo headless, por outro lado, é o navegador Chrome real e, portanto, é mais autêntico, confiável e oferece mais recursos. Isso o torna mais adequado para testes de apps da Web de alta precisão ou testes de extensão do navegador.

Em outras palavras, há uma compensação entre desempenho e autenticidade. Qual modo headless é mais adequado para você? Depende do seu caso de uso.

Um diagrama que ilustra as informações da lista anterior.

Os desenvolvedores e testadores que não exigem a funcionalidade completa do Chrome para os casos de uso de automação podem usar o modo headless antigo. Caso contrário, o novo modo headless é provavelmente a melhor opção.

Para garantir que os desenvolvedores e testadores continuem tendo a opção entre essas duas opções, temos o prazer de anunciar que a implementação headless antiga agora está disponível como um binário chrome-headless-shell independente. Esses novos chrome-headless-shell binários são gerados para cada versão do Chrome voltada para o usuário e estão disponíveis para download pela infraestrutura do Chrome for Testing a partir do Chrome 120.

Como posso receber binários chrome-headless-shell?

Assim como outros binários do Chrome for Testing, a maneira mais fácil de fazer o download do chrome-headless-shell para sua plataforma é usando nosso utilitário de linha de comando@puppeteer/browsers, disponível usando npm. Veja alguns exemplos:

# Download the latest available `chrome-headless-shell` binary corresponding to the Stable channel.
npx @puppeteer/browsers install chrome-headless-shell@stable

# Download a specific `chrome-headless-shell` version.
npx @puppeteer/browsers install chrome-headless-shell@120.0.6098.0

Se você preferir criar seus próprios scripts automatizados para fazer o download de binários chrome-headless-shell, temos o que você precisa. O Chrome for Testing oferece endpoints de API JSON com as versões mais recentes disponíveis por canal de lançamento do Chrome (Stable, Beta, Dev e Canary). Para ter uma visão geral rápida do status mais recente, consulte o painel de disponibilidade do Chrome for Testing.

Feedback

Aguardamos seu feedback sobre chrome-headless-shell. Se você encontrar algum problema, sinta-se à vontade para informá-lo.

Outros recursos

Confira alguns recursos úteis para começar:

Documentos

Ferramentas

  • chrome-remote-interface: módulo de nó que envolve o protocolo DevTools
  • Lighthouse: ferramenta automatizada para testar a qualidade de apps da Web; usa muito o protocolo
  • chrome-launcher: módulo de nó para iniciar o Chrome, pronto para automação

Demonstrações

  • "The Headless Web" (link em inglês): ótima postagem no blog de Paul Kinlan sobre como usar o modo headless com api.ai.

Perguntas frequentes

Preciso da flag --disable-gpu?

Apenas no Windows. Outras plataformas não exigem mais. A flag --disable-gpu é uma solução alternativa temporária para alguns bugs. Você não vai precisar dessa flag em versões futuras do Chrome. Consulte crbug.com/737678 para mais informações.

Então ainda preciso do Xvfb?

Não. A versão headless do 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 semelhantes ao 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 para fazer testes "headless".

Como faço para criar um contêiner do Docker que executa a versão headless do Chrome?

Confira lighthouse-ci (link em inglês). Ele tem um Dockerfile de exemplo que usa node:8-slim como uma imagem de base, 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.

Como isso se relaciona com o PhantomJS?

A versão headless do 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 a versão headless do Chrome usa a versão mais recente do Blink.

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

Onde posso informar bugs?

Para bugs na versão headless do Chrome, registre-os em crbug.com.

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