无头 Chrome shell

无头 Chrome shell 在 Chrome 59 中推出。它是一种在无头环境中运行 Chrome 浏览器的方式。 从本质上讲,它是在没有 Chrome 界面的情况下运行 Chrome!它将 Chromium 和 Blink 渲染引擎提供的所有现代 Web 平台功能 带到了命令行。

无头浏览器是自动化测试和服务器环境的绝佳工具,在这些环境中,您不需要可见的界面 shell。例如,您可能需要针对真实网页运行一些测试、创建网页的 PDF,或者只是检查浏览器如何渲染网址。

启动无头模式 (CLI)

要开始使用无头模式,最简单的方法是从命令行打开 Chrome 二进制文件。如果您已安装 Chrome 59 或更高版本,请使用 --headless 标志启动 Chrome:

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 应指向您的 Chrome 安装。具体位置因平台而异。由于我使用的是 Mac,因此为已安装的每个 Chrome 版本创建了便捷的别名。

如果您使用的是 Chrome 稳定版渠道,但无法获取 Beta 版,请使用 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"

下载 Chrome Canary 版。

命令行功能

在某些情况下,您可能不需要以 编程方式编写 无头 Chrome 的脚本。 有一些实用的命令行标志 可用于执行常见任务。

输出 DOM

--dump-dom 标志会将 document.body.innerHTML 输出到 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/

截取屏幕截图

如需截取网页的屏幕截图,请使用 --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/

使用 --screenshot 运行会在当前工作目录中生成一个名为 screenshot.png 的文件。如果您需要截取整页的屏幕截图,则需要执行更多操作。David Schnurr 撰写了一篇很棒的博文,其中介绍了相关内容。请参阅 将无头 Chrome 用作自动化屏幕截图工具

REPL 模式(读取-求值-输出循环)

--repl 标志会在一种模式下运行无头模式,您可以在浏览器中直接从命令行评估 JS 表达式:

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

如何在没有浏览器界面的情况下调试 Chrome?

当您使用 --remote-debugging-port=9222 运行 Chrome 时,它会启动一个启用了 DevTools protocol 的实例 。该协议用于与 Chrome 通信并驱动无头浏览器实例。Sublime、VS Code 和 Node 等工具也使用该协议来远程调试应用。#synergy

由于您没有浏览器界面来查看网页,因此请在另一个浏览器中前往 http://localhost:9222,以检查一切是否正常运行。您会看到一个可检查的网页列表,您可以点击这些网页并查看无头模式渲染的内容:

开发者工具远程
DevTools 远程调试界面

在这里,您可以像往常一样使用熟悉的 DevTools 功能来检查、调试和调整网页。如果您以编程方式使用无头模式,此页面也是一个强大的调试工具,可用于查看通过网络传输的所有原始 DevTools 协议命令,与浏览器通信。

以编程方式使用(Node)

木偶操作师

Puppeteer 是 Chrome 团队开发的 Node 库 。它提供了一个高级 API 来控制无头(或完整)Chrome。它与其他自动化测试库(如 Phantom 和 NightmareJS)类似,但仅适用于最新版本的 Chrome。

除其他功能外,Puppeteer 还可用于轻松截取屏幕截图、创建 PDF、浏览网页以及提取有关这些网页的信息。如果您想快速实现浏览器测试自动化,我建议您使用此库。它隐藏了 DevTools 协议的复杂性,并处理了启动 Chrome 调试实例等冗余任务。

安装方法如下:

npm i --save puppeteer

示例 - 输出用户代理

const puppeteer = require('puppeteer');

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

示例 - 截取网页的屏幕截图

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

如需详细了解完整的 API,请参阅 Puppeteer 的文档

CRI 库

chrome-remote-interface 是一个比 Puppeteer 的 API 更低级别的库。如果您想直接使用 DevTools 协议,我建议您使用此库。

启动 Chrome

chrome-remote-interface 不会为您启动 Chrome,因此您必须自行处理。

在 CLI 部分,我们 手动启动了 Chrome 使用 --headless --remote-debugging-port=9222。不过,如需完全实现测试自动化,您可能需要从应用中生成 Chrome。

一种方法是使用 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) => {
  ...
});

但是,如果您需要一个可在多个平台上运行的可移植解决方案,情况就会变得复杂。请注意 Chrome 的硬编码路径 :(

使用 ChromeLauncher

Lighthouse 是一款出色的 工具,可用于测试 Web 应用的质量。Lighthouse 中开发了一个用于启动 Chrome 的稳健模块,现在已提取出来供独立使用。 chrome-launcher NPM 模块 会查找 Chrome 的安装位置、设置调试实例、启动浏览器,并在程序完成后将其终止。最棒的是,借助 Node,它可跨平台运行!

默认情况下,chrome-launcher 会尝试启动 Chrome Canary 版 (如果已安装),但您可以更改此设置以手动选择要使用的 Chrome。如需使用它,请先从 npm 安装:

npm i --save chrome-launcher

示例 - 使用 chrome-launcher 启动无头模式

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

运行此脚本不会执行太多操作,但您应该会在任务管理器中看到一个 Chrome 实例启动并加载 about:blank。请注意,不会有任何浏览器界面。我们处于无头模式。

如需控制浏览器,我们需要 DevTools 协议!

检索有关网页的信息

如需安装该库,请执行以下操作:

npm i --save chrome-remote-interface
示例

示例 - 输出用户代理

const CDP = require('chrome-remote-interface');

...

launchChrome().then(async chrome => {
  const version = await CDP.Version({port: chrome.port});
  console.log(version['User-Agent']);
});

产生类似如下的结果:HeadlessChrome/60.0.3082.0

示例 - 检查网站是否具有 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.
});

})();

示例 - 使用 DOM API 提取网页的 <title>

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

})();

使用 Selenium、WebDriver 和 ChromeDriver

目前,Selenium 会打开一个完整的 Chrome 实例。换句话说,它是一种自动化解决方案,但并非完全无头。不过,只需稍作调整,即可将 Selenium 配置为运行无头 Chrome。如果您需要有关如何自行设置的 完整说明,我建议您参阅 使用无头 Chrome 运行 Selenium ,但我在下面提供了一些 示例供您入门。

使用 ChromeDriver

ChromeDriver 2.32 使用 Chrome 61,并且与无头 Chrome 配合使用效果良好。

安装:

npm i --save-dev selenium-webdriver chromedriver

示例:

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

使用 WebDriverIO

WebDriverIO 是 Selenium WebDriver 之上的更高级别 API。

安装:

npm i --save-dev webdriverio chromedriver

示例:在 chromestatus.com 上过滤 CSS 功能

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

})();

将旧版无头 Chrome 下载为 chrome-headless-shell

从 Chrome 112 版开始,Chrome 推出了新的无头模式 (--headless=new)。此模式使开发者能够在无人值守的环境中运行 Chrome 本身,而不是单独的二进制文件,而无需任何可见的界面,这对于测试和自动化用例非常有用。

旧版无头 shell 和新版无头模式有不同的用例:

  1. 旧版无头 shell 是 Chromium 的 //content 模块的轻量级封装容器,因此依赖项要少得多。具体而言,它不需要 X11/Wayland、D-Bus,并且在某些方面比功能齐全的 Chrome 浏览器性能更高。因此,它适用于自动化屏幕截图或网络抓取等用例。
  2. 另一方面,新版无头模式是真正的 Chrome 浏览器,因此更真实、更可靠,并提供更多功能。因此,它更适合高准确度的端到端 Web 应用测试或浏览器扩展程序测试。

换句话说,性能和真实性之间需要权衡。哪种无头模式最适合您?这取决于您的用例。

一个图表,用于说明前面列表中的信息。

对于自动化用例不需要完整 Chrome 功能的开发者和测试人员,可能需要使用旧版无头模式。否则,新版无头模式可能是最佳选择。

为了确保开发者和测试人员能够继续在这两个选项之间进行选择,我们很高兴地宣布,旧版无头模式实现现在以独立 chrome-headless-shell 二进制文件的形式提供。这些新的 chrome-headless-shell 二进制文件是为每个面向用户的 Chrome 版本生成的,并且从 Chrome 120 开始,可以通过 Chrome for Testing 基础架构 下载。

如何获取 chrome-headless-shell 二进制文件?

与其他 Chrome for Testing 二进制文件一样,为您的平台下载 chrome-headless-shell 的最简单方法是使用 我们的 @puppeteer/browsers 命令行实用程序,该实用程序可通过 npm 使用。以下是一些示例:

# 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

如果您更喜欢构建自己的自动化脚本来下载 chrome-headless-shell 二进制文件,我们也能满足您的需求。Chrome for Testing 提供 JSON API 端点,其中包含每个 Chrome 发布渠道(稳定版、Beta 版、开发者版和 Canary 版)的最新可用版本。如需快速了解最新状态,请参阅 Chrome for Testing 可用性信息中心

反馈

我们期待收到您对 chrome-headless-shell 的反馈。如果您遇到任何问题,欢迎随时报告

更多资源

以下是一些有用的资源,可帮助您入门:

文档

工具

演示

  • 无头 Web” - Paul Kinlan 撰写的一篇关于将无头模式与 api.ai 搭配使用的精彩博文 。

常见问题解答

我是否需要 --disable-gpu 标志?

仅限 Windows。其他平台不再需要此标志。--disable-gpu 标志是针对一些 bug 的临时解决方法。在 Chrome 的未来版本中,您将不需要此标志。如需了解详情,请参阅 crbug.com/737678

那么我仍然需要 Xvfb 吗?

不需要。无头 Chrome 不使用窗口,因此不再需要 Xvfb 等显示服务器。您可以放心地运行自动化测试,而无需使用它。

什么是 Xvfb?Xvfb 是适用于类 Unix 系统的内存中显示服务器,可让您在没有连接物理显示器的情况下运行图形应用(如 Chrome)。 许多人使用 Xvfb 运行早期版本的 Chrome 来执行“无头”测试。

如何创建运行无头 Chrome 的 Docker 容器?

请参阅 lighthouse-ci。它有一个 示例 Dockerfile ,该文件使用 node:8-slim 作为基础映像,并在 App Engine 柔性环境中安装并 运行 Lighthouse

我可以将此与 Selenium / WebDriver / ChromeDriver 搭配使用吗

可以。请参阅使用 Selenium、WebDriver 和 ChromeDriver

这与 PhantomJS 有何关系?

无头 Chrome 与 PhantomJS 等工具类似。两者都可用于在无头环境中进行自动化测试。两者之间的主要区别在于,Phantom 使用旧版 WebKit 作为其渲染引擎,而无头 Chrome 使用最新版本的 Blink。

目前,Phantom 还提供了比 DevTools 协议更高级别的 API。

在哪里报告 bug?

如需报告无头 Chrome 的 bug,请在 crbug.com 上提交。

如需报告 DevTools 协议中的 bug,请在 github.com/ChromeDevTools/devtools-protocol 上提交。