Improving web application automation testing with Groovy, Spock, and Geb

A presentation at South East European Testing Conference in October 2015 in Sofia, Bulgaria by Petyo Dimitrov

Slide 1

Slide 1

Improving web application automation testing with Groovy, Spock and Geb Petyo Dimitrov, Musala Soft

Slide 2

Slide 2

Agenda • Previous project experience • Identified problems • Introduce the testing framework – – – – Selenium / Web Driver Groovy Spock Geb • Demo 2/50

Slide 3

Slide 3

a Starfleet science officer chats up an Egyptian 3/50 goddess in a jazz club on the Moon

Slide 4

Slide 4

Previous project experience 4/50

Slide 5

Slide 5

Problems • different frameworks for DEV and TEST – no knowledge sharing • duplication of code between DEV and TEST – no code reuse • frameworks with high learning curve 5/50

Slide 6

Slide 6

Solution Web Driver Geb Spock Groovy 6/50

Slide 7

Slide 7

Selenium Web Driver

Slide 8

Slide 8

Selenium Web Driver • successor to the Selenium project • offers cross browser automation • API becoming W3C standard 8/50

Slide 9

Slide 9

Selenium Web Driver – example import org.openqa.selenium.; import org.openqa.selenium.firefox.; WebDriver driver = new FirefoxDriver(); driver.get(“http://google.com”); WebElement search = driver.findElement(By.name(“q”)); searchBox.sendKeys(“webdriver”); driver.findElement(By.name(“btnK”)).click(); 9/50

Slide 10

Slide 10

Selenium Web Driver – pros/cons Good: 1.Proven solution for cross browser automation 2.Stable API and feature set 3.Active development Bad: 1.Verbose code 2.Not a complete solution 3.Low level (Java invocations, XPath selectors) 10/50

Slide 11

Slide 11

Groovy

Slide 12

Slide 12

Groovy • • • • • • • dynamic object-oriented language concise syntax – write less code faster compatible with Java ~ zero learning curve executes inside the JVM supports functional programming features powerful collections API suitable base for defining DSL-s 12/50

Slide 13

Slide 13

Groovy – concise public class King { King king = new King(); private String name; king.setName(“Xerxes”); System.out.println( public String getName() { king.threaten(“Leonidas”)); return name; } public void setName(String name) { this.name = name; } public String threaten(String target) { return name + “: Surrender your weapons king ” + target + ” or I will destroy your farms, slay your people, and raze your city.”; } } 13/50

Slide 14

Slide 14

Groovy – concise (semicolon) public class King { King king = new King() private String name king.setName(“Xerxes”) System.out.println( public String getName() { king.threaten(“Leonidas”)) return name } public void setName(String name) { this.name = name } public String threaten(String target) { return name + “: Surrender your weapons king ” + target + ” or I will destroy your farms, slay your people, and raze your city.” } } 14/50

Slide 15

Slide 15

Groovy – concise (parentheses) public class King { King king = new King() private String name king.setName “Xerxes” System.out.println public String getName() { king.threaten(“Leonidas”) return name } public void setName(String name) { this.name = name } public String threaten(String target) { return name + “: Surrender your weapons king ” + target + ” or I will destroy your farms, slay your people, and raze your city.” } } 15/50

Slide 16

Slide 16

Groovy – concise (public & return) class King { King king = new King() private String name king.setName “Xerxes” System.out.println String getName() { king.threaten(“Leonidas”) name } void setName(String name) { this.name = name } String threaten(String target) { name + “: Surrender your weapons king ” + target + ” or I will destroy your farms, slay your people, and raze your city.” } } 16/50

Slide 17

Slide 17

Groovy – concise (optional types) class King { def king = new King() private String name king.setName “Xerxes” System.out.println String getName() { king.threaten(“Leonidas”) name } void setName(name) { this.name = name } String threaten(String target) { name + “: Surrender your weapons king ” + target + ” or I will destroy your farms, slay your people, and raze your city.” } } 17/50

Slide 18

Slide 18

Groovy – concise (properties) class King { String name def king = new King(name: “Xerxes”) println king.threaten(“Leonidas”) String threaten(String target) { name + “: Surrender your weapons king ” + target + ” or I will destroy your farms, slay your people, and raze your city.” } } 18/50

Slide 19

Slide 19

Groovy – concise (gstrings) class King { String name def king = new King(name: “Xerxes”) println king.threaten(“Leonidas”) String threaten(String target) { “${name}: Surrender your weapons king ${target} or I will destroy your farms, slay your people, and raze your city.” } } 19/50

Slide 20

Slide 20

Groovy – concise summary public class King { King king = new King(); private String name; king.setName(“Xerxes”); { System.out.println( public class StringKing getName() { king.threaten(“Leonidas”)); return String name; name String threaten(String target) { } “${name}: Comename) and get public void setName(String { them, ${target}.” } = name; this.name } } king = new King(name: “Leonidas”) public def String threaten(String target) { println return name king.threaten(“Xerxes”) + “: Surrender your weapons king ” + target + ” or I will destroy your farms, slay your people, and raze your city.”; } } 20/50

Slide 21

Slide 21

Surrender your weapons king Leonidas or I will destroy your farms, slay your people, and raze your city. Come and get them, Xerxes

Slide 22

Slide 22

Groovy – base for DSLs • DSL = Domain Specific Language • Why is groovy suitable? – flexible syntax: . and () are optional* – access to the code’s AST – meta-programming through ExpandoMetaClass take 2.pills of vitamins after 6.hours take(2.pills).of(vitamins).after(6.hours) 22/50

Slide 23

Slide 23

Spock

Slide 24

Slide 24

Spock • • • • • • unit testing framework for Groovy and Java applications highly expressive DSL has BDT and DDT features compatible with many IDEs and CIs inspired by JUnit, JMock, Mockito,… 24/50

Slide 25

Slide 25

Spock – example import spock.lang.* class UniformSpec extends Specification { def “uniform is blue by default” () { setup: def uniform = new Uniform() expect: uniform.color == “blue” } } public class Uniform { public String getColor(){ return “blue” } 25/50 }

Slide 26

Slide 26

Spock – failure example expect: uniform.color == “green” Condition not satisfied: uniform.color == “green” | | | | blue false | 4 differences (20% similarity) | (blu)e(-) | (gre)e(n) com.musala.muffin.spock.Uniform@6cf145b5 26/50

Slide 27

Slide 27

Spock – feature method blocks setup: expect: when: then: cleanup: where: • initializes the fixture • must be at the start of a method • must not be repeated • implicit – all code up to the first label • given: can be used instead 27/50

Slide 28

Slide 28

Spock – feature method blocks setup: expect: when: then: cleanup: where: • combines stimulus and response • may contain only conditions and variable definitions • suitable for no-side effect functions 28/50

Slide 29

Slide 29

Spock – feature method blocks setup: expect: when: then: cleanup: where: • always used together • express stimulus and response • response may contain: • conditions (including exceptions) • interactions • variable definitions 29/50

Slide 30

Slide 30

Spock – feature method blocks setup: expect: when: then: cleanup: where: • cleans allocated resources • mirror of setup: • invoked event in case of exceptions 30/50

Slide 31

Slide 31

Spock – feature method blocks setup: expect: when: then: cleanup: where: • parameterizes feature method with test data • must be last • uses << [] to supply list of values • uses | and || to supply table 31/50

Slide 32

Slide 32

Spock – example (blocks) @Unroll def “’#role officers’ wear ‘#color’” () { def crewMember = new CrewMember() when: “a crew member is assigned to a role” crewMember.assignTo(role) then: “the uniform is changed accordingly” crewMember.uniformColor == color where: role << [“science”, | color “security”] “science” color << [“blue”, | “blue” “red”] “security” | “red” } 32/50

Slide 33

Slide 33

Spock – example (mocking) def “assigning crew member to unknown role fails” () { String crewMember = “123” String role = “science” when: crewService.assignMemberToRole(crewMember, role) then: 1 * crewDao.getById(crewMember) >> new Pilot() 0 * crewDao._ 1 * roleDao.getById(role) >> null EntityNotFoundException ex = thrown() ex.message == “role does not exists” } 33/50

Slide 34

Slide 34

Spock – interactions target constraint argument constraint 1 * crewDao.getById(12345) >> new Pilot() cardinality method constraint returned value 0 * crewDao._ 0 * _ 34/50

Slide 35

Slide 35

Geb

Slide 36

Slide 36

Geb • browser automation solution • combines: – – – – power of WebDriver elegance of jQuery selectors robustness of Page objects expressiveness of Groovy • can be used for UI testing and scraping 36/50

Slide 37

Slide 37

Geb – navigator API (1) $(“css selector”, index, <attribute matcher>) $(“h2”)  all headings $(“h2.test”)  all headings with class test $(“h2”, 0)  first heading $(“h2”, 0..3)  first four headings $(“h2”, id: “subject”)  heading with id $(“h2”, id: “subject”, text: “muffin”) 37/50

Slide 38

Slide 38

Geb – navigator API (2) $(“h2”, $(“h2”, $(“h2”, $(“h2”, text: text: text: text: ~/mu.+/) startsWith(“mu”)) endsWith(“in”)) contains(“ff”)) $(“h2”).previous(), $(“h2”).next() $(“h2”).siblings(“div”) $(“h2”).parent(), $(“h2”).children() 38/50

Slide 39

Slide 39

Geb – script usage Browser browser = new Browser() browser.go “/system/login” assert browser.page.title == “Login page” assert browser.page.find(“h2”).text() == “Sign in” … browser.quit() 39/50

Slide 40

Slide 40

Geb – page objects, why? • pages isolate implementation details/change • pages prevent duplication • what does this code do? $(“input[name=un1]”).value(“qwerty”) $(“input[name=pd3]”).value(“qaz”) $(“input[type=submit]”).click() 40/50

Slide 41

Slide 41

Geb – page example class LoginPage extends Page { static url = “/system/login” static at = { title == “Login page”} static content = { username { $(“input[name=un1]”) } password { $(“input[name=pd3]”) } submit { $(“input[type=submit]”) } captcha(wait: true) { $(“img.cap”) } message(required: false) { $(“div”) } } } 41/50

Slide 42

Slide 42

Geb – modules class SearchResult extends Module { static content = { title { $(“div.main”).text() } description { $(“div.info”).text() } } } class SearchResultsPage extends Page { static content = { searchBox {module SearchBox, $(“input.search”) } results {moduleList SearchResult, $(“table tr”)} } 42/50 }

Slide 43

Slide 43

Geb – test example @Stepwise class LogicSpec extends GebReportingSpec { def “display login page”() { when: “I go to login page” to LoginPage def “attempt login with incorrect pass”() { when: “I enter wrong credentials” then: “Captcha is rendered” username = “user” at LoginPage password = “incorrect-pass” captcha.exists() submit.click() } then: “Login fails with error message” at LoginPage message == “incorrect credentails” } } 43/50

Slide 44

Slide 44

Geb – waiting • waitFor allows waiting for a condition • configurable, with default values waitFor { $(“div.error”) }.text() == “Error” waitFor(timeout: 10, retry: 0.5) {…} waitFor(“slow”) {…} 44/50

Slide 45

Slide 45

Geb – interactions • simple DSL syntax • based on WebDriver Actions interact { keyDown ALT clickAndHold <content-id> moveToElement $(“div.container”) keyUp ALT release() } 45/50

Slide 46

Slide 46

Geb – configuration • GebConfig.groovy driver = { new FirefoxDriver() // “firefox” } waiting { timeout = 2 slow { timeout = 100 } } reportsDir = “reports” 46/50

Slide 47

Slide 47

Geb – areas for improvement • IDE support • dynamic DSL limitations • browser dependent CSS3 support • keyword collisions for content (page, config, driver) 47/50

Slide 48

Slide 48

Slide 49

Slide 49

Thank You! Petyo.Dimitrov@musala.com Associate Software Architect, Musala Soft

Slide 50

Slide 50

Q&A