Chrome 拡張機能: Service Worker の停止をテストする方法

目的をたずねられた場合

Manifest V2 から Manifest V3 への移行には、根本的な変更が必要です。Manifest V2 では、拡張機能はバックグラウンド ページに配置されていました。バックグラウンド ページでは、拡張機能とウェブページ間の通信を管理していました。Manifest V3 では代わりに Service Worker を使用します。

この投稿では、拡張機能 Service Worker のテスト問題を掘り下げます。特に、Service Worker が停止された場合に、プロダクトが正常に機能するようにする方法を確認します。

Google とは

eyeo は、ユーザー、ブラウザ、広告主、パブリッシャーの皆様のためにバランスの取れた持続可能なオンライン価値交換を推進する企業です。Google は世界で 3 億人を超える広告フィルタ ユーザーに Acceptable Ads を許可しています。Acceptable Ads とは、広告が許容されるものであるか、煩わしくないかを判別する、独自に策定された広告基準です。

当社の Extension Engine チームは、世界で 1 億 1, 000 万人以上のユーザーが利用する AdBlock や Adblock Plus など、市場で最も人気のある広告ブロック ブラウザ拡張機能の一部を支えている広告フィルタ技術を提供しています。また、この技術はオープンソース ライブラリとして提供され、他の広告フィルタ ブラウザ拡張機能でも利用できます。

Service Worker とは

拡張機能 Service Worker は、ブラウザ拡張機能の中心的なイベント ハンドラです。これらはバックグラウンドで独立して実行されます。大まかには問題ありません。必要な処理のほとんどは、新しい Service Worker のバックグラウンド ページで行えます。ただし、バックグラウンド ページと比較すると、次のような違いがあります。

  • Service Worker は未使用時に終了します。そのためには、グローバル変数に依存するのではなく、アプリケーションの状態を保持する必要があります。つまり、システムへのすべてのエントリ ポイントは、システムの初期化前に呼び出す準備ができている必要があります。
  • イベント リスナーは、非同期コールバックを待つ前にアタッチする必要があります。停止中の Service Worker も、登録しているイベントを引き続き受け取ることができます。イベントループの最初のターンでイベントのリスナーが登録されていない場合、そのイベントによって Service Worker が起動されると、イベントは受信されません。
  • アイドル状態の終了により、完了前にタイマーが中断される可能性があります

Service Worker が停止されるのはどのような場合ですか。

Chrome 119 では、Service Worker が停止されています。

  • 30 秒間、イベントを受信していないか、拡張機能 API を呼び出していない場合。
  • デベロッパー ツールを開いている場合や、ChromeDriver ベースのテスト ライブラリを使用している場合は絶対に必要です(機能リクエストを参照)。
  • chrome://serviceworker-internals で [停止] をクリックした場合。

最新情報については、Service Worker のライフサイクルをご覧ください。

テストが問題となる理由

「Service Worker を効率的にテストする方法」に関する公式のガイダンスや、実際のテストの例があれば、理想的です。Service Worker のテストに取り組む中で、私たちはいくつかの課題に直面しました。

  • テスト拡張機能には状態があります。Service Worker が停止すると、その状態と登録済みイベントが失われます。テストフローでデータをどのように保持するか?
  • Service Worker が一時停止できる場合は、すべての機能が中断しても動作するかどうかをテストする必要があります。
  • Service Worker をランダムに一時停止するメカニズムをテストに導入しても、ブラウザには簡単に一時停止できる API がありません。W3C チームにこの機能の追加を依頼しましたが、現在も協議中です。

Service Worker の停止のテスト

テスト中に Service Worker の停止をトリガーするには、いくつかの方法を試しました。

アプローチ アプローチの問題
任意の時間(30 秒など)待ちます。 そのため、特に複数のテストを実行する場合に、テストが遅くなり、信頼性が低下します。WebDriver では機能しません。WebDriver は Chrome の DevTools API を使用しますが、DevTools が開いているときは Service Worker は一時停止されません。バイパスできたとしても、Service Worker が一時停止されたかどうかを確認する必要があり、そのための方法はありません。
Service Worker で無限ループを実行する 仕様では、ブラウザがこの機能を実装する方法によっては、終了することになる場合があります。この場合、Chrome は Service Worker を終了しないため、Service Worker が一時停止された場合のシナリオをテストできません。
一時停止されたかどうかを確認するメッセージが Service Worker にある メッセージを送信すると Service Worker が起動します。これを使用すると、Service Worker がスリープ状態だったかどうかを確認できますが、Service Worker を一時停止した直後に確認を行う必要があるテストの結果が破損します。
chrome.processes.terminate() を使用して Service Worker プロセスを強制終了する 拡張機能の Service Worker は拡張機能の他の部分とプロセスを共有するため、chrome.process.terminate() または Chrome のプロセス マネージャー GUI を使用してこのプロセスを強制終了すると、Service Worker だけでなく拡張機能ページも強制終了されます。

最終的に、Selenium WebDriver で chrome://serviceworker-internals/ を開き、Service Worker の「停止」ボタンをクリックすることで、Service Worker が停止された場合にコードがどう応答するかを確認するテストが完了しました。

これはこれまでのところ最適なオプションですが、Mocha テスト(拡張ページで実行される) は自身で行うことができないため、WebDriver ノード プログラムと通信する必要があるため、理想的ではありません。つまり、これらのテストは拡張機能だけでは実行できず、Selenium WebDriver を使用してトリガーする必要があります。

次の図は、さまざまなフローでブラウザ API と通信する仕組みと、「Service Worker の停止」メカニズムの追加がそれに及ぼす影響を示しています。

テストフローを示す図
Service Worker が一時停止されたフローをテストしています。

Service Worker を一時停止する新しいフロー(青色)では、Selenium WebDriver を追加して UI から一時停止を「クリック」し、ブラウザ API のアクションをトリガーします。

なお、Selenium WebDriver でこれを行うと、Service Worker を再開できなくなる Chrome のバグがありました。この問題は Chrome 116 で修正されましたが、回避策があります。すべてのタブで DevTools を自動的に開くように Chrome を設定すると、Service Worker が正しく起動します。

ボタンをクリックする API は安定しておらず、(古いブラウザの場合は)DevTools を開くとパフォーマンスが低下する可能性があるため、理想的ではないものの、テスト時にこの方法を採用しています。

すべての機能を網羅するにはどうすればよいでしょうか?ファズテスト

一時停止をテストするメカニズムが完成したら、それを自動テストスイートに組み込む方法を決定する必要がありました。標準的なテストは、バックグラウンド ページを操作する前に、chrome://serviceworker-internals/ のページで [停止] をクリックすることで WebDriver が Service Worker を停止する環境で実行しました。

ファズテストの実行例
テストの現在の設定を示す画像

サスペンド メカニズムが完全に安定していないため、すべてのテストを実行するわけではありません。また、不安定な状態を引き起こすこともあります。また、すべてのテストスイートをファズモードで実行するには時間がかかります。そのため、ファズモードでのテストでは、「類似の」ケースをすべてカバーするのではなく、最も重要なパスを選択しました。なお、機能テストを「ファズ」モードで実行すると、Service Worker の一時停止と再起動に時間がかかるため、テストのタイムアウトを増やす必要がありました。

これらのテストは、コードが失敗する多くの箇所を明らかにする大まかな最初のパスとして役立ちますが、Service Worker の停止がクラッシュを引き起こす可能性のある微妙な箇所をすべて発見できるとは限りません。

Google では、この種のテストを「ファズテスト」と呼んでいます。従来のファズテストでは、プログラムに対して無効な入力をスローし、妥当な応答があるか、少なくともクラッシュしないことを確認していました。この場合の「無効な入力」とは、Service Worker はいつでも停止されることです。また、広告フィルタリング機能を以前と同様に動作させなければならないという「合理的な動作」が期待されます。これは Manifest V3 で想定される動作であるため、実際には無効な入力ではありませんが、Manifest V2 では無効な入力であるため、妥当な用語のように感じられます。

まとめ

Service Worker は、(declarativeNetRequest ルールの他に)Manifest V3 で最大の変更点の一つです。Manifest V3 に移行するには、ブラウザ拡張機能のコード変更や、新しいテスト方法の変更が必要になる場合があります。また、永続状態を使用する拡張機能のデベロッパーは、Service Worker の予期しない停止を適切に処理できるように拡張機能を準備する必要があります。

残念ながら、私たちのユースケースに適した簡単な方法で停止を処理するための API はありません。サスペンション メカニズムに対する拡張機能のコードベースの堅牢性を初期段階でテストしたいと考えていたため、これを回避する必要がありました。同様の課題に直面している他の拡張機能のデベロッパーもこの回避策を使用できます。開発とメンテナンスのフェーズには時間がかかりますが、Service Worker が定期的に停止される環境で拡張機能を正常に動作させることができます。

すでに Service Worker の停止のテストに関する基本的なサポートはありますが、将来的には拡張機能内からの Service Worker のテストに対するプラットフォーム サポートの改善を期待しています。テストの実行時間とメンテナンスの労力を大幅に削減できる可能性があるためです。