Testing Web Applications
with GEB
Howard M. Lewis Ship
TWD Consulting
hlship@gmail.com
@hlship
                       © 2012 Howard M. Lewis Ship
Testing Web Applications




                    What's your process?
                      Manual?
                      Selenium?
                    What about Ajax?
Spoiled by jQuery

                <div class="summary">

                     …

                     <div class="subtotal">107.95</div>




           $(".order-summary .subtotal").text() == "107.95"




http://jquery.com/
Selenium WebDriver

   Drives:




   or limited HtmlUnit (browserless)



http://seleniumhq.org/
WebDriver driver = new FirefoxDriver();

driver.get("http://localhost:8080/order-summary/12345");

WebElement element = driver.findElement(
  By.cssSelector(".order-summary .subtotal");

assertEquals(element.getText(), "107.95");
driver.get("http://www.google.com");

WebElement element = driver.findElement(By.name("q"));

element.sendKeys("Cheese!");

element.submit();    Triggers Ajax Update

new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
    public Boolean apply(WebDriver d) {
        return d.getTitle().toLowerCase().startsWith("cheese!");
    }
};
Java == High Ceremony
Groovy == Low Ceremony
       + Dynamic
Power of Selenium WebDriver 2.15.0
Elegance of jQuery content selection
Robustness of Page Object modelling
Expressiveness of Groovy
First Class Documentation
Running GEB Interactively

geb.groovy
import groovy.grape.Grape
Grape.grab([group:'org.codehaus.geb', module:'geb-core', version:'0.6.3'])
Grape.grab([group:'org.seleniumhq.selenium', module:'selenium-firefox-driver',
  version:'2.15.0'])
Grape.grab([group:'org.seleniumhq.selenium', module:'selenium-support', ⏎
version:'2.15.0'])
import geb.*

import java.util.logging.*

new File("geb-logging.properties").withInputStream { ⏎
  LogManager.logManager.readConfiguration it }


geb-logging.properties
handlers=java.util.logging.ConsoleHandler

java.util.logging.ConsoleHandler.level=WARNING
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
GEB Interactive
   $ groovysh
   Groovy Shell (1.8.6, JVM: 1.6.0_29)
   Type 'help' or 'h' for help.
   -------------------------------------------------
   groovy:000> . geb.groovy
   ===> [import groovy.grape.Grape]
   ===> null
   ===> null
   ===> null
   ===> [import groovy.grape.Grape, import geb.*]
   ===> [import groovy.grape.Grape, import geb.*, import ⏎
   java.util.logging.*]
   ===> null
   groovy:000> b = new Browser(baseUrl: "http://google.com/")
   ===> geb.Browser@190a0d51
   groovy:000> b.go()
   ===> null
   groovy:000>
Google Demo
Google Demo

 b = new Browser(baseUrl: "http://google.com/")
 b.go()
 b.$("input", name:"q") << "geb"
 b.waitFor { b.title.toLowerCase().startsWith("geb") }
 b.$("a.l").text()
 b.$("a.l")*.text()
 b.$("a.l")*.@href
 b.$("input", name:"q") << "geb groovy" 
 b.$("a.l").first().click()
 b.quit()
             Not needed in 0.7
IMDB Demo
IMDB Demo

  b = new Browser()
  b.baseUrl = "http://imdb.com"
  b.go()
  b.$("#navbar-query") << "Bladerunner"
  b.$("#navbar-submit-button").click()
  b.waitFor { b.title == "IMDb Search" }
  b.$("#main table", 1).find("a")*.text()
  b.$("#main table", 1).find("a", text:"Blade Runner").click()
  assert b.$(".star-box-giga-star").text() == "9.9"
  b.$("table.cast_list td.name a")[0..3]*.text()
Browser                                 Page                         Navigator
 go() : void      Delegation         pageUrl : String                      x : int
quit() : void                           title : String                     y : int
page : Page                          $(…) : Navigator                   width : int
                                    waitFor(…) : Object                 height : int
                               startsWith(…) : TextMatcher         disabled : boolean
                                contains(…) : TextMatcher            empty : boolean
     Delegation




                               endsWith(…) : TextMatcher          displayed : boolean
                                                                add(String) : Navigator
                                                                       click() : void
                                                                  filter(...) : Navigator
 GebSpec                                                          find(…) : Navigator
                                                                not(String) : Navigator
                                                                    first() : Navigator
                                                                    last() : Navigator
                                                                 getAt(…) : Navigator
                                                              getAttribute(String) : String
                                                                has(String) : Navigator
                                                              parents(String) : Navigator
                                                             parentsUntil(String): Navigator
                                                                        size() : int
                                                                      text() : String
                                                                     value() : Object
$(css selector, index, attribute / text matchers)




 $("p") ➠ all <p>
                              CS
 $("p", 3) ➠ 4th <p>            S3

 $("p")[3] ➠ 4th <p>
 $("p")[0..2] ➠ 1st through 3rd <p>
Attribute Matchers
 $("a", text:"Blade Runner")

 ➠ All <a> tags whose text is "Blade Runner"
 $("a", href: contains("/name/")

 ➠ All <a> tags whose href attribute contains "/name/"
 $("a", href: ~/nm[0-9]+/)

 ➠ All <a> tags whose href attribute matches the
 pattern
Attribute Predicates
      Case Sensitive           Case Insensitive                       Description


startsWith             iStartsWith                start with value


contains               iContains                  contains the value anywhere


endsWith               iEndsWith                  end with value

                                                  contains value surrounded by whitespace (or at
constainsWord          iContainsWord
                                                  begin or end)

notStartsWith          iNotStartsWith             DOES NOT start with value


notContains            iNotContains               DOES NOT contain value anywhere


notEndsWith            iNotEndsWith               DOES NOT end with value

                                                  DOES NOT contain value (surrounded by
notContainsWord        iNotContainsWord
                                                  whitespace, or at begin or end)
Relative Traversal
                        <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.cd").parent()                  div.b

$("p.c").siblings()                 p.d p.e

$("div.a").children()               div.b div.f
Navigators are Groovy Collections

                                               each() is a Groovy Collection method
groovy:000> castList = [:]
===> {}
groovy:000> b.$("table.cast_list tr").tail().each
{ castList[it.find("td.name").text()] = it.find("td.character").text() }
===> […]
groovy:000> castList
===> {Harrison Ford=Rick Deckard, Rutger Hauer=Roy Batty, Sean Young=Rachael,
Edward James Olmos=Gaff, M. Emmet Walsh=Bryant, Daryl Hannah=Pris, William
Sanderson=J.F. Sebastian, Brion James=Leon Kowalski, Joe Turkel=Dr. Eldon
Tyrell, Joanna Cassidy=Zhora, James Hong=Hannibal Chew, Morgan Paull=Holden,
Kevin Thompson=Bear, John Edward Allen=Kaiser, Hy Pyke=Taffey Lewis}




http://groovy.codehaus.org/groovy-jdk/java/util/Collection.html
Forms

           <form id="navbar-form" …
             <input type="text" name="q" …




  groovy:000> b.q = "Galaxy Quest"
  ===> Galaxy Quest




  groovy:000> b.$("#navbar-form").q
  ===> Galaxy Quest
Pages and Modules
Problem: Repetition

$("a", text:"Contact Us").click()

waitFor { b.title == "Contact Us" }
                            $(".alert .btn-primary").click()

                            waitFor { b.title == "Contact Us" }




     ($(".search-form input[name='query']") << "search term").submit()
Solution: Model Pages not just DOM
    Browser                                Page
    go() : void    Delegation         pageUrl : String
   quit() : void                         title : String
   page : Page                       $(…) : Navigator
 at(…) : boolean                    waitFor(…) : Object
                                    verifyAt() : boolean
                                     browser: Browser

           class Home extends geb.Page {
             static url = ""
             static at = {
               title == "The Internet Movie Database (IMDb)"
             }
           }

           groovy:000>     b.$(".home").click(Home)
           ===> null
           groovy:000>     b.verifyAt()
           ===> true
           groovy:000>     b.at Home
           ===> true
           groovy:000>     b.page
           ===> Home
           groovy:000>     b.to Home
           ===> null
Page Content
 class Home extends Page {
   static at = { title == "The Internet Movie Database (IMDb)" }
   static url = ""
   static content = {
     boxOffice { $("h3", text:"Box Office").parent() }
     firstBoxOffice { boxOffice.find("a").first() }
   }
 }




 groovy:000> b.firstBoxOffice.click()
 ===> null
to / do / at

         groovy:000> b.to Home
         ===> null
         groovy:000> b.q = "Forbidden Planet"
         ===> Forbidden Planet
         groovy:000> b.searchForm.go.click()
         ===> null
         groovy:000> b.at Search
         ===> true
         groovy:000>
Content Options




static content = {
  boxOffice { $("h3", text:"Box Office").parent() }
  boxOfficeLinks { boxOffice.find("a", text: iNotStartsWith("see more")) }
  movieShowtimes(required:false) { $("h3", text:"Movie Showtimes").parent() }
  movieShowtimesGo(required:false) { movieShowtimes.find("input", value:"Go") }
}
Content Options
           Option               Type                Default           Description


                                                              Evaluate content once, or
cache               boolean                 false
                                                              on each access


                                                              Error on page load if
                                                              content does not exist (use
required            boolean                 true
                                                              false for optional or Ajax-
                                                              loaded)


                    Page or Class,                            On a link, identify the page
to                                          null
                    list of Page or Class                     the link submits to



                                                              Wait for content to become
wait                varies                  null
                                                              available (via Ajax/DHTML)
Page Methods
class Home extends Page {
  static at = { title == "The Internet Movie Database (IMDb)" }
  static url = ""
  static content = {
    boxOffice { $("h3", text:"Box Office").parent() }
    boxOfficeLinks { boxOffice.find("a", text: iNotStartsWith("see more")) }
  }

    def clickBoxOffice(index) {
      def link = boxOfficeLinks[index]
      def label = link.text()
      link.click()
      waitFor { title.startsWith(label) }
    }
}


groovy:000> b.to Home
===> null
groovy:000> b.clickBoxOffice 0
===> true
groovy:000>
Problem: Re-used web pages
          class Movie extends Page {

              static at = {
                assert title.startsWith("Blade Runner")
                true                                      GEB 0.7 will magically
              }                                            convert to asserts
              static content = {
                rating { $(".star-box-gig-start").text() }
                castList { $("table.cast_list tr").tail() }
              }
          }


 groovy:000> b.$("#main table", 2).find("a",7).click(Movie)
 ===> null
 groovy:000> b.page
 ===> Movie
 groovy:000> b.verifyAt()
 ERROR org.codehaus.groovy.runtime.powerassert.PowerAssertionError:
 assert title.startsWith("Blade Runner")
        |     |
        |     false
        The Bugs Bunny/Road-Runner Movie (1979) - IMDb
Solution: Configured Page Instances

     class Movie extends Page {
       String expectedTitle

         static at = {
           assert title.startsWith expectedTitle
           true
         }

         static content = {
           rating { $(".star-box-gig-start").text() }
           castList { $("table.cast_list tr").tail() }
         }
     }
class Search extends Page {
  static at = {
    assert title == "IMDb Search"
    true
  }

    static content = {
      mainTable { $("#main table") }
      matchingTitles { mainTable[2] }
      matchingTitlesLinks { matchingTitles.find("a", ⏎
                    href: contains("/title/tt")) }
    }

    def clickMatchingTitle(int index) {
      def link = matchingTitlesLinks[index]
      def label = link.text()
      link.click()
      browser.at new Movie(expectedTitle: label)
    }
}
click()




   groovy:000> b.searchForm.go.click()
   ===> null
   groovy:000> b.clickMatchingTitle 3
   ===> true




def clickMatchingTitle(int index) {
  def link = matchingTitlesLinks[index]
  def label = link.text()
  link.click()
  browser.at new Movie(expectedTitle: label)
}
Problem: Duplication on Pages
               b.$("#navbar-query") << "Bladerunner"
               b.$("#navbar-submit-button").click()




 Other examples:
   Login / Logout / Register
   "Contact Us" & other boilerplate
   "Mark Favorite"
   View Product Details
   Bid / Buy
Solution: Modules
  class SearchForm extends geb.Module {
    static content = {
      field { $("#navbar-query") }
      go(to: Search) { $("#navbar-submit-button") }
    }
  }

  class Home extends Page {
    static at = { title == "The Internet Movie Database (IMDb)" }
    static url = ""
    static content = {
      searchForm { module SearchForm }
    }
  }



  groovy:000> b.to Home
  ===> null
  groovy:000> b.searchForm.field << "Serenity"
  ===> [org.openqa.selenium.firefox.FirefoxWebElement@1ef44b1f]
  groovy:000> b.searchForm.go.click()
  ===> null
  groovy:000> b.page
  ===> Search
Problem: Repeating Elements




        <table class="cast_list">
          <tr>
            <td class="primary_photo"> …
            <td class="name"> …
            <td class="ellipsis"> …
            <td class="character"> …
Solution: Module Lists
class CastRow extends Module {
  static content = {
    actorName { $("td.name").text() }
    characterName { $("td.character").text() }
  }
}                    Scope limited to each <tr>
class Movie extends Page {
  String expectedTitle

    static at = { title.startsWith expectedTitle }

    static content = {
      rating { $(".star-box-gig-start").text() }
      castList { moduleList CastRow, $("table.cast_list tr").tail() }
    }
}
groovy:000> b.at(new Movie(expectedTitle: "Blade Runner"))
===> true
groovy:000> b.castList[0].actorName
===> Harrison Ford
groovy:000> b.castList[0].characterName
===> Rick Deckard
groovy:000> b.castList*.actorName
===> [Harrison Ford, Rutger Hauer, Sean Young, Edward James Olmos, ⏎
M. Emmet Walsh, Daryl Hannah, William Sanderson, Brion James, Joe Turkel, ⏎
Joanna Cassidy, James Hong, Morgan Paull, Kevin Thompson, John Edward Allen, ⏎
Hy Pyke]
JavaScript and Ajax
js object


   groovy:000> b = new Browser(baseUrl: "http://jquery.org")
   ===> geb.Browser@5ec22978
   groovy:000> b.go()
   ===> null
   groovy:000> b.js."document.title"
   ===> jQuery Project

                          Access simple page properties
Executing JavaScript
                                 Text evaluated in-browser

  groovy:000> b.js.exec '''
  groovy:001>   $("img").css("background-color", "red").fadeOut()
  groovy:002> '''
  ===> null
  groovy:000> b.js.exec 1, 2, "return arguments[0] + arguments[1];"
  ===> 3
jQuery Hook

                                      More methodMissing() magic!

  groovy:000> b.$("img").jquery.fadeIn()
  ===> [org.openqa.selenium.firefox.FirefoxWebElement@de86fd70,
  org.openqa.selenium.firefox.FirefoxWebElement@615e6612,
                             Silently fails unless jQuery on page
  org.openqa.selenium.firefox.FirefoxWebElement@52c13174,
  org.openqa.selenium.firefox.FirefoxWebElement@69e1ba19,
  org.openqa.selenium.firefox.FirefoxWebElement@2797f147,
  org.openqa.selenium.firefox.FirefoxWebElement@69cfbbda,
  org.openqa.selenium.firefox.FirefoxWebElement@27d5741a,
  org.openqa.selenium.firefox.FirefoxWebElement@10b36232,
  org.openqa.selenium.firefox.FirefoxWebElement@ec3e243f]
Waiting
          waitFor   { condition }
          waitFor   timeout { condition }
          waitFor   timeout, interval { condition }
          waitFor   "preset" { condition }
Testing Framework
Integration
Reporting
package myapp.tests
                                Base class that reports at end of each test
import geb.spock.*

class Login extends GebReportingSpec {

    def "successful login"() {

        when:
        go "login"
        username = "user1"
        report "login screen"             Capture HTML and screenshot
        login().click()

        then:
        title == "Welcome, User1"
    }
}

                            reports/myapp/tests/Login/1-1-login-login screen.html
                            reports/myapp/tests/Login/1-1-login-login screen.png
                            reports/myapp/tests/Login/1-2-login-end.html
                            reports/myapp/tests/Login/1-2-login-end.png
Base Classes
                                                 Report end of each test

Framework   Artifact     Base Class           Reporting Base Class

Spock       geb-spock    geb.spock.GebSpec    geb.spock.GebReportingSpec

Junit 4     geb-juni4    geb.junit4.GebTest   geb.junit4.GebReportingTest


Junit 3     geb-junit3   geb.junit3.GebTest   geb.junit3.GebReportingTest


TestNG      geb-testng   geb.testng.GebTest   geb.testng.GebReportingTest



                               Report failures only
Delegation
package myapp.tests                            Page


import geb.spock.*




                                                 Delegation
class Login extends GebReportingSpec {

    def "successful login"() {                Browser

        when:




                                                 Delegation
        go "login"
        username = "user1"
        report "login screen"
        login().click()
                                         Geb[Reporting]Spec

        then:
        title == "Welcome, User1"
    }
}
Configuration
GebConfig.groovy
       src/test/resources/GebConfig.groovy
       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() }
           }
       }




http://groovy.codehaus.org/gapi/groovy/util/ConfigSlurper.html
Environment
              $ grade test -Dgeb.env=chrome




   src/test/resources/GebConfig.groovy
   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() }
       }
   }
Waiting Configuration

      GebConfig.groovy
      waiting {
        timeout = 10
        retryInterval = 0.5

          presets {
            slow { timeout = 20, retryInterval = 1 }
            quick { timeout = 1 }
          }
      }
More Info
http://www.gebish.org
https://github.com/geb/geb
http://howardlewisship.com
Q&A

Testing Web Applications with GEB

  • 1.
    Testing Web Applications withGEB Howard M. Lewis Ship TWD Consulting hlship@gmail.com @hlship © 2012 Howard M. Lewis Ship
  • 2.
    Testing Web Applications What's your process? Manual? Selenium? What about Ajax?
  • 3.
    Spoiled by jQuery <div class="summary"> … <div class="subtotal">107.95</div> $(".order-summary .subtotal").text() == "107.95" http://jquery.com/
  • 4.
    Selenium WebDriver Drives: or limited HtmlUnit (browserless) http://seleniumhq.org/
  • 5.
    WebDriver driver =new FirefoxDriver(); driver.get("http://localhost:8080/order-summary/12345"); WebElement element = driver.findElement( By.cssSelector(".order-summary .subtotal"); assertEquals(element.getText(), "107.95");
  • 6.
    driver.get("http://www.google.com"); WebElement element =driver.findElement(By.name("q")); element.sendKeys("Cheese!"); element.submit(); Triggers Ajax Update new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver d) { return d.getTitle().toLowerCase().startsWith("cheese!"); } };
  • 7.
    Java == HighCeremony
  • 8.
    Groovy == LowCeremony + Dynamic
  • 9.
    Power of SeleniumWebDriver 2.15.0 Elegance of jQuery content selection Robustness of Page Object modelling Expressiveness of Groovy First Class Documentation
  • 10.
    Running GEB Interactively geb.groovy importgroovy.grape.Grape Grape.grab([group:'org.codehaus.geb', module:'geb-core', version:'0.6.3']) Grape.grab([group:'org.seleniumhq.selenium', module:'selenium-firefox-driver', version:'2.15.0']) Grape.grab([group:'org.seleniumhq.selenium', module:'selenium-support', ⏎ version:'2.15.0']) import geb.* import java.util.logging.* new File("geb-logging.properties").withInputStream { ⏎ LogManager.logManager.readConfiguration it } geb-logging.properties handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=WARNING java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
  • 11.
    GEB Interactive $ groovysh Groovy Shell (1.8.6, JVM: 1.6.0_29) Type 'help' or 'h' for help. ------------------------------------------------- groovy:000> . geb.groovy ===> [import groovy.grape.Grape] ===> null ===> null ===> null ===> [import groovy.grape.Grape, import geb.*] ===> [import groovy.grape.Grape, import geb.*, import ⏎ java.util.logging.*] ===> null groovy:000> b = new Browser(baseUrl: "http://google.com/") ===> geb.Browser@190a0d51 groovy:000> b.go() ===> null groovy:000>
  • 12.
  • 13.
    Google Demo b= new Browser(baseUrl: "http://google.com/") b.go() b.$("input", name:"q") << "geb" b.waitFor { b.title.toLowerCase().startsWith("geb") } b.$("a.l").text() b.$("a.l")*.text() b.$("a.l")*.@href b.$("input", name:"q") << "geb groovy"  b.$("a.l").first().click() b.quit() Not needed in 0.7
  • 14.
  • 15.
    IMDB Demo b = new Browser() b.baseUrl = "http://imdb.com" b.go() b.$("#navbar-query") << "Bladerunner" b.$("#navbar-submit-button").click() b.waitFor { b.title == "IMDb Search" } b.$("#main table", 1).find("a")*.text() b.$("#main table", 1).find("a", text:"Blade Runner").click() assert b.$(".star-box-giga-star").text() == "9.9" b.$("table.cast_list td.name a")[0..3]*.text()
  • 16.
    Browser Page Navigator go() : void Delegation pageUrl : String x : int quit() : void title : String y : int page : Page $(…) : Navigator width : int waitFor(…) : Object height : int startsWith(…) : TextMatcher disabled : boolean contains(…) : TextMatcher empty : boolean Delegation endsWith(…) : TextMatcher displayed : boolean add(String) : Navigator click() : void filter(...) : Navigator GebSpec find(…) : Navigator not(String) : Navigator first() : Navigator last() : Navigator getAt(…) : Navigator getAttribute(String) : String has(String) : Navigator parents(String) : Navigator parentsUntil(String): Navigator size() : int text() : String value() : Object
  • 17.
    $(css selector, index,attribute / text matchers) $("p") ➠ all <p> CS $("p", 3) ➠ 4th <p> S3 $("p")[3] ➠ 4th <p> $("p")[0..2] ➠ 1st through 3rd <p>
  • 18.
    Attribute Matchers $("a",text:"Blade Runner") ➠ All <a> tags whose text is "Blade Runner" $("a", href: contains("/name/") ➠ All <a> tags whose href attribute contains "/name/" $("a", href: ~/nm[0-9]+/) ➠ All <a> tags whose href attribute matches the pattern
  • 19.
    Attribute Predicates Case Sensitive Case Insensitive Description startsWith iStartsWith start with value contains iContains contains the value anywhere endsWith iEndsWith end with value contains value surrounded by whitespace (or at constainsWord iContainsWord begin or end) notStartsWith iNotStartsWith DOES NOT start with value notContains iNotContains DOES NOT contain value anywhere notEndsWith iNotEndsWith DOES NOT end with value DOES NOT contain value (surrounded by notContainsWord iNotContainsWord whitespace, or at begin or end)
  • 20.
    Relative Traversal <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.cd").parent() div.b $("p.c").siblings() p.d p.e $("div.a").children() div.b div.f
  • 21.
    Navigators are GroovyCollections each() is a Groovy Collection method groovy:000> castList = [:] ===> {} groovy:000> b.$("table.cast_list tr").tail().each { castList[it.find("td.name").text()] = it.find("td.character").text() } ===> […] groovy:000> castList ===> {Harrison Ford=Rick Deckard, Rutger Hauer=Roy Batty, Sean Young=Rachael, Edward James Olmos=Gaff, M. Emmet Walsh=Bryant, Daryl Hannah=Pris, William Sanderson=J.F. Sebastian, Brion James=Leon Kowalski, Joe Turkel=Dr. Eldon Tyrell, Joanna Cassidy=Zhora, James Hong=Hannibal Chew, Morgan Paull=Holden, Kevin Thompson=Bear, John Edward Allen=Kaiser, Hy Pyke=Taffey Lewis} http://groovy.codehaus.org/groovy-jdk/java/util/Collection.html
  • 22.
    Forms <form id="navbar-form" … <input type="text" name="q" … groovy:000> b.q = "Galaxy Quest" ===> Galaxy Quest groovy:000> b.$("#navbar-form").q ===> Galaxy Quest
  • 23.
  • 24.
    Problem: Repetition $("a", text:"ContactUs").click() waitFor { b.title == "Contact Us" } $(".alert .btn-primary").click() waitFor { b.title == "Contact Us" } ($(".search-form input[name='query']") << "search term").submit()
  • 25.
    Solution: Model Pagesnot just DOM Browser Page go() : void Delegation pageUrl : String quit() : void title : String page : Page $(…) : Navigator at(…) : boolean waitFor(…) : Object verifyAt() : boolean browser: Browser class Home extends geb.Page { static url = "" static at = { title == "The Internet Movie Database (IMDb)" } } groovy:000> b.$(".home").click(Home) ===> null groovy:000> b.verifyAt() ===> true groovy:000> b.at Home ===> true groovy:000> b.page ===> Home groovy:000> b.to Home ===> null
  • 26.
    Page Content classHome extends Page { static at = { title == "The Internet Movie Database (IMDb)" } static url = "" static content = { boxOffice { $("h3", text:"Box Office").parent() } firstBoxOffice { boxOffice.find("a").first() } } } groovy:000> b.firstBoxOffice.click() ===> null
  • 27.
    to / do/ at groovy:000> b.to Home ===> null groovy:000> b.q = "Forbidden Planet" ===> Forbidden Planet groovy:000> b.searchForm.go.click() ===> null groovy:000> b.at Search ===> true groovy:000>
  • 28.
    Content Options static content= { boxOffice { $("h3", text:"Box Office").parent() } boxOfficeLinks { boxOffice.find("a", text: iNotStartsWith("see more")) } movieShowtimes(required:false) { $("h3", text:"Movie Showtimes").parent() } movieShowtimesGo(required:false) { movieShowtimes.find("input", value:"Go") } }
  • 29.
    Content Options Option Type Default Description Evaluate content once, or cache boolean false on each access Error on page load if content does not exist (use required boolean true false for optional or Ajax- loaded) Page or Class, On a link, identify the page to null list of Page or Class the link submits to Wait for content to become wait varies null available (via Ajax/DHTML)
  • 30.
    Page Methods class Homeextends Page { static at = { title == "The Internet Movie Database (IMDb)" } static url = "" static content = { boxOffice { $("h3", text:"Box Office").parent() } boxOfficeLinks { boxOffice.find("a", text: iNotStartsWith("see more")) } } def clickBoxOffice(index) { def link = boxOfficeLinks[index] def label = link.text() link.click() waitFor { title.startsWith(label) } } } groovy:000> b.to Home ===> null groovy:000> b.clickBoxOffice 0 ===> true groovy:000>
  • 31.
    Problem: Re-used webpages class Movie extends Page { static at = { assert title.startsWith("Blade Runner") true GEB 0.7 will magically } convert to asserts static content = { rating { $(".star-box-gig-start").text() } castList { $("table.cast_list tr").tail() } } } groovy:000> b.$("#main table", 2).find("a",7).click(Movie) ===> null groovy:000> b.page ===> Movie groovy:000> b.verifyAt() ERROR org.codehaus.groovy.runtime.powerassert.PowerAssertionError: assert title.startsWith("Blade Runner") | | | false The Bugs Bunny/Road-Runner Movie (1979) - IMDb
  • 32.
    Solution: Configured PageInstances class Movie extends Page { String expectedTitle static at = { assert title.startsWith expectedTitle true } static content = { rating { $(".star-box-gig-start").text() } castList { $("table.cast_list tr").tail() } } }
  • 33.
    class Search extendsPage { static at = { assert title == "IMDb Search" true } static content = { mainTable { $("#main table") } matchingTitles { mainTable[2] } matchingTitlesLinks { matchingTitles.find("a", ⏎ href: contains("/title/tt")) } } def clickMatchingTitle(int index) { def link = matchingTitlesLinks[index] def label = link.text() link.click() browser.at new Movie(expectedTitle: label) } }
  • 34.
    click() groovy:000> b.searchForm.go.click() ===> null groovy:000> b.clickMatchingTitle 3 ===> true def clickMatchingTitle(int index) { def link = matchingTitlesLinks[index] def label = link.text() link.click() browser.at new Movie(expectedTitle: label) }
  • 35.
    Problem: Duplication onPages b.$("#navbar-query") << "Bladerunner" b.$("#navbar-submit-button").click() Other examples: Login / Logout / Register "Contact Us" & other boilerplate "Mark Favorite" View Product Details Bid / Buy
  • 36.
    Solution: Modules class SearchForm extends geb.Module { static content = { field { $("#navbar-query") } go(to: Search) { $("#navbar-submit-button") } } } class Home extends Page { static at = { title == "The Internet Movie Database (IMDb)" } static url = "" static content = { searchForm { module SearchForm } } } groovy:000> b.to Home ===> null groovy:000> b.searchForm.field << "Serenity" ===> [org.openqa.selenium.firefox.FirefoxWebElement@1ef44b1f] groovy:000> b.searchForm.go.click() ===> null groovy:000> b.page ===> Search
  • 37.
    Problem: Repeating Elements <table class="cast_list"> <tr> <td class="primary_photo"> … <td class="name"> … <td class="ellipsis"> … <td class="character"> …
  • 38.
    Solution: Module Lists classCastRow extends Module { static content = { actorName { $("td.name").text() } characterName { $("td.character").text() } } } Scope limited to each <tr> class Movie extends Page { String expectedTitle static at = { title.startsWith expectedTitle } static content = { rating { $(".star-box-gig-start").text() } castList { moduleList CastRow, $("table.cast_list tr").tail() } } }
  • 39.
    groovy:000> b.at(new Movie(expectedTitle:"Blade Runner")) ===> true groovy:000> b.castList[0].actorName ===> Harrison Ford groovy:000> b.castList[0].characterName ===> Rick Deckard groovy:000> b.castList*.actorName ===> [Harrison Ford, Rutger Hauer, Sean Young, Edward James Olmos, ⏎ M. Emmet Walsh, Daryl Hannah, William Sanderson, Brion James, Joe Turkel, ⏎ Joanna Cassidy, James Hong, Morgan Paull, Kevin Thompson, John Edward Allen, ⏎ Hy Pyke]
  • 40.
  • 41.
    js object groovy:000> b = new Browser(baseUrl: "http://jquery.org") ===> geb.Browser@5ec22978 groovy:000> b.go() ===> null groovy:000> b.js."document.title" ===> jQuery Project Access simple page properties
  • 42.
    Executing JavaScript Text evaluated in-browser groovy:000> b.js.exec ''' groovy:001> $("img").css("background-color", "red").fadeOut() groovy:002> ''' ===> null groovy:000> b.js.exec 1, 2, "return arguments[0] + arguments[1];" ===> 3
  • 43.
    jQuery Hook More methodMissing() magic! groovy:000> b.$("img").jquery.fadeIn() ===> [org.openqa.selenium.firefox.FirefoxWebElement@de86fd70, org.openqa.selenium.firefox.FirefoxWebElement@615e6612, Silently fails unless jQuery on page org.openqa.selenium.firefox.FirefoxWebElement@52c13174, org.openqa.selenium.firefox.FirefoxWebElement@69e1ba19, org.openqa.selenium.firefox.FirefoxWebElement@2797f147, org.openqa.selenium.firefox.FirefoxWebElement@69cfbbda, org.openqa.selenium.firefox.FirefoxWebElement@27d5741a, org.openqa.selenium.firefox.FirefoxWebElement@10b36232, org.openqa.selenium.firefox.FirefoxWebElement@ec3e243f]
  • 44.
    Waiting waitFor { condition } waitFor timeout { condition } waitFor timeout, interval { condition } waitFor "preset" { condition }
  • 45.
  • 46.
    Reporting package myapp.tests Base class that reports at end of each test import geb.spock.* class Login extends GebReportingSpec { def "successful login"() { when: go "login" username = "user1" report "login screen" Capture HTML and screenshot login().click() then: title == "Welcome, User1" } } reports/myapp/tests/Login/1-1-login-login screen.html reports/myapp/tests/Login/1-1-login-login screen.png reports/myapp/tests/Login/1-2-login-end.html reports/myapp/tests/Login/1-2-login-end.png
  • 47.
    Base Classes Report end of each test Framework Artifact Base Class Reporting Base Class Spock geb-spock geb.spock.GebSpec geb.spock.GebReportingSpec Junit 4 geb-juni4 geb.junit4.GebTest geb.junit4.GebReportingTest Junit 3 geb-junit3 geb.junit3.GebTest geb.junit3.GebReportingTest TestNG geb-testng geb.testng.GebTest geb.testng.GebReportingTest Report failures only
  • 48.
    Delegation package myapp.tests Page import geb.spock.* Delegation class Login extends GebReportingSpec { def "successful login"() { Browser when: Delegation go "login" username = "user1" report "login screen" login().click() Geb[Reporting]Spec then: title == "Welcome, User1" } }
  • 49.
  • 50.
    GebConfig.groovy src/test/resources/GebConfig.groovy 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() }     } } http://groovy.codehaus.org/gapi/groovy/util/ConfigSlurper.html
  • 51.
    Environment $ grade test -Dgeb.env=chrome src/test/resources/GebConfig.groovy 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() }     } }
  • 52.
    Waiting Configuration GebConfig.groovy waiting { timeout = 10 retryInterval = 0.5 presets { slow { timeout = 20, retryInterval = 1 } quick { timeout = 1 } } }
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.