はい。どーも。チーム内でジェバンジェリスト呼ばわりされてるシーバです。Geb: ジェブ。あんまり広めるような熱意はないけどー。
WebDriver直接よりもGebでPageObjectパターン使ってSpockでテストをするのが好きなので。
Gebのドキュメント を流し読みして気に入ったところをメモ。
Configuration の章
The Book Of Geb - Configuration - version 0.10.0
GebConfig.groovyって名前のファイルをclasspathのルートディレクトリに入れておいたら読み込んでくれる。
src/test/resources直下に入れとくと良いね。
んで、システムプロパティのgeb.envの値によって別の設定を適用するとかもできるので便利。
import org.openqa.selenium.firefox.FirefoxDriver driver = { new FirefoxDriver() } baseUrl = "http://localhost/" reportsDir = "target/geb-reports"
driver
ここでdriverの設定を色々しておくことができる。
インスタンス化だけじゃなくてプロキシ設定したりも。
geb.env使ったらドライバー切り替えることもできるね。
baseUrl
baseUrlを設定できる。
指定しておくとGebのテストの中は相対パス的なもので操作できる。
サンプル:
http://www.gebish.org/manual/current/browser.html#the_base_url
reportsDir
レポート出力ディレクトリ。
autoClearCookies
クッキーを消さないようにする設定もある。
デフォルトではテストケース毎にクッキーがクリアされるので、セッションをキープしたいときとかにfalseを設定したりすると良い。
autoClearCookies = false
他にも色々あるけど。気になるのはこれくらい。
Browser の章
The Book Of Geb - The Browser - version 0.10.0
GebConfigでdriver定義して、Spock用のGebSpecを使うから、直接BrowserやDriverを使うことはないんだけど、Specのうらっかわにあるのはこいつらなので意識はしておく。
to()
to() = via() + at()
みたいなイメージ。ページに移動(via)したあとに、そのページにいることを確認する(at)。
んー。あとはいいや。
Navigator API(Content)
The Book Of Geb - Interacting with content - version 0.10.0
jQueryみたいな感じでDOMにアクセスできる。ここがGebのメインかな。
けど、いっぱいあるからまた次の機会に書こう。Σ(゚Д゚;エーッ!
(追記: 結局書いた)
$関数
$関数で要素にアクセスできる。こいつは、Navigatorオブジェクトを返す。
見つからなかったら"empty"ナビゲーターを返す。
定義
$(«css selector», «index or range», «attribute / text matchers»)
例えばこれだと、
$("h1", 2, class: "heading")
h1タグでクラスが"heading"の要素の中の、3番目(indexが2ってことだからね)のものを指す。
どのパラメータもオプショナルなので、こんな風にも書ける
$("div p", 0) $("div p", title: "something") $(0) $(title: "something")
index or range の部分
こんなHTMLのときに
<p>a</p> <p>b</p> <p>c</p>
こんな感じになる。へぇー。
$("p", 0).text() == "a" $("p", 2).text() == "c" $("p", 0..1)*.text() = ["a", "b"] $("p", 1..2)*.text() = ["b", "c"]
attribute / text matchers の部分
タグの属性が指定できるのと。あと、テキストは"text:"で指定できる。
<p attr1="a" attr2="b">p1</p> <p attr1="a" attr2="c">p2</p>
$("p", attr1: "a").size() == 2 $("p", attr2: "c").size() == 1 $("p", attr1: "a", attr2: "b").size() == 1 $("p", text: "p1").size() == 1 $("p", text: "p1", attr1: "a").size() == 1
パターンも使える。
$("p", text: ~/p./).size() == 2
こんなんもできるよ!詳細は The Book Of Geb - Interacting with content - version 0.10.0
$("p", text: startsWith("p")).size() == 2 $("p", text: endsWith("2")).size() == 1
Finding & Filtering
んー。$が返したNavigatorに対してまた$で探すこともできるよ。ってくらいでいいかな。
$("div").$("p.a")
Traversing
ご近所さんアクセス。よくあるやつすね。
<div class="a"> <div class="b"> <p class="c"></p> <p class="d"></p> <p class="e"></p> </div> <div class="f"></div> </div>
$("p.d").previous() // 'p.c' $("p.e").prevAll() // 'p.c' & 'p.d' $("p.d").next() // 'p.e' $("p.c").nextAll() // 'p.d' & 'p.e' $("p.d").parent() // 'div.b' $("p.c").siblings() // 'p.d' & 'p.e' $("div.a").children() // 'div.b' & 'div.f'
へー。こんなんもできるんか。
$("p").next(".c") $("p").next(class: "c") $("p").next("p", class: "c")
closestとかxxxUntilみたいなのもあるみたいだけど、使わんかな。
Clicking
Navigatorオブジェクトにはclick()メソッドがあって、名前の通りその要素をクリックする。
複数の要素を指す場合は、最初の要素だけに対してクリックが呼ばれるんだって。
clickメソッドにPageクラスを渡すと、click(ExamplePage)みたいにすると、クリックした後にそのページにtoしてるのと同じ動作をするみたい。
なので、ExamplePageのatアサーションが呼ばれて、カレントページがExamplePageになる。
タグ名、属性、テキスト、クラス属性へのアクセス
<p title="a" class="a para">a</p> <p title="b" class="b para">b</p> <p title="c" class="c para">c</p>
最初の要素の値を返すみたい。
$("p").text() == "a" $("p").tag() == "p" $("p").@title == "a" $("p").classes() == ["a", "para"]
全部欲しかったらこんな感じ。
$("p")*.text() == ["a", "b", "c"] $("p")*.tag() == ["p", "p", "p"] $("p")*.@title == ["a", "b", "c"] $("p")*.classes() == [["a", "para"], ["b", "para"], ["c", "para"]]
フォーム
フォーム用には便利なショートカットを用意してくれてる。
<form> <input type="text" name="geb" value="testing" /> </form>
testingからgoodnessに書き換え。(1行目と3行目はアサーションね。)
$("form").geb == "testing" $("form").geb = "goodness" $("form").geb == "goodness"
こういう処理のショートカットになってます
$("form").find("input", name: "geb").value() == "testing" $("form").find("input", name: "geb").value("goodness") $("form").find("input", name: "geb").value() == "goodness"
select
valueだけじゃなくて、textでも指定できるんだってー。
<select name="artist"> <option value="1">Ima Robot</option> <option value="2">Edward Sharpe and the Magnetic Zeros</option> <option value="3">Alexander</option> </select>
$("form").artist = "1" // 最初のんが選択される $("form").artist = 2 // 2番目のんが選択される $("form").artist = "Ima Robot" // テキスト見て、最初のんが選択される
マルチセレクトの場合はこんな感じ:
$("form").genres = ["2", "3"]
checkbox
true/falseでも値でも設定できる。
radio
ラベル名でも設定できるんだって。
インタラクション
インタラクション周り(キーを押したり、ダブルクリックしたり、ドラッグ&ドロップしたり)は今はいっかな。
Pages の章
The Book Of Geb - Pages - version 0.10.0
PageObjectパターン用に、Pageスーパークラスを継承して使う。
コンテンツテンプレート
こんな風にDSLを使って、ページ内のコンテンツを定義しておける。
class ExamplePage extends Page { static content = { theDiv { $('div#a') } theDivText { theDiv.text() } } }
Navigationオブジェクトを返すことが多いけど、別にStringでもbooleanでも何でもOKだし、パラメータ使っても大丈夫。
例えば
class ExamplePage extends Page { static content = { theDiv { id -> $('div', id: id) } } }
みたいにtheDivという名前のコンテンツテンプレートを定義しておくと、こんな風にテスト書ける:
when: to ExamplePage then: theDiv("a").text() == "a"
指定の方法:
名前 (オプション) { 定義 }
requiredオプション(デフォルト値: true)
static content = { theDiv (required: false) { $('div', id: "b") } }
requiredオプションがtrueだと、その要素が存在しない場合に、そのコンテンツにアクセスしようとしたら例外発生。
(Navigatorオブジェクト以外を返す場合はこのオプションは無視される。)
toオプション(デフォルト値: null)
class ExamplePage extends Page { static content = { helpLink(to: HelpPage) { $("a", text: "Help") } } }
コンテンツがクリックされたときに、どのページオブジェクトに遷移するかを指定できる。
上記の場合は、helpLink.click()を呼び出すと、HelpPageにto()で移動したのと同じ状態になる。
つまり、atアサーションが実行されて、カレントページがHelpPageになってるってこと。
オプションその他
cache, wait, toWait, pageとかあるね。今のところ興味ない。
at
atは to ExamplePage で移動したりat ExamplePageでチェックするときに呼び出される:
class ExamplePage extends Page { static at = { $("h1").text() == "Example" } }
そのページにいることをチェックするためなので、タイトル指定する事が多いかな
static at = { title == "Geb" }
url
urlは そのページに移動するときに使われる。設定のところで触れたbaseUrlがここで使われる:
class ExamplePage extends Page { static url = "examples" }
toとパラメータ
例えばbaseUrlが
baseUrl="http://myapp.com/"
で、Pageクラスが
class ExamplePage extends Page { static url = "example" }
のとき。
to ExamplePage
だと "http://myapp.com/example" に遷移する。
to ExamplePage, 1, 2
だと、"http://myapp.com/example/1/2"
パラメータを渡すと、パスになる。
to ExamplePage, q: "hoge"
だと、"http://myapp.com/example?q=hoge"
名前付きパラメータは、クエリパラメータになる。
Pageの継承
をしたら、contentはマージされるす。
Modules の章
The Book Of Geb - Modules - version 0.10.0
モジュールは、こう・・・再利用可能なカタマリを定義しておく感じなの。Moduleクラスを継承して作る。
こんな風にモジュールを定義しておいて:
class ExampleModule extends Module { static content = { button { $("input", type: "submit") } } }
ページクラスでこういう風に使う:
class ExamplePage extends Page { static content = { theModule { module ExampleModule } } }
と、こんな風にアクセスできる:
to ExamplePage theModule.button.click()
便利!
パラメタライズ
モジュールの定義もパラメタライズすることができて:
class ExampleModule extends Module { def buttonName static content = { button { $("input", type: "submit", name: buttonName) } } }
これはつまり、name属性が、buttonNameって変数の値に一致する要素ってことだけど、このbuttonNameをPageクラスのテンプレートで受け取ることができる:
class ExamplePage extends Page { static content = { theModule { name -> module ExampleModule, buttonName: name } } }
なので、呼び出すときに指定することができる:
to ExamplePage
theModule("something").button.click()
モジュールの入れ子
も、できるよ!
class ExampleModule extends Module { static content = { innerModule { module InnerModule } } } class InnerModule extends Module { static content = { button { $("input", type: "submit") } } } class ExamplePage extends Page { static content = { theModule { module ExampleModule } } }
便利!
Moduleのbase
さっきは
theModule { module ExampleModule }
って書き方をしたから、「ページ全体の中で」$("input", type: "submit")に一致する要素を取ってきてたんだけど。
こうすると、form要素の中で探す:
theModule { module ExampleModule, $("form") }
別の書き方としては、baseフィールドをModuleにつけてあげる:
class ExampleModule extends Module { static base = { $("form") } static content = { button { $("input", type: "submit") } } }
その場合は呼び出し側はこれでいい:
theModule { module ExampleModule }
両方を組み合わせることもできて:
theModule { module ExampleModule, $("div.a") }
class ExampleModule extends Module { static base = { $("form") } static content = { button { $("input", type: "submit") } } }
こうすると、aクラスをもったdiv要素の中にあるformの中のsubmitを取ってくる。
繰り返し要素に対するModule
こんなHTMLの場合:
<table> <tr> <th>Product</th><th>Quantity</th><th>Price</th> </tr> <tr> <td>The Book Of Geb</td><td>1</td><td>5.99</td> </tr> <tr> <td>Geb Single-User License</td><td>1</td><td>99.99</td> </tr> <tr> <td>Geb Multi-User License</td><td>1</td><td>199.99</td> </tr> </table>
行のModuleを用意して:
class CartRow extends Module { static content = { cell { $("td", it) } productName { cell(0).text() } quantity { cell(1).text().toInteger() } price { cell(2).text().toDouble() } } }
moduleListを使うと良い:
class CheckoutPage extends Page { static content = { cartItems { moduleList CartRow, $("table tr").tail() } // ヘッダー行をスキップしたいのでtail() } }
そしたら
then: cartItems.every { it.price > 0.0 }
とか
then: cartItems[0].productName == "The Book Of Geb"
とかできる。
んだけど、これだと必要ない場合でも全要素をとってきてしまうので、こんな風に宣言しておけば:
class CheckoutPage extends Page { static content = { cartItems { index -> moduleList CartRow, $("table tr").tail(), index } } }
こんな風に使えるよ:
then: cartItems.every { it.price > 0.0 } cartItems(0).productName == "The Book Of Geb" cartItems(1..2)*.productName == ["Geb Single-User License", "Geb Multi-User License"]
その他にも、さっきかいたパラメタライズのやり方でも同じことができるよね:
static content = { myContent { index -> moduleList MyModule, $(".myModuleClass"), index, myParam: 'param value' } }
おわり
あとはたまにざーっと目を通しておいて、他にどんな機能があるかタイトルだけ覚えておけば十分かなー。