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

Test de aceptación con Geb - Commit Conf 2018

Sergio del Amo
December 19, 2018
85

Test de aceptación con Geb - Commit Conf 2018

En esta charla descubrirás Geb. Una solución de automatización del uso del navegador que te permitirá escribir test de aceptación que con facilidad y que te permitirán dormir bien por las noches.

Durante la charla realizaremos tests de la web Commit Conf 2018 https://2018.commit-conf.com

De este modo veras las características de Geb aplicadas a un ejemplo real.

Trataremos los siguientes conceptos:

• Gestión de las instancias de WebDriver. Como ejecutar nuestros tests en Chrome, Firefox o headless. • Configuración Built-in • Uso del Page Object Pattern para obtener unos tests de aceptación fáciles de mantener. • Navegación y selección de contenido • Integración con diferentes framework de test

Audiencia: Aunque Geb es una capa de Groovy encima de WebDriver no necesitas conocimientos específicos de Groovy. Cualquier desarrollador, es especial aquellos con conocimientos Java o lenguajes de JVM podrá seguir la charla sin problemas.

Sergio del Amo

December 19, 2018
Tweet

Transcript

  1. View Slide

  2. · Micronaut / Grails OCI Team
    · Gudalajara, Spain
    · Curator of Groovycalamari.com
    · @sdelamo
    · http://sergiodelamo.es
    · greachconf.com organizer

    View Slide

  3. http://groovycalamari.com

    View Slide

  4. http://greachconf.com

    View Slide

  5. Browser Automation Solution
    · Acceptance Testing Web Applications
    · Automating Web Sites
    · Screen Scraping

    View Slide

  6. What is Geb?
    · Cross browser automation capabilities of WebDriver
    · A Groovy layer on top of WebDriver
    · WebDriver instance management

    View Slide

  7. What is Geb?
    · jQuery-like Navigator API and elegance of jQuery content
    selection
    · Robustness of Page Object modelling
    · Integrates with various test frameworks
    · Built-in configuration mechanism

    View Slide

  8. About the Project
    Free Open Source, Apache License, Version 2.0.
    · Home Page http://www.gebish.org
    · The Book of Geb
    · Source Code
    · User Mailing List
    · Geb in Maven Central

    View Slide

  9. Marcin Erdmann
    Geb project Lead
    http://blog.proxerd.pl

    View Slide

  10. Project Components
    The heart is the geb-core component which is all you really need
    (plus WebDriver).

    View Slide

  11. For testing, you probably also want one of these as well:
    · geb-spock
    · geb-junit3
    · geb-junit4
    · geb-testng
    · geb-easyb

    View Slide

  12. WebDriver API
    Geb sits on top of WebDriver so you very rarely deal with its API,
    though it's accessible if you need it.
    Geb never talks to the actual browser.
    That's what WebDriver does.

    View Slide

  13. Driver dependency
    You need to pull in a specific driver implementation for each
    browser you want to work with.

    org.seleniumhq.selenium
    selenium-firefox-driver
    2.20.0

    View Slide

  14. Driver management
    WebDriver Binaries Driver Gradle Plugin
    A plugin that downloads and caches WebDriver binaries specific to
    the OS the build runs on.

    View Slide

  15. jQuery
    http://jquery.com/
    jQuery - write more, do less
    jQuery provides an incredibly powerful API for navigating and
    selecting content.
    $("div#footer").prev().childen();
    CSS based, a whole lot better than XPath.

    View Slide

  16. Geb's inspiration
    Geb features a Navigator API that it inspired by jQuery.
    // This is Geb code, not jQuery JavaScript…
    $("h1").previous().children();
    API is not identical.

    View Slide

  17. Groovy
    · Compiled, never interpreted
    · Dynamic, optionally typed
    · 99% Java syntax compatible
    · Concise, clear and pragmattic
    · Great for DSLs
    · A comfortable Java alternative for most

    View Slide

  18. Required Apache Groovy
    Knowledge
    http://groovy-lang.org
    You don't need to be a Groovy ninja to use Geb.
    Groovy knowledge can definitely help when things go wrong
    though.

    View Slide

  19. Geb & Groovy
    Geb uses Groovy's dynamism to remove boilerplate.
    import geb.*
    Browser.drive {
    to GoogleHomePage
    at GoogleHomePage
    search.forTerm "wikipedia"
    at GoogleResultsPage
    assert firstResultLink.text() == "Wikipedia"
    firstResultLink.click()
    waitFor { at WikipediaPage }
    }

    View Slide

  20. Page Objects
    The key to not pulling your hair out when dealing with web tests.
    In a phrase: Domain Modelling.
    By modelling and creating abstractions, we can isolate
    implementation detail.

    View Slide

  21. Page Objects
    $("input[name=username]").value("user")
    $("input[name=password]").value("password")
    $("input[type=submit]").click()
    Is far more fragile than this…
    loginPage.login("user", "password")

    View Slide

  22. Page Objects
    Just Good Programming
    Geb builds the Page Object pattern directly into the framework
    (though it is optional).

    View Slide

  23. Geb Pages
    The Page Object Pattern allows us to apply the same principles of
    modularity, reuse and encapsulation that we use in other aspects
    of programming to avoid such issues in browser automation code

    View Slide

  24. Browser has-a Page
    The to() and click() methods are changing the underlying page.
    You can refer to the current page's content and methods just by
    name.

    View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. View Slide

  31. Geb's Page Objects
    Features the Content DSL for naming content in a dynamic and
    powerful way.
    import geb.*
    class GoogleResultsPage extends Page {
    static at = { waitFor { title.endsWith("Google Search") } }
    static content = {
    search { $("#sb").module(GoogleSearchModule) }
    results { $("li.g") }
    result { i -> results[i] }
    resultLink { i -> result(i).find("a.l", 0) }
    firstResultLink { resultLink(0) }
    }
    }

    View Slide

  32. Geb's Page Objects
    Very lightweight, minimum requirements are low.
    import geb.*
    class WikipediaPage extends Page {
    static at = { title == "Wikipedia" }
    }

    View Slide

  33. Modules

    View Slide

  34. View Slide

  35. View Slide

  36. Modules
    Modules are repeating and/or reappearing content.



    TitleAuthor




    Zero History
    William Gibson


    The Evolutionary Void
    Peter F. Hamilton



    View Slide

  37. Modules
    Modules have a base, from which all content lookups are relative.
    class BooksPage extends Page {
    static content = {
    bookResults {
    $("table#book-results tbody tr", it).module(BookRow)
    }
    }
    }
    class BookRow extends Module {
    static content = {
    cell { $("td", it) }
    title { cell(0).text() }
    author { cell(1).text() }
    }
    }

    View Slide

  38. Modules
    We now have a model for a row in our table.
    expect:
    bookResults(0).title == "Zero History"
    bookResults(1).author == "Peter F. Hamilton
    Can be used for any reused/repeating content.
    Note: talking about domain objects, not HTML tables and rows.

    View Slide

  39. Pagination Example

    View Slide

  40. Pagination Example
    static content = {
    pagination { $('ul.pagination').module(Pagination) }
    }

    View Slide

  41. class Pagination extends Module {
    static content = {
    links(required: false) { $('a') }
    currentPage(required: false) { $('.currentStep')?.text()?.toInteger() ?: 1 }
    nextLink(required: false) { links.filter('.nextLink') }
    previousLink(required: false) { links.filter('.prevLink') }
    }
    void toPage(int pageNumber) {
    def link = links.filter(text: "$pageNumber")
    if (!link) {
    def exceptionMsg = "Page number $pageNumber not present in pagination"
    throw new IllegalArgumentException(exceptionMsg)
    }
    link.click()
    }
    boolean isLastPage() { nextLink.empty }
    void nextPage() { toPage(currentPage + 1) }
    void previousPage() { toPage(currentPage + -1) }
    boolean isFirstPage() { previousLink.empty}
    }

    View Slide

  42. Inheritance
    Pages (and modules) can be arranged in inheritance hierarchies.
    class Footer extends Module {
    static content = {
    copyright { $("p.copyright") }
    }
    }
    class StandardPage extends Page {
    static content = {
    footer { $('footer').module(Footer) }
    }
    }
    class FrontPage extends StandardPage {}

    View Slide

  43. Testing
    Geb's testing adapters. Geb can be used with…
    · Spock
    · JUnit (3 & 4)
    · TestNG
    · EasyB
    · Cucumber (Cuke4Duke)

    View Slide

  44. Screenshots
    Geb can dump HTML and screenshots for each “test” to help in
    debugging.
    If using Spock, extend GebReportingSpec instead GebSpec.
    report()

    View Slide

  45. Navigator API
    jQuery inspired content selection/navigation
    The $() method
    Returns a Navigator object.
    General format:
    $(«css selector», «index/range», «attribute/text matchers»)

    View Slide

  46. Examples
    $("div") // all divs
    $("div", 0) // first div
    $("div", 0..2) // first three divs
    // The third section heading with text “Geb”
    $("h2", 2, id: "section", text: "Geb")

    View Slide

  47. CSS Selector
    Full CSS3 if the target browser supports it.
    $("div.some-class p:first[title='something']")
    $("ul li a")
    $("table tr:nth-child(2n+1) td")
    $("div#content p:first-child::first-line")
    CSS lookups are fast.

    View Slide

  48. Attribute/Text matching
    Can match on attribute values:
    //
    $("div", foo: "bar")

    View Slide

  49. Attribute/Text matching
    The “text” attribute is special:
    //foo
    $("div", text: "foo")

    View Slide

  50. Attribute/Text matching
    Can use Regular Expressions:
    //foo
    $("div", text: ~/f.+/)

    View Slide

  51. Predicates
    Geb supplies some handy predicates:
    $("p", text: startsWith("p"))
    $("p", class: contains("section"))
    $("p", id: endsWith(~/\d/))
    There are more of these.

    View Slide

  52. Relative Content
    $() returns a Navigator that allows you to find relative content.
    $("p").previous()
    $("p").prevAll()
    $("p").next()
    $("p").nextAll()
    $("p").parent()
    $("p").siblings()
    $("div").children()

    View Slide

  53. Relative Content
    Most of these methods take selectors, indexes and attribute text/
    matchers too.
    $("p").nextAll(".listing")

    View Slide

  54. Content DSL
    Content definitions can build upon each other.
    Content definitions are actually templates.
    class GoogleResultsPage extends Page {
    static content = {
    results { $("li.g") }
    result { i -> results[i] }
    resultLink { i -> result(i).find("a.l", 0) }
    firstResultLink { resultLink(0) }
    }
    }

    View Slide

  55. Optional Content
    By default, Geb will error if the content you select doesn't exist.
    The “required” option disables this check.
    class OptionalPage extends Page {
    static content = {
    errorMsg(required: false) { $("p.errorMsg") }
    }
    }

    View Slide

  56. Dynamic Content
    Geb will wait for some time for this content to appear.
    Same semantics as the waitFor {} method that can be used
    anywhere.
    class DynamicPage extends Page {
    static content = {
    errorMsg(wait: true) { $("p.errorMsg") }
    }
    }

    View Slide

  57. Navigation go method
    Getting around. The go() method
    browser.go "https://2018.commit-conf.com/"

    View Slide

  58. Navigation
    Getting around. The to() method
    class CommitConfHomePage extends Page {
    static url = "https://2018.commit-conf.com/"
    }
    Pages can define a url that defines the page location.

    View Slide

  59. Navigation
    The to() method sends the browser there and sets that as the
    current page object.
    to CommitConfHomePage

    View Slide

  60. Navigation
    The page url can be relative (will be resolved against a config
    driven base).
    browser.baseUrl = "https://2018.commit-conf.com"
    class CommitConfHomePage extends Page {
    static url = "/"
    }
    to CommitConfHomePage

    View Slide

  61. Dynamic URL
    url = baseUrl + url + covertToPath
    browser.baseUrl = 'http://localhost:8080'
    browser.to EditRoomPage, 1

    View Slide

  62. Content based navigation
    class FrontPage {
    static content = {
    aboutUsLink(to: AboutUsPage) {
    $("div#nav ul li a", text: iStartsWith("About Us"))
    }
    }
    }
    to FrontPage
    aboutUsLink.click()
    page instanceof AboutUsPage
    When this content is clicked, the underlying page will be changed
    implicitly.

    View Slide

  63. Geb Environments
    ./gradlew -Dgeb.env=chromeHeadless test
    // src/main/test/resources/GebConfig.groovy
    environments {
    chrome {
    driver = { new ChromeDriver() }
    }
    chromeHeadless {
    driver = {
    ChromeOptions o = new ChromeOptions()
    o.addArguments('headless')
    new ChromeDriver(o)
    }
    }

    View Slide

  64. · JavaScript interface
    · jQuery interface
    · Direct Downloading
    · Multi Window support
    · Frame support

    View Slide

  65. · Page Change Listening
    · Actions (e.g. Drag & Drop)
    · Caching expensive content lookups
    · alert()/confirm() handling
    · Scripting style (sans Page Objects)
    · Configuration & Environments

    View Slide

  66. Q & A

    View Slide