Começar a usar a Headless Chrome

Texto longo, leia o resumo

Chrome sem comando está disponível no Chrome 59. É uma forma de executar o navegador Chrome em um ambiente headless. Essencialmente, executar Chrome sem o Chrome! Ele oferece todos os recursos modernos da plataforma da Web, pelo Chromium e pelo mecanismo de renderização Blink à linha de comando.

Por que isso é útil?

Um navegador sem comando é uma ótima ferramenta para testes automatizados e ambientes de servidor não precisam de um shell de interface visível. Por exemplo, talvez você queira executar alguns testes no uma página da Web real, criar um PDF ou simplesmente analisar 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. O local exato de plataforma para plataforma. Como estou 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 a versão Beta, recomendamos usando 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 realizar 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ê está procurando capturas de tela de página inteira, estão um pouco mais envolvidos. Temos um ótimo blog de David Schnurr que explica tudo. Finalizar compra 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 flag --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
$

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

Quando você executa o Chrome com o --remote-debugging-port=9222, ele inicia uma instância com o protocolo DevTools ativado. A é usado para se comunicar com o Chrome e conduzir a conexão do navegador. Ele também é usado por ferramentas como Sublime, VS Code e Node a depuração remota de um aplicativo. #synergy

Como você não tem a interface do navegador para acessar a página, acesse http://localhost:9222 em outro navegador para verificar se está tudo funcionando. Você verá uma lista de páginas inspecionáveis, onde você pode clicar e ver o que o headless está renderizando:

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 na página como faria normalmente. Se você usar o Headless programaticamente, ele também é uma ferramenta de depuração poderosa para ver todo o protocolo bruto do DevTools que passam pela rede, comunicando-se com o navegador.

Como usar programaticamente (Node)

Animador de fantoches

Puppeteer é uma biblioteca de nós. desenvolvida pela equipe do Chrome. Ele fornece uma API de alto nível para controlar (ou versão completa) do Chrome. É semelhante a outras bibliotecas de teste automatizadas, como a Phantom e NightmareJS, mas só funciona 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 pelas páginas e buscar informações sobre elas. Recomendo a biblioteca se quiser automatizar rapidamente o teste do navegador. Ele esconde as complexidades do protocolo 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 (em inglês) para saber mais sobre a API completa.

Biblioteca CRI

chrome-remote-interface é uma biblioteca de nível inferior à API do Puppeteer. Eu recomendo se você quiser ser perto do metal e usar o protocolo DevTools diretamente.

Iniciando o Chrome

O Chrome-remote-interface não inicia o Chrome para você, então você terá que e cuidar disso sozinho.

Na seção da CLI, iniciamos o Chrome manualmente usando --headless --remote-debugging-port=9222 No entanto, para automatizar totalmente os testes, você provavelmente querem gerar o Chrome a partir 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ários plataformas. Veja o caminho codificado para o Chrome :(

Como usar o ChromeLauncher

O Lighthouse é um ambiente maravilhoso para testar a qualidade dos seus apps da Web. Um módulo avançado para lançamento O Chrome foi desenvolvido no Lighthouse e agora é extraído para uso independente. O módulo chrome-launcher do NPM vai encontrar onde O Chrome está instalado. Configure uma instância de depuração, inicie o navegador e elimine-o quando o programa for concluído. A melhor parte é que ele funciona em várias plataformas, graças à Nó!

Por padrão, chrome-launcher tentará iniciar o Chrome Canary (se for instalado), mas você pode mudar isso para selecionar manualmente qual Chrome usar. Para Use-o. 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 adianta muitas coisas, mas você verá uma instância do O Chrome foi iniciado no gerenciador de tarefas que carregou about:blank. Lembre-se, há não haverá nenhuma 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, é um automatizada, mas não completamente headless. No entanto, o selênio pode ser configurado para executar a versão headless do Chrome com um pouco de trabalho. Eu recomendo Como executar o Selenium com o Headless Chrome se você quiser que instruções completas sobre como fazer a configuração por conta própria, mas também mostrei algumas abaixo para você começar.

Como usar o ChromeDriver

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: nó que encapsula o protocolo DevTools
  • Lighthouse: ferramenta automatizada para testes. 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 (link em inglês) - Excelente blog de Paul Kinlan publicarmos 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 sinalização --disable-gpu é uma uma solução temporária para alguns bugs. Essa flag não será necessária em versões futuras do Chrome. Consulte crbug.com/737678 para 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 são mais necessários. 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 para 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 fazer a verificação sem comando testes.

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 que usa node:8-slim como imagem de base, as instalações + executa o Lighthouse no App Engine Flex.

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

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

Qual é a relação com o PhantomJS?

A headless Chrome é semelhante a ferramentas como o PhantomJS. Ambos podem ser usadas para testes automatizados em um ambiente sem comando. A principal diferença entre os dois é que o Phantom usa uma versão mais antiga do WebKit como sua renderização Engine, 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 DevTools em github.com/ChromeDevTools/devtools-protocol (link em inglês).