Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Luke Daley on Geb

Luke Daley on Geb

More Decks by Enterprise Java User Group Austria

Other Decks in Technology

Transcript

  1. Geb T o t a l l y G r

    o o v y B r o w s e r A u t o m a t i o n pronounced “jeb”
  2. Geb… What is it? Geb is a browser automation solution.

    It brings together the power of WebDriver, the elegance of jQuery content selection, the robustness of Page Object modelling and the expressiveness of the Groovy language.
  3. WebDriver (a.k.a. Selenium 2) Write to the WebDriver interface —

    run against any implementation — in theory
  4. jQuery “jQuery is a fast and concise JavaScript Library that

    simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.” - jquery.com
  5. jQuery “jQuery is a fast and concise JavaScript Library that

    simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.” - jquery.com
  6. jQuery Geb takes inspiration from jQuery // CSS 3 selectors

    $("div.some-class p:first[title='something']") // Find via index and/or attribute matching $("h1", 2, class: "heading") $("p", name: "description") $("ul.things li", 2) // Chaining $("div").find(".b") $("div").filter(".c").parents() $("p.c").siblings()
  7. “Page Objects are the embodiment of Programming Motherf#$&*r” - Samuel

    L Jackson http://programming-motherf #$&*r.com/ Page Objects
  8. Use of proper programming techniques to avoid duplication and brittleness.

    Page Objects driver.findElement(By.name("username")).sendKeys("username"); driver.findElement(By.name("password")).sendKeys("password"); driver.findElement(By.name("submitButton")).click(); Don’t do this
  9. Use of proper programming techniques to avoid duplication and brittleness.

    Page Objects login(“username”, “password”) Do do this
  10. Geb uses Groovy’s DSL capabilities and dynamism to facilitate Page

    Objects Page Objects import geb.Page class LoginPage extends Page { static content = { loginButton(to: AdminPage) { $("input[name=login]") } } def login(usernameValue, passwordValue) { username = usernameValue password = passwordValue loginButton.click() } }
  11. • WebDriver (browser automation) • jQuery (content traversal / interaction)

    • Page Objects (proper programming) • Groovy (minimal syntax, no ceremony) Geb… What is it? The product of…
  12. Geb Quickstart @Grapes([ @Grab("org.codehaus.geb:geb-core:0.6.1"), @Grab("org.seleniumhq.selenium:selenium-firefox-driver:2.9.0") ]) import geb.Browser Browser.drive {

    go "http://google.com/ncr" assert title == "Google" waitFor { q() }.value "wikipedia" waitFor { title.endsWith("Google Search") } $("li.g", 0).find("a.l").with { assert text() == "Wikipedia" click() } waitFor { title == "Wikipedia" } }
  13. Testing with Geb • JUnit (3 & 4) • TestNG

    • EasyB • Cuke4Duke (Cucumber for the JVM) • Spock Adapters for…
  14. Geb & Spock Geb ❤’s Spock import geb.spock.GebSpec class LoginSpec

    extends GebSpec { def "login to admin section"() { given: to LoginPage when: loginForm.with { username = "admin" password = "password" } and: loginButton.click() then: at AdminPage } }
  15. To & at def "at the front page"() { when:

    to FrontPage then: at FrontPage } to() navigates to a page at() verifies we are where we expect to be
  16. To & at class FrontPage extends NavPage { static url

    = "home" static at = { assert nav.currentName == "Event Home" true } } to() uses url property to determine page address (combined with base url configuration)
  17. To & at class FrontPage extends NavPage { static url

    = "home" static at = { assert nav.currentName == "Event Home" true } } at() runs the page’s at checker (do some checks on the page content)
  18. Page Delegation def "click the contact us link"() { when:

    footer.contactUsLink.click() then: at ContactUsPage } Unresolved properties/methods are automatically forwarded to the browser’s current page instance
  19. Page Content class BasePage extends geb.Page { static content =

    { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } The content block is a DSL for naming the content on the page.
  20. Content Lookups class BasePage extends geb.Page { static content =

    { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } The $() function is used to select page content It’s jQuery like
  21. The $() Function $(«css selector», «index / range», «attribute /

    text matchers») Lookups are a combination of CSS selectors, index or ranges and attribute and/or text matchers
  22. Css Selectors $(«css selector», «index / range», «attribute / text

    matchers») Any CSS expression supported by the browser (Complete CSS3, except for HTMLUnit) $("div.some-class p:first[title='something']") $("ul li a") $("table tr:nth-child(2n+1) td") $("div#content p:first-child::first-line")
  23. Indexes & Ranges $(«css selector», «index / range», «attribute /

    text matchers») Select content by index or grab a bunch with a range <p>a</p> <p>b</p> <p>c</p> $("p", 0) // a $("p", 2) // c $("p", 0..1) // a & b $("p", 1..2) // b & c
  24. Attribute/Text Matching $(«css selector», «index / range», «attribute / text

    matchers») Values of tag attributes can be matched “text” is a special attribute that matches the node text $("p", attr1: "a") $("p", attr2: "c") $("p", attr1: "a", attr2: "b") $("p", text: "p1") $("p", text: "p1", attr1: "a") $("p", text: ~/p./) $("p", text: startsWith("p")) $("p", text: endsWith("2")) <p attr1="a" attr2="b">p1</p> <p attr1="a" attr2="c">p2</p>
  25. Attribute/Text Matching $(«css selector», «index / range», «attribute / text

    matchers») Use regular expressions to do partial matches $("p", attr1: "a") $("p", attr2: "c") $("p", attr1: "a", attr2: "b") $("p", text: "p1") $("p", text: "p1", attr1: "a") $("p", text: ~/p./) $("p", text: startsWith("p")) $("p", text: endsWith("2")) <p attr1="a" attr2="b">p1</p> <p attr1="a" attr2="c">p2</p>
  26. $(«css selector», «index / range», «attribute / text matchers») Use

    builtin predicates for common partial matching <p attr1="a" attr2="b">p1</p> <p attr1="a" attr2="c">p2</p> $("p", attr1: "a") $("p", attr2: "c") $("p", attr1: "a", attr2: "b") $("p", text: "p1") $("p", text: "p1", attr1: "a") $("p", text: ~/p./) $("p", text: startsWith("p")) $("p", text: endsWith("2")) Attribute/Text Matching
  27. Builtin Predicates All builtin predicates take strings and regular expressions

    startsWith iStartsWith contains iContains endsWith iEndsWith containsWord iContainsWord notStartsWith iNotStartsWith notContains iNotContains notEndsWith iNotEndsWith notContainsWord iNotContainsWord
  28. Relative Content <div class="a"> <div class="b"> <p class="c"></p> <p class="d"></p>

    <p class="e"></p> </div> <div class="f"></div> </div> $() returns a “navigator” object which has methods for finding relative content $("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' Plus more…
  29. Collection Goodness <p>1</p> <p>2</p> Navigators are collections of collections (like

    jQuery) $("p").max { it.text() }.text() == "2" $("p").each { println it.text() } includes all the cool Groovy collection methods
  30. Building Content class BasePage extends geb.Page { static content =

    { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } Content definitions can build on other defined content
  31. Modules class BasePage extends geb.Page { static content = {

    container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } Modules are repeating/reappearing content that are modelled independently
  32. Modules class Footer extends geb.Module { static content = {

    auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Modules define content, just like pages
  33. Module Base class Footer extends geb.Module { static content =

    { auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Content lookups are relative to the module base
  34. Module Base class BasePage extends geb.Page { static content =

    { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } You can specify the base in the content DSL when defining the module in the owner
  35. Content Templates class Footer extends geb.Module { static content =

    { auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Content blocks can take parameters
  36. Repeating Modules <table id="book-results"> <thead> <tr> <th>Title</th><th>Author</th> </tr> </thead> <tbody>

    <tr> <td>Zero History</td><td>William Gibson</td> </tr> <tr> <td>The Evolutionary Void</td><td>Peter F. Hamilton</td> </tr> </tbody> </table> Modules are great for repeating content
  37. Repeating Modules Modules are great for repeating content class BooksPage

    extends Page { static content = { bookResults { module BookRow, $("table#book-results tbody tr", it) } } } class BookRow extends Module { static content = { cell { $("td", it) } title { cell(0).text() } author { cell(1).text() } } }
  38. Repeating Modules Modules are great for repeating content expect: bookResults(0).title

    == "Zero History" bookResults(1).author == "Peter F. Hamilton
  39. Content “to” class Footer extends geb.Module { static content =

    { auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Content can define what page it navigates to when it is clicked
  40. Form Shortcuts def "fill in the form except for a

    message"() { when: name = "Peter Niederwieser" email = "[email protected]" sendButton.click() then: at ContactUsPage errorMessage == "message is required" } Assigning to undefined properties sets the form value of the control with that name
  41. Content is anything def "fill in the form except for

    a message"() { when: name = "Peter Niederwieser" email = "[email protected]" sendButton.click() then: at ContactUsPage errorMessage == "message is required" } Defined content in DSL can be anything
  42. Content is anything class ContactUsPage extends BasePage { static content

    = { errorMessage(required: false) { $("span.errorMessage").text() } sendButton(to: [ContactUsPage, FrontPage]) { $("input#FeedbackAdd_0") } } } Defined content in DSL can be anything
  43. Optional Content class ContactUsPage extends BasePage { static content =

    { errorMessage(required: false) { $("span.errorMessage").text() } sendButton(to: [ContactUsPage, FrontPage]) { $("input#FeedbackAdd_0") } } } By default, a content template must return something Specify “required: false” to allow empty/false
  44. Inheritance class ContactUsPage extends BasePage { static content = {

    errorMessage(required: false) { $("span.errorMessage").text() } sendButton(to: [ContactUsPage, FrontPage]) { $("input#FeedbackAdd_0") } } } Pages can extend each other, inheriting all defined content and methods etc. Works for modules too
  45. WebDriver Access driver.navigate().refresh() You can “drop down” to the WebDriver

    level whenever necessary $("p").firstElement() access the WebDriver instance
  46. WebDriver Access driver.navigate().refresh() You can “drop down” to the WebDriver

    level whenever necessary $("p").firstElement() access the WebElement instances
  47. JavaScript Interface <script type="text/javascript"> function addThem(a,b) { return a +

    b; } </script> Call JavaScript methods like they are Groovy js.addThem(1, 2) == 3 This is Groovy code
  48. JavaScript Interface <script type="text/javascript"> var aVariable = 1; </script> Access

    global JavaScript variables js.aVariable == 1 This is Groovy code
  49. jQuery Adapter The ‘jquery’ property on a navigator gives a

    jQuery object for matched content $("div#a").jquery.mouseover() useful for WebDriver interaction shortcomings
  50. Dynamic Pages Use waitFor() to deal with AJAX & effects

    // use default waiting configuration waitFor { «something» } // wait for up to 10 seconds, using the default retry interval waitFor(10) { «something» } // wait for up to 10 seconds, waiting half a second between retries waitFor(10, 0.5) { «something» } // use the preset “quick” as the wait settings waitFor("quick") { «something» }
  51. Dynamic Pages Use waitFor() to deal with AJAX & effects

    // wait for content to be present waitFor { $("p") } // wait for a condition waitFor { $("p").text() == "Message sent!" } // wait for any Groovy truth value waitFor { $("p").collect { it.text() } } // wait for a JavaScript redirect waitFor { at Somepage }
  52. Dynamic Pages Content can be specified with implicit waiting Geb

    will automatically wait for it when it’s requested class DynamicPage extends Page { static content = { dynamicallyAdded(wait: true) { $("p.dynamic") } } }
  53. Direct Downloading Download content for inspection The download request includes

    the browser’s cookies def "check user pdf"() { given: to LoginPage login "admin", "password" expect: at AccountPage when: byte[] pdfBytes = downloadBytes($("a.detailsPdf").@href) then: extractTextFromPdf(pdfBytes).contains "Username: admin" } Uses HttpUrlConnection
  54. Configuration Geb loads “GebConfig.groovy” from the classpath Configure the driver

    and other settings import org.openqa.selenium.firefox.FirefoxDriver import org.openqa.selenium.chrome.ChromeDriver driver = { new FirefoxDriver() } // use firefox by default waiting { timeout = 2 // default wait is two seconds } environments { chrome { driver = { new ChromeDriver() } } } Uses Groovy’s ConfigSlurper
  55. Configuration Environment is controlled by “geb.env” JVM system property grails

    -Dgeb.env=chrome test-app gradle test -Dgeb.env=chrome