Webエンジニアの森脇です。LCLでは、以前より「Capybara + PhantomJS」でE2Eテストを行っていましたが、「Puppeteer + Headless Chrome」へ変更しました。 元々は、軽くPuppeteerを触ってみるだけのつもりでしたが、できが良く本格的にE2Eテストへ導入することにしました。
本記事では、変更の経緯や、PuppeteerでE2Eテストを実装する上でのTIPSを紹介します。なお、Capybara + PhantomJSを利用したE2Eテストは、以下の記事でご紹介しております。
変更の経緯
PhantomJSは古めのWebkitをベースにしているため、一部のCSSがうまく適用されず、Headless Chromeへ移行を以前より考えていました。そんな中、PhantomJSの開発が終了したこともあり、移行することにしました。
Puppeteerとは
Puppeteerとは、Headless Chromeを扱うためのAPIが提供されているNodeライブラリです。Chrome DevToolsチームがメンテナンスを行っており、今後のE2Eテスト実装の本命になりそうです。
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(); })();
デフォルトでは、スクリーンショットのサイズが、800px x 600pxとなっています。Viewportを指定することで、カスタマイズ可能です。
await page.setViewport({width: 1536,height: 768});
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'});
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 });
画像の差分比較
画像の差分比較は、pixelmatchというJavaScriptライブラリを利用しています。pixelmatchでは、2つの画像をピクセル単位で比較して差分画像を生成できます。LCLのE2Eテストでは、正となるスクリーンショットと、Puppeteerで取得したスクリーンショットを比較しています。
まとめ
Puppeteerは、npmで簡単に環境構築ができ、APIや公式ドキュメントが豊富です。恐らく、E2Eテストで実現したいことは、ほぼ実現できるのではないかと感じています。 E2Eテスト以外にも色々な使いみちがありそうなので、今後色々やっていきます。