Testing Web Applications with GEB

9,232 views

Published on

As presented at No Fluff Just Stuff San Antonio, April 14th 2012.

Published in: Technology, Education
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
9,232
On SlideShare
0
From Embeds
0
Number of Embeds
1,053
Actions
Shares
0
Downloads
116
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide

Testing Web Applications with GEB

  1. 1. Testing Web Applicationswith GEBHoward M. Lewis ShipTWD Consultinghlship@gmail.com@hlship © 2012 Howard M. Lewis Ship
  2. 2. Testing Web Applications Whats your process? Manual? Selenium? What about Ajax?
  3. 3. Spoiled by jQuery <div class="summary"> … <div class="subtotal">107.95</div> $(".order-summary .subtotal").text() == "107.95"http://jquery.com/
  4. 4. Selenium WebDriver Drives: or limited HtmlUnit (browserless)http://seleniumhq.org/
  5. 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. 6. driver.get("http://www.google.com");WebElement element = driver.findElement(By.name("q"));element.sendKeys("Cheese!");element.submit(); Triggers Ajax Updatenew WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver d) { return d.getTitle().toLowerCase().startsWith("cheese!"); }};
  7. 7. Java == High Ceremony
  8. 8. Groovy == Low Ceremony + Dynamic
  9. 9. Power of Selenium WebDriver 2.15.0Elegance of jQuery content selectionRobustness of Page Object modellingExpressiveness of GroovyFirst Class Documentation
  10. 10. Running GEB Interactivelygeb.groovyimport groovy.grape.GrapeGrape.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.propertieshandlers=java.util.logging.ConsoleHandlerjava.util.logging.ConsoleHandler.level=WARNINGjava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
  11. 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. 12. Google Demo
  13. 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. 14. IMDB Demo
  15. 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. 16. Browser Page Navigator go() : void Delegation pageUrl : String x : intquit() : void title : String y : intpage : 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. 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. 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. 19. Attribute Predicates Case Sensitive Case Insensitive DescriptionstartsWith iStartsWith start with valuecontains iContains contains the value anywhereendsWith iEndsWith end with value contains value surrounded by whitespace (or atconstainsWord iContainsWord begin or end)notStartsWith iNotStartsWith DOES NOT start with valuenotContains iNotContains DOES NOT contain value anywherenotEndsWith iNotEndsWith DOES NOT end with value DOES NOT contain value (surrounded bynotContainsWord iNotContainsWord whitespace, or at begin or end)
  20. 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. 21. Navigators are Groovy Collections each() is a Groovy Collection methodgroovy: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, WilliamSanderson=J.F. Sebastian, Brion James=Leon Kowalski, Joe Turkel=Dr. EldonTyrell, 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. 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. 23. Pages and Modules
  24. 24. 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()
  25. 25. 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
  26. 26. 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
  27. 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. 28. Content Optionsstatic 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. 29. Content Options Option Type Default Description Evaluate content once, orcache boolean false on each access Error on page load if content does not exist (userequired boolean true false for optional or Ajax- loaded) Page or Class, On a link, identify the pageto null list of Page or Class the link submits to Wait for content to becomewait varies null available (via Ajax/DHTML)
  30. 30. Page Methodsclass 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===> nullgroovy:000> b.clickBoxOffice 0===> truegroovy:000>
  31. 31. 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
  32. 32. 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() } } }
  33. 33. 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) }}
  34. 34. click() groovy:000> b.searchForm.go.click() ===> null groovy:000> b.clickMatchingTitle 3 ===> truedef clickMatchingTitle(int index) { def link = matchingTitlesLinks[index] def label = link.text() link.click() browser.at new Movie(expectedTitle: label)}
  35. 35. 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
  36. 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. 37. Problem: Repeating Elements <table class="cast_list"> <tr> <td class="primary_photo"> … <td class="name"> … <td class="ellipsis"> … <td class="character"> …
  38. 38. Solution: Module Listsclass 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() } }}
  39. 39. groovy:000> b.at(new Movie(expectedTitle: "Blade Runner"))===> truegroovy:000> b.castList[0].actorName===> Harrison Fordgroovy:000> b.castList[0].characterName===> Rick Deckardgroovy: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. 40. JavaScript and Ajax
  41. 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. 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. 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. 44. Waiting waitFor { condition } waitFor timeout { condition } waitFor timeout, interval { condition } waitFor "preset" { condition }
  45. 45. Testing FrameworkIntegration
  46. 46. Reportingpackage myapp.tests Base class that reports at end of each testimport 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. 47. Base Classes Report end of each testFramework Artifact Base Class Reporting Base ClassSpock geb-spock geb.spock.GebSpec geb.spock.GebReportingSpecJunit 4 geb-juni4 geb.junit4.GebTest geb.junit4.GebReportingTestJunit 3 geb-junit3 geb.junit3.GebTest geb.junit3.GebReportingTestTestNG geb-testng geb.testng.GebTest geb.testng.GebReportingTest Report failures only
  48. 48. Delegationpackage myapp.tests Pageimport geb.spock.* Delegationclass 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. 49. Configuration
  50. 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. 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. 52. Waiting Configuration GebConfig.groovy waiting { timeout = 10 retryInterval = 0.5 presets { slow { timeout = 20, retryInterval = 1 } quick { timeout = 1 } } }
  53. 53. More Info
  54. 54. http://www.gebish.org
  55. 55. https://github.com/geb/geb
  56. 56. http://howardlewisship.com
  57. 57. Q&A

×