Node.jsとPlaywrightでFirefoxを自動操縦する

これまで社内向けにPuppeteerにてChromeを自動操縦するアプリケーションを複数作成してきました。その自動化の結果として利用者の作業負担が減るとともに、ミスが減り余計なサイトの操作をする学習コストも減らすことが出来ました。

しかし、一部のPCで原因不明のPage CrashエラーSTATUS_STACK_BUFFER_OVERRUNのエラーが出るケースがあり、それをきっかけとして、Puppeteerと同じ源流を持つMicrosoftのPlaywrightを使ってみる事にしました。今回はファイルのダウンロードをするコードを記述してます。

今回使用するライブラリ等

Playwrightは本来Typescriptで記述するのが定石のようですが、通常のJavaScriptであっても利用は可能です。よって、今回は上記のライブラリだけをインストールして利用します。プロジェクトに対して以下のコマンドでインストールしておきます。

※puppeteer-coreがFirefox操作に対応したのでpuppeteer-firefoxは廃止されました。

また、Puppeteerと一定の互換性があるため、流儀やコードはそのまま利用可能な部分も非常に多いです。Puppeteerについては以下のエントリーを参考にしてみてください。

playwright-firefoxを入れておかないと初回起動時にエラーが出ます。Firefox Nightlyを使いたい場合は、playwright-firefoxもnpmで入れておきましょう(@playwright/testではなくplaywrightの場合、npmでインストール時に3つのコアをダウンロードしてくれますので不要です)

npmでインストールした場合、「C:\Users\ユーザ名\AppData\Local\ms-playwright」にfirefoxはダウンロードされます。

Node.jsとPuppeteerでChromeを自動操縦する

Playwrightについて

概要

PuppetterとPlaywrightはどうやら同じ作者さんが、それぞれGoogleに居たとき、Microsoftに転籍してから作ったプロダクトのようで、非常に親しい存在でありながら、かなり毛色の違う点もあります。

  • Puppeteerと異なり、FirefoxやSafari(Webkit)の操縦もすることが可能
  • 通常のNode.jsだけじゃなく、TypeScriptやPython, Java, .NET等でも利用する事も可能

しかし、親しいだけで完全互換ではありません。特に今回のファイルのダウンロード等に関しては、仕様が大分異なる点もあります。また、ドキュメント量に関しては正直、Playwrightは後発ということもあって、Puppeteerと比較すると少ないのが難点です。

※現在、PuppeteerでもFirefox Nightlyを操作出来ますが、Playwrightの場合Firefox Nightlyの改造版(自動ダウンロードされる)を操作することになります。

図:FireFoxの改造版を動作してる様子

図:Firefoxの別途インストールを要求

Electron等で配布するような場合

現在、exectablePathで指定出来るのは、EdgeとChromiumのみで、FirefoxやSafariはexeを指定しても動作しません。指定しても通常のFireFoxでは以下のようなエラーが出て起動できません。Nightlyをインストールしても同様のエラーで、jugglerという特殊なバージョンでなければ動作しないようです。

よって、Electron等にPlaywrightを組み込んで配布するようなプログラムの場合は、クライアントPCにてnpmでインストール等当然できないので、以下のような措置が必要となります。

  1. C:\Users\ユーザ名\AppData\Local\ms-playwright\firefox-1323\firefox」の場所にFirefox Jugglerがインストールされてる必要性がある
  2. 1.のファイル群は開発マシンの中にだけ存在するので、上記のファイル群をパッケージにして別途配布する。
  3. 1.のケースの場合、exectablePathにてfirefox.exeを指定しても動作します

コードと解説

ソースコード

Puppeteerと大きくことなる点がいくつかあるので、そこを注意しながら記述する必要があります。

解説

どのブラウザを利用するか?を決めるのが1行目。変数名でfirefoxを指定するとFirefoxを利用する事が可能になります。Firefox自体は初回に自動ダウンロードされるため、ユーザが別途インストールする必要はありません(またこのときダウンロードされるのは、Firefox Nightlyになります)

webkitを指定すればSafariを、Chromiumを指定すればChromiumを操作するようになります。

次に、browserの定義でfirefox.launchにオプション指定することが可能です。ダウンロードをさせる場合には以下の2つの指定が必要です。Puppeteerの場合は、cdpsessionにてdownload先の変更をしていましたが、Playwrightの場合はオプション指定で変更が必要になっています。

ダウンロードについても単純にボタンをクリックして終わり・・・ではなく、イベントの定義が必要となっていて、定義をしておき、その結果としてダウンロードされるファイルは通常のファイルではなくテンポラリファイルになっています。よって、fs.renameSyncにてそれを本来のファイル名にリネームする処理を追加しています。パスは変数downloadの中に格納されるので、download.path()でフルパスを取得することが可能となっています。

page.waitForEvent('download')が、ダウンロード待機できるのは、デフォルトでは30秒までなので、操作が多いコードの上部でセットしてしまうと操作中にTimeoutしてしまうので、ダウンロード直前にセットすると良いでしょう。またTimeoutの指定秒数を指定する事も可能です。

ややこしい反面、きちんとダウンロード待機してくれるので、コード自体はPuppeteerよりもスッキリしてると思います。また、公式サイトにはPromiseでダウンロードボタンをクリックして完了するまでを括って、ダウンロード完了待ちの事例が掲載されています。

Puppeteerを使ってボタンクリックとダウンロード

viewportの指定

上記のコードではブラウザの画面サイズ等していせずに起動していますが、予め開くサイズを指定した状態で起動させる事も可能。以下のようにviewportを指定する事が可能です。Puppeteerとは指定方法が異なる点の1つです。

pkgでEXE化できるのか?

Puppeteerの場合、puppeteer-coreを利用していたので、pkgにて単体のEXE化するにあたって以下のコマンドで簡単にパッケージにできました。ブラウザコンポーネントを含んでいないので、node_modulesなどがパッケージにされずといったこともありませんでした。

しかし、今回のplaywrightの場合、バイナリのEXEが含まれている為、@playwright/testの場合はCannot include directory %1のエラーが出て失敗。playwrightの場合はUnexpected characterのエラーが出て失敗。

playwright-coreの場合、インストール済みのブラウザは利用できるものの、firefoxはexectablePathで指定ができない為使えません。

よって現時点ではEXEでパッケージにしてどうこうはできそうにないです。

タイムアウト設定

Puppeteerの場合、page.setDefaultNavigationTimeout(90000);とするだけで、デフォルトのタイムアウト設定を90秒に指定することが可能です。Playwrightの場合も同様の設定が出来るのですが、通常は以下のようにBrowserContextに対してオプション指定するようです。

他にも、browserContext.setDefaultTimeoutなどがありますが、page.setDefaultNavigationTimeout, page.setDefaultTimeout(timeout), browserContext.setDefaultNavigationTimeoutが優先されるようになっているみたい。

他の操縦手段

puppeteer-coreで操縦

前述にもあるように、FireFoxを操縦出来るといっても、現状通常配布されてるFireFoxが動くのではなく、jugglerと呼ばれる特殊なバージョンが必要で、しかもこれがnpmでなければ入手ができない。これでは配布するにはこの版を別途用意して配布するインストーラ等に含めなければならず、実用的ではないです。

調べてみると、playwrightにはpuppeteer-coreのようにplaywright-coreと呼ばれる版も存在し、なおかつpuppeteer-coreでも現在はfirefox Nightlyが操縦できるそうで。puppeteer-coreの場合は、オプションとして以下のように指定すると操縦が可能になる。

  • userDataDirを指定しろということが言われてるのですがこれが、変動するので、今調査中(指定していなくても動く)
  • argsで['-wait-for-browser']を指定しないと起動する前に次の処理に行ってしまうので必ず指定します。
  • 管理者がインストールしてて排除できない拡張機能などがある場合、クラッシュするのでその場合は--disable-extensionsを外しましょう。ただし、skyseaの拡張機能が入ってると必ずクラッシュするようです。
  • puppeteer-coreが新しすぎると最新のNightly Buildでクラッシュするので、現在自分はpuppeteer-core@13.2.0をインストールして社内運用でも問題なく動作しています。
  • Chromeの場合正常に動作する「await page.waitForNavigation({waitUntil:"networkidle2"})」がFirefoxの場合正常に動作しません。まだサポートされていないようです。

FireFox Nightlyはインストーラを実行すると最初に管理者権限を要求されるものの、キャンセルする事でユーザディレクトリにインストールしてくれるので、「C:\Users\ユーザ名\AppData\Local\Firefox Nightly」にインストールされるようになりますので、これをexecutablePathに指定します。管理者権限の場合は普通にc:\program files\以下に入るので、要注意です。

また、productオプションを指定し、firefoxとすることで使い慣れたpuppeteer-coreからfirefoxを操縦が可能になります。

また、Nightlyのインストーラを直リンクでダウンロードさせるならば、このURLから対応のバージョンを探してダウンロードさせることも可能です。Version109aのWindows x64版だとこちらのリンクになります。

図:nightly buildをダウンロード

Firefox Nightlyのプロファイルディレクトリの特定

前述のpuppeteer-coreで操縦する場合、userDataDirにFirefox Nightlyのプロファイルディレクトリの指定をしておいがほうが良いです。しかしこのディレクトリ名は固定ではなく、作成された時にランダムな文字列で生成されるために、またこれをユーザに登録させるというのは賢い手法とは言えないので、プログラムから特定して変数に格納する必要があります。

Node.jsの場合以下のようなコードで特定することが可能です。

図:この値を取得して指定したい

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)