Testing Web Applications with GEB
-
Upload
howard-lewis-ship -
Category
Technology
-
view
5.445 -
download
1
description
Transcript of Testing Web Applications with GEB
© 2012 Howard M. Lewis Ship
Testing Web Applications with GEBHoward M. Lewis ShipTWD [email protected]@hlship
Testing Web Applications
What's your process?
Manual?
Selenium?
What about Ajax?
Spoiled by jQuery
$(".order-summary .subtotal").text() == "107.95"
<div class="summary">
…
<div class="subtotal">107.95</div>
http://jquery.com/
Drives:
or limited HtmlUnit (browserless)
Selenium WebDriver
http://seleniumhq.org/
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");
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
Java == High Ceremony
Groovy == Low Ceremony + Dynamic
Power of Selenium WebDriver 2.15.0
Elegance of jQuery content selection
Robustness of Page Object modelling
Expressiveness of Groovy
First Class Documentation
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
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>
Google Demo
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
IMDB Demo
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()
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
$("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
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
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)
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
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
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
Pages and Modules
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()
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 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
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>
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") } }
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 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>
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
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() } }}
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) }}
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()
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
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
Problem: Repeating Elements
<table class="cast_list"> <tr> <td class="primary_photo"> … <td class="name"> … <td class="ellipsis"> … <td class="character"> …
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>
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]
JavaScript and Ajax
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
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
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!
WaitingwaitFor { condition }waitFor timeout { condition }waitFor timeout, interval { condition }waitFor "preset" { condition }
Testing Framework Integration
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
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
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
Configuration
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
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() } }}
Waiting Configuration
GebConfig.groovywaiting { timeout = 10 retryInterval = 0.5
presets { slow { timeout = 20, retryInterval = 1 } quick { timeout = 1 } } }
More Info
http://www.gebish.org
https://github.com/geb/geb
http://howardlewisship.com
Q & A