Geb 自分用メモ

はい。どーも。チーム内でジェバンジェリスト呼ばわりされてるシーバです。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")
css selector の部分

WebDriverがサポートしてるのんだったら何でもOKよ。

$("div.some-class p:first[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' }
}

おわり

あとはたまにざーっと目を通しておいて、他にどんな機能があるかタイトルだけ覚えておけば十分かなー。