Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

End-to-end testing with geb

999 views

Published on

Why and when you need end-to-end tests, a spooky story with a 15 years software beast, and how to develop concise, maintainable functional tests using Groovy, Spock and Geb.

Published in: Software
  • Be the first to comment

  • Be the first to like this

End-to-end testing with geb

  1. 1. End-to-end testing A “spocky” story
  2. 2. Blatant self-promotion Jesús L. Domínguez Muriel @jdmuriel Freelance, working at DIGIBÍS
  3. 3. End to end testing - Chapter I: Why - Chapter II: A spooky story - Chapter III: Doing it with geb
  4. 4. Chapter I- Why: Sometimes, software starts small...
  5. 5. ... and then it grows
  6. 6. Then, any small change can awake the beast
  7. 7. Ways to handle a 15 year software beast - Flee - Rewrite - Refactor
  8. 8. How to handle a 15 year software beast (I): Flee (No quiero mirar a nadie :-P )
  9. 9. How to handle a 15 year software beast (II): Rewrite (Usually not an option)
  10. 10. How to handle a 15 year software beast (III): Refactor - Refactor need tests - You usually have untestable code - Start testing at the lowest level possible - Unit - Integration - End-to-end - Add new code with good testing practices - References - https://www.youtube.com/watch?v=6W-EyIfUTmI
  11. 11. End-to-end tests - Aka functional tests - Test the application as an end user would do it - Backend + frontend + everything in between - Good for - Acceptance criteria in user stories - Systems with many coordinated pieces - Testing untestable code
  12. 12. End to end testing wisdom (I) “End to end tests are like cats” (they always do the same until they don’t)
  13. 13. End to end testing wisdom (II) ““As anyone who has tried knows, maintaining a large suite of functional web tests for a changing application can become an expensive and frustrating process””
  14. 14. Chapter II: a spooky story Our story starts with a library
  15. 15. With the years, it somehow has grown in other thing
  16. 16. Digibib - later Digiarch, Digihub, Digimus - 2003 - 2017 - 43 jars, 136 dependencies - Several hundred thousands lines of code - Our own framework -in 2003 there is not Spring - XML based :-( - Reimplementations - Code who nobody knows if it is used or not - Several generations of front-ends - Some time around 2012 we start thinking we need some tests
  17. 17. First iteration: Selenium IDE for non technical users - Firefox extension - Records browser activity - Generates an script - Small IDE to edit the script - Script can be replayed - Can be automated with some effort - Usable by trained non technical users…?
  18. 18. - Firefox extension - Records browser activity - Generates an script - Small IDE to edit the script - Script can be replayed - Can be automated with some effort - Usable by trained non technical users…? First iteration: Selenium IDE for non technical users
  19. 19. Selenium IDE: not what we expected
  20. 20. - Selecting the element to check not easy for non technical users (even if the tool allows selecting it in the browser) - By CSS - By XPath - Changes in a page require recapture of whole, long scripts - Firefox updates break the extension - Automation too brittle - Multi script test execution too slow - We never get end users to really use the IDE Selenium IDE: not what we expected
  21. 21. Second iteration: exporting scripts to Java Advantages - News scripts can be created faster - Easier to integrate with jenkins - Easier to use other browsers - Improved speed using headless browser PhantomJS
  22. 22. Second iteration: exporting scripts to Java Disadvantages
  23. 23. “The key to not pulling your hair out when dealing with web tests” - @ldaley Tests call domain methods, no HTML becomes Access to the specific HTML and CSS only within that Page Objects If the HTML is changed, only the affected Page object must be changed Even Martin Fowler said it!!! (https://martinfowler.com/bliki/PageObject.html) Page object pattern
  24. 24. So, we have work to do 3 or 4 most tested pages are already converted to Page objects Login Search form Many still to do: Results Configuration, Indexes, etc.
  25. 25. Chapter III: Enters geb Developer focused tool Uses Groovy's dynamism to remove boilerplate, achieve pseudo English code Uses WebDriver (evolution of Selenium 2) - Cross browser Inspired by jQuery, robust Page Object modelling support Good documentation. The “Book of Geb” http://www.gebish.org/manual/current/ Luke Daley (Gradle, Ratpack), Marcin Erdmann (current)
  26. 26. Why geb Concise Team already using Groovy with Spock for tests Standard, tested implementation of utilities we were implementing ad hoc: - Driver configuration - Timeout configuration - Screenshots - Integration You can give a talk!!!
  27. 27. Why not geb Dependency madness (specially with Maven and eclipse) No autocomplete (on Eclipse) Oriented to Groovy developers
  28. 28. Geb in 10 slides - Browser Browser.drive { go "http://gebish.org" assert title == "Geb - Very Groovy Browser Automation" $("div.menu a.manuals").click() waitFor { !$("#manuals-menu").hasClass("animating") } $("#manuals-menu a")[0].click() assert title.startsWith("The Book Of Geb") } Browser always have a current page and delegates methods and properties to it.
  29. 29. Geb in 10 slides - Test adapter class GebishOrgTest extends GebSpec { @Test void “clicking first manual goes to book of geb”() { given: go "http://gebish.org" //Delegate to browser when: $("div.menu a.manuals").click() //Delegate to page waitFor { !$("#manuals-menu").hasClass("animating") } $("#manuals-menu a")[0].click() then: title.startsWith("The Book Of Geb") } } //Automatic screenshot reporting after every test
  30. 30. Geb in 10 slides - $(), Navigator Inspired by jQuery: //$("<css selector>", index_or_range, <attribute/text matcher>) $("div>p", 0..2, class: "heading", text: iEndsWith("!")) == [“a!”, “b!”] $("p", text: ~/This./).size() == 2 $(By.id("some-id")) $(By.xpath('//p[@class="xpath"]')) //Navigators are iterable $("p").max { it.text() }.text() == "2" $("p")*.text().max() == "2" //Methods for filtering, element traversal $("div").filter(".a").not(".c").has("p", text: "Found!").hasNot("br") $("div").find("p").parent().next().children("a", href: contains("www")) //nextAll(), previous(), siblings(), parents(), closest(), //nextUntil(), prevUntil(), parentsUntil()
  31. 31. Geb in 10 slides - $(), Navigator //Composition $( $("p.a"), $("p.b") ) //Methods $("a.login").click() $("input") << "test" << Keys.chord(Keys.CONTROL, "c") //Properties displayed, focused //single element needed height, width, x, y text(), tag(), classes(), @attribute //CSS Properties css("<css property>")
  32. 32. Geb in 10 slides - Page objects class GebHomePage extends Page { static url = "http://gebish.org" static at = { title.contains "Groovy" } static content = { toggle { $("div.menu a.manuals") } linksContainer { $("#manuals-menu") } links { linksContainer.find("a") } //stacked content } } class TheBookOfGebPage extends Page { //url is optional static at = { title.startsWith("The Book Of Geb") } } void “clicking first manual goes to book of geb”() { given: to GebHomePage //checks at when: toggle.click() waitFor { !linksContainer.hasClass("animating") } links[0].click() then: at TheBookOfGebPage }
  33. 33. Geb in 10 slides - Module class GebHomePage extends Page { static url = "http://gebish.org" static at = { title.contains "Groovy" } static content = { manualsMenu { module(ManualsMenuModule)} } } class ManualsMenuModule extends Module { static content = { toggle { $("div.menu a.manuals") } linksContainer { $("#manuals-menu") } links { linksContainer.find("a") } } void open() { toggle.click() waitFor { !linksContainer.hasClass ("animating") } } } void “clicking first manual goes to book of geb”() { given: to GebHomePage //checks at when: manualsMenu.open() manualsMenu.links[0].click() then: at TheBookOfGebPage }
  34. 34. Geb in 10 slides - Content DSL, moduleList() static content = { «name»(«options map») { «definition» } theDiv(required: false) { $("div", id: "a") } theDiv(min: 1, max: 2) { $("div", id: "a") } theDiv(cache: false) { $("div", id: "a") } helpLink(to: HelpPage) { $("a", text: "Help") } //helpLink.click() sets browser page loginButton(to: [LoginSuccessfulPage, LoginFailedPage]) { $("input.loginButton") } dynamicallyAdded(wait: true) { $("p.dynamic") } someDiv { $("div#aliased") } aliasedDiv(aliases: "someDiv") firstCartItem { $("table tr", 0) module (CartRow)} cartItems { $("table tr").tail().moduleList(CartRow) } assert cartItems.every { it.price > 0.0 }
  35. 35. Geb in 10 slides - Interact API, javascript interface interact { clickAndHold($('#draggable')) moveByOffset(150, 200) release() } //All WebDriver Actions <html> <head> <script type="text/javascript"> var aVariable = 1; </script> </head> </html> assert Browser.js.aVariable == 1 $("div#a").jquery.mouseover()
  36. 36. Geb in 10 slides - Configuration GebConfig.groovy or GebConfig class in classpath driver = “firefox” //driver = { new FirefoxDriver() } environments { prod { driver = chrome } } waiting { timeout = 10 retryInterval = 0.5 presets { slow { Timeout = 20 } } } retorsDir = “target/geb-reports”
  37. 37. Geb in 10 slides - Download API Browser.drive { to LoginPage login("me", "secret") def pdfBytes = downloadBytes(pdfLink.@href) } //downloadStream, downloadText, downloadContent Browser.drive { go "/" def jsonBytes = downloadBytes { HttpURLConnection connection -> connection.setRequestProperty("Accept", "application/json") } }
  38. 38. Geb in 10 slides - windows & frames <a href="http://www.gebish.org" target="myWindow">Geb</a> Browser.drive { go() $("a").click() withWindow("myWindow", close: true) { assert title == "Geb - Very Groovy Browser Automation" } withWindow({ title == "Geb - Very Groovy Browser Automation" }) { assert $(".slogan").text().startsWith("Very Groovy browser automation.") } } //withNewWindow(), withFrame(), withNewFrame()
  39. 39. Closures everywhere! Contents, modules, waitFor, browser, interact Rewritten AST “a la Spock”, so that every expression is turned in an assert (in ats, waits) Wrapped classes - all page, browser available in test specs MethodMissing, PropertyMissing Geb’s black magic
  40. 40. Summary If you have a big application, use end-to-end tests If you use Groovy, use Geb Don’t walk alone in a misty forest...
  41. 41. Thanks Questions? @jdmuriel

×