ヘッドレス Chrome ことはじめ

TL;DR

Headless Chrome が Chrome 59 に搭載されます!これは Chrome をヘッドレス環境で実行する手段です。Chrome をクローム(ブラウザーのUIのこと)なしに実行します!ヘッドレス Chrome によって、Chromium とそのエンジン Blink が提供するモダンなウェブプラットフォームの機能すべてがコマンドラインにもたらされるのです。

でも、いったいその何が便利なんでしょうか?

ヘッドレスブラウザは、GUI を持つ必要のない自動テスト環境やサーバー環境にとてもよいツールです。例としては、実際のウェブページに対してなにかテストを実行する、そのページの PDF を生成する、またはただ、そのページがどう表示されるかを検証するなどが挙げられるでしょうか。

注:ヘッドレスモードは Chrome 59 から、Mac と Linux で提供されます。Windows のサポートはもうちょっとです!現在使っている Chrome のバージョンを調べるには、chrome://version を開きます。

ヘッドレス Chrome を立ち上げる (CLI)

もっとも楽なヘッドレスモードの起動方法は、Chrome のバイナリをコマンドラインから開くことです。Chrome 59 以降を用意し、--headless フラグをつけて Chrome を実行します。

chrome \
  --headless \                   # Chrome をヘッドレスモードで実行する
  --disable-gpu \                # 暫定的に必要なフラグ
  --remote-debugging-port=9222 \
  https://www.chromestatus.com   # 開きたい URL(デフォルトは about:blank)

注:現在は、--disable-gpu を含めなければいけません。このフラグはそのうち不要になります。

ここで chrome はインストールされた Chrome のパスを指します。正確なインストール先はプラットフォームによって異なります。わたしは Mac を使っているのですが、以下のようなエイリアスを作っています。

もし Chrome の Stable(安定版)を使っており、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 のダウンロードはこちら

コマンドラインの機能

やりたいことによっては、プログラムを書く必要さえありません。よくあるタスクには、便利なコマンドラインフラグが用意されています。

DOM を出力する

--dump-dom フラグを使うと、document.body.innerHTML 標準出力に表示します。

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

PDF を作成する

--print-to-pdf フラグを使うと、ページの PDF を作成します。

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

スクリーンショットを撮る

スクリーンショットを撮るには、--screenshot フラグを使用します。

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

# レターヘッドの大きさ
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 フラグつきでヘッドレス Chrome を実行すると、現在のディレクトリに screenshot.png というファイルが生成されます。ページ全体のスクリーンショットを撮りたい場合、やることは少し複雑になります。こちらは David Schnurr がすでに “Using headless Chrome as an automated screenshot tool” というブログ記事で取り上げているので、そちらをご覧ください。

ブラウザ UI なしに Chrome をデバッグ?

Chrome を --remote-debugging-port=9222 フラグつきで実行すると、DevTools Protocol が有効になった状態でインスタンスが起動します。このプロトコルは Chrome と通信し、ヘッドレスブラウザのインスタンスを制御するのに使われています。また、Sublime や VS Code、Node がアプリケーションをリモートデバッグする際にも使われます。 #シナジー

ヘッドレスモードではページを見るためのブラウザ UI が存在しないため、他のブラウザから http://localhost:9222 に移動し、問題がないかを確認します。ページに移動すると、ヘッドレス Chrome がレンダリングしている、検証可能なページのリストを見られます。

DevTools のリモートデバッグ UI

リモートデバッグ UI では、検証やデバッグ、コンテンツの編集など、いつも使っている DevTools の機能を普段通りに使えます。ヘッドレス Chrome をプログラムから利用する場合も、ブラウザと通信している生の DevTools プロトコルのコマンドを確認できるためとても便利です。

プログラムから利用する(Node)

Chrome の起動

前のセクションでは、--headless --remote-debugging-port=9222 フラグをつけ Chrome を手動で起動していました。しかしテストを完全に自動化するには、アプリケーション「から」Chrome を実行したくなるのではないでしょうか。

それを実現するひとつの方法が child_process です。

const execFile = require('child_process').execFile;

function launchHeadlessChrome(url, callback) {
  // macOS を想定
  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 のパスを見てください :(

Lighthouse の ChromeLauncher を使う

Lighthouse ウェブアプリのクオリティをテストする素晴らしいツールです。ご存じないかもしれませんが、Lighthouse には Chrome を操作するとても便利なヘルパーモジュールが搭載されています。そのモジュールのひとつが ChromeLauncher です。ChromeLauncher は Chrome がどこにインストールされているかを探したり、デバッグ用のインスタンスをセットアップしたり、Chrome を起動したり、プログラムが終了したときに Chrome も終了したりと、いろんなことをしてくれます。なによりも嬉しいのが、Node のお陰でクロスプラットフォームなことです!

注:Lighthouse チームはよりよい API を搭載した ChromeLauncher のスタンドアロン版を検討しています。気になることがあれば、ぜひフィードバックをお願いします。

デフォルトでは、ChromeLauncher は Chrome Canary を起動しようとします(インストールされていれば)。もちろんどの Chrome を利用するかは決められます。ChromeLauncher を使うには、まず Lighthouse をインストールします。

yarn add lighthouse

- ChromeLauncher を使ってヘッドレス Chrome を起動する

const {ChromeLauncher} = require('lighthouse/lighthouse-cli/chrome-launcher');

/**
 * デバッグ用の Chrome インスタンスをポート 9222 で起動する。
 * @param {boolean=} headless Chrome をヘッドレスモードで起動。
 *     デフォルトは true。値を false にセットすると通常モードで起動。
 * @return {Promise<ChromeLauncher>}
 */
function launchChrome(headless = true) {
  const launcher = new ChromeLauncher({
    port: 9222,
    autoSelectChrome: true, // false にした場合は手動で Chrome を選択する
    additionalFlags: [
      '--window-size=412,732',
      '--disable-gpu',
      headless ? '--headless' : ''
    ]
  });

  return launcher.run().then(() => launcher)
    .catch(err => {
      return launcher.kill().then(() => { // エラーな場合 Chrome を終了
        throw err;
      }, console.error);
    });
}

launchChrome(true).then(launcher => {
  ...
});

このスクリプトは小さいものですが、about:blank を読み込んだ Chrome のインスタンスがタスクマネージャに現れるのを確認できると思います。なお、ブラウザ UI は立ち上がりません。ヘッドレスですから。

立ち上がった Chrome を操作するには、DevTools プロトコルが必要です!

ページの情報を取得する

chrome-remote-interfaceDevTools Protocol 上に構築された、ハイレベルな API を提供するとてもいい Node のパッケージです。これを使うとヘッドレス Chrome を操作し、ページを移動し、そしてページに関する情報を取得できます。

要注意:DevTools プロトコルでほんとに色んなことができるので、最初はすこしウッとなるかと思います。まずは DevTools Protocol Viewer を眺めましょう。そのあと chrome-remote-interface の API ドキュメンテーションを読み、生プロトコルをどうラップしているのか確認するとといでしょう。

では、ライブラリをインストールしましょう。

yarn add chrome-remote-interface

- UA 文字列を出力する

launchChrome().then(launcher => {
  chrome.Version().then(version => console.log(version['User-Agent']));
});

HeadlessChrome/60.0.3082.0 といった文字列が出てくるでしょう。

- サイトに web app manifest があるかを確認する

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

function onPageLoad(Page) {
  return Page.getAppManifest().then(response => {
    if (!response.url) {
      console.log('Site has no app manifest');
      return;
    }
    console.log('Manifest: ' + response.url);
    console.log(response.data);
  });
}

launchChrome().then(launcher => {

  chrome(protocol => {
    // DevTools プロトコルから、必要なタスク部分を抽出する。
    // API ドキュメンテーション: https://chromedevtools.github.io/devtools-protocol/
    const {Page} = protocol;

    // まず、使用する Page ドメインを有効にする。
     Page.enable().then(() => {
      Page.navigate({url: 'https://www.chromestatus.com/'});

      // window.onload を待つ。
      Page.loadEventFired(() => {
        onPageLoad(Page)).then(() => {
          protocol.close();
          launcher.kill(); // Chrome を終了させる。
        });
      });
    });

  }).on('error', err => {
    throw Error('Cannot connect to Chrome:' + err);
  });

});

- DOM API を使ってページの <title> を抽出する

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

function onPageLoad(Runtime) {
  const js = "document.querySelector('title').textContent";

  // ページ内で JS の式を評価する。
  return Runtime.evaluate({expression: js}).then(result => {
    console.log('Title of page: ' + result.result.value);
  });
}

launchChrome().then(launcher => {

  chrome(protocol => {
    // DevTools プロトコルから、必要なタスク部分を抽出する。
    // API ドキュメンテーション: https://chromedevtools.github.io/devtools-protocol/
    const {Page, Runtime} = protocol;

    // まず、使用するドメインを有効にする。
    Promise.all([
      Page.enable(),
      Runtime.enable()
    ]).then(() => {
      Page.navigate({url: 'https://www.chromestatus.com/'});

      // window.onload を待つ。
      Page.loadEventFired(() => {
        onPageLoad(Runtime).then(() => {
          protocol.close();
          launcher.kill(); // Chrome を終了させる。
        });
      });

    });

  }).on('error', err => {
    throw Error('Cannot connect to Chrome:' + err);
  });

});

あわせて読みたい

ヘッドレス Chrome を始めるにあたり便利なリソースを紹介します。

ドキュメンテーション

ツール

  • chrome-remote-interface - DevTools プロトコルのラッパーを提供する Node モジュール
  • Lighthouse - ウェブアプリのクオリティをテストする自動化ツール

デモ

  • "The Headless Web" - ヘッドレス Chrome と api.ai を組み合わせる、Paul Kinlan のとてもよいブログ記事

FAQ

--diable-gpu フラグは必要ですか?

今のところですが、必要です。--disable-gpu フラグはいくつかのバグを回避するための暫定的な手段です。将来の Chrome のバージョンでは必要なくなるでしょう。詳しくは https://crbug.com/546953#c152https://crbug.com/695212 をご覧ください。

Xvfb はまだ必要なのでしょうか?

いいえ。ヘッドレス Chrome はウインドウを仕様しないため、Xvfb などのディスプレイサーバはもう必要ありません。自動化テストを Xvfb なしに実行できます。うれしいですね。

Xvfb がわからない?Xvfb は Unix ライクなシステム向けに提供される、インメモリなディスプレイサーバです。これを使うと、グラフィカルなアプリケーション(Chrome など)を、ディスプレイを物理的に接続せず実行できます。「ヘッドレス」なテストを実行する際、以前は Xvfb を利用して古い Chrome を動かしていました。

ヘッドレス Chrome を実行できる Docker コンテナはどうやって作れますか?

lighthouse-ci をチェックしてください。Ubuntu をベースイメージに、App Engine Flexible コンテナ内で Lighthouse をインストールし実行する Dockerfile の例 があります。

PhantomJS との関連は?

ヘッドレス Chrome は PhantomJS に似ています。どちらもヘッドレス環境での自動化テストに使われます。大きな違いは、PhantomJS が古い WebKit を使用するのに対し、ヘッドレス Chrome は最新版の Blink を使用するということです。

現時点で、PhantomJS のほうが DevTools Protocol よりもハイレベルな API を提供しています。

ヘッドレス Chrome を Selenium / WebDriver / ChromeDriver と組み合わせられますか?

現時点で、Selenium は Chrome のフルインスタンスを実行します。つまり、Selenium は自動化ソリューションではありますが、完全にヘッドレスではないということです。しかし、将来的には --headless の採用もありうるでしょう。

Selenium でヘッドレス Chrome を試したいという方は、Running Selenium with Headless Chrome を読んでセットアップしてみましょう。

注:ChromeDriver でバグに出会ったかもしれません。執筆時点での最新版(2.29)は Chrome 58 のみをサポートしています。ヘッドレス Chrome は Chrome 59 以降が必要です。

バグの報告先はどこですか?

ヘッドレス Chrome についてのバグは crbug.com にお願いします。

DevTools プロトコルに関するバグは github.com/ChromeDevTools/devtools-protocol にお願いします。