Geb Best Practices

124 views

Published on

Slides for Geb Best Practices talk delivered during Greach 2017

Published in: Engineering
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
124
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
2
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Geb Best Practices

  1. 1. Geb Best Practices Marcin Erdmann
  2. 2. @marcinerdmann •  Groovy enthusiast since 2010 •  Geb user since 2011 •  Geb lead since 2014 •  Open source contributor •  Testing junkie
  3. 3. 1.0!!! 08.10.2016
  4. 4. 50 Geb contributors •  Robert Fletcher •  Peter Niederwieser •  Alexander Zolotov •  Christoph Neuroth •  Antony Jones •  Jan-Hendrik Peters •  Tomás Lin •  Jason Cahoon •  Tomasz Kalkosiński •  Rich Douglas Evans •  Ian Durkan •  Colin Harrington •  Bob Herrmann •  George T Walters II •  Craig Atkinson •  Andy Duncan •  John Engelman •  Michael Legart •  Graeme Rocher •  Craig Atkinson •  Ken Geis •  Chris Prior •  Kelly Robinson •  Todd Gerspacher •  David M. Carr •  Tom Dunstan •  Brian Kotek •  David W Millar •  Ai-Lin Liou •  Varun Menon •  Anders D. Johnson •  Hiroyuki Ohnaka •  Erik Pragt •  Vijay Bolleypally •  Pierre Hilt •  Yotaro Takahashi •  Jochen Berger •  Matan Katz •  Victor Parmar •  Berardino la Torre •  Markus Schlichting •  Andrey Hitrin •  Leo Fedorov •  Chris Byrneham •  Aseem Bansal •  Tomasz Przybysz •  Brian Stewart •  Jacob Aae Mikkelsen •  Patrick Radtke •  Leonard Brünings
  5. 5. Breaking changes in 1.0 •  Methods like isDisplayed() and text() throw an exception when called on multi element navigators •  Weakly typed module() and moduleList() removed •  isDisabled() and isReadOnly() removed from Navigator
  6. 6. Heresy warning...
  7. 7. Advanced usage a.k.a. Tips & Tricks
  8. 8. Module is-a Navigator •  Modules wrap around their base Navigator •  All Navigator methods can be called on Module instances •  Navigator methods can be used in module implementations
  9. 9. Module is-a Navigator <html> <form method="post" action="login"> ... </from> </html> class LoginFormModule extends Module { static base = { $("form") } } class LoginPage extends Page { static content = { form { module LoginFormModule } } }
  10. 10. Module is-a Navigator to LoginPage assert form.@method == "post” assert form.displayed
  11. 11. Overriding value() on module Class DatepickerModule extends Module { LocalDate value() { ... } Navigator value(LocalDate date) { ... } } class DatepickerPage extends Page { static content = { date { module DatepickerModule } } }
  12. 12. Overriding value() on module to DatepickerPage date.value(LocalDate.of(2017, 4, 1)) assert date.value() == LocalDate.of(2017, 4, 1) to DatepickerPage date = LocalDate.of(2017, 4, 1) assert date == LocalDate.of(2017, 4, 1)
  13. 13. Advanced navigation and types •  Page.convertToPath(Object[]) is used to translate arguments passed to Browser.to() into a path •  Consider overloading convertToPath() for more expressive navigation
  14. 14. Advanced navigation and types class PostPage extends Page { static url = “posts” String convertToPath(Post post) { “${post.id}/${post.title.toLowerCase().replaceAll(“ ”, “-”)}” } } def post = new Post(id: 1, title: “My first post”) to PostPage, post assert title == post.title
  15. 15. Parameterised pages •  Pages can be instantiated and passed to via(), to() and at() •  Useful when you need to differ implementation based on the object represented by the page
  16. 16. Parameterised pages navigation class PostPage extends Page { Post post String getPageUrl() { “posts/${post.id}/${post.title.toLowerCase().replaceAll(“ ”, “-”)}” } } def post = new Post(id: 1, title: “My first post”) to(new PostPage(post: post)) assert title == post.title
  17. 17. Navigators are iterable •  Navigators are backed by a collection of WebElements •  Navigators implement Iterable<Navigator>
  18. 18. Navigators are iterable <html> <p>First paragraph</p> <p>Second paragraph</p> </html> $(“p”).text() // throws exception $(“p”)[0].text() // returns “First paragraph” $(“p”)*.text() // returns [“First paragraph”, “Second paragraph”] //returns [0: “First paragraph”, 1: “Second paragraph”] $(“p”).withIndex().collectEntries { [(it.second): it.first.text()] }
  19. 19. What’s onLoad() for? class CookieBarPage extends Page { boolean autoCloseCookieBar = true static content = { cookieBar { module(CookieBarModule) } } void onLoad(Page previousPage) { if (autoCloseCookieBar && cookieBar) { cookieBar.close() } } }
  20. 20. Injecting javascript into page js.exec ''' var url = 'https://code.jquery.com/jquery-3.2.1.min.js'; var scriptElement = document.createElement('script'); scriptElement.setAttribute('src', url); document.head.appendChild(scriptElement); '''
  21. 21. Navigator’s elements in js void waitForCssTransition(Navigator navigator, WaitingSupport waiting, JavascriptInterface js, Closure trigger) { js.exec(navigator.singleElement(), ''' var o = jQuery(arguments[0]); window.setTransitionFinishedClass = function() { $(this).addClass('transitionFinished'); } o.bind('transitionend', window.setTransitionFinishedClass); ''') try { trigger.call() waiting.waitFor { hasClass('transitionFinished') } } finally { js.exec(navigator.singleElement, ''' var o = jQuery(arguments[0]); o.unbind('transitionend', window.setTransitionFinishedClass); o.removeClass('transitionFinished') window.setTransitionFinishedClass = undefined; ''') } }
  22. 22. Alternatives to inheritance •  Using inheritance for code sharing leads to complex inheritance structures •  @Delegate can be used for code sharing via delegation •  Traits can be used for code sharing
  23. 23. Alternatives to inheritance class TransitionSupport { private final Navigator navigator private final WaitingSupport waiting private final JavascriptInterface js TransitionSupport(Navigator navigator, WaitingSupport waiting, JavascriptInterface js) { ... } void waitForCssTransition(Closure trigger) { ... } } class TransitioningModule extends Module { @Delegate TransitionSupport transitionSupport = new TransitionSupport(this, this, js) }
  24. 24. Alternatives to inheritance trait TransitionSupport implements Navigator, WaitingSupport { abstract JavascriptInterface getJs() void waitForCssTransition(Closure trigger) { ... } } class TransitioningModule extends Module implements TransitionSupport { }
  25. 25. Dynamic base url class RatpackApplicationGebSpec extends GebSpec { def application = new GroovyRatpackMainApplicationUnderTest() def setup() { browser.baseUrl = application.address.toString() } }
  26. 26. Testing responsive apps class TestsMobileViewExtension extends AbstractAnnotationDrivenExtension<TestsMobileView> { void visitFeatureAnnotation(TestsMobileView annotation, FeatureInfo feature) { feature.addInterceptor { invocation -> def window = new ConfigurationLoader().conf.driver.manage().window() def originalSize = window.size try { window.size = new Dimension(320, 568) invocation.proceed() } finally { window.size = originalSize } } } }
  27. 27. Best practices a.k.a. Marcin’s musings
  28. 28. Strongly typed Geb code •  Better authoring, i.e. autocompletion •  Better navigation, i.e. going to method definition, finding usages •  Refactoring support
  29. 29. What IntelliJ understands? •  Delegation to Browser methods in specs •  Delegation to Page methods in specs •  Content definitions
  30. 30. Tracking current page type •  Capture current page in a variable •  Use return values of via(), to() and at() •  Return new page instance from methods triggering page transitions
  31. 31. Tracking current page def homePage = to HomePage homePage.loginPageLink.click() def loginPage = at LoginPage def securePage = loginPage.login("user", "password")
  32. 32. Tracking current page to(HomePage).loginPageLink.click() def securePage = at(LoginPage).login("user", "password")
  33. 33. At checks •  Keep them quick and simple •  Check if you are at the right page •  Don’t test any page logic
  34. 34. What’s missing here? class CookieBarPage extends Page { boolean autoCloseCookieBar = true static content = { cookieBar { $('.cookie-message’) } cookieBarText { $('.cookie-message-text').text() } cookieBarCloseButton { $('.cookie-message-button > input') } } }
  35. 35. What are modules good for? •  More than reuse •  Modeling logical components •  Hiding component structure from tests •  Hiding complex interactions from tests
  36. 36. Invest time in fixture builders •  Aim for easy, expressive and flexible data setup •  Keep data setup close to tests •  Setup only the data necessary for the test •  Groovy Remote Control can be very helpful •  Fixtures are not not only about data
  37. 37. Geb’s test harness html fixture def “can access element classes”() { given: html { div(id: "a", 'class': "a1 a2 a3") div(id: "b", 'class': "b1”) } expect: $("#a").classes() == ["a1", "a2", "a3"] $("#b").classes() == ["b1”] }
  38. 38. Example data fixture DSL bootstrapper.post { id = “69f10bb6-118e-11e7-93ae-92361f002671” title = “My first post” tags = [“Groovy”, “Geb”] author { firstName = “Marcin” lastName = “Erdmann” } }
  39. 39. Use real browser in CI •  Available headless drivers are either limited, not developed anymore or not properly tested •  Use a real browser with a virtual frame buffer, i.e. Xvfb •  Use a cloud browser provider
  40. 40. reportOnTestFailuresOnly
  41. 41. To waitFor() or not?
  42. 42. To waitFor() or not? •  Ask yourself if action is asynchronous •  Watch out for quick asynchronous events
  43. 43. waitFor() considered harmful •  Best not used directly in tests •  Hide asynchronous behaviour in action methods on modules and pages
  44. 44. Increasing the timeout is (usually) not the answer
  45. 45. Use wait presets
  46. 46. Browser test are costly! •  Slow, hard to maintain and flakey •  Mind what you test at such high level •  ...but they provide a lot of confidence
  47. 47. Cross browser tests are costlier
  48. 48. Cloud browser providers •  Gradle plugins for driving browsers with Geb in BrowserStack and Suace Labs •  Can test applications via a tunnel which are not available on the Internet
  49. 49. Using attributes map selectors •  Map selectors are now translated to CSS attribute selectors •  As quick as using css selectors but reads better especially when using dynamic values
  50. 50. Using attributes map selectors $(‘button’, type: ‘select’) $(‘button[type=“select”]’) $(itemtype: ‘http://schema.org/Person’) $(‘[itemtype=“http://schema.org/Person”]’)
  51. 51. Selecting by text is slow! •  Selecting by text is a Geb extension not something that CSS supports •  Narrow down selected elements as much as possible when selecting by text
  52. 52. Questions?
  53. 53. Thank you!

×