LCL Engineers' Blog

バス比較なび・格安移動・バスとりっぷを運営する LCLの開発者ブログです。

E2EテストをPhantomJSから、Puppeteer + Headless Chromeへ移行しました

Webエンジニアの森脇です。LCLでは、以前より「Capybara + PhantomJS」でE2Eテストを行っていましたが、「Puppeteer + Headless Chrome」へ変更しました。 元々は、軽くPuppeteerを触ってみるだけのつもりでしたが、できが良く本格的にE2Eテストへ導入することにしました。

本記事では、変更の経緯や、PuppeteerでE2Eテストを実装する上でのTIPSを紹介します。なお、Capybara + PhantomJSを利用したE2Eテストは、以下の記事でご紹介しております。

techblog.lclco.com

変更の経緯

PhantomJSは古めのWebkitをベースにしているため、一部のCSSがうまく適用されず、Headless Chromeへ移行を以前より考えていました。そんな中、PhantomJSの開発が終了したこともあり、移行することにしました。

jser.info

Puppeteerとは

Puppeteerとは、Headless Chromeを扱うためのAPIが提供されているNodeライブラリです。Chrome DevToolsチームがメンテナンスを行っており、今後のE2Eテスト実装の本命になりそうです。

github.com

Puppeteerの動作環境

本記事の内容は、以下の環境で動作確認をしています。

  • Node v8.11.3
  • Puppeteer v1.5.0

Puppeteerは、Node v6.4.0以上で動きますが、 async/awaitがサポートされているNode v7.6.0以上を使うのが望ましいです(公式ドキュメントのサンプルも、async/awaitが利用されています)

Puppeteerでの実装サンプル

GitHubの公式ドキュメントが充実しており、ここを見るだけで大体のケースは事足りるかと思います。よく使うものだけピックアップして紹介します。

スクリーンショット取得

gotoで該当のページへ遷移し、screenshotでスクショを取得し保存します。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.bushikaku.net');
  await page.screenshot({path: 'tmp/example.png'});
  await browser.close();
})();

f:id:lcl-engineer:20180625153934p:plain

デフォルトでは、スクリーンショットのサイズが、800px x 600pxとなっています。Viewportを指定することで、カスタマイズ可能です。

await page.setViewport({width: 1536,height: 768});

f:id:lcl-engineer:20180625154642p:plain

fullPageをtrueに設定すると、スクロール分含めて全画面が取得できます。

await page.screenshot({ path: 'tmp/example.png', fullPage: true});

また、DOMを指定することで一部の箇所のみのスクリーンショットも取得可能です。

const element = await page.$('.index_search_box');
await element.screenshot({path: 'tmp/exmaple_dom.png'});

f:id:lcl-engineer:20180625155555p:plain

UserAgentの指定

setUserAgentで任意UAを設定可能です。

await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1');

ViewportとUserAgentを同時指定もできます。

const options = {
   viewport: {
     width: 320,
     height: 580,
   },
   userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
};

await page.emulate(options);  

iPhone等のデバイスは、puppeteerで予め定義されており、デバイス名を指定することでエミュレートできます。

const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone 6'];

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.emulate(iPhone);
  // ・・・
})();

事前定義されているデバイスの一覧は、DeviceDescriptors.jsで確認できます。

puppeteer/DeviceDescriptors.js at master · GoogleChrome/puppeteer · GitHub

特定のリクエストのブロック

E2Eテストでは、不要なリクエストをブロックすることで実行時間の短縮が可能です。Puppeteerには、リクエストをインターセプトする仕組みが用意されており柔軟な制御が可能です。

例えば、以下の例では画像リクエストだけブロックしています。

await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
   if(interceptedRequest.resourceType()=='image')
      interceptedRequest.abort();
   else
      interceptedRequest.continue();    
});

urlを利用した制御を行うことで、自サイトのドメインだけ許可することや、特定のドメインだけブロックすることも可能です。

await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
   const url = interceptedRequest.url();
    if(url.indexOf('www.bushikaku.net') > 0 )
      interceptedRequest.continue();
    else
      interceptedRequest.abort();
});

LCLではホワイトリストのドメインを用意し、広告や3rd party JavaScriptのリクエストをブロックし、E2Eテストの実行時間短縮につなげています。

デバッグ

headlessをfalseにすることで、Chromiumが起動して実行の様子が確認できます。slowMoオプションで、実行速度を落とすとより確認がしやすくなります。

const browser = await puppeteer.launch({
    headless: false,
    slowMo: 250
});

f:id:lcl-engineer:20180626181425p:plain

画像の差分比較

画像の差分比較は、pixelmatchというJavaScriptライブラリを利用しています。pixelmatchでは、2つの画像をピクセル単位で比較して差分画像を生成できます。LCLのE2Eテストでは、正となるスクリーンショットと、Puppeteerで取得したスクリーンショットを比較しています。

f:id:lcl-engineer:20180627001645p:plain

GitHub - mapbox/pixelmatch: The smallest, simplest and fastest JavaScript pixel-level image comparison library

まとめ

Puppeteerは、npmで簡単に環境構築ができ、APIや公式ドキュメントが豊富です。恐らく、E2Eテストで実現したいことは、ほぼ実現できるのではないかと感じています。 E2Eテスト以外にも色々な使いみちがありそうなので、今後色々やっていきます。