Slide 1

Slide 1 text

Geb T o t a l l y G r o o v y B r o w s e r A u t o m a t i o n pronounced “jeb”

Slide 2

Slide 2 text

Geb… What is it? Geb is a browser automation solution. It brings together the power of WebDriver, the elegance of jQuery content selection, the robustness of Page Object modelling and the expressiveness of the Groovy language.

Slide 3

Slide 3 text

Provides a standardised API for “driving” browsers. WebDriver (a.k.a. Selenium 2)

Slide 4

Slide 4 text

import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; // class FirefoxDriver implements WebDriver WebDriver driver = new FirefoxDriver(); WebDriver (a.k.a. Selenium 2)

Slide 5

Slide 5 text

WebDriver (a.k.a. Selenium 2) Write to the WebDriver interface — run against any implementation — in theory

Slide 6

Slide 6 text

jQuery “jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.” - jquery.com

Slide 7

Slide 7 text

jQuery “jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.” - jquery.com

Slide 8

Slide 8 text

jQuery Geb takes inspiration from jQuery // CSS 3 selectors $("div.some-class p:first[title='something']") // Find via index and/or attribute matching $("h1", 2, class: "heading") $("p", name: "description") $("ul.things li", 2) // Chaining $("div").find(".b") $("div").filter(".c").parents() $("p.c").siblings()

Slide 9

Slide 9 text

Page Objects … What are they?

Slide 10

Slide 10 text

“Page Objects are the embodiment of Programming Motherf#$&*r” - Samuel L Jackson http://programming-motherf #$&*r.com/ Page Objects

Slide 11

Slide 11 text

Use of proper programming techniques to avoid duplication and brittleness. Page Objects driver.findElement(By.name("username")).sendKeys("username"); driver.findElement(By.name("password")).sendKeys("password"); driver.findElement(By.name("submitButton")).click(); Don’t do this

Slide 12

Slide 12 text

Use of proper programming techniques to avoid duplication and brittleness. Page Objects login(“username”, “password”) Do do this

Slide 13

Slide 13 text

Geb uses Groovy’s DSL capabilities and dynamism to facilitate Page Objects Page Objects import geb.Page class LoginPage extends Page { static content = { loginButton(to: AdminPage) { $("input[name=login]") } } def login(usernameValue, passwordValue) { username = usernameValue password = passwordValue loginButton.click() } }

Slide 14

Slide 14 text

• Closures • methodMissing() / propertyMissing() • Duck Typing • Minimal punctuation

Slide 15

Slide 15 text

to SomePage login "admin", "password" at AdminPage

Slide 16

Slide 16 text

• WebDriver (browser automation) • jQuery (content traversal / interaction) • Page Objects (proper programming) • Groovy (minimal syntax, no ceremony) Geb… What is it? The product of…

Slide 17

Slide 17 text

Geb Quickstart @Grapes([ @Grab("org.codehaus.geb:geb-core:0.6.1"), @Grab("org.seleniumhq.selenium:selenium-firefox-driver:2.9.0") ]) import geb.Browser Browser.drive { go "http://google.com/ncr" assert title == "Google" waitFor { q() }.value "wikipedia" waitFor { title.endsWith("Google Search") } $("li.g", 0).find("a.l").with { assert text() == "Wikipedia" click() } waitFor { title == "Wikipedia" } }

Slide 18

Slide 18 text

Demo Google Wikipedia With Groovy Console

Slide 19

Slide 19 text

Testing with Geb • JUnit (3 & 4) • TestNG • EasyB • Cuke4Duke (Cucumber for the JVM) • Spock Adapters for…

Slide 20

Slide 20 text

Geb & Spock Geb ❤’s Spock import geb.spock.GebSpec class LoginSpec extends GebSpec { def "login to admin section"() { given: to LoginPage when: loginForm.with { username = "admin" password = "password" } and: loginButton.click() then: at AdminPage } }

Slide 21

Slide 21 text

Demo ContactUsFormSpec github.com/geb/geb-uberconf-2011-example

Slide 22

Slide 22 text

To & at def "at the front page"() { when: to FrontPage then: at FrontPage } to() navigates to a page at() verifies we are where we expect to be

Slide 23

Slide 23 text

To & at class FrontPage extends NavPage { static url = "home" static at = { assert nav.currentName == "Event Home" true } } to() uses url property to determine page address (combined with base url configuration)

Slide 24

Slide 24 text

To & at class FrontPage extends NavPage { static url = "home" static at = { assert nav.currentName == "Event Home" true } } at() runs the page’s at checker (do some checks on the page content)

Slide 25

Slide 25 text

Page Delegation def "click the contact us link"() { when: footer.contactUsLink.click() then: at ContactUsPage } Unresolved properties/methods are automatically forwarded to the browser’s current page instance

Slide 26

Slide 26 text

Page Content class BasePage extends geb.Page { static content = { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } The content block is a DSL for naming the content on the page.

Slide 27

Slide 27 text

Content Lookups class BasePage extends geb.Page { static content = { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } The $() function is used to select page content It’s jQuery like

Slide 28

Slide 28 text

The $() Function $(«css selector», «index / range», «attribute / text matchers») Lookups are a combination of CSS selectors, index or ranges and attribute and/or text matchers

Slide 29

Slide 29 text

Css Selectors $(«css selector», «index / range», «attribute / text matchers») Any CSS expression supported by the browser (Complete CSS3, except for HTMLUnit) $("div.some-class p:first[title='something']") $("ul li a") $("table tr:nth-child(2n+1) td") $("div#content p:first-child::first-line")

Slide 30

Slide 30 text

Indexes & Ranges $(«css selector», «index / range», «attribute / text matchers») Select content by index or grab a bunch with a range

a

b

c

$("p", 0) // a $("p", 2) // c $("p", 0..1) // a & b $("p", 1..2) // b & c

Slide 31

Slide 31 text

Attribute/Text Matching $(«css selector», «index / range», «attribute / text matchers») Values of tag attributes can be matched “text” is a special attribute that matches the node text $("p", attr1: "a") $("p", attr2: "c") $("p", attr1: "a", attr2: "b") $("p", text: "p1") $("p", text: "p1", attr1: "a") $("p", text: ~/p./) $("p", text: startsWith("p")) $("p", text: endsWith("2"))

p1

p2

Slide 32

Slide 32 text

Attribute/Text Matching $(«css selector», «index / range», «attribute / text matchers») Use regular expressions to do partial matches $("p", attr1: "a") $("p", attr2: "c") $("p", attr1: "a", attr2: "b") $("p", text: "p1") $("p", text: "p1", attr1: "a") $("p", text: ~/p./) $("p", text: startsWith("p")) $("p", text: endsWith("2"))

p1

p2

Slide 33

Slide 33 text

$(«css selector», «index / range», «attribute / text matchers») Use builtin predicates for common partial matching

p1

p2

$("p", attr1: "a") $("p", attr2: "c") $("p", attr1: "a", attr2: "b") $("p", text: "p1") $("p", text: "p1", attr1: "a") $("p", text: ~/p./) $("p", text: startsWith("p")) $("p", text: endsWith("2")) Attribute/Text Matching

Slide 34

Slide 34 text

Builtin Predicates All builtin predicates take strings and regular expressions startsWith iStartsWith contains iContains endsWith iEndsWith containsWord iContainsWord notStartsWith iNotStartsWith notContains iNotContains notEndsWith iNotEndsWith notContainsWord iNotContainsWord

Slide 35

Slide 35 text

Relative Content

$() returns a “navigator” object which has methods for finding relative content $("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.d").parent() // 'div.b' $("p.c").siblings() // 'p.d' & 'p.e' $("div.a").children() // 'div.b' & 'div.f' Plus more…

Slide 36

Slide 36 text

Collection Goodness

1

2

Navigators are collections of collections (like jQuery) $("p").max { it.text() }.text() == "2" $("p").each { println it.text() } includes all the cool Groovy collection methods

Slide 37

Slide 37 text

Building Content class BasePage extends geb.Page { static content = { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } Content definitions can build on other defined content

Slide 38

Slide 38 text

Modules class BasePage extends geb.Page { static content = { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } Modules are repeating/reappearing content that are modelled independently

Slide 39

Slide 39 text

Modules class Footer extends geb.Module { static content = { auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Modules define content, just like pages

Slide 40

Slide 40 text

Module Base class Footer extends geb.Module { static content = { auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Content lookups are relative to the module base

Slide 41

Slide 41 text

Module Base class BasePage extends geb.Page { static content = { container { $("div#container") } pageName { container.find("h1").text() } footer { module Footer, $("div#footer") } } } You can specify the base in the content DSL when defining the module in the owner

Slide 42

Slide 42 text

Content Templates class Footer extends geb.Module { static content = { auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Content blocks can take parameters

Slide 43

Slide 43 text

Repeating Modules TitleAuthor Zero HistoryWilliam Gibson The Evolutionary VoidPeter F. Hamilton Modules are great for repeating content

Slide 44

Slide 44 text

Repeating Modules Modules are great for repeating content class BooksPage extends Page { static content = { bookResults { module BookRow, $("table#book-results tbody tr", it) } } } class BookRow extends Module { static content = { cell { $("td", it) } title { cell(0).text() } author { cell(1).text() } } }

Slide 45

Slide 45 text

Repeating Modules Modules are great for repeating content expect: bookResults(0).title == "Zero History" bookResults(1).author == "Peter F. Hamilton

Slide 46

Slide 46 text

Content “to” class Footer extends geb.Module { static content = { auxillaryPageLink { $("ul.links a", text: iContains(it)) } contactUsLink(to: ContactUsPage) { auxillaryPageLink("contact us") } } } Content can define what page it navigates to when it is clicked

Slide 47

Slide 47 text

Form Shortcuts def "fill in the form except for a message"() { when: name = "Peter Niederwieser" email = "[email protected]" sendButton.click() then: at ContactUsPage errorMessage == "message is required" } Assigning to undefined properties sets the form value of the control with that name

Slide 48

Slide 48 text

Content is anything def "fill in the form except for a message"() { when: name = "Peter Niederwieser" email = "[email protected]" sendButton.click() then: at ContactUsPage errorMessage == "message is required" } Defined content in DSL can be anything

Slide 49

Slide 49 text

Content is anything class ContactUsPage extends BasePage { static content = { errorMessage(required: false) { $("span.errorMessage").text() } sendButton(to: [ContactUsPage, FrontPage]) { $("input#FeedbackAdd_0") } } } Defined content in DSL can be anything

Slide 50

Slide 50 text

Optional Content class ContactUsPage extends BasePage { static content = { errorMessage(required: false) { $("span.errorMessage").text() } sendButton(to: [ContactUsPage, FrontPage]) { $("input#FeedbackAdd_0") } } } By default, a content template must return something Specify “required: false” to allow empty/false

Slide 51

Slide 51 text

Inheritance class ContactUsPage extends BasePage { static content = { errorMessage(required: false) { $("span.errorMessage").text() } sendButton(to: [ContactUsPage, FrontPage]) { $("input#FeedbackAdd_0") } } } Pages can extend each other, inheriting all defined content and methods etc. Works for modules too

Slide 52

Slide 52 text

WebDriver Access driver.navigate().refresh() You can “drop down” to the WebDriver level whenever necessary $("p").firstElement() access the WebDriver instance

Slide 53

Slide 53 text

WebDriver Access driver.navigate().refresh() You can “drop down” to the WebDriver level whenever necessary $("p").firstElement() access the WebElement instances

Slide 54

Slide 54 text

JavaScript Interface function addThem(a,b) { return a + b; } Call JavaScript methods like they are Groovy js.addThem(1, 2) == 3 This is Groovy code

Slide 55

Slide 55 text

JavaScript Interface var aVariable = 1; Access global JavaScript variables js.aVariable == 1 This is Groovy code

Slide 56

Slide 56 text

jQuery Adapter The ‘jquery’ property on a navigator gives a jQuery object for matched content $("div#a").jquery.mouseover() useful for WebDriver interaction shortcomings

Slide 57

Slide 57 text

Dynamic Pages Use waitFor() to deal with AJAX & effects // use default waiting configuration waitFor { «something» } // wait for up to 10 seconds, using the default retry interval waitFor(10) { «something» } // wait for up to 10 seconds, waiting half a second between retries waitFor(10, 0.5) { «something» } // use the preset “quick” as the wait settings waitFor("quick") { «something» }

Slide 58

Slide 58 text

Dynamic Pages Use waitFor() to deal with AJAX & effects // wait for content to be present waitFor { $("p") } // wait for a condition waitFor { $("p").text() == "Message sent!" } // wait for any Groovy truth value waitFor { $("p").collect { it.text() } } // wait for a JavaScript redirect waitFor { at Somepage }

Slide 59

Slide 59 text

Dynamic Pages Content can be specified with implicit waiting Geb will automatically wait for it when it’s requested class DynamicPage extends Page { static content = { dynamicallyAdded(wait: true) { $("p.dynamic") } } }

Slide 60

Slide 60 text

Direct Downloading Download content for inspection The download request includes the browser’s cookies def "check user pdf"() { given: to LoginPage login "admin", "password" expect: at AccountPage when: byte[] pdfBytes = downloadBytes($("a.detailsPdf").@href) then: extractTextFromPdf(pdfBytes).contains "Username: admin" } Uses HttpUrlConnection

Slide 61

Slide 61 text

Demo ScrapePdfSpec github.com/geb/geb-uberconf-2011-example

Slide 62

Slide 62 text

Configuration Geb loads “GebConfig.groovy” from the classpath Configure the driver and other settings import org.openqa.selenium.firefox.FirefoxDriver import 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() } } } Uses Groovy’s ConfigSlurper

Slide 63

Slide 63 text

Configuration Environment is controlled by “geb.env” JVM system property grails -Dgeb.env=chrome test-app gradle test -Dgeb.env=chrome

Slide 64

Slide 64 text

Demo Multi Browser github.com/geb/geb-uberconf-2011-example

Slide 65

Slide 65 text

More Geb Where to find more info

Slide 66

Slide 66 text

www.github.com/geb

Slide 67

Slide 67 text

gebish.org/manual/current

Slide 68

Slide 68 text

www.gebish.org