Spring Boot + npm + Geb で入力フォームを作ってテストする ( その34 )( Geb でテストを作成する )
概要
記事一覧はこちらです。
Spring Boot + npm + Geb で入力フォームを作ってテストする ( その33 )( ESLint を導入する ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- Geb で入力画面1、入力画面2のテストを作成してみます。
- Form に値を一括セット/検証する方法を調べていたら結構時間がかかってしまいました。
参照したサイト・書籍
The Book Of Geb
http://www.gebish.org/manual/current/Gebチートシート
https://qiita.com/itagakishintaro/items/1fa06904bd0a6de73ee2Geb Advent Calendar 2016
https://qiita.com/advent-calendar/2016/gebGeb 自分用メモ
http://bufferings.hatenablog.com/entry/2015/06/11/010321Web画面自動テストフレームワーク「Geb」の紹介
http://lab.astamuse.co.jp/entry/geb_test_01Can selenium handle autocomplete?
https://stackoverflow.com/questions/663034/can-selenium-handle-autocomplete
目次
- Firefox Quantum がインストールされていました
- FormModule クラスを作成する
- 入力画面1の InquiryInput01Page クラスを変更し、テストデータを用意する
- 入力画面2の InquiryInput02Page クラスを作成し、テストデータを用意する
- Geb のテストを作成する
- 続きます。。。
手順
Firefox Quantum がインストールされていました
Firefox を再起動したら Firefox Quantum になっていました。そういえば正式リリース日でしたね(2017/11/15 に書いています)。既存の Firefox とは別にインストールする必要があるのかな、と思っていたら自動でアップデートされていました。
サンプルで書いていた Geb のテストを試したら問題なく動くようなので、このまま続けます。
FormModule クラスを作成する
画面共通のボタンを定義したり、Form に値を一括セット/検証するメソッドを作成したいので、Geb の Module を継承したクラスを作成します。
src/test/groovy/geb の下に module パッケージを作成します。src/test/groovy/geb/module の下に FormModule.groovy を新規作成し、以下の内容を記述します。
package geb.module import geb.Module import org.openqa.selenium.WebElement /** * フォーム共通の content や、テストに使用するメソッドを定義するクラス */ class FormModule extends Module { static content = { btnBack { $(".js-btn-back") } btnNext { $(".js-btn-next") } } /** * Form の入力項目に値を一括セットする * valueList は以下の形式の Map である * <pre>{@code * static initialValueList = [ * "#lastname" : "", * "#firstname" : "", * "#lastkana" : "", * "#firstkana" : "", * "input[name='sex']": null, * "#age" : "", * "#job" : "" * ] *}</pre> * * @param valueList セットするセレクタと値を記述した Map */ void setValueList(valueList) { valueList.each { $(it.key).value(it.value) } } /** * セレクタに値がセットされているかを検証する * このメソッドは Spock の then, expect で使用する想定である * * @param valueList 検証するセレクタと値を記述した Map * @return true 固定 */ boolean assertValueList(valueList) { valueList.each { key, value -> if ($(key).first().attr("type") == "radio") { WebElement element = $(key).allElements().find { it.selected } if (element == null) { assert null == value } else { assert element.getAttribute("value") == value } } else { assert $(key).value() == value } } true } }
入力画面1の InquiryInput01Page クラスを変更し、テストデータを用意する
入力画面1用に作成していた InquiryInput01Page クラスで FormModule を使用するようにし、かつテストで使用するデータも記述します。src/test/groovy/geb/page/inquiry/InquiryInput01Page.groovy を以下のように変更します。
package geb.page.inquiry import geb.Page import geb.module.FormModule class InquiryInput01Page extends Page { static url = "/inquiry/input/01" static at = { title == "入力フォーム - 入力画面1" } static content = { form { module FormModule } } static initialValueList = [ "#lastname" : "", "#firstname" : "", "#lastkana" : "", "#firstkana" : "", "input[name='sex']": null, "#age" : "", "#job" : "" ] static maxLengthValueList = [ "#lastname" : "あ" * 20, "#firstname": "あ" * 20, "#lastkana" : "あ" * 20, "#firstkana": "あ" * 20, "#age" : "9" * 3, ] }
static content = { ... }
からbtnNext { $(".js-btn-next") }
を削除し、form { module FormModule }
を追加します。static initialValueList = [ ... ]
を追加します。static maxLengthValueList = [ ... ]
を追加します。
入力画面2の InquiryInput02Page クラスを作成し、テストデータを用意する
入力画面2用の InquiryInput02Page クラスを作成します。テストで使用するデータも記述します。
src/test/groovy/geb/page/inquiry の下に InquiryInput02Page.groovy を新規作成し、以下の内容を記述します。
package geb.page.inquiry import geb.Page import geb.module.FormModule class InquiryInput02Page extends Page { static url = "/inquiry/input/01" static at = { title == "入力フォーム - 入力画面2" } static content = { form { module FormModule } } static initialValueList = [ "#zipcode1": "", "#zipcode2": "", "#address" : "", "#tel1" : "", "#tel2" : "", "#tel3" : "", "#email" : "", ] static maxLengthValueList = [ "#zipcode1": "x" * 3, "#zipcode2": "x" * 4, "#address" : "あ" * 256, "#tel1" : "9" * 5, "#tel2" : "9" * 4, "#tel3" : "9" * 4, "#email" : "x" * 256, ] }
Geb のテストを作成する
入力画面1、入力画面2でできるテストを作成してみます。
src/test/groovy/geb/gebspec の下に inquiry パッケージを作成した後、inquiry パッケージの下に InquiryTestSpec.groovy を新規作成して以下の内容を記述します。
package geb.gebspec.inquiry import geb.page.inquiry.InquiryInput01Page import geb.page.inquiry.InquiryInput02Page import geb.spock.GebSpec import org.openqa.selenium.Keys import org.openqa.selenium.WebElement import spock.lang.Unroll class InquiryTestSpec extends GebSpec { def "入力画面1の画面初期表示時に想定している値がセットされている"() { setup: "入力画面1を表示する" to InquiryInput01Page expect: "初期値が表示されている" form.assertValueList(initialValueList) } def "入力画面1の各入力項目に最大文字数を入力できる"() { setup: "入力画面1を表示し入力項目に最大文字数の文字を入力する" to InquiryInput01Page form.setValueList(maxLengthValueList) expect: "入力した最大文字数の文字が入力されている" form.assertValueList(maxLengthValueList) } def "入力画面1に入力後、入力画面2へ遷移→入力画面1へ戻ると入力した値が表示される"() { given: "入力画面1を表示する" to InquiryInput01Page when: "最大文字数の文字を入力して次へボタンをクリックする" form.setValueList(maxLengthValueList) $("#inquiryInput01Form").sex = "1" form.btnNext.click(InquiryInput02Page) then: "入力画面2へ遷移し初期値が表示されている" form.assertValueList(initialValueList) and: "戻るボタンをクリックする" form.btnBack.click(InquiryInput01Page) then: "入力した最大文字数の文字が入力されている" form.assertValueList(maxLengthValueList) $("#inquiryInput01Form").sex == "1" } def "入力画面2の各入力項目に最大文字数を入力できる"() { setup: "入力画面1を表示して最大文字数の文字を入力してから次へボタンをクリックする" to InquiryInput01Page form.setValueList(maxLengthValueList) $("#inquiryInput01Form").sex = "1" form.btnNext.click(InquiryInput02Page) and: "入力画面2で最大文字数の文字を入力する" form.setValueList(maxLengthValueList) expect: "入力した最大文字数の文字が入力されている" form.assertValueList(maxLengthValueList) } def "入力画面2で郵便番号を入力してautocompleteで表示された住所を選択する"() { given: "入力画面1から入力画面2へ遷移する" to InquiryInput01Page form.setValueList(maxLengthValueList) $("#inquiryInput01Form").sex = "1" form.btnNext.click(InquiryInput02Page) when: "郵便番号を入力する" $("#inquiryInput02Form").zipcode1 = "100" $("#inquiryInput02Form").zipcode2 = "0005" << Keys.TAB and: "autocomplete のドロップダウンメニューが表示されたら最初の選択肢を選択する" waitFor(5) { $(".ui-autocomplete .ui-menu-item") } List<WebElement> elementList = $(".ui-autocomplete .ui-menu-item > div").allElements() elementList.first().click() then: "住所にクリックした選択肢がセットされている" $("#inquiryInput02Form").address == "東京都千代田区丸の内" } @Unroll def "入力画面2の電話番号とメールアドレスの組み合わせテスト(#tel1,#tel2,#tel3,#email)"() { given: "入力画面1から入力画面2へ遷移する" to InquiryInput01Page form.setValueList(maxLengthValueList) $("#inquiryInput01Form").sex = "1" form.btnNext.click(InquiryInput02Page) when: "電話番号と郵便番号を入力する" $("#inquiryInput02Form").tel1 = tel1 << Keys.TAB $("#inquiryInput02Form").tel2 = tel2 << Keys.TAB $("#inquiryInput02Form").tel3 = tel3 << Keys.TAB $("#inquiryInput02Form").email = email << Keys.TAB then: "エラーメッセージの表示状況をチェックする" $("#form-group-tel .js-errmsg").text() == telErrMsg $("#form-group-email .js-errmsg").text() == emailErrMsg where: tel1 | tel2 | tel3 | email || telErrMsg | emailErrMsg "03" | "1234" | "5678" | "tanaka@sample.co.jp" || "" | "" "03" | "1234" | "5678" | "" || "" | "" "" | "" | "" | "tanaka@sample.co.jp" || "" | "" "" | "" | "" | "" || "電話番号とメールアドレスのいずれか一方を入力してください" | "電話番号とメールアドレスのいずれか一方を入力してください" "3" | "1234" | "5678" | "" || "市外局番の先頭には 0 の数字を入力してください" | "" "03" | "123" | "5678" | "" || "市外局番+市内局番の組み合わせが数字6桁になるように入力してください" | "" "03" | "1234" | "567" | "" || "加入者番号には4桁の数字を入力してください" | "" } }
テストを書いてみて思ったのは、以下の点でした。
- InquiryInput01Page にも InquiryInput02Page にも
static content = { form { module FormModule } }
と書いており、テストはform.~
から書いて問題なく動作するのかな?と不思議に思っていましたが、to ...
やform.btnNext.click(...Page)
を呼び出して画面遷移した後の Page クラスの form を見てくれるようです。 - 値をセットするだけなら不要ですが、Javascript の blur イベントを発生させたい場合には
<< Keys.TAB
が必須でした。 - autocomplete のテストは時間がかかりましたが、それ以外は MockMvc と比較するとテストが書きやすいです。ただし実行に時間がかかるので、何でも Geb でテストを作成するという訳にはいかなそうです。
作成したテストを実行してみます。Tomcat を起動した後、テストを実行します。
テストは全て成功しました。最初のテストが 10数秒、それ以降のテストが2~4秒程度かかるようです。ブラウザを起動してテストをするので、やっぱり結構時間がかかりますね。
Headless Chrome に切り替えてみます。src/test/resources/GebConfig.groovy を以下のように変更します。
driver = { // FirefoxOptions firefoxOptions = new FirefoxOptions() // firefoxOptions.setHeadless(true) // new FirefoxDriver(firefoxOptions) ChromeOptions chromeOptions = new ChromeOptions() chromeOptions.setHeadless(true) new ChromeDriver(chromeOptions) }
テストを実行します。
Firefox headless モードの時と同様にテストは全て成功しました。Firefox headless モードと比較すると最初のテストは数秒速いですが、その後のテストが 0.5~2秒くらい遅い気がします。
最後に HtmlUnit に切り替えてみます。src/test/resources/GebConfig.groovy を以下のように変更します。
driver = { // FirefoxOptions firefoxOptions = new FirefoxOptions() // firefoxOptions.setHeadless(true) // new FirefoxDriver(firefoxOptions) new HtmlUnitDriver(true) }
テストを実行します。
なぜか全部失敗しましたね。。。 java.lang.NoSuchMethodError: com.gargoylesoftware.htmlunit.html.DomElement.getScriptableObject()Ljava/lang/Object;
というエラーが出ています。
続きます。。。
HtmlUnit で失敗する原因を調べます。また Geb でもう少しいろいろ試してみます。
履歴
2017/11/17
初版発行。