Testing Web Applications with GEB

57
© 2012 Howard M. Lewis Ship Testing Web Applications with GEB Howard M. Lewis Ship TWD Consulting [email protected] @hlship

description

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

Transcript of Testing Web Applications with GEB

Page 1: Testing Web Applications with GEB

© 2012 Howard M. Lewis Ship

Testing Web Applications with GEBHoward M. Lewis ShipTWD [email protected]@hlship

Page 2: Testing Web Applications with GEB

Testing Web Applications

What's your process?

Manual?

Selenium?

What about Ajax?

Page 3: Testing Web Applications with GEB

Spoiled by jQuery

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

<div class="summary">

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

http://jquery.com/

Page 4: Testing Web Applications with GEB

Drives:

or limited HtmlUnit (browserless)

Selenium WebDriver

http://seleniumhq.org/

Page 5: Testing Web Applications with GEB

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");

Page 6: Testing Web Applications with GEB

driver.get("http://www.google.com");

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

element.sendKeys("Cheese!");

element.submit();

new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver d) { return d.getTitle().toLowerCase().startsWith("cheese!"); }};

Triggers Ajax UpdateTriggers Ajax Update

Page 7: Testing Web Applications with GEB

Java == High Ceremony

Page 8: Testing Web Applications with GEB

Groovy == Low Ceremony + Dynamic

Page 9: Testing Web Applications with GEB

Power of Selenium WebDriver 2.15.0

Elegance of jQuery content selection

Robustness of Page Object modelling

Expressiveness of Groovy

First Class Documentation

Page 10: Testing Web Applications with GEB

Running GEB Interactively

import 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.groovy

handlers=java.util.logging.ConsoleHandler

java.util.logging.ConsoleHandler.level=WARNINGjava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter

geb-logging.properties

Page 11: Testing Web Applications with GEB

GEB Interactive$ groovyshGroovy 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.*]===> nullgroovy:000> b = new Browser(baseUrl: "http://google.com/")===> geb.Browser@190a0d51groovy:000> b.go()===> nullgroovy:000>

Page 12: Testing Web Applications with GEB

Google Demo

Page 13: Testing Web Applications with GEB

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

Page 14: Testing Web Applications with GEB

IMDB Demo

Page 15: Testing Web Applications with GEB

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()

Page 16: Testing Web Applications with GEB

Browser

go() : voidquit() : voidpage : Page

Page

pageUrl : Stringtitle : String

$(…) : NavigatorwaitFor(…) : Object

startsWith(…) : TextMatchercontains(…) : TextMatcherendsWith(…) : TextMatcher

Navigator

x : inty : int

width : intheight : int

disabled : booleanempty : boolean

displayed : booleanadd(String) : Navigator

click() : voidfilter(...) : Navigatorfind(…) : Navigator

not(String) : Navigatorfirst() : Navigatorlast() : Navigator

getAt(…) : NavigatorgetAttribute(String) : String

has(String) : Navigatorparents(String) : Navigator

parentsUntil(String): Navigatorsize() : int

text() : Stringvalue() : Object

Delegation

GebSpec

Deleg

atio

n

Page 17: Testing Web Applications with GEB

$("p") ➠ all <p>

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

$("p")[3] ➠ 4th <p>

$("p")[0..2] ➠ 1st through 3rd <p>

$(css selector, index, attribute / text matchers)

CSS3

Page 18: Testing Web Applications with GEB

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

Page 19: Testing Web Applications with GEB

Attribute PredicatesCase Sensitive Case Insensitive Description

startsWith iStartsWith start with value

contains iContains contains the value anywhere

endsWith iEndsWith end with value

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

notStartsWith iNotStartsWith DOES NOT start with value

notContains iNotContains DOES NOT contain value anywhere

notEndsWith iNotEndsWith DOES NOT end with value

notContainsWord iNotContainsWord DOES NOT contain value (surrounded by whitespace, or at begin or end)

Page 20: Testing Web Applications with GEB

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

Page 21: Testing Web Applications with GEB

Navigators are Groovy Collections

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

each() is a Groovy Collection method

Page 22: Testing Web Applications with GEB

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

Page 23: Testing Web Applications with GEB

Pages and Modules

Page 24: Testing Web Applications with GEB

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()

Page 25: Testing Web Applications with GEB

Solution: Model Pages not just DOMBrowser

go() : voidquit() : voidpage : Page

at(…) : boolean

Page

pageUrl : Stringtitle : String

$(…) : NavigatorwaitFor(…) : ObjectverifyAt() : booleanbrowser: Browser

Delegation

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

groovy:000> b.$(".home").click(Home) ===> nullgroovy:000> b.verifyAt()===> truegroovy:000> b.at Home===> truegroovy:000> b.page===> Homegroovy:000> b.to Home===> null

Page 26: Testing Web Applications with GEB

Page Contentclass 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

Page 27: Testing Web Applications with GEB

to / do / at

groovy:000> b.to Home===> nullgroovy:000> b.q = "Forbidden Planet"===> Forbidden Planetgroovy:000> b.searchForm.go.click()===> nullgroovy:000> b.at Search===> truegroovy:000>

Page 28: Testing Web Applications with GEB

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") } }

Page 29: Testing Web Applications with GEB

Content OptionsOption Type Default Description

cache boolean false Evaluate content once, or on each access

required boolean true

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

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

the link submits to

wait varies null Wait for content to become available (via Ajax/DHTML)

Page 30: Testing Web Applications with GEB

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>

Page 31: Testing Web Applications with GEB

Problem: Re-used web pagesclass Movie extends Page {

static at = { assert title.startsWith("Blade Runner") true }

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)===> nullgroovy:000> b.page===> Moviegroovy:000> b.verifyAt()ERROR org.codehaus.groovy.runtime.powerassert.PowerAssertionError:assert title.startsWith("Blade Runner") | | | false The Bugs Bunny/Road-Runner Movie (1979) - IMDb

GEB 0.7 will magically convert to asserts

Page 32: Testing Web Applications with GEB

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() } }}

Page 33: Testing Web Applications with GEB

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) }}

Page 34: Testing Web Applications with GEB

groovy:000> b.searchForm.go.click()===> nullgroovy: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) }

click()

Page 35: Testing Web Applications with GEB

Problem: Duplication on Pagesb.$("#navbar-query") << "Bladerunner"b.$("#navbar-submit-button").click()

Other examples:

Login / Logout / Register

"Contact Us" & other boilerplate

"Mark Favorite"

View Product Details

Bid / Buy

Page 36: Testing Web Applications with GEB

Solution: Modulesclass 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===> nullgroovy:000> b.searchForm.field << "Serenity"===> [org.openqa.selenium.firefox.FirefoxWebElement@1ef44b1f]groovy:000> b.searchForm.go.click()===> nullgroovy:000> b.page===> Search

Page 37: Testing Web Applications with GEB

Problem: Repeating Elements

<table class="cast_list"> <tr> <td class="primary_photo"> … <td class="name"> … <td class="ellipsis"> … <td class="character"> …

Page 38: Testing Web Applications with GEB

Solution: Module Listsclass CastRow extends Module { static content = { actorName { $("td.name").text() } characterName { $("td.character").text() } }} 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() } }}

Scope limited to each <tr>

Page 39: Testing Web Applications with GEB

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]

Page 40: Testing Web Applications with GEB

JavaScript and Ajax

Page 41: Testing Web Applications with GEB

js object

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

Access simple page properties

Page 42: Testing Web Applications with GEB

Executing JavaScript

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

Text evaluated in-browser

Page 43: Testing Web Applications with GEB

jQuery Hook

groovy:000> b.$("img").jquery.fadeIn()===> [org.openqa.selenium.firefox.FirefoxWebElement@de86fd70, org.openqa.selenium.firefox.FirefoxWebElement@615e6612, 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]

Silently fails unless jQuery on page

More methodMissing() magic!

Page 44: Testing Web Applications with GEB

WaitingwaitFor { condition }waitFor timeout { condition }waitFor timeout, interval { condition }waitFor "preset" { condition }

Page 45: Testing Web Applications with GEB

Testing Framework Integration

Page 46: Testing Web Applications with GEB

Reportingpackage myapp.tests

import geb.spock.*

class Login extends GebReportingSpec {

def "successful login"() {

when: go "login" username = "user1" report "login screen" login().click()

then: title == "Welcome, User1"

}}

Capture HTML and screenshot

Base class that reports at end of each test

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

Page 47: Testing Web Applications with GEB

Base Classes

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 end of each test

Report failures only

Page 48: Testing Web Applications with GEB

Delegationpackage myapp.tests

import geb.spock.*

class Login extends GebReportingSpec {

def "successful login"() {

when: go "login" username = "user1" report "login screen" login().click()

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

Browser

Page

Geb[Reporting]Spec

Deleg

atio

nDe

legat

ion

Page 49: Testing Web Applications with GEB

Configuration

Page 50: Testing Web Applications with GEB

src/test/resources/GebConfig.groovy

GebConfig.groovyimport org.openqa.selenium.firefox.FirefoxDriverimport 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

Page 51: Testing Web Applications with GEB

Environment$ grade test -Dgeb.env=chrome

src/test/resources/GebConfig.groovyimport org.openqa.selenium.firefox.FirefoxDriverimport 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() }    }}

Page 52: Testing Web Applications with GEB

Waiting Configuration

GebConfig.groovywaiting { timeout = 10 retryInterval = 0.5

presets { slow { timeout = 20, retryInterval = 1 } quick { timeout = 1 } } }

Page 53: Testing Web Applications with GEB

More Info

Page 54: Testing Web Applications with GEB

http://www.gebish.org

Page 55: Testing Web Applications with GEB

https://github.com/geb/geb

Page 56: Testing Web Applications with GEB

http://howardlewisship.com

Page 57: Testing Web Applications with GEB

Q & A