最近、GebというGroovyでSeleniumを簡単に使えるものが流行っているらしいですね。名前もよく見るので、ちょっと試してみることにしました。
あと、どうせならということでScalaでもSeleniumを簡単に使うというアプローチがあればということで調べてみたら、ScalaTestが挙がったのでこちらも合わせて。
このブログでSeleniumといえば、以前にSeleniumそのもので遊んでみました的なエントリを書いたことがあったようです。
Selenium WebDriverで遊ぶ
http://d.hatena.ne.jp/Kazuhira/20130103/1357213044
今回は、こちらをGroovy(Geb)とScala(ScalaTest)で置き換えてみましょう。
別に本題と関係ないんですけど今回Seleniumを使うにあたり、Firefox 36とselenium-firefox-driver 2.44.0以下の組み合わせだとSeleniumがクラッシュしてハマりました。
Issue 8399: Firefox 36 breaks WebDriver 2.44.0
https://code.google.com/p/selenium/issues/detail?id=8399
仕方ないのでしばらく放置かなぁと思ったら、翌日に2.45.0がリリースされていて、めでたく続行可能になりました。なにか、引きが強いようです…。
では、続けます。
やることは、2つのシナリオです。ブラウザは、Firefoxとします。
Geb
Groovyで、Seleniumを簡単に使えるようにするライブラリらしく、直接Seleniumを使うよりもコードが簡単になったり、jQueryライクなセレクタが使えたりといろいろ便利なようです。
発音は、"Jeb”だそうな。
Gebを使う時には、Browserというクラスを使うようなのですが、Browser#driveにClosureを渡す書き方と、普通にBrowserのインスタンスに対してメソッド呼び出ししていく書き方があるようです。今回は、これを交互に書いてみました。
まずは最初のシナリオ。こちらは、Browser#driveにClosureを渡すスタイルで書いています。
@Grab('org.gebish:geb-core:0.10.0') @Grab('org.seleniumhq.selenium:selenium-firefox-driver:2.45.0') @Grab('org.seleniumhq.selenium:selenium-support:2.45.0') import geb.Browser import org.openqa.selenium.Keys import org.openqa.selenium.support.ui.ExpectedCondition import org.openqa.selenium.support.ui.WebDriverWait Browser.drive { go 'https://www.google.co.jp/' assert title == 'Google' $('input[name="q"]').with { value('Cheese!') it << Keys.chord(Keys.ENTER) // firstElement().submit() // SeleniumのWebElementを使う場合はこちら } def pred = { d -> d.title.toLowerCase().startsWith("cheese!") } as ExpectedCondition (new WebDriverWait(driver, 10)).until(pred) assert title == 'Cheese! - Google 検索' }.quit()
依存関係の解決には、Grapeを使っています。
最初、Javaのコードをまともに移植しようとして、submitが見つからなくて考え込んだりしていました。ドキュメントの渡り歩き方にもまだ慣れがないですからねぇ…。
続いて、スクリーンショットを撮る方。こちらは、Browserのインスタンスに対して、メソッド呼び出しする方向で。
@Grab('org.gebish:geb-core:0.10.0') @Grab('org.seleniumhq.selenium:selenium-firefox-driver:2.45.0') @Grab('org.seleniumhq.selenium:selenium-support:2.45.0') import geb.Browser import org.openqa.selenium.Keys import org.openqa.selenium.OutputType import org.openqa.selenium.support.ui.ExpectedConditions import org.openqa.selenium.support.ui.WebDriverWait import java.nio.file.Files import java.nio.file.Paths def takeScreenshot = { driver, path -> Files.write(Paths.get(path), driver.getScreenshotAs(OutputType.BYTES)) } def browser = new Browser() browser.go 'https://www.google.co.jp/' assert browser.title == 'Google' takeScreenshot(browser.driver, 'capture1.png') browser.$('input[name="q"]').with { value('Cheese!') it << Keys.chord(Keys.ENTER) // firstElement().submit() // SeleniumのWebElementを使う場合はこちら } (new WebDriverWait(browser.driver, 10)).until(ExpectedConditions.titleContains("Google 検索")) assert browser.title == 'Cheese! - Google 検索' takeScreenshot(browser.driver, 'capture2.png') browser.quit()
スクリーンショットを撮るところ、普通に書いてしまいましたが、合ってる…?
なかなか簡潔に使えて良いと思いますー。
ScalaTest
ScalaTestに、Selenium向けのDSLがあるようです。
Using Selenium
http://www.scalatest.org/user_guide/using_selenium
自分はScalaTest派なので、ちょっと嬉しかったり。
sbtの設定は、このように。
build.sbt
name := "scalatest-selenium" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.5" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") updateOptions := updateOptions.value.withCachedResolution(true) libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "2.2.4" % "test", "org.seleniumhq.selenium" % "selenium-java" % "2.45.0" % "test", "com.google.code.findbugs" % "jsr305" % "3.0.0" % "provided" )
Firefoxを使うので、「selenium-firefox-driver」だけでもいいのかなぁと思いましたが、それだとうまくいかずドキュメント通りに「selenium-java」を足すことにしました…。
あと、JSR-305向けのfindbugsの依存関係があるのですが、これは以下のWarningを消すためです。
[warn] Class javax.annotation.Nullable not found - continuing with a stub. [warn] one warning found
ScalaTestでのSeleniumの使い方ですが、各ブラウザに相当するトレイトをMix-inする簡易な方法と、WebBrowserトレイトをMix-inしてWebDriverのインスタンスはimplicitとして持つ方法があるようです。なお、各ブラウザに相当するトレイト(例えばFirefoxトレイト)は、WebBrowserトレイトのサブトレイトです。
では、最初のシナリオを書いてみます。
src/test/scala/org/littlewings/scalatest/ScalaTestSeleniumUnitSpec.scala
package org.littlewings.scalatest import org.scalatest.{ BeforeAndAfterAll, FunSpec } import org.scalatest.Matchers._ import org.scalatest.selenium.Firefox import org.openqa.selenium.WebDriver import org.openqa.selenium.support.ui.{ ExpectedCondition, WebDriverWait } class ScalaTestSeleniumUnitSpec extends FunSpec with Firefox with BeforeAndAfterAll { describe("ScalaTest Selenium Unit Style Spec") { it("Google Search") { go to ("https://www.google.co.jp/") pageTitle should be ("Google") click on "q" enter("Cheese!") submit() (new WebDriverWait(webDriver, 10)).until(new ExpectedCondition[Boolean] { override def apply(d: WebDriver): Boolean = d.getTitle.toLowerCase.startsWith("cheese!") }) pageTitle should be ("Cheese! - Google 検索") } } override def afterAll(): Unit = quit() }
Firefoxを使うために、FirefoxトレイトをMix-inしています。BeforeAndAfterAllトレイトをMix-inしているのは、最後にquitメソッドを呼ぶためだけです。
続いて、スクリーンショットを撮る方。こちらは、WebDriverのインスタンスをimplicit valで定義しています。
src/test/scala/org/littlewings/scalatest/ScalaTestSeleniumScreenshotSpec.scala
package org.littlewings.scalatest import org.scalatest.{ BeforeAndAfterAll, FunSpec } import org.scalatest.Matchers._ import org.scalatest.selenium.WebBrowser import org.openqa.selenium.WebDriver import org.openqa.selenium.firefox.FirefoxDriver import org.openqa.selenium.support.ui.{ ExpectedConditions, WebDriverWait } class ScalaTestSeleniumScreenshotSpec extends FunSpec with WebBrowser with BeforeAndAfterAll { implicit val webDriver: WebDriver = new FirefoxDriver describe("ScalaTest Selenium Using WebDriver Spec") { it("Take Screenshot") { setCaptureDir(".") go to ("https://www.google.co.jp/") pageTitle should be ("Google") capture to "capture1.png" click on "q" enter("Cheese!") submit() (new WebDriverWait(webDriver, 10)) .until(ExpectedConditions.titleContains("Google 検索")) pageTitle should be ("Cheese! - Google 検索") capture to "capture2.png" } } override def afterAll(): Unit = webDriver.quit() }
こちらでは、capture toでスクリーンショットを保存できるのですが、
capture to "capture1.png"
保存先はデフォルトでは一時ディレクトリ(Linuxなら/tmp)だったりします。これは、setCaptureDirで変更することができます。
setCaptureDir(".")
今回は、実行時のカレントディレクトリとしました。
なお、このScalaTestのSelenium DSLですが、各メソッド(例えばpageTitle)にimplicit parameterがいて、WebDriverのインスタンスを要求するようです。このあたりを少し楽にしてくれるのが、各ブラウザごとのトレイトみたいです。
久々にこういうのやると、面白いですね。