geckodriver(SeleniumのWebDriver)をMavenコマンド使ってパス上に配置する方法

Selenium 3より、`WebDriver`経由でFirefoxを操作する際に[geckodriver](https://github.com/mozilla/geckodriver)が必要になったらしく、geckodriverの実行ファイルをコマンドパス上(`PATH`環境変数に設定されているディレクトリ上 or Selenium実行時の作業ディレクトリ上)に事前に配置しておく必要があるようです。Selenium 2ではFirefoxを使う場合は追加で実行ファイルは必要なかったので、Selenium 2から3に移行する時に最初にぶちあたる可能性がある問題です。

で、今回は・・・
geckodriverを手動でコマンドパス上に配置する（GitHubのリリースページからアーカイブファイルのダウンロード＋アーカイブファイルの解凍＋実行ファイルの配布を行う）のではなく、Mavenコマンド([Maven Download Plugin](https://github.com/maven-download-plugin/maven-download-plugin))を使ってgeckodriverのインストール作業を自動化する方法を紹介します。


# 検証バージョン

* Selenide 4.3
* Selenium 3.0.1 (Selenide 4.3の依存バージョン)
* geckodriver 0.14.0
* Firefox 52.0.1 (64 ビット)
* Maven Download Plugin 1.3.0
* IntelliJ IDEA 2016.3.5


# 実践アプリ

本エントリーで記載した内容は、以下のアプリケーションで実践しています。以降の説明も、このアプリケーションをベースに説明していきます。

* https://github.com/kazuki43zoo/mybatis-spring-boot-jpetstore/tree/maven-download-plugin

> **2017/3/28 追記：**
>
> geckodriverのインストール作業を自動化してくれる[WebDriverManager](https://github.com/bonigarcia/webdrivermanager)というライブラリがあったので、`maven-download-plugin`というブランチで管理するよう変更しました。
>
> **2017/4/1 追記:**
>
> 「[WebDriverManagerを使用してgeckodriver(SeleniumのWebDriver)を自動インストールする方法](http://qiita.com/kazuki43zoo/items/71f4c09e644b45a4002c)」を書きました。

<p>

> Note:
>
> 原因はわかっていませんが、たまにテストが失敗することがあります・・・（時間がある時に調査してみます） :sweat_smile:
>


# geckodriverをインストールしないでテストを実行してみると・・・

Maven Download Pluginを使用してgeckodriverのインストール作業を自動化する方法を紹介する前に、geckodriverをコマンドパス上に配置しないでIDE上でSelenium(`JpetstoreApplicationTests`)を実行すると、どのようなエラーになるのか見ておきましょう。

## デフォルト状態の`FirefoxWebDriver`使用時 

デフォルト状態の`FirefoxWebDriver`(`com.codeborne.selenide.Configuration#browser` = `foirefox`)を使用すると、以下のようなエラーができます。

```
...
2017-03-26 18:45:30.634  INFO 45389 --- [           main] c.c.selenide.webdriver.WebDriverFactory  : Firefox 48+ is currently not supported by Selenium Firefox driver. Use browser=marionette with geckodriver, when using it.
org.openqa.selenium.firefox.NotConnectedException: Unable to connect to host 127.0.0.1 on port 7055 after 45000 ms. Firefox console output:
531710	addons.xpi	DEBUG	getModTime: Recursive scan of {972ce4c6-7e08-4474-a285-3208198ce6fd}
1490521531711	DeferredSave.extensions.json	DEBUG	Save changes
...
```

直接的なエラーはFirefoxを操作するドライバとの接続タイプアウトのようですが、根本原因は、「Firefox 48+ is currently not supported by Selenium Firefox driver. Use browser=marionette with geckodriver, when using it.」となっているので、Firefox 48以上を使う場合は`FirefoxWebDriver`をMarionetteモードに変更した上でgeckodriverも必要だよ！ということみたいです。

## Marionetteモードの`FirefoxWebDriver`使用時

で、Marionetteモードの`FirefoxWebDriver`(`com.codeborne.selenide.Configuration#browser` = `marionette`)を使用してみると、以下のようなエラーができます。

```
...
java.lang.IllegalStateException: The path to the driver executable must be set by the webdriver.gecko.driver system property; for more information, see https://github.com/mozilla/geckodriver. The latest version can be downloaded from https://github.com/mozilla/geckodriver/releases

	at com.google.common.base.Preconditions.checkState(Preconditions.java:469)
	at org.openqa.selenium.remote.service.DriverService.findExecutable(DriverService.java:109)
...
```

エラーメッセージをみると、コマンドパス上にgeckodriverの実行ファイルがみつからないから、システムプロパティ「`webdriver.gecko.driver`」に実行ファイルのパスを指定して〜ということみたいです。

## geckodriverの実行ファイルをコマンドパス上に配置してテストを実行する

エラーメッセージにはシステムプロパティに実行ファイルのパスを設定する旨が促されていますが、コマンドパス上(`PATH`環境変数に設定されているディレクトリ上 or Selenium実行時の作業ディレクトリ上)にgeckodriverを配置することで解決することができます。ここでは、[geckodriverのGitHubのリリースページ](https://github.com/mozilla/geckodriver/releases)からダウンロードしたファイルを、Selenium実行時の作業ディレクトリ上に配置してみます。

* [GitHubのリリースサイト](https://github.com/mozilla/geckodriver/releases/tag/v0.14.0)からアーカイブファイルをダウンロードする
* ダウンロードしたアーカイブファイルを解凍する。
* 解凍後の実行ファイル(`geckodriver` or `geckodriver.exe`)をSelenium実行時の作業ディレクトリ上に配置する。

```
$ tree
.
├── geckodriver // Mavenプロジェクトのルートディレクトリ上に配置
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
...
```

geckodriverの実行ファイルをコマンドパス上に配置した後にSeleniumを実行すると、テストが成功することが確認できると思います。

> **Note:**
>
> なお、本エントリーの検証バージョンであるSelenium 3.0を使用する場合は、geckodriverは0.14.0を使う必要があります。投稿時点でのgeckodriverの最新バージョンは0.15.0ですが、リリースノートに記載があるようにSelenium 3.3が必要になります。


# Maven Download Pluginのセットアップ

前置きが少し長くなりましたが・・・ここからは、Maven Download Pluginを使用してgeckodriverのインストール作業を自動化する方法を紹介していきます。
まず、`pom.xml`にMaven Download Pluginの設定を追加しましょう。

```xml:pom.xml
<build>
	<plugins>
		<!-- ... -->
		<!-- (1) -->
		<plugin>
			<groupId>com.googlecode.maven-download-plugin</groupId>
			<artifactId>download-maven-plugin</artifactId>
			<version>1.3.0</version>
			<executions>
				<execution>
					<id>install-geckodriver</id>
					<phase>initialize</phase>
					<goals>
						<goal>wget</goal>
					</goals>
					<configuration>
						<url>https://github.com/mozilla/geckodriver/releases/download/v${geckodriver.version}/geckodriver-v${geckodriver.version}-${geckodriver.os}.${geckodriver.archive.extenstion}</url>
						<unpack>true</unpack>
						<failOnError>false</failOnError>
						<outputDirectory>${project.basedir}</outputDirectory>
					</configuration>
				</execution>
			</executions>
		</plugin>
		<!-- ... -->
	</plugins>
</build>

<!-- ... -->

<properties>
	<!-- ... -->
	<geckodriver.version>0.14.0</geckodriver.version> <!-- (2) -->
</properties>

<!-- ... -->

<!-- (3) -->
<profiles>
	<profile>
		<id>linux</id>
		<activation>
			<os>
				<family>unix</family>
			</os>
		</activation>
		<properties>
			<geckodriver.os>linux64</geckodriver.os>
			<geckodriver.archive.extenstion>tar.gz</geckodriver.archive.extenstion>
		</properties>
	</profile>
	<profile>
		<id>mac</id>
		<activation>
			<os>
				<family>mac</family>
			</os>
		</activation>
		<properties>
			<geckodriver.os>macos</geckodriver.os>
			<geckodriver.archive.extenstion>tar.gz</geckodriver.archive.extenstion>
		</properties>
	</profile>
	<profile>
		<id>windows</id>
		<activation>
			<os>
				<family>dos</family>
			</os>
		</activation>
		<properties>
			<geckodriver.os>win64</geckodriver.os>
			<geckodriver.archive.extenstion>zip</geckodriver.archive.extenstion>
		</properties>
	</profile>
</profiles>
```

| 項番 | 説明 |
| ---- | ---- |
| (1)  | Maven Download Pluginの設定を追加する。ここでは、`initialize`フェーズにgeckodriverのアーカイブファイルをダウンロードし、プロジェクトルートに解凍した実行ファイルを配置するようにしている。なお、本設定例では、アーカイブファイル内の可変値(バージョン値、OS識別子、拡張子)をプロパティから取得するようにしている。 |
| (2)  | geckodriverのバージョンを指定するプロパティを定義する。プロパティ化しておくことで、Mavenコマンド実行時に任意のバージョンに切り替えることが可能になる。また、ダウンロードURL内にバージョン情報が２箇所あり、プロパティ化することでバージョン値の一元管理できるのもプロパティ化するモチベーションのひとつである。 |
| (3)  | geckodriverの実行ファイルはOS毎に用意されているため、Mavenの実行環境に依存する可変値(OS識別子、拡張子)をMavenプロファイルの機能を使用して解決している。 |

> **Note: OS毎のプロファイル設定**
>
> Window環境用のプロファイル設定は、OSの種類(Windows 7 vs Windows 10 / 32bit vs 64bitとか)によっては`<family>dos</family>`ではダメかもしれないので、各自がサポートする環境にあわせてプロファイルの設定値の変更やプロファイルの追加を行ってください。


# Mavenコマンドを使用したgeckodriverのインストール

geckodriverをSelenium実行時の作業ディレクトリ上に配置だけする場合は、以下のMavenコマンドを実行してください。（実際にファイルが配置されるか確認するために、すでにファイルがある場合は削除しておきましょう）

```
$ ./mvnw initialize
[INFO] Scanning for projects...
[INFO]                                                                         
...
[INFO] --- download-maven-plugin:1.3.0:wget (install-geckodriver) @ mybatis-spring-boot-jpetstore ---
Downloading: https://github.com/mozilla/geckodriver/releases/download/v0.14.0/geckodriver-v0.14.0-macos.tar.gz
1264K downloaded
[INFO] Expanding: /Users/xxx/git/mybatis-spring-boot-jpetstore/geckodriver-v0.14.0-macos.tar.gz into /Users/xxx/git/mybatis-spring-boot-jpetstore
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.320 s
[INFO] Finished at: 2017-03-26T20:12:14+09:00
[INFO] Final Memory: 19M/220M
[INFO] ------------------------------------------------------------------------
```

# Mavenコマンドを使用したテスト

Mavenコマンドを使用したテスト(`test`フェーズ)は、`initialize`フェーズの後に実行されるため、geckodriverをインストールするための特別な手順は必要ありません。通常のテストを実施する時と同様に、以下のMavenコマンドを実行してください。（実際にファイルが配置されるか確認するために、すでにファイルがある場合は削除しておきましょう）

```
$ ./mvnw test
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building jpetstore on spring-boot + mybatis + thymeleaf 1.1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] --- download-maven-plugin:1.3.0:wget (install-geckodriver) @ mybatis-spring-boot-jpetstore ---
[INFO] Got from cache: /Users/xxx/.m2/repository/.cache/download-maven-plugin/geckodriver-v0.14.0-macos.tar.gz_8669dbe69397f3acc7ccf20a0fb1da45
[INFO] Expanding: /Users/xxx/git/mybatis-spring-boot-jpetstore/geckodriver-v0.14.0-macos.tar.gz into /Users/xxx/git/mybatis-spring-boot-jpetstore
...

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
20:25:00.239 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.kazuki43zoo.jpetstore.JpetstoreApplicationTests]
...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-03-26 20:25:00.892  INFO 45776 --- [           main] c.k.jpetstore.JpetstoreApplicationTests  : Starting JpetstoreApplicationTests on xxx.local with PID 45776 (started by xxx in /Users/xxx/git/mybatis-spring-boot-jpetstore)
...
2017-03-26 20:25:06.588  INFO 45776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 60264 (http)
2017-03-26 20:25:06.592  INFO 45776 --- [           main] c.k.jpetstore.JpetstoreApplicationTests  : Started JpetstoreApplicationTests in 6.024 seconds (JVM running for 6.726)
1490527506784   geckodriver     INFO    Listening on 127.0.0.1:33431
2017-03-26 20:25:06.814  INFO 45776 --- [ null to remote] o.o.selenium.remote.ProtocolHandshake    : Attempting bi-dialect session, assuming Postel's Law holds true on the remote end
1490527507010   mozprofile::profile     INFO    Using profile path /var/folders/zk/p0w312d968b6tvfvlnxm0rph0000gn/T/rust_mozprofile.1Nbf021ofXT7
1490527507023   geckodriver::marionette INFO    Starting browser /Applications/Firefox.app/Contents/MacOS/firefox-bin
1490527507028   geckodriver::marionette INFO    Connecting to Marionette on localhost:60348
...
Results :

Tests run: 6, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 32.045 s
[INFO] Finished at: 2017-03-26T20:25:27+09:00
[INFO] Final Memory: 35M/538M
[INFO] ------------------------------------------------------------------------
```

Mavenの実行ログをみると・・・2回目以降は、Mavenのローカルリポジトリに保存してあるキャッシュからアーカイブファイルを取得しているので、ネートワークの負荷を気にする必要もなさそうですね。

# VCS(Version Control System)の管理対象外にしておこう！

geckodriverは実行ファイルであり、GitやSVNのようなVCS(Version Control System)で管理すべきファイルではないため、VCS管理対象外のファイルに指定しておくのがよいでしょう。

* VCSとしてGitを使っている場合の設定例

```text:.gitignore
...
geckodriver*
...
```

Windows用の実行ファイルの拡張子が`.exe`なので、「`geckodriver*`」というパターン指定にしています。

# まとめ

本エントリーで紹介した方法がベストプラクティスかどうかはわかりませんが、Selenium 3 + Forefoxを使う際は、geckodriverの実行ファイルを自動でインストールする(コマンドパスへ配置する)仕組みにしておくとよいと思います。
また、Forefox以外のブラウザ(Chromeとか)を使ってテストする際に必要となる実行ファイルについても、同じ仕組みで自動インストールできそうな気がしてきました。（今度試してみよう〜）

> **2017/3/28 追記：**
>
> WebDriver毎の実行ファイルをインストールしてくれる[WebDriverManager](https://github.com/bonigarcia/webdrivermanager)というライブラリがありました。
>
> **2017/4/1 追記:**
>
> 「[WebDriverManagerを使用してgeckodriver(SeleniumのWebDriver)を自動インストールする方法](http://qiita.com/kazuki43zoo/items/71f4c09e644b45a4002c)」を書きました。


# 参考サイト

* https://github.com/maven-download-plugin/maven-download-plugin
* http://maven-download-plugin.github.io/maven-download-plugin/docsite/1.3.0/wget-mojo.html