Scala + Selenium はじめの一歩

# 前置き

ScalaTestでSelenium-WebDriverのテストコードを書くことになったので、その勉強メモです。
書き手はScala経験ゼロ。Seleniumはときどき使います。使用言語はおもにRuby、たまにVB、Java。開発スキルはさほどありません。

このメモでは、MacにJavaやScala（sbt）をインストールするところから、SeleniumでChromeを操作してScalaTestDocのサイトへアクセスし、キーワード "Assertions" を検索してスクリーンショットを保存するところまでの手順をまとめます。

※2019/6/9: 各環境の最新バージョンに合わせて内容を更新しました。

# 環境

* OS: Mac OS X 10.14.3 Mojave
* Homebrew: 2.1.4-67-gc95cf90
* Java: openjdk version "11.0.2" 2019-01-15
* Scala: 2.12.8
* sbt: 1.2.8
* ScalaTest: 3.0.5
* Selenium-Webdriver: 3.141.59
* Google Chrome: 75.0.3770.80（Official Build）
* ChromeDriver: 75.0.3770.8

Google Chromeはインストール済み、Homebrewの環境は構築済みであることを前提とします。

# 必要なものを入手

## ChromeDriverのインストール

HomebrewでChromeDriverをインストールします。

```bash:コマンド
$ brew tap homebrew/cask
$ brew cask install chromedriver
$ chromedriver -v
ChromeDriver 75.0.3770.8 (681f24ea911fe754973dda2fdc6d2a2e159dd300-refs/branch-heads/3770@{#40})
```

## Javaのインストール

HomebrewでJDKをインストールします。
今回はOpenJDKのバージョン11を使用します。

```bash:コマンド
$ brew tap homebrew/cask-versions
$ brew cask install java11
$ echo 'export JAVA_HOME=`/usr/libexec/java_home -v 11`' >> ~/.bash_profile
$ source ~/.bash_profile
$ java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
```

## Scala-sbtのインストール

HomebrewでScalaのビルドツールであるsbtをインストールします。

```bash:コマンド
$ brew install sbt
```

テストプロジェクト用の作業ディレクトリを作成します。

```bash:コマンド
$ mkdir ~/selenium-test; cd $_
```

作業ディレクトリの直下にsbtのビルド定義ファイル（build.sbt）を作成し、使用するScalaのバージョンを指定します。

```bash:コマンド
$ echo 'scalaVersion := "2.12.8"' > build.sbt
```

sbtのライブラリとScalaをダウンロードします。

```bash:コマンド
$ sbt sbtVersion
Copying runtime jar.
Getting org.scala-sbt sbt 1.2.8  (this may take some time)...
downloading file:////Users/hoge/.sbt/preloaded/org.scala-sbt/sbt/1.2.8/jars/sbt.jar ...
	[SUCCESSFUL ] org.scala-sbt#sbt;1.2.8!sbt.jar (4ms)
# 中略
:: retrieving :: org.scala-sbt#boot-app
	confs: [default]
	80 artifacts copied, 0 already retrieved (28561kB/93ms)
Getting Scala 2.12.7 (for sbt)...
:: retrieving :: org.scala-sbt#boot-scala
	confs: [default]
	5 artifacts copied, 0 already retrieved (19715kB/26ms)
[info] Updated file /Users/hoge/selenium-test/project/build.properties: set sbt.version to 1.2.8
[info] Loading project definition from /Users/hoge/selenium-test/project
[info] Updating ProjectRef(uri("file:/Users/hoge/selenium-test/project/"), "selenium-test-build")...
[info] Done updating.
[info] Loading settings for project selenium-test from build.sbt ...
[info] Set current project to selenium-test (in build file:/Users/hoge/selenium-test/)
[info] 1.2.8

$ sbt scalaVersion
[info] Loading project definition from /Users/hoge/selenium-test/project
[info] Loading settings for project selenium-test from build.sbt ...
[info] Set current project to selenium-test (in build file:/Users/hoge/selenium-test/)
[info] 2.12.8
```

# ソースを作成

## ビルド定義ファイルの更新

作業ディレクトリ/build.sbt に依存ライブラリ（libraryDependencies）の情報を追加します。

```scala:build.sbt
scalaVersion := "2.12.8"

libraryDependencies ++= Seq(
  "org.scalatest" %% "scalatest" % "3.0.5" % "test",
  "org.seleniumhq.selenium" % "selenium-java" % "3.141.59" % "test"
)
```

## テストコードの作成

テストコード用のディレクトリとファイルを作成します。

```bash:コマンド
$ mkdir -p src/test/scala
$ touch src/test/scala/SeleniumTestSpec.scala
```

作業ディレクトリ/src/test/scala/SeleniumTestSpec.scala にテストコードを書きます。

```scala:SeleniumTestSpec.scala
package example

import org.scalatest._
import org.scalatest.selenium.Chrome

class SeleniumTestSpec extends FlatSpec with Matchers with Chrome {

  "www.scalatest.org" should "have the correct title" in {
    go to "http://www.scalatest.org/"
    pageTitle should be ("ScalaTest")
  }

  "Assertions Page" should "be found in ScalaTestDoc" in {
    go to "http://www.scalatest.org/"
    click on linkText("Scaladoc")
    click on linkText("Scaladoc for ScalaTest 3.0.0")
    click on "index-input"
    enter("Assertions")
    click on xpath(".//*[@id='tpl']/ol[1]/ol/li[1]/a[2]/span")

    switch to frame("template")
    find(tagName("h1")).get.text should be ("Assertions")

    setCaptureDir(".")  // スクリーンショットの保存先としてカレントディレクトリを指定
    capture to "Assertions Page.png"
    quit()
  }
}
```

ChromeDriverは以下のように指定することもできます。

```scala:SeleniumTestSpec.scala
package example

import org.scalatest._
import org.scalatest.selenium.WebBrowser
import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeDriver

class SeleniumTestSpec extends FlatSpec with Matchers with WebBrowser {
  implicit val webDriver: WebDriver = new ChromeDriver
  // ここにテストを書く
}
```

ここまでの段階で、以下のようなファイルの配置になります。

```bash:コマンド
$ tree
.
├── build.sbt
├── project
│   ├── build.properties
│   └── target
│       ├── config-classes
│     （中略）
└── src
    └── test
        └── scala
            └── SeleniumTestSpec.scala
```

# テストを実行

作成したソースをコンパイルして実行します。

```bash:コマンド
$ sbt test           # コンパイルしてテストを実行するコマンド

# 必要に応じて以下のようなコマンドも使えます
$ sbt test:compile   # コンパイルのみ行うコマンド
$ sbt update         # 依存ライブラリのダウンロードを行うコマンド
```

```bash:コマンド
$ sbt test
[info] Loading project definition from /Users/hoge/selenium-test/project
[info] Loading settings for project selenium-test from build.sbt ...
[info] Set current project to selenium-test (in build file:/Users/hoge/selenium-test/)
[info] Updating ...
# 初回実行時に build.sbt に追加した依存ライブラリが自動的にダウンロードされます
[info] downloading https://repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.8/scala-library-2.12.8.jar ...
[info] downloading https://repo1.maven.org/maven2/org/scalatest/scalatest_2.12/3.0.5/scalatest_2.12-3.0.5.jar ...
[info] downloading https://repo1.maven.org/maven2/org/seleniumhq/selenium/selenium-java/3.141.59/selenium-java-3.141.59.jar ...
# 中略
[info] Done updating.
[info] Compiling 1 Scala source to /Users/hoge/selenium-test/target/scala-2.12/test-classes ...
[info] Non-compiled module 'compiler-bridge_2.12' for Scala 2.12.8. Compiling...
[info]   Compilation completed in 8.106s.
[info] Done compiling.
# 中略
Starting ChromeDriver 75.0.3770.8 (681f24ea911fe754973dda2fdc6d2a2e159dd300-refs/branch-heads/3770@{#40}) on port 29044
Only local connections are allowed.
Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
M月 dd, yyyy h:mm:ss a org.openqa.selenium.remote.ProtocolHandshake createSession
情報: Detected dialect: W3C
[info] SeleniumTestSpec:
[info] www.scalatest.org
[info] - should have the correct title
[info] Assertions Page
[info] - should be found in ScalaTestDoc
[info] Run completed in 13 seconds, 133 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 185 s, completed yyyy/MM/dd H:mm:ss
```

テストが実行できました。[^WARNING]

[^WARNING]: この記事の環境ではコンパイル時に`WARNING: Illegal reflective access by com.google.protobuf.UnsafeUtil`という警告が出ますが、sbt 1.3で修正されるようです。（[Issue](https://github.com/sbt/zinc/issues/459)）

![Assertions Page_20190609.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79324/e4712981-bff0-73d4-2bcc-53ca1b11af24.png "Assertions Page.png")
※保存されたスクリーンショット（Assertions Page.png）

初回実行時はライブラリのダウンロードやコンパイルなどに時間がかかりTotal timeが長くなっていますが、2回目以降はもっと短い時間で終わります。

```bash:コマンド（2回目）
$ sbt test
[info] Loading project definition from /Users/hoge/selenium-test/project
# 中略
[info] SeleniumTestSpec:
[info] www.scalatest.org
[info] - should have the correct title
[info] Assertions Page
[info] - should be found in ScalaTestDoc
[info] Run completed in 13 seconds, 238 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 16 s, completed yyyy/MM/dd H:mm:ss
```

---
# 参考サイト
* [Scala](https://www.scala-lang.org)
* [ScalaTest - Using Selenium](http://www.scalatest.org/user_guide/using_selenium)
* [sbt Reference Manual - Testing](https://www.scala-sbt.org/1.x/docs/Testing.html)
* [CLOVER - Groovy（Geb）とScala（ScalaTest）を使ってSeleniumで遊ぶ](https://kazuhira-r.hatenablog.com/entry/20150301/1425200953)
* [Scaladays 2014 introduction to scalatest selenium dsl](https://www.slideshare.net/matthewfarwell/scaladays-2014-introduction-to-scalatest-selenium-dsl)
* [OpenJDK 11 を Homebrew で macOS にインストールする - Qiita](https://qiita.com/niwasawa/items/fed67553845f8d1b72bc)