かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その35 )( Geb でテストを作成する2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その34 )( Geb でテストを作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 前回 HtmlUnit でテストを実行してエラーが発生したので、その原因を調査します。
    • Geb で気になる点があるので確認します。

参照したサイト・書籍

目次

  1. HtmlUnit でテストが失敗した原因を取り除いてみる
  2. HtmlUnit は Geb のテスト対象ブラウザから外す
  3. Geb でいろいろ試してみる
    1. 画面の外にボタンがあるとクリックできない?
    2. maxlength を超える文字列は入力できない
    3. FormModule クラスの setValueList メソッドを値セット+Tab キークリックするよう変更する
  4. 次回は。。。

手順

HtmlUnit でテストが失敗した原因を取り除いてみる

前回 HtmlUnit でテストを実行したら java.lang.NoSuchMethodError: com.gargoylesoftware.htmlunit.html.DomElement.getScriptableObject()Ljava/lang/Object; のエラーが出て失敗しました。

ライブラリの依存関係に問題がある気がします。gradlew dependencies コマンドを実行してみたところ、以下の内容が出力されました。

+--- org.seleniumhq.selenium:htmlunit-driver:2.27
|    +--- org.seleniumhq.selenium:selenium-api:3.4.0 -> 3.6.0
|    +--- org.seleniumhq.selenium:selenium-support:3.4.0 -> 3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-api:3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)
|    |    +--- net.bytebuddy:byte-buddy:1.7.5
|    |    +--- org.apache.commons:commons-exec:1.3
|    |    +--- commons-codec:commons-codec:1.10
|    |    +--- commons-logging:commons-logging:1.2
|    |    +--- com.google.code.gson:gson:2.8.0 -> 2.8.1
|    |    +--- com.google.guava:guava:23.0 -> 22.0 (*)
|    |    +--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|    |    +--- org.apache.httpcomponents:httpcore:4.4.6
|    |    +--- net.java.dev.jna:jna:4.1.0 -> 4.2.2
|    |    \--- net.java.dev.jna:jna-platform:4.1.0 (*)
|    \--- net.sourceforge.htmlunit:htmlunit:2.27 -> 2.21
|         +--- xalan:xalan:2.7.2
|         |    \--- xalan:serializer:2.7.2
|         +--- org.apache.commons:commons-lang3:3.4 -> 3.5
|         +--- org.apache.httpcomponents:httpclient:4.5.2 -> 4.5.3 (*)
|         +--- org.apache.httpcomponents:httpmime:4.5.2 -> 4.5.3
|         |    \--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|         +--- commons-codec:commons-codec:1.10
|         +--- net.sourceforge.htmlunit:htmlunit-core-js:2.17
|         +--- net.sourceforge.htmlunit:neko-htmlunit:2.21
|         |    \--- xerces:xercesImpl:2.11.0
|         |         \--- xml-apis:xml-apis:1.4.01
|         +--- net.sourceforge.cssparser:cssparser:0.9.18
|         |    \--- org.w3c.css:sac:1.3
|         +--- commons-io:commons-io:2.4 -> 2.5
|         +--- commons-logging:commons-logging:1.2
|         \--- org.eclipse.jetty.websocket:websocket-client:9.2.15.v20160210 -> 9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-io:9.4.6.v20170531
|              |    \--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-client:9.4.6.v20170531
|              |    +--- org.eclipse.jetty:jetty-http:9.4.6.v20170531
|              |    |    +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              |    |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              \--- org.eclipse.jetty.websocket:websocket-common:9.4.6.v20170531
|                   +--- org.eclipse.jetty.websocket:websocket-api:9.4.6.v20170531
|                   +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|                   \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
+--- org.seleniumhq.selenium:selenium-support:3.6.0 (*)
+--- org.seleniumhq.selenium:selenium-api:3.6.0
\--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)

net.sourceforge.htmlunit:htmlunit:2.27 -> 2.21 とバージョンダウンされているモジュールがありました。net.sourceforge.htmlunit:htmlunit の 2.27 が使用されるよう build.gradle を以下のように変更します。

dependencies {
    ..........

    // for Geb + Spock
    ..........
    testCompile("org.seleniumhq.selenium:htmlunit-driver:2.27")
    testCompile("net.sourceforge.htmlunit:htmlunit:2.27")
    ..........
}
  • testCompile("net.sourceforge.htmlunit:htmlunit:2.27") を追加します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

再び gradlew dependencies コマンドを実行すると、今度は net.sourceforge.htmlunit:htmlunit:2.27 と出力されました。

+--- org.seleniumhq.selenium:htmlunit-driver:2.27
|    +--- org.seleniumhq.selenium:selenium-api:3.4.0 -> 3.6.0
|    +--- org.seleniumhq.selenium:selenium-support:3.4.0 -> 3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-api:3.6.0
|    |    +--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)
|    |    +--- net.bytebuddy:byte-buddy:1.7.5
|    |    +--- org.apache.commons:commons-exec:1.3
|    |    +--- commons-codec:commons-codec:1.10
|    |    +--- commons-logging:commons-logging:1.2
|    |    +--- com.google.code.gson:gson:2.8.0 -> 2.8.1
|    |    +--- com.google.guava:guava:23.0 -> 22.0 (*)
|    |    +--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|    |    +--- org.apache.httpcomponents:httpcore:4.4.6
|    |    +--- net.java.dev.jna:jna:4.1.0 -> 4.2.2
|    |    \--- net.java.dev.jna:jna-platform:4.1.0 (*)
|    \--- net.sourceforge.htmlunit:htmlunit:2.27
|         +--- xalan:xalan:2.7.2
|         |    \--- xalan:serializer:2.7.2
|         +--- org.apache.commons:commons-lang3:3.5
|         +--- org.apache.httpcomponents:httpmime:4.5.3
|         |    \--- org.apache.httpcomponents:httpclient:4.5.3 (*)
|         +--- net.sourceforge.htmlunit:htmlunit-core-js:2.27
|         +--- net.sourceforge.htmlunit:neko-htmlunit:2.27
|         |    \--- xerces:xercesImpl:2.11.0
|         |         \--- xml-apis:xml-apis:1.4.01
|         +--- net.sourceforge.cssparser:cssparser:0.9.23
|         |    \--- org.w3c.css:sac:1.3
|         +--- commons-io:commons-io:2.5
|         +--- commons-logging:commons-logging:1.2
|         \--- org.eclipse.jetty.websocket:websocket-client:9.4.5.v20170502 -> 9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-io:9.4.6.v20170531
|              |    \--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              +--- org.eclipse.jetty:jetty-client:9.4.6.v20170531
|              |    +--- org.eclipse.jetty:jetty-http:9.4.6.v20170531
|              |    |    +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|              |    |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              |    \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
|              \--- org.eclipse.jetty.websocket:websocket-common:9.4.6.v20170531
|                   +--- org.eclipse.jetty.websocket:websocket-api:9.4.6.v20170531
|                   +--- org.eclipse.jetty:jetty-util:9.4.6.v20170531
|                   \--- org.eclipse.jetty:jetty-io:9.4.6.v20170531 (*)
+--- net.sourceforge.htmlunit:htmlunit:2.27 (*)
+--- org.seleniumhq.selenium:selenium-support:3.6.0 (*)
+--- org.seleniumhq.selenium:selenium-api:3.6.0
\--- org.seleniumhq.selenium:selenium-remote-driver:3.6.0 (*)

再度 HtmlUnit でテストを実行してみると、今度は成功するテストが出てきました。ただし、まだ一部のテストが失敗しています。失敗したテストを見てみると、

f:id:ksby:20171121005024p:plain

こちらは autocomplete のテストで失敗しており、5秒経過しても指定した selector が見つからないようです。

f:id:ksby:20171121005223p:plain

こちらはエラーメッセージが表示されていないようです。

いろいろ試してみると、後者のエラーメッセージが出ない方はテストを以下のように変更すると解消されました。

        when: "電話番号と郵便番号を入力する"
        $("#inquiryInput02Form").tel1 = tel1 << Keys.TAB
        $("#inquiryInput02Form").tel2 = tel2 << Keys.TAB
        $("#inquiryInput02Form").tel3 = tel3 << Keys.TAB
        $("#inquiryInput02Form").email = email << Keys.TAB

↓↓↓

        when: "電話番号と郵便番号を入力する"
        $("#inquiryInput02Form").tel1 = tel1
        $("#tel2") << ""
        $("#inquiryInput02Form").tel2 = tel2
        $("#tel3") << ""
        $("#inquiryInput02Form").tel3 = tel3
        $("#email") << ""
        $("#inquiryInput02Form").email = email
        $("#tel1") << ""

テストを実行すると autocomplete 以外のテストが成功します。

f:id:ksby:20171122011927p:plain

HtmlUnit だと Keys.TAB を送信しても次の入力項目にフォーカスが移動せず、フォーカスを移動したい入力項目に << "" を送信しないといけないようです。

ということは autocomplete のテストを以下のように修正すると、

        when: "郵便番号を入力する"
        $("#inquiryInput02Form").zipcode1 = "100"
        $("#inquiryInput02Form").zipcode2 = "0005" << Keys.TAB

↓↓↓

        when: "郵便番号を入力する"
        $("#inquiryInput02Form").zipcode1 = "100"
        $("#inquiryInput02Form").zipcode2 = "0005"
        $("#address") << ""

今度は全てのテストが成功しました。

f:id:ksby:20171122012953p:plain

HtmlUnitGeb のテスト対象ブラウザから外す

$("#inquiryInput02Form").tel1 = tel1 << Keys.TAB のように記述して TAB キーを押したら次の入力項目にフォーカスが移動できた方が個人的には分かりやすく、かつテストが書きやすいのと、FirefoxChrome の headless が十分使いやすいので、HtmlUnitGeb のテスト対象ブラウザから外すことにします。

build.gradle を以下のように変更します。

dependencies {
    ..........

    // for Geb + Spock
    testCompile("org.gebish:geb-spock:2.0")
    testCompile("org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-firefox-driver:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-support:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-api:${seleniumVersion}")
    testCompile("org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}")
}

..........

// for Geb + Spock Integration Test
def drivers = ["chrome", "firefox"]
drivers.each { driver ->
    task "${driver}Test"(type: Test) {
        // 前回実行時以降に何も更新されていなくても必ず実行する
        outputs.upToDateWhen { false }
        systemProperty "geb.env", driver
        exclude "ksbysample/**"
    }
}
task gebTest {
    dependsOn drivers.collect { tasks["${it}Test"] }
    enabled = false
}
  • 以下の2行を削除します。
    • testCompile("org.seleniumhq.selenium:htmlunit-driver:2.27")
    • testCompile("net.sourceforge.htmlunit:htmlunit:2.27")
  • def drivers = ["chrome", "firefox", "htmlunit"]def drivers = ["chrome", "firefox"] に変更します。

変更後、Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

src/test/resources/GebConfig.groovy を以下のように変更します。

environments {

    chrome {
        driver = {
            ChromeOptions chromeOptions = new ChromeOptions()
            chromeOptions.setHeadless(true)
            new ChromeDriver(chromeOptions)
        }
    }

    firefox {
        driver = {
            FirefoxOptions firefoxOptions = new FirefoxOptions()
            firefoxOptions.setHeadless(true)
            new FirefoxDriver(firefoxOptions)
        }
    }

}
  • environments から htmlunit { new HtmlUnitDriver(true) } を削除します。

gebTest タスクを実行して "BUILD SUCCESSFUL" が表示されることを確認します。

f:id:ksby:20171123230003p:plain

Geb でいろいろ試してみる

画面の外にボタンがあるとクリックできない?

Geb について書かれた記事を見ていると、ボタンがブラウザの Window 内に表示されていない場合クリックできないと書かれたものを何回か見かけので、確認してみます。

まずは <br> タグを大量に入れて、「次へ」ボタンを画面の外に追い出します。

f:id:ksby:20171123230939p:plain

src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy に以下のテストを記述します。

    def "次へボタンが画面の外にあるとクリックできない?" () {
        setup: "入力画面1を表示してデータを入力する"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)
        $("#inquiryInput01Form").sex = "1"
        // 念のため、フォーカスを「お名前(漢字)」へ移動する
        $("#lastname") << ""

        expect: "次へボタンをクリックする"
        form.btnNext.click(InquiryInput02Page)
    }

テストを実行すると問題なく成功しますね。。。

f:id:ksby:20171123232224p:plain

src/test/resources/GebConfig.groovy を以下のように変更して headless モードを解除し、

driver = {
    FirefoxOptions firefoxOptions = new FirefoxOptions()
//    firefoxOptions.setHeadless(true)
    new FirefoxDriver(firefoxOptions)
}

src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy に Thread.sleep(3000) を追加して、

    def "次へボタンが画面の外にあるとクリックできない?" () {
        setup: "入力画面1を表示してデータを入力する"
        to InquiryInput01Page
        form.setValueList(maxLengthValueList)
        $("#inquiryInput01Form").sex = "1"
        // 念のため、フォーカスを「お名前(漢字)」へ移動する
        $("#lastname") << ""
        Thread.sleep(3000)

        expect: "次へボタンをクリックする"
        form.btnNext.click(InquiryInput02Page)
    }

テストを実行してブラウザの動きを見てみたところ、form.btnNext.click(InquiryInput02Page) の処理を実行する時に「次へ」ボタンへフォーカスが移動して画面の見える位置に移動していました。Firefox Quantum だと問題なく動作するようです。

src/test/resources/GebConfig.groovy を以下のように変更してブラウザを Chrome に変更しても、

driver = {
//    FirefoxOptions firefoxOptions = new FirefoxOptions()
//    firefoxOptions.setHeadless(true)
//    new FirefoxDriver(firefoxOptions)

    ChromeOptions chromeOptions = new ChromeOptions()
    chromeOptions.setHeadless(true)
    new ChromeDriver(chromeOptions)
}

テストは成功しました。Chrome も問題ないようです。

f:id:ksby:20171123232814p:plain

gradle から gebTest タスクを実行しても成功しました。

f:id:ksby:20171123233129p:plain

画面の外にボタンがあっても問題なくクリックできるようです。変更したソースは元に戻します。

maxlength を超える文字列は入力できない

Geb で値をセットした時に maxlength を超える文字列がセットされるのか気になったので、確認してみます。

src/test/groovy/geb/gebspec/inquiry/InquiryTestSpec.groovy に以下のテストを記述して、

    def "maxlength を超えた文字列は入力できない?"() {
        setup:
        to InquiryInput01Page
        // lastname の maxlength は 20 なので 21文字セットしてみる
        $("#lastname").value("あ" * 20 + "い")

        expect:
        $("#lastname") == "あ" * 20 + "い"
    }

テストを実行すると失敗しました。21文字目の "い" がセットされていませんでした。

f:id:ksby:20171123235347p:plain

maxlength を超える文字列はセットできないようです。

FormModule クラスの setValueList メソッドを値セット+Tab キークリックするよう変更する

src/test/groovy/geb/module/FormModule.groovy の setValueList メソッドは今は以下の実装ですが、

    void setValueList(valueList) {
        valueList.each {
            $(it.key).value(it.value)
        }
    }

空のデータをセットしてエラーメッセージを表示する以下のテストを実行した時に、

    def "テスト"() {
        setup:
        to InquiryInput01Page
        form.setValueList(initialValueList)
        Thread.sleep(10000)

        expect:
        true
    }

年齢に空文字列をセットしたのに、フォーカスが移動していないのでエラーメッセージが表示されていないことに気づきました。

f:id:ksby:20171124001756p:plain

src/test/groovy/geb/module/FormModule.groovy を以下のよう変更し、値をセットした後にTABキーをクリックして blur イベントが発生するようにします。

    void setValueList(valueList) {
        valueList.each {
            $(it.key).value(it.value)
            $(it.key) << Keys.TAB
        }
    }
  • $(it.key) << Keys.TAB を追加します。

これでさっきのテストを実行すると、今度は年齢に空文字列をセットしてTABキーがクリックされるのでエラーメッセージが表示されます。

f:id:ksby:20171124002255p:plain

次回は。。。

Geb を使用したテストの作り方が簡単ながらも分かったので一旦ここで終了します。入力画面3に進む前に少し迷走する予定です。。。

履歴

2017/11/24
初版発行。