liguofeng29’s blog

個人勉強用ブログだっす。

groovy,geb(selenium),spockによる自動化テスト その8 - Page Object Pattern - Pageの属性

Pageオブジェクトの属性

url属性 : PageのURLを表す

・シナリオ

  1. 相対パス指定のpageに移動 (to ExamplePage1)
  2. 絶対パス指定のpageに移動 (to ExamplePage2)
  3. 引数指定のpageに移動 (to ExamplePageWithArguments)
  4. インスタンス引数指定のpageに移動 (to ExamplePageWithInstance, new Person(id : “12345”))
/**
 * Page Object
 *
 * URL
 * urlは baseUrl + パスで構成
 * Page Objectのurlは、相対、絶対両方OK
  */
import geb.Browser
import geb.Page

Browser.drive {
    // baseUrl
    config.baseUrl = "http://www.gebish.org/"

    to ExamplePage1

    sleep 3 * 1000 // for debug

    to ExamplePage2

    sleep 3 * 1000 // for debug

    to ExamplePageWithArguments, "args1", "args2"
    // will go to「http://www.gebish.org/manual/args1/args2」

    sleep 3 * 1000 // for debug

    to ExamplePageWithInstance, new Person(id : "12345")
    // will go to [http://www.gebish.org/manual/12345]
    sleep 3 * 1000 // for debug
}.quit()

/** 相対url **/
class ExamplePage1 extends Page {
    static url = "manual/current/all.html"
}

/** 絶対url **/
class ExamplePage2 extends Page {
    static url = "http://www.gebish.org/manual/current/#at-checking"
}

/** 引数ありurl **/
class ExamplePageWithArguments extends Page {
    static url = "http://www.gebish.org/manual"
}

/** インスタンスを引数とするurl **/
class ExamplePageWithInstance extends Page{
    static url = "http://www.gebish.org/manual/"

    String convertToPath(Person person) {
        person.id.toString()
    }
}

class Person {
    String id
}

f:id:liguofeng29:20170302221619g:plain

at属性 : 呼ばれたpageが予期したpageであるのかを検証する

・シナリオ

  1. Yahooに移動する (to YahooTopWithRightAt)
  2. 検索ボタンをクリックする
  3. Yahooページに移動する (YahooTopWithWrongAt )
  4. at処理でgeb.waiting.WaitTimeoutExceptionが発生する
/**
 * Page Object
 *
 * at
 * 呼ばれたpageが予期したpageであるのかを検証する
 */
import geb.Browser
import geb.Page

def keywords = 'javait.hatenablog.com'

Browser.drive {

    to YahooTopWithRightAt
    println 'ログイン画面 : ' + title
    検索(keywords)
    println 'ホーム画面 : ' + title


    // geb.waiting.WaitTimeoutException発生
    to YahooTopWithWrongAt
    println 'to YahooTopWithWrongAtのat条件を満たさないので、処理はここまでがこないはず'

    sleep 10 * 1000 // for debug
}.quit()


class YahooTopWithRightAt extends Page {

    static url = 'http://www.yahoo.co.jp/'

    static at = {
        title.endsWith("Yahoo! JAPAN")
    }

    static content = {
        検索欄 { $('#srchtxt') }
        検索ボタン { $('#srchbtn') }
    }

    // pageが提供するサービス
    void 検索 (String keywords) {
        検索欄 = keywords
        検索ボタン.click()
    }
}

class YahooTopWithWrongAt extends Page {

    static url = 'http://www.yahoo.co.jp/'

    static at = {
        // GebConfig.groovyに設定したtimeoutでエラー発生
        title.endsWith("xxxxxxxxxxxxxxx wrong")
    }

    static content = {
        検索欄 { $('#srchtxt') }
        検索ボタン { $('#srchbtn') }
    }

    // pageが提供するサービス
    void 検索 (String keywords) {
        検索欄 = keywords
        検索ボタン.click()
    }
}

Content DSL : pageの要素を定義する

/**
 * Page Object
 *
 * Content DSL
 * 引数:
 * required : default true
 *            要素が存在しない場合、RequiredPageContentNotPresent例外を投げるかどうか
 *
 * cache :  defalut false
 *          ページをリロードしないと値はそのままだ。
 *
 * to    :  指定pageのURLを開く
 * wait  :  defalut false 値、true, a string, a number, a 2 element list of numbers
 * toWait : default false 、toと組み合わせで使う
 *          主に非同期遷移とかの場合、ページ遷移を待つのか?未検証。
 *
 * page  :  defalut null
 */
import geb.Browser
import geb.Page

def testPage = new File('src/main/java/html/script24.html')

Browser.drive {
    to YahooTopTestRequired

    /** required 検証 **/
    /** http://www.gebish.org/manual/current/#code-required-code **/
    try {
        println 存在しない要素1
    } catch (MissingPropertyException e) {
        println "required: true の場合は、MissingPropertyException発生 : " + 存在しない要素2
    }

    println "required: false の場合は、null : " + 存在しない要素2

    /** cache 検証 **/
    /** http://www.gebish.org/manual/current/#code-cache-code **/

    to YahooTopTestCache

    assert キャッシュ利用する == "value1"
    assert キャッシュ利用しない == "value1"

    value1 = "value99"

    assert キャッシュ利用する == "value1"        // 値が変わらない
    // assert キャッシュ利用しない == "value99"   // 変わるはずだが・・・・ 公式ページのコードでもやったが。

    /** page 検証 **/
    // 通常IFrame操作で利用する。

    /** to 検証 **/
    /* The to option allows the definition of which page the browser will be sent to if the content is clicked. */
    /** http://www.gebish.org/manual/current/#content-dsl-to **/
    config.baseUrl = new File(testPage.getAbsolutePath()).toURI()
    to PageTestTo
    リンク.click()

    /** NOTE
   *
   * toを使う際に、指定pageにはatを実装すべきである
   * toを指定することで実際に遷移が行われるわけではなく、指定pageのatが実行される。
   *
   * テキスト.click()をクリックしてもページ遷移は発生しない。
   */

     sleep 10 * 1000 // for debug
}.quit()

class YahooTopTestRequired extends Page {

    static url = 'http://www.yahoo.co.jp/'

    static at = { title.endsWith("Yahoo! JAPAN") }

    static content = {
        検索欄 (required: true) { $('#srchtxt') }
        検索ボタン (required: true) { $('#srchbtn') }

        存在しない要素1 (required: true) { $('#abcde1').text() }
        存在しない要素2 (required: false) { $('#abcde2').text() }
    }
}

class YahooTopTestCache extends Page {

    def value1 = "value1"

    static url = 'http://www.yahoo.co.jp/'

    static at = { title.endsWith("Yahoo! JAPAN") }

    static content = {
        検索欄 (required: true) { $('#srchtxt') }
        検索ボタン (required: true) { $('#srchbtn') }

        キャッシュ利用する   (cache: true) { value1 }
        // キャッシュ利用しない (cache: false) { value1 }
        キャッシュ利用しない  { value1 }
    }
}

class PageTestTo extends Page {
    static url = "script24.html"
    static content = {
        テキスト(to : GoogleTopTestTo) {$('#div1')}
        リンク (to : GoogleTopTestTo) {$('#googleLink')}
    }
}

class GoogleTopTestTo extends Page {
    static url = 'http://www.google.co.jp/'
    static at = { waitFor { title.startsWith("Google")} }
}

groovy,geb(selenium),spockによる自動化テスト その7 - Page Object Pattern

gebのPage Object

Page Object Patternとは?

PageObjectデザインパターンとは、アプリケーションの画面を1つのオブジェクトとしてとらえるデザインパターンの1種のことです。 Seleniumの公式サイトでも推奨されている、保守性の高いテストコードの書き方です。

公式サイトに記載されているPageObjectの原則は以下のようなものです。
・The public methods represent the services that the page offers(publicメソッドは、ページが提供するサービスを表す)
・Try not to expose the the internals of the page (ページの内部を公開しないこと)
・Generally don’t make assertions (原則としてassertionを行わないこと)
・Methods return other PageObjects (メソッドは他のPageObjectsを返す)
・Need not represent an entire page (ページ全体を表す必要はない)
・Different results for the same action are modelled as different as different methods (同じアクションに対して異なる結果となる場合には異なるメソッドとしてモデル化する)

参考:http://softwaretest.jp/labo/tech/labo-292/

geb.Page

シナリオ :
  1. yahoo.co.jpを開く
  2. 検索文字を入力
  3. 検索ボタンをクリック
import geb.Browser
import geb.Page

def keywords = 'javait.hatenablog.com'

Browser.drive {

    to YahooTop
    println 'ログイン画面 : ' + title

    検索(keywords)

    println 'ホーム画面 : ' + title

    waitFor {
        $('h3').size() > 0
    }

    $('h3').each { println "* ${it.text()}" }

    sleep 10 * 1000 // for debug

}.quit()

/**
 * Page Object
 *
 * url:相対パス、絶対パス
 * at: page確認条件
 * content DSL:pageの要素
 */
class YahooTop extends Page {

    static url = 'http://www.yahoo.co.jp/'

    static at = {
        // String geb.Page.getTitle()
        waitFor { title.endsWith("Yahoo! JAPAN")
    } }

    static content = {
        検索欄 { $('#srchtxt') }
        検索ボタン { $('#srchbtn') }
    }

    // pageが提供するサービス
    void 検索 (String keywords) {
        検索欄 = keywords
        検索ボタン.click()
    }
}

f:id:liguofeng29:20170302220102g:plain

groovy,geb(selenium),spockによる自動化テスト その6

gebでjavascriptを利用する

  1. javascript変数を取得
  2. メソッド呼び出し
  3. js追加

script6.html

<html>
<title>for javascript</title>
<script type="text/javascript">
   var var1 = 100;

   function add(a, b) {
       return a + b;
   }
</script>
<body>
</body>
</html>

script6_js.groovy

/**
 * Geb for javascript
 *
 * http://www.gebish.org/manual/
 *
 * 1. get variable from javascript
 * 2. call method
 * 3. add script
 */
import geb.Browser

def testPage = new File('src/main/java/html/script6.html')

Browser.drive {
    // 指定URLでブラウザオープン
    go testPage.toURI().toString()

    // JavascriptInterface geb.Browser.getJs()

    // get variable
    assert js.var1 == 100
    // 2. call method
    assert js.add(1, 10) == 11
    // 3. add script
    assert js."document.title" == "for javascript"
    js."alert('add script')"

    sleep 10 * 1000
}.quit()

f:id:liguofeng29:20170301210637g:plain

gebでpopupを制御する

SeleniumのWebDriverはalert, confirm, promptに対するソリューションを提供してないらしい。
gebでは、AlertAndConfirmSupporでalert,confirmに対する操作が可能。
※ promptはサポートしてないらしい。

API from AlertAndConfirmSuppor

  • alert

    • def withAlert(Closure actions)
    • def withAlert(Map params, Closure actions)
    • void withNoAlert(Closure actions) → 意図してない個所でalertが発生してするとAssertionError発生
  • confirm

    • def withConfirm(boolean ok, Closure actions)
    • def withConfirm(Closure actions)
    • def withConfirm(Map params, Closure actions)
    • def withConfirm(Map params, boolean ok, Closure actions)
    • void withNoConfirm(Closure actions)
/**
 * Geb for popup
 *
 * http://www.gebish.org/manual/
 *
 * use AlertAndConfirmSupport for alert,confirm,prompt
 *
 * Geb does not provide any support for prompt() due to its infrequent and generally discouraged use.

 */
import geb.Browser
import geb.Page

def testPage = new File('src/main/java/html/script7.html')

Browser.drive {
    // baseUrl設定
    config.baseUrl = new File(testPage.getAbsolutePath()).toURI()

    to PopupPage

    assert withAlert(wait: true) { showAlert.click() } == "alert!"
    //assert withAlert(wait: true) { showAlert.click() } == "some alert!" // エラー

    // actionsでalertが表示されるとAssertionError発生する
    withNoAlert { $("input", name: "dontShowAlert").click() }

    assert withConfirm(true) { showConfirm.click() } == "confirm?"
    sleep 2 * 1000
    assert withConfirm(false) { showConfirm.click() } == "confirm?"

    sleep 10 * 1000
}.quit()

class PopupPage extends Page{
    // baseURL +
    static url = "script7.html"
    static content = {
        showAlert      {$("input", name: "showAlert")}
        donotShowAlert {$("input", name: "dontShowAlert")}
        showConfirm    {$("input", name: "showConfirm")}
    }
}

f:id:liguofeng29:20170301211736g:plain

waitFor

Ajaxなので非同期処理の際に、処理完了を待つ際に使う。
geb.PageクラスのAPIである。

/**
 * Geb for waitFor
 * http://www.gebish.org/manual/
 * Ajaxなど処理待ちが発生する場合、waitForを利用して待つことができる
 *
 * API:
 *
 * waitFor {}           デフォルト
 * waitFor(10) {}       最長10秒待ち
 * waitFor(10, 0.5){}   最長10秒待ち、 0.5秒間隔
 * waitFor("quick"){}   GebConfig.groovyの設定
 *
 *
 * Geb does not provide any support for prompt() due to its infrequent and generally discouraged use.

 */
import geb.Browser

Browser.drive {
    go 'https://www.google.com/recaptcha/demo/ajax'

    $('input', value: 'Click Me').click()
    waitFor {
        $('#recaptcha_challenge_image').size() > 0
    }
    println $('#recaptcha_challenge_image').attr('src')

    sleep 10 * 1000
}.quit()

f:id:liguofeng29:20170301212218g:plain

WebAPI呼び出し

{
    "name": "Alex",
    "age": "20"
}
/**
 * Geb for calling api
 * http://www.gebish.org/manual/
 */
import geb.Browser
import groovy.json.JsonSlurper

def testPage = new File('src/main/java/json/json-data1.json')

Browser.drive {
    URL apiUrl = testPage.toURL()
    def data = new JsonSlurper().parseText(apiUrl.text)

    println data.name
    println data.age

    assert data.name == 'Alex'
    assert data.age == '20'

}.quit()

groovy,geb(selenium),spockによる自動化テスト その5

gebでframeを操作する方法

  1. 直接操作する
  2. Page Objectを利用する

script5.html

<html>
<body>
    <iframe name="header" src="script5_header_frame.html"></iframe>
    <iframe id="content" src="script5_content_frame.html"></iframe>
    <iframe id="footer" src="script5_footer_frame.html"></iframe>
</body>
</html>

script5_header_frame.html

<html>
<body>
    <span>HEADER</span>
</body>
</html>

script5_content_frame.html

<html>
<body>
    <div>CONTENT1</div>
    <div>CONTENT2</div>
    <div>CONTENT3</div>
</body>
</html>

script5_footer_frame.html

<html>
<body>
    <span>FOOTER</span>
</body>
</html>

script5_Geb_API_Frame.groovy

/**
 * Geb for Frame API
 *
 * http://www.gebish.org/manual/
 *
 * Frameを操作する方法
 * 1. 直接操作
 * 2. Page Object Model利用
 *
 */
import geb.Browser
import geb.Page

def testPage = new File('src/main/java/html/script5.html')

Browser.drive {
    // baseUrl設定
    config.baseUrl = new File(testPage.getAbsolutePath()).toURI()

    // 指定URLでブラウザオープン
    go testPage.toURI().toString()

    /** 1. 直接操作 ** **/
    // page.withFameと同価
    withFrame('header') {assert $('span').text() == 'HEADER' }
    withFrame('footer') {assert $('span').text() == 'FOOTER' }

    withFrame(0) { assert $('span').text() == 'HEADER' }
    withFrame(2) { assert $('span').text() == 'FOOTER' }

    withFrame($('#content')) { assert $('div', 2).text() == 'CONTENT3' }


    /** 2. Page Object **/
    to LayoutPage
    withFrame(contentsFrame) { println "contents frame内のdiv要素数 : " + ($('div').size()) }

    sleep 10 * 1000
}.quit()


class LayoutPage extends Page {
    // baseUrl + url
    static url = 'script5.html'

    static content = {
        contentsFrame(page: ContentsPage){$('#content')}
    }
}

class ContentsPage extends Page {
    // baseUrl + url
    static url = 'script5_content_frame.html'
}

console

contents frame内のdiv要素数 : 3

groovy,geb(selenium),spockによる自動化テスト その4

Geb API

form, input, checkbox, radioなどのHTMLElementの操作する

script4.html

<form id="form1">
    <div class="input1">
        <input name="username1" type="text" placeholder="input your name">
        <input name="password1" type="text" placeholder="input your password">
    </div>
    <div class="input2">
        <input name="username2" type="text" placeholder="input your name">
        <input name="password2" type="text" placeholder="input your password">
    </div>
        <div class="input3">
        <input name="username3" type="text" placeholder="input your name">
        <input name="password3" type="text" placeholder="input your password">
    </div>
</form>


<form id="form2">
    <input type="checkbox" name="checkbox1" value="single-check1"/>
    <input type="checkbox" name="checkbox2" value="single-check2"/>
</form>

<form id="form3">
    <input type="checkbox" name="multicheck" value="multi-check1"/>
    <input type="checkbox" name="multicheck" value="multi-check2"/>
    <input type="checkbox" name="multicheck" value="multi-check3"/>
</form>

<form id="form4">
    <input type="radio" name="sex" value="male" />
    <input type="radio" name="sex" value="female" />
</form>

<form id="form5">
    <input type="file" name="csvFile" />
</form>

<form id="form6">
    <select name="optionlist1">
        <option value="1">option1</option>
        <option value="2">option2</option>
        <option value="3">option3</option>
    </select>
    <select name="optionlist2">
        <option value="1">option1</option>
        <option value="2">option2</option>
        <option value="3">option3</option>
    </select>
</form>

script4_Geb_API.groovy

/**
 * Geb API
 *
 * http://www.gebish.org/manual/
 */
import geb.Browser

def testPage = new File('src/main/java/html/script4.html')

Browser.drive {
    // 指定URLでブラウザオープン
    go testPage.toURI().toString()

    /**************/
    /** for form **/
    /**************/
    // direct
    $('input', name: 'username1').value("ユーザ1")
    $('input', name: 'password1').value("パスワード1")

    // use shortcuts
    def form = $('#form1')
    form.username2 = "ユーザ2"
    form.password2 = "パスワード2"

    // use with
    $('#form1').with {
        username3 = 'ユーザ3'
        password3 = 'パスワード3'
    }

    // get value
    println ($('input', name: 'username1').value())
    println ($('#form1').password1)

    /******************/
    /** for checkbox **/
    /******************/
    // click and set value
    $('input', name: 'checkbox1').click()
    $('checkbox', name: 'checkbox1').value(true) // or false

    // click and set value with shortcut
    $('#form2').checkbox2().click()
    $('#form2').checkbox2().value(false)       // or true

    // get value
    println ($('input', name: 'checkbox1').value())
    println ($('#form2').checkbox2().value())


    /************************/
    /** for multi-checkbox **/
    /************************/
    $('#form3').multicheck = true // check all
    println ($('#form3').multicheck) // [multi-check1, multi-check2, multi-check3]

    /************************/
    /** for radio          **/
    /************************/
    $('#form4').sex = "female" // set
    println ($('#form4').sex)  // get female
    assert $('#form4').sex == 'female' // assert

    /************************/
    /** for input file     **/
    /************************/
    // need to set absolute path
    $('#form5').csvFile = new File("src/main/java/csv/data.csv").getAbsolutePath()
    // Or
    // $('input', name : 'csvFile').value('../csv/data.csv')

    /*************************/
    /** for drop-down select**/
    /*************************/
    $('select', name: 'optionlist1').value('2') // Or 'option2'

    // use shortcut
    $('#form6').optionlist2 = 'option3'
    // $('#form6').optionlist = 3

    // get value
    println ($('#form6').optionlist1) // print 2

    // assert
    assert $('#form6').optionlist2 == '3'

    sleep 10 * 1000

}.quit()

f:id:liguofeng29:20170301201129g:plain