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として公開された。

これを利用するのは非常に簡単だ。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.tsFrameSession#_startScreencast() で実装されている。ページで明示的に内部API CRPage#startScreencast() を呼び出すか、ブラウザコンテキストのオプションにて recordVideo フィールドが指定されていれば、この処理が呼び出しされる。

https://github.com/microsoft/playwright/blob/v1.6.2/src/server/chromium/crPage.ts#L764-L787

CRPage#startScreencast() 周辺の実装を読むと以下のことを行っている。

  1. VideoRecorder.launch を起動し、ファイルへの記録を開始する(後述)
  2. Chrome CDPクライアントにて Page.startScreencast メソッドを呼び出す。jpegで送信するようオプション指定。
  3. CDPの Page.screencastFrame イベントをハンドリングし、到着したフレームをBase64でエンコード
  4. 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 で記述されている。処理の流れとしては以下の通り。

  1. _launch メソッドにてffmpegを起動する。主な引数を -f image2pipe -c:v mjpeg -i - -c:v vp8 hogehoge.webp とし、標準入力にてjpegを待ち受ける。
  2. フレームを受信する毎に writeFrame メソッドが呼び出される
  3. 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が最も秀でているように思える。

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は簡易的な録画機能を提供するなど行っており、両者の実装は徐々に広がっており短期的に両者の互換性は失われていくかもしれないと感じた。