Selenium で Mozilla ブラウザ拡張の E2E テストを書く
Web ブラウザをリモート操作する WebDriver の一つである "GeckoDriver" (通称 "FirefoxDriver") は、リモート操作のセッションで ".xpi" Mozillaブラウザ拡張ファイルをインストールする機能を提供している。
この機能は、手元で開発中の Mozilla ブラウザ拡張に対し、End-to-End テストを実施するのに便利である。
しかし残念ながら、自分が実際に ".xpi" ブラウザ拡張をインストールする Selenium GeckoDriver を使った End-to-End テストを実装したところ、ちゃんと動作するようになるまでに、いくつかの落とし穴に遭遇する羽目になってしまった。そこで今回は、Selenium GeckoDriver を使って ".xpi" ファイルをインストールするベストな方法を共有したいと思う。
なお、自分は C# (.NET) で Seleium を常用しているため、以下では Selenium の C# バインディングに基づいて説明する。
"FirefoxProfile.AddExtension" を使ってはいけない
Visual Studio などの IDE 上で開発していると、そのインテリセンスなどで、 "FirefoxProfile" クラスに "AddExtension()" というメソッドがあることに気づくかも知れない。見た感じ、このメソッド名は、拡張機能をインストールするのに最適な機能であるように感じる。
しかしそのメソッド名の見た目に反して、 "FirefoxProfile.AddExtension()" メソッドは正しく動作しなかった。なぜこのメソッドが自分が期待したようには動作しないのか (仕様なのか使用方法が間違っているのか) はとくに深追いしていないためわかっていないが、とにかく、このメソッドで拡張機能をインストールできたことは一度もない。
// ⚠️ これは NG! 😱
var profile = new FirefoxProfile();
profile.AddExtension(pathToXpiFile);
"FirefoxDriver.InstallAddOnFromFile" を使用する
代わりに、"FirefoxDriver" の "InstallAddOnFromFile() "メソッドを使うとよい。このメソッドの第一引数には、".xpi "ファイルへのフルパスを指定する。FirefoxDriver.InstallAddOnFromFile" メソッドは期待どおり正常に動作し、ブラウザ拡張がインストールされた。特に難しいことは何もなかった。
// 👍 これなら OK! 😊
using var driver = new FirefoxDriver();
driver.InstallAddOnFromFile(pathToXpiFile);
なお、"FirefoxDriver" クラスは "InstallAddOn()" メソッドも公開しており、こちらはブラウザ拡張機能のバイナリコンテンツを base64 エンコードされた文字列として指定することができる。
"FirefoxProfile.SetPreference" を使ってはいけない
ブラウザ拡張機能の End-to-End テストにおいては、ストアの審査に提出する前の開発段階であるからして、 ".xpi" ブラウザ拡張ファイルには署名が施されていないのが通常だ。しかし、Web ブラウザは通常、セキュリティ上の問題から、署名されていないブラウザ拡張ファイルのインストールを拒否するのが既定の設定である。そこで、End-to-End テスト時には、 Web ブラウザのこの制限・制約を一時無効にする必要がでてくる。
ということで、署名なし拡張のインストール方法をインターネット検索してみると、その検索結果に、環境設定 "xpinstall.signatures.required" を false に設定する方法を見つけることができる。
しかし、"FirefoxProfile" クラスの "SetPreference" メソッドを使用する以下のコードは、決してうまくいかなかった。具体的には、下記コードを実行すると、
// ⚠️ これは NG! 😱
var profile = new FirefoxProfile();
profile.AddExtension(pathToXpiFile);
以下のような例外が発生してしまうのだ。
System.ArgumentException: Preference xpinstall.signatures.required may not be overridden:
frozen value=False, requested value=False"
"FirefoxOptions.SetPreference" を使用する
ではどうするかというと、 "FirefoxProfile" クラスではなく、 "FirefoxOptions" クラスの "SetPreference "メソッドを使用する。この方法なら問題なく動作する。
// 👍 これは OK! 😊
var option = new FirefoxOptions();
option.SetPreference("xpinstall.signatures.required", false);
using var driver = new FirefoxDriver(option);
通常版 Firefox を使ってはいけない
ここまで済ませてもなお、以下の実行時エラーに遭遇してしまった。
Could not install add-on: ....xpi: ERROR_SIGNEDSTATE_REQUIRED:
The addon must be signed and isn't.
インターネット検索でいろいろ調べた結果、どうやら環境設定を如何にしようとも、通常版の Firefox ブラウザでは、署名されていない ".xpi" ブラウザ拡張ファイルのインストールは、絶対に許可されないように実装されているようである。
Firefox の Developer または Enterprise エディションを使用する
この問題ついては PC に Firefox Web ブラウザーの Developer Edition または Enterprise Edition をインストールし、通常版ではなくそれら Edition 上で実行することで解決される。Firefox Developer Edition の詳細については、以下のリンクを参照。
まとめ
Selenium WebDriver を使って ".xpi" ブラウザ拡張ファイルをインストールするために、なぜこんなに落とし穴があるのかよくわかってはいない。何か歴史的事情なのか、あるいは自分が使い方をよくわかっていないだけなのか。何はともあれ、この記事が他の開発者の時間を節約する助けになれば幸いである。
この記事のサンプルコード全体は、以下の GitHub リポジトリで見ることができる。
Web ブラウザをリモート操作する WebDriver の一つである "GeckoDriver" (通称 "FirefoxDriver") は、リモート操作のセッションで ".xpi" Mozillaブラウザ拡張ファイルをインストールする機能を提供している。
この機能は、手元で開発中の Mozilla ブラウザ拡張に対し、End-to-End テストを実施するのに便利である。
しかし残念ながら、自分が実際に ".xpi" ブラウザ拡張をインストールする Selenium GeckoDriver を使った End-to-End テストを実装したところ、ちゃんと動作するようになるまでに、いくつかの落とし穴に遭遇する羽目になってしまった。そこで今回は、Selenium GeckoDriver を使って ".xpi" ファイルをインストールするベストな方法を共有したいと思う。
なお、自分は C# (.NET) で Seleium を常用しているため、以下では Selenium の C# バインディングに基づいて説明する。
"FirefoxProfile.AddExtension" を使ってはいけない
Visual Studio などの IDE 上で開発していると、そのインテリセンスなどで、 "FirefoxProfile" クラスに "AddExtension()" というメソッドがあることに気づくかも知れない。見た感じ、このメソッド名は、拡張機能をインストールするのに最適な機能であるように感じる。
しかしそのメソッド名の見た目に反して、 "FirefoxProfile.AddExtension()" メソッドは正しく動作しなかった。なぜこのメソッドが自分が期待したようには動作しないのか (仕様なのか使用方法が間違っているのか) はとくに深追いしていないためわかっていないが、とにかく、このメソッドで拡張機能をインストールできたことは一度もない。
// ⚠️ これは NG! 😱
var profile = new FirefoxProfile();
profile.AddExtension(pathToXpiFile);
"FirefoxDriver.InstallAddOnFromFile" を使用する
代わりに、"FirefoxDriver" の "InstallAddOnFromFile() "メソッドを使うとよい。このメソッドの第一引数には、".xpi "ファイルへのフルパスを指定する。FirefoxDriver.InstallAddOnFromFile" メソッドは期待どおり正常に動作し、ブラウザ拡張がインストールされた。特に難しいことは何もなかった。
// 👍 これなら OK! 😊
using var driver = new FirefoxDriver();
driver.InstallAddOnFromFile(pathToXpiFile);
なお、"FirefoxDriver" クラスは "InstallAddOn()" メソッドも公開しており、こちらはブラウザ拡張機能のバイナリコンテンツを base64 エンコードされた文字列として指定することができる。
"FirefoxProfile.SetPreference" を使ってはいけない
ブラウザ拡張機能の End-to-End テストにおいては、ストアの審査に提出する前の開発段階であるからして、 ".xpi" ブラウザ拡張ファイルには署名が施されていないのが通常だ。しかし、Web ブラウザは通常、セキュリティ上の問題から、署名されていないブラウザ拡張ファイルのインストールを拒否するのが既定の設定である。そこで、End-to-End テスト時には、 Web ブラウザのこの制限・制約を一時無効にする必要がでてくる。
ということで、署名なし拡張のインストール方法をインターネット検索してみると、その検索結果に、環境設定 "xpinstall.signatures.required" を false に設定する方法を見つけることができる。
しかし、"FirefoxProfile" クラスの "SetPreference" メソッドを使用する以下のコードは、決してうまくいかなかった。具体的には、下記コードを実行すると、
// ⚠️ これは NG! 😱
var profile = new FirefoxProfile();
profile.AddExtension(pathToXpiFile);
以下のような例外が発生してしまうのだ。
System.ArgumentException: Preference xpinstall.signatures.required may not be overridden:
frozen value=False, requested value=False"
"FirefoxOptions.SetPreference" を使用する
ではどうするかというと、 "FirefoxProfile" クラスではなく、 "FirefoxOptions" クラスの "SetPreference "メソッドを使用する。この方法なら問題なく動作する。
// 👍 これは OK! 😊
var option = new FirefoxOptions();
option.SetPreference("xpinstall.signatures.required", false);
using var driver = new FirefoxDriver(option);
通常版 Firefox を使ってはいけない
ここまで済ませてもなお、以下の実行時エラーに遭遇してしまった。
Could not install add-on: ....xpi: ERROR_SIGNEDSTATE_REQUIRED:
The addon must be signed and isn't.
インターネット検索でいろいろ調べた結果、どうやら環境設定を如何にしようとも、通常版の Firefox ブラウザでは、署名されていない ".xpi" ブラウザ拡張ファイルのインストールは、絶対に許可されないように実装されているようである。
Firefox の Developer または Enterprise エディションを使用する
この問題ついては PC に Firefox Web ブラウザーの Developer Edition または Enterprise Edition をインストールし、通常版ではなくそれら Edition 上で実行することで解決される。Firefox Developer Edition の詳細については、以下のリンクを参照。
まとめ
Selenium WebDriver を使って ".xpi" ブラウザ拡張ファイルをインストールするために、なぜこんなに落とし穴があるのかよくわかってはいない。何か歴史的事情なのか、あるいは自分が使い方をよくわかっていないだけなのか。何はともあれ、この記事が他の開発者の時間を節約する助けになれば幸いである。
この記事のサンプルコード全体は、以下の GitHub リポジトリで見ることができる。