groovy,geb(selenium),spockによる自動化テスト その8 - Page Object Pattern - Pageの属性
Pageオブジェクトの属性
url属性 : PageのURLを表す
・シナリオ
- 相対パス指定のpageに移動 (to ExamplePage1)
- 絶対パス指定のpageに移動 (to ExamplePage2)
- 引数指定のpageに移動 (to ExamplePageWithArguments)
- インスタンス引数指定の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 }
at属性 : 呼ばれたpageが予期したpageであるのかを検証する
・シナリオ
- Yahooに移動する (to YahooTopWithRightAt)
- 検索ボタンをクリックする
- Yahooページに移動する (YahooTopWithWrongAt )
- 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
シナリオ :
- yahoo.co.jpを開く
- 検索文字を入力
- 検索ボタンをクリック
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() } }
groovy,geb(selenium),spockによる自動化テスト その6
gebでjavascriptを利用する
- javascript変数を取得
- メソッド呼び出し
- 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()
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")} } }
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()
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を操作する方法
- 直接操作する
- 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()