Puppeteer/Playwrightでの画面録画・コードリーディング
この記事は kb Advent Calendar 2020 1 日目の記事です。 https://adventar.org/calendars/5280
Chrome 等のブラウザを自動操作するためのライブラリとして、Google が開発する Puppeteer と、Firefox 等もサポートに加えた Microsoft の Playwright などが有る。現時点で機能差は多くないが、Playwright にはブラウザ画面を webm として録画出来る機能が有る。しかし Puppeteer には実装されていない。何故 Puppeteer では実装されないのかを中心に、今回調査を行った。
Playwright での録画
Microsoft のライブラリ実装の Playwright では、録画向けの API が提供されている。v1.4.0 のリリースで公開され、次の v1.5.0 で API が若干変わり stable として公開された。
- https://github.com/microsoft/playwright/releases/tag/v1.4.0
- https://github.com/microsoft/playwright/releases/tag/v1.5.0
これを利用するのは非常に簡単だ。Context を作成する際に recordVideo
フィールドを指定すれば録画が始まり、コンテキストが終了した際に映像が指定したディレクトリに保存される。
import * as playwright from "playwright";
(async () => {
const browser = await playwright.chromium.launch({
headless: false,
});
const context = await browser.newContext({
recordVideo: {
dir: ".",
size: { height: 900, width: 1600 },
},
viewport: {
height: 900,
width: 1600,
},
});
const page = await context.newPage();
await page.goto("https://google.com/");
await page.type('input[aria-label="検索"]', "playwright microsoft", {
delay: 50,
});
await page.waitForTimeout(1000);
await context.close();
await browser.close();
})();
以下はこの機能で録画した映像。
フレームレートも十分で、テスト中で行った操作やコケた箇所を判断するには十分な映像だと考えられる。
Playwright での実装
利用するのが非常に容易な一方で、実装は複雑なものになっている。
録画を行う際の下準備は主に src/server/chromium/crPage.ts
の FrameSession#_startScreencast()
で実装されている。ページで明示的に内部 API CRPage#startScreencast()
を呼び出すか、ブラウザコンテキストのオプションにて recordVideo
フィールドが指定されていれば、この処理が呼び出しされる。
https://github.com/microsoft/playwright/blob/v1.6.2/src/server/chromium/crPage.ts#L764-L787
CRPage#startScreencast()
周辺の実装を読むと以下のことを行っている。
VideoRecorder.launch
を起動し、ファイルへの記録を開始する(後述)- Chrome CDP クライアントにて
Page.startScreencast
メソッドを呼び出す。jpeg で送信するようオプション指定。 - CDP の
Page.screencastFrame
イベントをハンドリングし、到着したフレームを Base64 でエンコード - 1 にて起動したインスタンスへ
videoRecorder.writeFrame(encodedFrameBuffer, captureTimestamp)
として送信
Chrome は Page.startScreencast
メソッドが呼び出されると、フレームごとに jpeg でのキャプチャを行い Page.screencastFrame
イベントをクライアントに送信する。
Chrome の Page.startScreencast
メソッドは現時点で Experimental な API としてドキュメントに記述されている。 https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-startScreencast
startScreencast
内で作成された実際に録画を行っているインスタンスは src/server/chromium/videoRecorder.ts
で記述されている。処理の流れとしては以下の通り。
_launch
メソッドにて ffmpeg を起動する。主な引数を-f image2pipe -c:v mjpeg -i - -c:v vp8 hogehoge.webp
とし、標準入力にて jpeg を待ち受ける。- フレームを受信する毎に
writeFrame
メソッドが呼び出される writeFrame
内では目標 FPS に達さない場合、到着時間を元にフレームを複数回 ffpmeg に標準入力を通じて送信する。Chrome が実際に出力するフレーム 10fps 程度で変動する一方で、ffmpeg での記録は 25fps で行うため。
https://github.com/microsoft/playwright/blob/v1.6.2/src/server/chromium/videoRecorder.ts#L88-L105
Playwright は実装が Client,Server で分かれており、Server が実際に Chrome CDP 等を操作してブラウザの操作を行う。通常の利用では Client,Server は同一のプロセスで処理され 2 つの実装はコード上の物だけとなるが、ユーザが明示的に指定することで WebSocket で通信する 2 つのプロセスとして実行することが出来る。
Client,Server が異なるプロセスで実行されている場合、動画はユーザが指定したオプション recordVideo.dir
ディレクトリには記録されず /tmp
に記録され、コンテキストを終了した際に Server から Client に動画ファイルが WebSocket ストリームを通じて転送される。
https://github.com/microsoft/playwright/blob/v1.6.2/src/browserServerImpl.ts#L179-L192
Puppeteer での録画
Playwright での実装をまとめると、Chrome の API Page.startScreencast
を利用することでフレームの画像を受信し、手元の ffmpeg にて webp 動画を作成している。これなら Puppeteer でも実装できそうだが、現在のところ実装されていない。その Issue はこれ。 https://github.com/puppeteer/puppeteer/issues/478
過去に Puppeteer でも Page.startScreencast
を利用した実装が検討された。 https://github.com/puppeteer/puppeteer/pull/881 2017 年のこの PR にて現在の Playwright と同様の機能が実装されたが、一度 jpeg にエンコードする無駄で複雑なプロセスはパフォーマンス面・動画品質の面で良くないと判断された。そして、Chronium 側にビデオストリームを録画する機能が実装されるのを待とうという選択がなされた。 https://bugs.chromium.org/p/chromium/issues/detail?id=781117
現状 Puppeteer で録画を行うための代替えの方法としては以下が挙げられている。この中では Puppeteer での e2e テスト時に動画を撮影するという用途では puppetcam が最も秀でているように思える。
- https://github.com/clipisode/puppeteer-recorder ページの初期読み込みを動画にするスクリプト。スクリーンショットを撮影して ffmpeg でエンコード。
- https://github.com/tungs/timesnap setTimeout 等の時間に関連するオブジェクトを全て上書きし、1 フレームずつアニメーションを動かした上でスクリーンショットを撮影、PNG 画像を出力し ffmpeg で合成
- https://github.com/muralikg/puppetcam 録画を行う拡張機能を利用する
puppetcam の実装
https://github.com/muralikg/puppetcam
puppetcam では、Chrome で実行する拡張機能の background.js
content_script.js
が実装の本体と言える。前者で録画を行い、後者で Puppeteer ユーザとの対話を行っている。
- Chrome の拡張機能内
background.js
にてchrome.desktopCapture
を行うことでコンテンツの録画を開始する content_script.js
内でwindow.postMessage
を用いたメッセージの橋渡しを行い、Puppeteer と拡張機能間の通信を行う- 記録が終了されれば objectURL が発行され、ダウンロードが開始する
- xvfb を利用することで Headless の際にも正常に画面が描画される
利用している API が Chrome の一般的な拡張機能で利用されているものを利用しており、今後の変更される可能性が低いよう感じる。また、他の実装と異なり画像へのエンコードを行わないのでリソースの利用効率も非常に良い実装である。しかし、ライブラリとして隠蔽されているとは言えず、利用者には一定の負担が有るだろう。
まとめ
- Microsoft が開発する Playwright での録画はブラウザでの自動テストを記録するために非常に手軽に利用できる
- Google が開発する Puppeteer では録画機能が実装されていない
- Puppeteer で録画を行うのであれば、コミュニティライブラリや自前の実装が必要となり一定の苦痛が有るだろう
Puppeteer は独自のセレクタの導入・Playwright は簡易的な録画機能を提供するなど行っており、両者の実装は徐々に広がっており短期的に両者の互換性は失われていくかもしれないと感じた。