--headless時代の本命? Chrome を Node.jsから操作するライブラリ puppeteer について
puppeteer はHeadless Chrome をNode.jsで操作しやすくしたライブラリです。今日(※ 2017/8/17)一日で凄い勢いでGitHubのトレンド入りしており、TLでも話題になっていたので、早速触ってみました。
Node.jsでChromeを操作するというコンテキストにおいては、Nightmare.jsと同じレイヤに属するプロダクトですね。Nightmare.jsはElectronを介在させることで、Chromeの操作を実現していましたが、今年の5月にChromeでheadlessモードが利用可能になって以降1、headless Chromeを直接操作するライブラリが色々と出始めていますね。この系統は、chromyや、やはり先日GitHubでトレンド入りしていたchromelessなどが挙げられます。
puppeteerがこれらのライブラリと一線を画すのは、なんと言っても本家ChromeのDevTool開発チームが作成・メンテナンスしている、という点でしょう。
仕組み
puppeteerは、WebdriverIOのようなクロスブラウザ対応のツールとは異なり、Chrome DevTools Protocol2 を利用してNode.jsからChromeの開発者ツールへ接続して操作を行います。
触ってみる
この手のツールの定番、スクリーンショットの取得をやってみます。
まずはインストール。
npm i puppeteer
postinstallフックにより、chromiumもインストールされます。インストールされるchromiumのバージョンはpuppeteerのpackage.jsonで一意に定まるようになっているため、npmやyarnのロックファイルと併用することで、利用するchromiumのバージョンをコントロール可能です。
続いてブラウザを操作するスクリプトを書いていきます。
const fs = require('fs');
const assert = require('assert');
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://github.com/Quramy');
await page.screenshot({path: 'example.png'});
browser.close();
assert(fs.existsSync('example.png'));
console.log(' 🎉 ');
})();
特に説明不要ですね。GitHubのプロフィールページを表示してキャプチャを撮るスクリプトです。
下記のコマンドで動作させ、example.pngに画像が出力されていれば成功です。
node script.js
CIでの動作
実際のところ、ローカルでキャプチャを取得しても面白みもへったくれもありません。やはりCIで動かしてこそでしょう。
TravisCI
特に難しい点はありません。通常のNode.jsプロジェクトと同様にymlを書くだけで簡単に動作します。
os:
- linux
language: node_js
node_js:
- '8'
script:
- node script.js
WebDriverやNightmare.jsの場合は仮想フレームバッファの設定など、ブラウザをCIで動作させるための特殊な設定が必要なのですが、それすらも不要なのは気分がいいですね。
WerckerCI
折角なので、DockerベースのCIでも試してみました。普段から使っているという理由でOracle WerckerCIを選択。
Node.js公式のDocker imageを利用したところ、
(node:227) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to connect to chrome!
(node:227) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
npm info lifecycle [email protected]~posttest: [email protected]
というエラーが表示されてしまっため、多少のworkaroundを施しています3。どうやら、Debian系のOSで動作させる場合には、追加でlibxcb関連をインストールした上で、SUID Sandboxを無効化4する必要があるらしいです。
box: node:8
build:
steps:
- script:
name: Workaround for GoogleChrome/puppeteer#290
code: sh ./fix_290.sh
- npm-install
- npm-test
#!/bin/bash
apt-get update
apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
// argsを明示
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
とすることで、動作しました。Travisに比べると少しややこしいですね...。
真面目にやるのであれば、上記の依存関係を焼き付けたimageを作ってしまった方が良さそうな気もします5。
Werckerだけでなく、CircleCI 2.xのようなDocker baseのCIでpuppeteerを動作させる際は同様の考慮が必要になりそうなので、何かの助けになれば幸いです。
なお、動作検証に利用したレポジトリは https://github.com/Quramy/puppeteer-example です。参考まで。
おわりに
API一覧をざっと眺めてみると、Nightmare.jsで実現できていたことはほぼ全てpuppeteerでも実現できそうな印象です。
スクリプトを送り込むinjectFileや、ブラウザ上でscriptを評価するevaluate、そして、Node.js上で定義した関数をブラウザ上に公開する exposeFunctionあたりを駆使すればほぼ何でも出来るんじゃないかこれは、という予感です。
飽くまでChrome専用の操作には限定されますが、e2eテスト環境であったり、お手軽なDOM環境が欲しいときには重宝しそうです。
僕のチームでは、開発しているWebアプリケーションについては、ブラウザで動作させて画像のキャプチャを取得し、前回との差分を検知することで回帰テストを回しており、これに特化した reg-suit というツールを作成したりしています。
このテストフローについては、別途まとめた記事にしたいと思っていますが、最終的には「如何にCIでキャプチャ画像を取得するか」という点に難しさが収束していく感があり、puppeteerのようなブラウザ操作ツールを上手く活用できる色々と捗りそう。
それでは、また。
-
v59 より. https://developers.google.com/web/updates/2017/05/nic59 ↩
-
https://github.com/GoogleChrome/puppeteer/issues/290#issuecomment-322921352 ↩
-
https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md ↩
-
Node.js 8ベースで作成して、https://hub.docker.com/r/regviz/node-xcb/ に公開しています ↩