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

A presentation at ISTAcon in November 2014 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/54

Slide 3

Slide 3

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

Slide 4

Slide 4

Previous project experience 4/54

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/54

Slide 6

Slide 6

Solution Web Driver Geb Spock Groovy 6/54

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/54

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/54

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/54

Slide 11

Slide 11

Groovy

Slide 12

Slide 12

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

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/54

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/54

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/54

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/54

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/54

Slide 18

Slide 18

Groovy – concise (println) class King { def king = new King() private String name king.setName “Xerxes” println king.threaten(“Leonidas”) String getName() { 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.” } } 18/54

Slide 19

Slide 19

Groovy – concise (properties) class King { String name def king = new King() 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/54

Slide 20

Slide 20

Groovy – concise (constructor) 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.” } } 20/54

Slide 21

Slide 21

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.” } } 21/54

Slide 22

Slide 22

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.”; } } 22/54

Slide 23

Slide 23

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

Slide 24

Slide 24

Groovy – closures (1) def multiply = { x, y -> x * y } println(multiply(2, 4)) // prints 8 def square = { it * it } println(square(2)) // prints 4 def calc = { int x, int y = 10 -> x * y} println(calc(2, 4)) // prints 8 println(calc(2)) // prints 20 24/54

Slide 25

Slide 25

Groovy – closures (2) for (String it: new String[] {“one”, “five”}){ if (it.length() < 4) { System.out.println(it); } } [“one”, “five”].findAll{it.size() < 4}.each{println it} 25/54

Slide 26

Slide 26

Groovy – collections • lists: [2, 4, 6, 8] • ranges: [1..5] • maps: [name: “Leonidas”, title: “king”] • enhanced methods: [“ant”, “bison”, “cat”].each {println it} assert [“ant”, “bison”, “cat”].findAll { it.size( > 4)} == “bison” assert [“ant”, “bison”, “cat”].collect(it[0]) == [“a”, “b”, “c”] assert [“ant”, “bison”, “cat”].*size() == [3, 5, 3] 26/54

Slide 27

Slide 27

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) 27/54

Slide 28

Slide 28

Groovy – more… • the Groovy Truth: assert assert assert assert new Object() “text” [1, 2, 3] 10 assert assert assert assert null “” [] 0 • power asserts 28/54

Slide 29

Slide 29

Spock

Slide 30

Slide 30

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,… 30/54

Slide 31

Slide 31

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

Slide 32

Slide 32

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 32/54

Slide 33

Slide 33

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 33/54

Slide 34

Slide 34

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 34/54

Slide 35

Slide 35

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 35/54

Slide 36

Slide 36

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

Slide 37

Slide 37

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 37/54

Slide 38

Slide 38

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” } 38/54

Slide 39

Slide 39

Spock – example (mocking) def “officer is assigned to role” () { 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” } 39/54

Slide 40

Slide 40

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

Slide 41

Slide 41

Geb

Slide 42

Slide 42

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 42/54

Slide 43

Slide 43

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”) 43/54

Slide 44

Slide 44

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() 44/54

Slide 45

Slide 45

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() 45/54

Slide 46

Slide 46

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() 46/54

Slide 47

Slide 47

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”) } } } 47/54

Slide 48

Slide 48

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”)} } 48/54 }

Slide 49

Slide 49

Geb – test example @Stepwise class LogicSpec extends GebReportingSpec { def “go to login page”() { when: to LoginPage def “attempt login with incorrect pass”() { when: then: username = “user” at LoginPage password = “incorrect-pass” captcha.exists() submit.click() } then: at LoginPage message == “incorrect credentails” } } 49/54

Slide 50

Slide 50

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

Slide 51

Slide 51

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

Slide 52

Slide 52

Geb – JavaScript • access to global variables and functions js.globalVar = 1 assert js.globalVar • access to jQuery object on Navigators • jQuery must be supported for the app $(“input”).jquery.keydown() 52/54

Slide 53

Slide 53

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

Slide 54

Slide 54

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

Slide 55

Slide 55

Slide 56

Slide 56

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

Slide 57

Slide 57

Q&A