このページの本文へ

ブラウザーテストが捗る!Node.jsで使えるヘッドレスChromeが便利すぎる

2017年09月14日 18時01分更新

文●Brian Greig

  • この記事をはてなブックマークに追加
本文印刷
Google Chromeにヘッドレスモードが実装され、コマンドラインやNode.jsからChromeを操作できるようになりました。ユーザー行動をシミュレーションしてテストする方法を解説します。

Webサイトに変更を加えるとき、ユーザーの取りうる行動を繰り返し試して、一貫したユーザー体験を実現できているか確認します。一貫性を保ちながら手軽に試すために、スクリプトで自動化するライブラリーを使って前提条件の表明(アサーション)を確認したり、結果をもとにドキュメントを整備したりします。ヘッドレスブラウザー(headless、GUIを持たないこと)とは、サイト上でのユーザーが取る行動をスクリプトで実行し、実行結果も保存できる、テスト向けのコマンドラインツールです。

開発者の多くはヘッドレスブラウザーに長年、PhantomJSCasperJSなどのツールを使用してきました。しかし、恋と同じように、私たちの心は別のものへと移ります。Chrome 59から(Windows版は60から)、独自のヘッドレスブラウザーが付きました。現在はまだSeleniumをサポートしていませんが、ChromiumとBlinkレンダリングエンジンを採用しているので、実際のChromeユーザーのシミュレーションできます。

本記事のコードはすべてGitHubリポジトリにあります。

コマンドラインからヘッドレスChromeを実行

コマンドラインからヘッドレスChromeの実行は簡単です。MacではChromeのエイリアスを設定し、—headlessパラメーターを付けて実行します。

alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome”
chrome --headless --disable-gpu --remote-debugging-port=9090 https://www.sitepoint.com/

Linuxはもっと簡単です。

google-chrome --headless --disable-gpu --remote-debugging-port=9090 https://www.sitepoint.com/
  • --headless:ユーザーインターフェイスやディスプレイサーバー無しで実行する
  • --disable-gpu:GPUハードウェア・アクセラレーションを無効にする。現在、一時的に必要
  • --remote-debugging-port:指定したポートでHTTPプロトコル経由でのリモートデバッグができる

リクエストしたページに対する操作もできます。たとえばdocument.body.innerHTMLを「標準出力」で出力します。

google-chrome --headless --disable-gpu --dump-dom http://endless.horse/

ほかにできることを知りたいならパラメーターの完全なリストを確認ください。

Node.jsでヘッドレスChromeを実行する

コマンドラインではなく、Node.jsでヘッドレスChromeを実行するために以下のモジュールが必要です。

  • chrome-remote-interface:コマンドと通知のシンプルな抽象化を提供するJavaScript API
  • chrome-launcher:マルチプラットフォーム対応でNode内からChromeを実行できるようにする

これで環境の構築ができます。Nodeとnpmがインストールされていない場合はこちらのチュートリアルを参考にインストールします。

mkdir headless
cd headless
npm init -y
npm install chrome-remote-interface --save
npm install chrome-launcher --save

完了後ヘッドレスChromeのセッションを開始します。プロジェクトフォルダー内に、index.jsファイルを作成します。

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

(async function() {
  async function launchChrome() {
    return await chromeLauncher.launch({
      chromeFlags: [
        '--disable-gpu',
        '--headless'
      ]
    });
  }
  const chrome = await launchChrome();
  const protocol = await CDP({
    port: chrome.port
  });

  // ALL FOLLOWING CODE SNIPPETS HERE

})();

始めに依存オブジェクトをリクエストし、Chromeセッションをインスタンス化する即時関数を作ります。執筆時点では--disable-gpuフラグが必須です。これは不具合の一時回避策としてGoogleが推薦しているだけなので、不要かもしれません。ここではasync/awaitを使い、以降の処理に進む前に確実にヘッドレスブラウザーを立ち上げます。

注意:使う関数には、ページ描画やそのほか処理実行の時間を確保するため、次の処理に進む前に完了が必須のアクションがあります。処理の多くはノンブロッキング処理(そのまま処理を進める)なので、プロミスで実行を停止します。非同期の関数はMozilla Developer Networkもしくはこの記事を参照してください。

今回のテストに必要なドメイン(領域)を定義します。

const {
  DOM,
  Page,
  Emulation,
  Runtime
} = protocol;
await Promise.all([Page.enable(), Runtime.enable(), DOM.enable()]);

UI上に描画されるコンテンツにアクセスするために、Pageオブジェクトを使います。どこに移動するのか、どの要素を操作するのか、どこのスクリプトを実行するのかも、Pageオブジェクトに指定します。もっとも重要なオブジェクトです。

ページを操作してみる

セッションを開始してドメインが定義したら、Webサイトを確認します。上記で有効化したPageドメインで開始位置を指定します。

Page.navigate({
  url: 'https://en.wikipedia.org/wiki/SitePoint'
});

これでページを読み込みます。loadEventFiredメソッドで実行する処理を定義すれば、ユーザーの取る行動の模擬テストができます。今回の例では最初の段落のコンテンツを取得します:

Page.loadEventFired(async() => {
  const script1 = "document.querySelector('p').textContent"
  // Evaluate script1
  const result = await Runtime.evaluate({
    expression: script1
  });
  console.log(result.result.value);

  protocol.close();
  chrome.kill(); 
});

コマンドnode index.jsでスクリプトを実行すると以下の出力が表示されます。

SitePoint is a Melbourne, Australia-based website, and publisher of books, courses and articles for web developers. In January 2014, SitePoint.com had an Alexa ranking of 889,[1] and a Quantcast rating of 14,934.[2]

スクリーンショットの撮影

さらに上記script1を変えれば、リンクをクリックしたり、フォームを埋めたり、クエリセレクタを使い連続した処理をさせたりできます。各処理はJSON形式の設定ファイルに保存し、Nodeスクリプト上に読み込まれて連続で実行します。実行結果がUI/UXに求めることを満たせたかはMochaなどのテストプラットフォームで確認します。

テストスクリプトを強化し、Webページを移動しながらスクリーンショットを撮る機能を備えた関数「captureScreenshot」が用意されています。

const chromeLauncher = require('chrome-launcher');
const CDP = require('chrome-remote-interface');
const file = require('fs');

(async function() {
  ...

  Page.loadEventFired(async() => {
    const script1 = "document.querySelector('p').textContent"
    // Evaluate script1
    const result = await Runtime.evaluate({
      expression: script1
    });
    console.log(result.result.value);

    const ss = await Page.captureScreenshot({format: 'png', fromSurface: true});
    file.writeFile('screenshot.png', ss.data, 'base64', function(err) {
      if (err) {
        console.log(err);
      }
    });

    protocol.close();
    chrome.kill();
  });
})();

fromSurfaceフラグも、執筆時点ではクロスプラットフォーム対応のために必要なフラグです。将来のバージョンでは不要になる可能性があります。

コマンドnode index.jsでスクリプトを実行すると、以下のように出力されます。

Headless Chrome: Output of the screenshot command

最後に

いま自動化のためにスクリプトを書いているのなら、Chromeのヘッドレスブラウザーをおすすめします。Seleniumをはじめとしたツールとの統合が完全ではないものの、Chromeのレンダリングエンジンでシミュレーションができる利点をあなどってはいけません。完全な自動化を活用しながらユーザーエクスペリエンスを改善する、優れた方法です。

(原文:Quick Tip: Getting Started with Headless Chrome in Node.js

[翻訳:西尾 健史/編集:Livit

Web Professionalトップへ

WebProfessional 新着記事