この記事は 『CRESCO Advent Calendar 2017』 12日目の記事です。

 

こんにちは。技術研究所の「310」です。
私は普段、自動テスト関連の活動をしています。主にSeleniumを使ったE2Eテストを手探りでやっています。
最初は動かすことや、安定性を高めることに必死でしたが、段々こなれてくると色んなことが気になってしまいます。
実はWebDriverによって方言があって、同じ内容のテストケースをブラウザ数分作ってみたり、テスト対象のブラウザ名をプロパティファイルに記述して実行して、また別のブラウザ名に書き換えて実行してみたり、定期的にWebDriverが更新されていないか人力クローリングしたり。(そこを自動化しないあたり)

それらも色々調べてみると、便利ツールが用意されていたりしたので、いっぺんに使ってしまおうと思いついた結果がこの記事です。
もっとスタイリッシュな方法があれば教えてください。

どうしてもやりたかったこと

  • どのブラウザのテストでも、書くのは1つのテストコード
  • 1コマンドで指定のブラウザ全てのテストを実行
  • 意識せずに常に最新のWebDriverを使用

これらを要件として、Gradleプロジェクトで実装したいと思います。
ちなみにGradleは初めて使います。変な使い方してたらごめんなさい。

build.gradle

まずは元となるbuild.gradleファイル。
Geb + Spockでテストコードを書いて、WebDriverの管理にWebDriverManagerを使用します。
基本的にGeb内でWebDriver毎の差異は吸収されているので、方言問題は解消されたと言えます。簡単。

apply plugin: 'groovy'
repositories {
jcenter()
}
dependencies {
testCompile "org.codehaus.groovy:groovy:2.4.8"
testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.8.1'
testCompile group: 'com.codeborne', name: 'phantomjsdriver', version: '1.4.3'
testCompile group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '2.0.0'
testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'
testCompile group: 'org.gebish', name: 'geb-core', version: '2.0'
testCompile group: 'org.gebish', name: 'geb-spock', version: '2.0'
}

テストコード

次に今回の例で使用するテストコード。
Yahoo!JAPANを開いて、「クレスコエンジニアブログ」を検索する簡単なものです。ゲシュタルト崩壊。
個人的にテストコードは日本語が入っている方が好きです。

import geb.spock.GebSpec
import org.junit.Test
class ExampleTest extends GebSpec {
@Test
def "Yahoo!JAPANでクレスコエンジニアブログを検索する"() {
when: 'Yahoo!を表示する'
go 'http://www.yahoo.co.jp'
and: '検索欄に「クレスコエンジニアブログ」と入力する'
$('input[name=p]').value('クレスコエンジニアブログ')
then: '検索欄に「クレスコエンジニアブログ」と入力されていること'
assert $('input[name=p]').value() == 'クレスコエンジニアブログ'
and: '検索ボタンを押す'
$('#srchbtn').click()
}
}

/src/test/groovyに配置します。

カスタムタスク

ここからが本題です。実際にテストを実行するタスクを作っていきます。普通のGradleプロジェクトだと

gradle test

でテストを実行することになると思いますが、当然デフォルトの状態ではマルチブラウザテストはできません。
GitHubに公開されているGebのサンプルを参考に書いてみます。
今回は仮にchromeとfirefoxを対象として、gradle.buildに以下を追加します。

// テスト対象となるブラウザ名 (1)
ext {
browsers = [ "chrome", "firefox" ]
}
// 対象ブラウザのテストを実行するタスクを動的に生成 (2)
browsers.each { browser ->
task "$browser"(type: Test) {
// 前回と差分がなくても必ず実行する
outputs.upToDateWhen { false }
systemProperty "geb.env", browser
}
}
// 生成したタスクをtestタスクの中で全て実行 (3)
test {
dependsOn browsers.collect { tasks["${it}"] }
// 本来のtestタスクをスキップ
enabled = false
}

(1)でテスト対象のブラウザ名を列挙し、(2)でそれと同じ名前のタスクを生成し、その中で個別に”geb.env”というプロパティにブラウザ名を設定しています。このgeb.envは一体どこで使われるのかというと、次に出てくるGebConfig.groovyになります。
GebConfig.groovyは読んで字の如く、Geb用の設定ファイルです。
(3)はデフォルトで用意されているtestタスクをカスタマイズし、動的に生成したタスクに依存するように設定しています。つまり、testタスクが実行されるときに生成した個別のタスクも実行されるわけです。

GebConfig

GebConfigでは、テストの実行環境やDriverの共通の設定などをしていきます。
こちらもGebのサンプルを参考に実装していきます。

import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.firefox.FirefoxDriver
// テスト実行時の随所の待ち時間
waiting {
timeout = 3
}
// geb.envの設定値によって生成するドライバーを変える
environments {
chrome {
driver = { new ChromeDriver() }
}
firefox {
driver = { new FirefoxDriver() }
}
edge {
// (以下略)

これを/src/test/resourceに配置します。
environmentsが先ほどのgeb.envと対応していて、設定値に合わせて用意するWebDriverを切り替えるわけですね。賢い。
そしてここで登場するのがWebDriverManagerです。WebDriverの管理は全てお任せしちゃいます。

ChromeDriverManager.getInstance().setup()
driver = "org.openqa.selenium.chrome.ChromeDriver"

このようにして書くだけで、今まで面倒だったSystemPropertyに対するWebDriverのパス指定などもせずにインスタンスが得られます。頼もしい。これで意識せずとも常に最新のWebDriverが与えられます!
他のWebDriverもこれにならって書きます。

import io.github.bonigarcia.wdm.ChromeDriverManager
import io.github.bonigarcia.wdm.EdgeDriverManager
import io.github.bonigarcia.wdm.FirefoxDriverManager
import io.github.bonigarcia.wdm.InternetExplorerDriverManager
import io.github.bonigarcia.wdm.PhantomJsDriverManager
// テスト実行時の随所の待ち時間
waiting {
timeout = 3
}
// geb.envが設定されなかった場合のデフォルトブラウザ
PhantomJsDriverManager.getInstance().setup()
driver = "org.openqa.selenium.phantomjs.PhantomJSDriver"
// geb.envの設定値によって生成するドライバーを変える
environments {
chrome {
ChromeDriverManager.getInstance().setup()
driver = "org.openqa.selenium.chrome.ChromeDriver"
}
firefox {
FirefoxDriverManager.getInstance().setup()
driver = "org.openqa.selenium.firefox.FirefoxDriver"
}
edge {
EdgeDriverManager.getInstance().setup()
driver = "org.openqa.selenium.edge.EdgeDriver"
}
ie {
InternetExplorerDriverManager.getInstance().setup()
driver = "org.openqa.selenium.ie,InternetExplorerDriver"
}
phantomjs {
PhantomJsDriverManager.getInstance().setup()
driver = "org.openqa.selenium.phantomjs.PhantomJSDriver"
}
}

ここには色んな種類のWebDriverを書いておくと便利な気がします。気がするだけです。
(ちなみにOperaDriverは愚直にSystemPropertyを設定すると上手く動作しないようで、WebDriverManagerさんをもってしても起動できませんでした)

実行結果

それでは実行してみましょう!トリガーはtestタスクになりますが、buildタスク内でtestタスクも実行されるので基本、buildを叩くだけでよいです。(よく分かってない)

$ gradle build
:compileJava NO-SOURCE
:compileGroovy NO-SOURCE
:processResources NO-SOURCE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava UP-TO-DATE
:compileTestGroovy
:processTestResources
:testClasses
:chrome
:firefox
:test SKIPPED
:check UP-TO-DATE
:build UP-TO-DATE
BUILD SUCCESSFUL in 26s
6 actionable tasks: 4 executed, 2 up-to-date

「:chrome」と「:firefox」の文字がありますね。実行されたようです。テスト結果は、デフォルトだと/build/reports/tests/{ブラウザ名}に出力されています。

JUnitなどと同じようにキレイにレポートしてくれます。ブラウザ毎の結果が見れるので、ありがたいですね。
buildコマンド一発で指定したブラウザのマルチブラウザテストが実行できました。これで冒頭に挙げた要件は全てクリア!めでたし。

おわりに

どこに拘っているのか、というような話に付き合っていただきましてありがとうございました。
今回初めてGradleを使ってみて、ビルドスクリプトの便利さを実感しました。それとSelenium関連のライブラリがたくさん出てきていてどんどん便利になっていく反面、どれを使っていいのか分からなくなっていきますね。これからも調査を続けます…。
あと全然関係ない話ですが、EclipseでGroovyを書こうとするとなかなかに難易度が高かったです。おかげ様でIntellijデビューできました。おしゃれ。
よいお年を。