testing web applications with geb

Post on 06-May-2015

5.451 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

© 2012 Howard M. Lewis Ship

Testing Web Applications with GEBHoward M. Lewis ShipTWD Consultinghlship@gmail.com@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

top related