geb for testing your grails application

Post on 10-Aug-2015

36 Views

Category:

Software

5 Downloads

Preview:

Click to see full reader

TRANSCRIPT

GEB FOR TESTING YOUR GRAILSAPPLICATION

Jacob Aae Mikkelsen

AGENDAFunctional testing

Geb - cudos and history

How Geb works

Geb and Grails 2 and 3

Browser support

Javascript

JACOB AAE MIKKELSENSenior Engineer at Lego

Microservice based architechture on JVM

Previously 4 years at Gennemtænkt IT

Consultant on Groovy and Grails

External Associate Professor - University of SouthernDenmark

@JacobAae

Blogs The Grails Diary

FUNCTIONAL TESTINGIgnores the specifics of the underlying softwarecomponent under test.

Merely asserts that providing certain input results incertain output.

Web-application: Programmatically controlling a webbrowser to simulate the actions of a user on a web page.

Traditionally been tedious, cumbersome and brittleto do

Geb helps ease the pain

GEB

CREDITSLuke Daley

Marcin Erdmann

GEB HISTORYStarted in November 2009

Created by Luke Daley

Current project lead Marcin Erdman

WHY GEBjQuery like selector syntax

Power of WebDriver (Easier api)

Robustness of Page Object modeling

Expressiveness of the Groovy language

Integrates well with build systems (Gradle/Maven)

Excellent user manual/documentation

GEB IMPLEMENTATIONBuild on top of the WebDriver browser automation library

successor to the Selenium Remote Control (RC) testingframework.

Selenium RC → JavaScript to interact

WebDriver → native browser drivers

Use JUnit or Spock

USING GEB

GEB SELECTORS (1)Jquery like syntax

// match all 'p' elements on page$("p")

// match the first 'p' element on the page$("p", 0)

// All 'p' elements with a title value 'section'$("div", title: "section")

// match the first 'p' element title attribute 'section'$("p", 0, title: "section")

// match the first 'p' element with the class 'main'$("p.main", 0)

GEB SELECTORS (2)Selecting returns Navigator objects

// The parent of the first div$("div", 0).parent()

// All tables with a cellspacing// attribute value of 0 that are nested in a paragraph$("p").find("table", cellspacing: '0')

RETRIVING INFORMATION$("p").text() == "a"$("p").tag() == "p"$("p").@title == "a"$("p").classes() == ["a", "para"]

INTERACTION WITH CONTENTclick()

isDisplayed()

withConfirm

withAlert

$("a.btn").click()$("div").isDisplayed()

withConfirm $("button.delete").click()

SENDING INPUTimport org.openqa.selenium.Keys

// Shorthand for sendKeys() method of WebDriver.$("div") << "abc"

$("input", name: "foo") << Keys.chord(Keys.CONTROL, "c")

MORE INTERACTION WITH FORMSConsider the following HTML…

<form> <input type="text" name="geb" value="Functional" /></form>

The value can be read and written via property notation…

$("form").geb == "Functional"$("form").geb = "Testing"$("form").geb == "Testing"

These are literally shortcuts for…

$("form").find("input", name: "geb").value() == "Functional"$("form").find("input", name: "geb").value("Testing")$("form").find("input", name: "geb").value() == "Testing"

VARIABLES AVAILABLEtitle

browser

currentUrl

currentWindow

MORE POSSIBILITIESUploading files

Interaction closures

Simulate drag-n-drop

Control-clicking

Interacting with javascript

js object

STRUCTURING GEB TESTSLets test a CRUD part of a grails application registrering

conference attendees

Lets test the following

1. Goto list of attendees page

2. Create new employee (incl. invalid data once)

3. Update the employee

4. Check data is updated

GEB SPEC BASICSimport geb.spock.GebSpec

@Stepwise // Ensures the tests are run sequentiallyclass AttendeeFunctionalSpec extends GebSpec

// Spock specs here

GEB SPEC (1)The naive inmaintainable way!

void "Go to list page ­ check initial state"() when:"The home page is visited" go '/attendee/index'

then: title == "Attendee List"

GEB SPEC (2)The naive inmaintainable way!

void "Click new attendee button"() when: $("a.create").click()

then: title == "Create Attendee"

GEB SPEC (3)The naive inmaintainable way!

void "Submit form with errors"() when: $("button.btn­primary").click()

then: title == "Create Attendee"

GEB SPEC (4)The naive inmaintainable way!

void "Submit form with no errors"() when: $('form').name = 'Bobby' $('form').email = 'bobby@mail.dk'

and: $("button.btn­primary").click()

then: title == 'Show Attendee' $('span.property­value').find it.text() == 'Bobby' $('span.property­value').find it.text() == 'bobby@mail.dk'

GEB SPEC (5)The naive inmaintainable way!

void "Click Edit Button"() when: $("a.btn­primary").click()

then: title == 'Edit Attendee'

GEB SPEC (6)The naive inmaintainable way!

void "Update Attendee"() when: $('form').name = 'Alison' $('form').email = 'alison@gr8.dk'

and: $("button.btn­primary").click()

then: title == 'Show Attendee' $('span.property­value').find it.text() == 'Alison' $('span.property­value').find it.text() == 'alison@gr8.dk'

GEB SPEC - THE BETTER WAYIf we make a few scenarios, there will be

Much duplication

Many places to correct if we change the layout / DOM

We can correct this using pages and modules

PAGE OBJECTSDescribes a web page

Url

How to check if we are at the correct place

Content we wish to interact with

PAGE OBJECTSimport eu.gr8conf.grailsdemo.modules.NavigationBarModuleimport geb.Page

class AttendeeShowPage extends Page

static url = "/attendee/show"

static at = title ==~ /Show Attendee/

static content = attProp $('span.property­label') name attProp.find it.text() == 'Name'.next().text() email attPro.find it.text() == 'Email'.next().text() editButton $("a.btn­primary")

MODULESDescribes repeated content

Across pages

Within the same page

MODULESimport geb.Module

class NavigationBarModule extends Module

static base = $('nav.navbar')

static content = home(required: false) $('a.home') listAttendee(required: false) $('a.list') newAttendee(required: false) $('a.create')

MODULESstatic content = // Like this, the module does not need a base// form module NavigationBarModule, $('nav.navbar') form module NavigationBarModule

MODULE FOR REPEATED CONTENTIN A PAGE

import geb.Module

class AttendeeListItemModule extends Module

static content = data $("td", it) name data(0).text() email data(1).text() nationality data(2).text() dateCreated data(3).text() lastUpdated data(4).text()

MODULE FOR REPEATED CONTENTIN A PAGE

AttendeeListPage.groovy

static content = menubar module NavigationBarModule attendees moduleList AttendeeListItemModule, $("table tr").tail()

MODULE FOR REPEATED CONTENTIN A PAGE

when:to AttendeeListPage

then:attendees*.name.contains('Guillaume Laforge')

GEB SPEC - STRUCTURED (1)Lets try to restructure the ugly spec from before

void "Go to list page ­ check initial state"() when: to AttendeeIndexPage

then: at AttendeeIndexPage

GEB SPEC - STRUCTURED (2)void "Click new attendee button"() when: menubar.newAttendee.click()

then: at AttendeeCreatePage

GEB SPEC - STRUCTURED (3)void "Submit form with errors"() when: submitButton.click()

then: at AttendeeCreatePage

GEB SPEC - STRUCTURED (4)void "Submit form with no errors"() when: form.name = 'Bob' form.email = 'bob@somemail.com'

and: submitButton.click()

then: at AttendeeShowPage name == 'Bob' email == 'bob@somemail.com'

GEB SPEC - STRUCTURED (5)void "Click Edit Button"() when: editButton.click()

then: at AttendeeEditPage

GEB SPEC - STRUCTURED (6)void "Update Attendee"() when: form.name = 'Alice' form.email = 'alice@somemail.com'

and: updateButton.click()

then: at AttendeeShowPage title == 'Show Attendee' name == 'Alice' email == 'alice@somemail.com'

GEB WITH GRAILS

GEB AND GRAILS 2.XMust install plugin in BuildConfig.groovy

dependencies ... test("org.seleniumhq.selenium:selenium­support:2.45.0") test("org.seleniumhq.selenium:selenium­firefox­driver:2.45.0") test "org.gebish:geb­spock:0.10.0"plugins ... test "org.grails.plugins:geb:0.10.0"

GEB TESTS IN GRAILS 2.XTests placed in test/functional folder

Running the tests

grails test­app functional:

GEB AND GRAILS 3Geb is default in build.gradle

dependencies ... testCompile "org.grails.plugins:geb"

// Note: It is recommended to update to a more robust driver // (Chrome, Firefox etc.) testRuntime 'org.seleniumhq.selenium:selenium­htmlunit­driver:2.44.0

GEB TESTS IN GRAILS 3Creating Geb Spec

grails create­functional­test MyGebScenario

Placing the test in src/integration-test/groovy

Running the tests

grails test­app ­integration

GENERATED CLASS@Integration@Rollbackclass ManyAttendeesSpec extends GebSpec

void "test something"() when:"The home page is visited" go '/'

then:"The title is correct" $('title').text() == "Welcome to Grails"

INTERACTING WITH APPLICATIONWhen some functionality is needed that is not exposed

through the browser, it can be necessary to interact with theapplication under test.

GRAILS 2.5Tests not running in same JVM

Done with remote-control plugin

Send a closure for execution in application

compile ":remote­control:2.0"

REMOTE CONTROLsetup: 'Create some item not available through GUI'def id = remote Item item = new Item(name: "MyItem") item.save() item.id

GRAILS 3.0Application is in same jvm

Interaction is possible directly

Tests run more like integration tests

INTERACTING WITH APPLICATIONvoid "Test Pagination is shown with 15 attendees"() setup: Attendee.withNewTransaction 15.times new Attendee(name: "N$it", email: "m$it@t.dk").save()

when: to AttendeeIndexPage

then: hasPagination()

INTERACTING WITH APPLICATIONstatic content = menubar module NavigationBarModule pagination(required: false) $('span.currentStep')

boolean hasPagination() pagination.text()

CONFIGURATION AND BROWSERSUPPORT

GEBCONFIGConfiguration for Geb is placed in GebConfig.groovy

In Grails 3, place it in ´src/integration-test/groovy`

http://www.gebish.org/manual/current/configuration.html

DRIVERIt is possible to configure the browser used.

build.gradle

compile 'org.seleniumhq.selenium:selenium­chrome­driver:2.45.0'compile 'org.seleniumhq.selenium:selenium­firefox­driver:2.45.0'

FIREFOXGebConfig.groovy

import org.openqa.selenium.firefox.FirefoxProfileimport org.openqa.selenium.firefox.FirefoxDriver

driver = FirefoxProfile profile = new FirefoxProfile() profile.setPreference("browser.download.folderList", 2) profile.setPreference("browser.download.dir", "/tmp") profile.setPreference( "browser.helperApps.neverAsk.saveToDisk", "text/csv")

def driverInstance = new FirefoxDriver(profile) driverInstance.manage().window().maximize() driverInstance

CHROMENeeds ChromeDriver downloaded

Pretty fast and stable

CHROME (1)GebConfig.groovy

private String driverLocationDependingOnOperatingSystem() String os = System.getProperty("os.name").toLowerCase(); def loc = "http://chromedriver.storage.googleapis.com/2.15" if( os.contains('mac')) return "$loc/chromedriver_mac32.zip" if( os.contains('win')) return "$loc/chromedriver_win32.zip" return "$loc/chromedriver_linux64.zip"

CHROME (2)GebConfig.groovy

private void downloadDriver(File file, String path) if (!file.exists()) def ant = new AntBuilder() ant.get(src: path, dest: 'driver.zip') ant.unzip(src: 'driver.zip', dest: file.parent) ant.delete(file: 'driver.zip') ant.chmod(file: file, perm: '700')

CHROME (3)GebConfig.groovy

def chromeDriver = new File('build/drivers/chrome/chromedriver')downloadDriver(chromeDriver, driverLocationDependingOnOperatingSystem())System.setProperty('webdriver.chrome.driver', chromeDriver.absolutePath)

driver = def driverInstance = new ChromeDriver()

def browserWindow = driverInstance.manage().window() // width, height browserWindow.size = new Dimension(1000, 2500) browserWindow.position = new Point(0, 0)

driverInstance

WAITINGGebConfig.groovy

waiting timeout = 10 retryInterval = 0.5

baseNavigatorWaiting = trueatCheckWaiting = true

USING WAITING<div class="fade­me­in" style="display: none"> Hi ­ are yo waiting for me?</div><script> $('div.fade­me­in').delay(3000).slideDown();</script>

static content = fadeInMessage $('div.fade­me­in')

then:waitFor fadeInMessage.text() == 'Hi ­ are yo waiting for me?'

REPORTING

TEST REPORTSNicely formatted

Spock power-assert format

SCREENSHOTSScreenshots and HTML from end of each test:

Extend from GebReportingSpec

class AttendeeFunctionalSpec extends GebReportingSpec

GebConfig.groovy

reportsDir = new File("build/geb­reports")

AD-HOC SCREENSHOTSreport "When­form­is­just­filled"

Saves a report in reportsDir

Numbered in increasing order

JAVASCRIPTIn case you need to interact using javascript

EXECUTING JAVASCRIPTClicking a button that is hidden will create a

ElementNotVisibleException

<fieldset class="well" style="display: none"> <g:link class="btn" action="index">List</g:link></fieldset>

EXECUTING JAVASCRIPTJavascriptExecutor executor = (JavascriptExecutor) driverexecutor.executeScript('jQuery(".well").show();')

WRAPPING JAVASCRIPTdef js( String script ) (driver as JavascriptExecutor).executeScript( script )

js('jQuery(".well").show();')

OTHER USAGESScreenscraping of a site

Solving complex problems like 2048

RESOURCEShttp://gebish.org

https://github.com/geb

https://gist.github.com/melix/9619800

https://fbflex.wordpress.com/2010/08/25/geb-and-grails-tips-tricks-and-gotchas/

https://github.com/JacobAae/eu-gr8conf-grailsdemo

QUESTIONS?

top related