• Like
Groovier Selenium (Djug)
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

Groovier Selenium (Djug)

  • 13,271 views
Published

Presentation on using Groovy's metaprogramming capabilities to simplify writing Selenium tests.

Presentation on using Groovy's metaprogramming capabilities to simplify writing Selenium tests.

Published in Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • nice work
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
13,271
On SlideShare
0
From Embeds
0
Number of Embeds
3

Actions

Shares
Downloads
129
Comments
1
Likes
5

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Groovier Selenium Denver JUG 8/13/2008 Frederic Jean fred@fredjean.net frederic.jean@sun.com Wednesday, August 13, 2008 1
  • 2. who am i Wednesday, August 13, 2008 2
  • 3. Topics (Really) Quick intro to Selenium Groovy Metaprogramming through progressive refactorings of a single, simple yet verbose Java Selenium RC Driver example. A few sundry items Wednesday, August 13, 2008 3
  • 4. Selenium UI testing tool Runs in browser Well suited for Ajax applications Wednesday, August 13, 2008 4
  • 5. Selenium IDE Firefox plugin Simplifies writing and testing Selenese test case Can record and play back Selenese tests Wednesday, August 13, 2008 5
  • 6. Selenese HTML Tables Action Target Value Intepreted by Selenium Core Actions match JavaScript functions Wednesday, August 13, 2008 6
  • 7. Example BlogTest open / clickAndWait link=Adoption Out of my mind... : category assertTitle adoption Wednesday, August 13, 2008 7
  • 8. Selenese Locators Allows an action to target a specific DOM element on the page <type>=<locator> Wednesday, August 13, 2008 8
  • 9. Selenese Locators Locator Type Description The name of an input element on a name form The id associated with an element id on a page The text contained within an anchor link element (<a/>) A JavaScript expression that dom returns an element An XPath expression pointing to an xpath element on the page Wednesday, August 13, 2008 9
  • 10. Sample Test Wednesday, August 13, 2008 10
  • 11. Selenese TestSuites Groups and organizes individual Selenese tests Can be run through ant Wednesday, August 13, 2008 11
  • 12. Selenium RC Runs as a process on a system Listens to requests on a specific port Has drivers for different languages Wednesday, August 13, 2008 12
  • 13. Selenium RC Drivers Drives the Selenium RC Ser ver programatically Allows integration with xUnit frameworks Flow control and conditionals Java driver provides a SeleneseTestCase class Wednesday, August 13, 2008 13
  • 14. Generating Java Test Wednesday, August 13, 2008 14
  • 15. Generated Java package com.example.tests; import com.thoughtworks.selenium.*; import java.util.regex.Pattern; public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp(quot;http://fredjean.net/quot;, quot;*chromequot;); } public void testNew() throws Exception { selenium.open(quot;/quot;); selenium.click(quot;link=Adoptionquot;); selenium.waitForPageToLoad(quot;30000quot;); assertEquals(quot;Out of my mind... : category adoptionquot;, selenium.getTitle()); selenium.click(quot;link=We Are Out!quot;); for (int second = 0;; second++) { if (second >= 60) fail(quot;timeoutquot;); try { if (selenium.isTextPresent(quot;Commentsquot;)) break; } catch (Exception e) {} Thread.sleep(1000); } assertTrue(selenium.isTextPresent(quot;Trackbacksquot;)); } } Wednesday, August 13, 2008 15
  • 16. Generated Java Where package com.example.tests; import com.thoughtworks.selenium.*; is it used? import java.util.regex.Pattern; public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp(quot;http://fredjean.net/quot;, quot;*chromequot;); } public void testNew() throws Exception { selenium.open(quot;/quot;); selenium.click(quot;link=Adoptionquot;); selenium.waitForPageToLoad(quot;30000quot;); assertEquals(quot;Out of my mind... : category adoptionquot;, selenium.getTitle()); selenium.click(quot;link=We Are Out!quot;); for (int second = 0;; second++) { if (second >= 60) fail(quot;timeoutquot;); try { if (selenium.isTextPresent(quot;Commentsquot;)) break; } catch (Exception e) {} Thread.sleep(1000); } assertTrue(selenium.isTextPresent(quot;Trackbacksquot;)); } } Wednesday, August 13, 2008 16
  • 17. Generated Java package com.example.tests; import com.thoughtworks.selenium.*; Need to rename class. import java.util.regex.Pattern; public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp(quot;http://fredjean.net/quot;, quot;*chromequot;); } public void testNew() throws Exception { selenium.open(quot;/quot;); selenium.click(quot;link=Adoptionquot;); selenium.waitForPageToLoad(quot;30000quot;); assertEquals(quot;Out of my mind... : category adoptionquot;, selenium.getTitle()); selenium.click(quot;link=We Are Out!quot;); for (int second = 0;; second++) { if (second >= 60) fail(quot;timeoutquot;); try { if (selenium.isTextPresent(quot;Commentsquot;)) break; } catch (Exception e) {} Thread.sleep(1000); } assertTrue(selenium.isTextPresent(quot;Trackbacksquot;)); } } Wednesday, August 13, 2008 17
  • 18. Generated Java from Must inherit SeleneseTestCase package com.example.tests; import com.thoughtworks.selenium.*; import java.util.regex.Pattern; public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp(quot;http://fredjean.net/quot;, quot;*chromequot;); } public void testNew() throws Exception { selenium.open(quot;/quot;); selenium.click(quot;link=Adoptionquot;); selenium.waitForPageToLoad(quot;30000quot;); assertEquals(quot;Out of my mind... : category adoptionquot;, selenium.getTitle()); selenium.click(quot;link=We Are Out!quot;); for (int second = 0;; second++) { if (second >= 60) fail(quot;timeoutquot;); try { if (selenium.isTextPresent(quot;Commentsquot;)) break; } catch (Exception e) {} Thread.sleep(1000); } assertTrue(selenium.isTextPresent(quot;Trackbacksquot;)); } } Wednesday, August 13, 2008 18
  • 19. Generated Java package com.example.tests; import com.thoughtworks.selenium.*; import java.util.regex.Pattern; Should rename method public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp(quot;http://fredjean.net/quot;, quot;*chromequot;); } public void testNew() throws Exception { selenium.open(quot;/quot;); selenium.click(quot;link=Adoptionquot;); selenium.waitForPageToLoad(quot;30000quot;); assertEquals(quot;Out of my mind... : category adoptionquot;, selenium.getTitle()); selenium.click(quot;link=We Are Out!quot;); for (int second = 0;; second++) { if (second >= 60) fail(quot;timeoutquot;); try { if (selenium.isTextPresent(quot;Commentsquot;)) break; } catch (Exception e) {} Thread.sleep(1000); } assertTrue(selenium.isTextPresent(quot;Trackbacksquot;)); } } Wednesday, August 13, 2008 19
  • 20. Generated Java package com.example.tests; selenium.this import com.thoughtworks.selenium.*; import java.util.regex.Pattern; selenium.that public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { } selenium.thisandthat setUp(quot;http://fredjean.net/quot;, quot;*chromequot;); public void testNew() throws Exception { selenium.open(quot;/quot;); selenium.click(quot;link=Adoptionquot;); selenium.waitForPageToLoad(quot;30000quot;); assertEquals(quot;Out of my mind... : category adoptionquot;, selenium.getTitle()); selenium.click(quot;link=We Are Out!quot;); for (int second = 0;; second++) { if (second >= 60) fail(quot;timeoutquot;); try { if (selenium.isTextPresent(quot;Commentsquot;)) break; } catch (Exception e) {} Thread.sleep(1000); } assertTrue(selenium.isTextPresent(quot;Trackbacksquot;)); } } Wednesday, August 13, 2008 20
  • 21. Generated Java package com.example.tests; import com.thoughtworks.selenium.*; import java.util.regex.Pattern; public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp(quot;http://fredjean.net/quot;, quot;*chromequot;); } public void testNew() throws Exception { Where did waitForTextPresent go? selenium.open(quot;/quot;); selenium.click(quot;link=Adoptionquot;); selenium.waitForPageToLoad(quot;30000quot;); assertEquals(quot;Out of my mind... : category adoptionquot;, selenium.getTitle()); selenium.click(quot;link=We Are Out!quot;); for (int second = 0;; second++) { if (second >= 60) fail(quot;timeoutquot;); try { if (selenium.isTextPresent(quot;Commentsquot;)) break; } catch (Exception e) {} Thread.sleep(1000); } assertTrue(selenium.isTextPresent(quot;Trackbacksquot;)); } } Wednesday, August 13, 2008 21
  • 22. Generated Java Good start Needs some work to be useful Certainly faster than coding by hand Noisy Wednesday, August 13, 2008 22
  • 23. Groovy package com.example.tests import com.thoughtworks.selenium.* class NewGroovyTest extends SeleneseTestCase { void setUp() { setUp quot;http://fredjean.net/quot;, quot;*chromequot; } void testNew() { selenium.open quot;/quot; selenium.click quot;link=Adoptionquot; selenium.waitForPageToLoad quot;30000quot; assert quot;Out of my mind... : category adoptionquot; == selenium.title selenium.click quot;link=We Are Out!quot; for (second in 0..60) { if (second == 60) fail quot;timeoutquot; if (selenium.isTextPresent(quot;Commentsquot;)) break; sleep(1000) } assert selenium.isTextPresent(quot;Trackbacksquot;) } } Wednesday, August 13, 2008 23
  • 24. Groovy Less noisy than Java Still repetitive waitForTextPresent is still missing Wednesday, August 13, 2008 24
  • 25. Metaprogramming Writing of computer programs that write or manipulate other programs (or themselves) as their data. http://en.wikipedia.org/wiki/Metaprogramming Wednesday, August 13, 2008 25
  • 26. Metaprogramming Increases code expressiveness Allows SMEs to understand the code Domain Specific Languages Wednesday, August 13, 2008 26
  • 27. Meta Object Protocol Establishes the rules behind method calling in Groovy Provides the hooks to modify your program's behavior invokeMethod propertyMissing methodMissing Wednesday, August 13, 2008 27
  • 28. Metaclass All Groovy objects have one Can be defined for Java objects Per class vs per instance Allows developers to quot;mutatequot; a class Wednesday, August 13, 2008 28
  • 29. Delegation For ward method calls to another object Tedious to do in Java Extend delegate Manually code delegation code Almost trivial in Groovy ExpandoMetaClass Wednesday, August 13, 2008 29
  • 30. Groovy Delegation /** * Called when a method cannot be found in the class * or the meta class for an object or class. * @param name The name of the missing method * @param args The arguments for the method */ void methodMissing(String name, args) { selenium.quot;$namequot;(* args) } /** * Called when a property cannot be found in the class * or the meta class associated with a class or object. * @param name The name of the property */ void propertyMissing(String name) { selenium.quot;$namequot; } Wednesday, August 13, 2008 30
  • 31. Goodbye Repetition void testDelegation() { open quot;/quot; click quot;link=Adoptionquot; waitForPageToLoad quot;30000quot; assert quot;Out of my mind... : category adoptionquot; == title click quot;link=We Are Out!quot; for (second in 0..60) { if (second == 60) fail quot;timeoutquot; if (isTextPresent(quot;Commentsquot;)) break; sleep(1000) } assert isTextPresent(quot;Trackbacksquot;) } Wednesday, August 13, 2008 31
  • 32. Performance Hit Wednesday, August 13, 2008 32
  • 33. Performance Hit Wednesday, August 13, 2008 33
  • 34. Intercept, Cache, Invoke /** * Called when a method cannot be found in the class * or the meta class for an object or class. * @param name The name of the missing method * @param args The arguments for the method */ void methodMissing(String name, args) { NewGroovyTest.metaClass.quot;$namequot; = { Object varArgs -> delegate.selenium.metaClass.invokeMethod(delegate.selenium, name, varArgs) } selenium.quot;$namequot;(* args) } Wednesday, August 13, 2008 34
  • 35. Performance Hit Wednesday, August 13, 2008 35
  • 36. Groovy Delegation Results in cleaner test code Almost trivial to implement in Groovy Performance hit can be mitigated Wednesday, August 13, 2008 36
  • 37. waitForTextPresent? void testDelegation() { open quot;/quot; click quot;link=Adoptionquot; waitForPageToLoad quot;30000quot; assert quot;Out of my mind... : category adoptionquot; == title click quot;link=We Are Out!quot; for (second in 0..60) { if (second == 60) fail quot;timeoutquot; if (isTextPresent(quot;Commentsquot;)) break; sleep(1000) } assert isTextPresent(quot;Trackbacksquot;) } Wednesday, August 13, 2008 37
  • 38. waitForTextPresent? void testDelegation() { open quot;/quot; Replaces waitFor... click quot;link=Adoptionquot; waitForPageToLoad quot;30000quot; with a loop assert quot;Out of my mind... : category adoptionquot; == title click quot;link=We Are Out!quot; for (second in 0..60) { if (second == 60) fail quot;timeoutquot; if (isTextPresent(quot;Commentsquot;)) break; sleep(1000) } assert isTextPresent(quot;Trackbacksquot;) } Wednesday, August 13, 2008 38
  • 39. waitForTextPresent? void testDelegation() { open quot;/quot; click quot;link=Adoptionquot; waitForPageToLoad quot;30000quot; assert quot;Out of my mind... : category adoptionquot; == title click quot;link=We Are Out!quot; for (second in 0..60) { How about if (second == 60) fail quot;timeoutquot; assertTextPresent? if (isTextPresent(quot;Commentsquot;)) break; sleep(1000) } assert isTextPresent(quot;Trackbacksquot;) } Wednesday, August 13, 2008 39
  • 40. waitForTextPresent? Selenese generates waitFor, verify, and assert methods Java driver doesn't provide them Java -> Explicitly typed language JavaScript -> What's a type? Wednesday, August 13, 2008 40
  • 41. Wait a minute... JavaScript is a dynamic language... Groovy is a dynamic language... Why not synthesize these methods in Groovy? Wednesday, August 13, 2008 41
  • 42. Synthetic Methods Methods that don't really exist Grails finder methods Person.findByFirstNameAndAge(...) Wednesday, August 13, 2008 42
  • 43. Steps to Take Identify synthetic methods Implement behavior Locate actual getter method Wednesday, August 13, 2008 43
  • 44. Identifying Methods def methodMissing(String name, args) { switch (name) { case ~/waitForNot.*/: return waitForNot(name, args) case ~/waitFor.*/: return waitFor(name, args) case ~/assertNot.*/: assertNot(name, args) break case ~/assert.*/: assertThat(name, args) break case ~/verifyNot.*/: return verifyNot(name, args) case ~/verify.*/: return verifyThat(name, args) default: return createAndCallMethod(name, args) } } Wednesday, August 13, 2008 44
  • 45. Implement Behavior private waitFor(name, args) { // Make the bold assumption that the time out is the first param. def timeout = args[0] if (timeout instanceof Integer) { args = args[1..args.length - 1].toArray() } else { timeout = 60000 } def methodName = getMethodName(quot;waitForquot;, name); for (i in 0..(timeout / 1000)) { if (quot;$methodNamequot;(* args)) { return true; } sleep(1000) } fail(quot;Timeout occured in $name for $argsquot;) } Wednesday, August 13, 2008 45
  • 46. Locating Getter def getMethodName(prefix, name) { [quot;isquot;, quot;getquot;].collect { name.replaceFirst(prefix, it) }.find { delegate.selenium.metaClass.respondsTo(delegate.selenium, it) } } Wednesday, August 13, 2008 46
  • 47. Loop Begone! void testDelegation() { open quot;/quot; click quot;link=Adoptionquot; waitForPageToLoad quot;30000quot; assert quot;Out of my mind... : category adoptionquot; == title click quot;link=We Are Out!quot; waitForTextPresent quot;Commentsquot; assertTextPresent quot;Trackbacksquot; } Wednesday, August 13, 2008 47
  • 48. Refactor! Move methods to super class methodMissing propertyMissing Supporting methods Group Groovy tests in one suite Wednesday, August 13, 2008 48
  • 49. GroovierSelenium Extends SeleneseTestCase with methodMissing Allows Groovy users to write tests that almost look like Selenese http://groovierselenium.googlecode.com Licensed under ASLv2.0 Wednesday, August 13, 2008 49
  • 50. Near Future JUnit 4.5 test runners GroovierSeleniumRunner GroovySuiteRunner @Selenium annotation Wednesday, August 13, 2008 50
  • 51. NetBeans & Groovy Grails and Groovy Plugin integrated with NetBeans 6.5 Adds Groovy Support to Java Projects Wednesday, August 13, 2008 51
  • 52. Looking Back Talked about Selenium Leveraging Groovy metaprogramming Delegating to another object Creating synthetic methods GroovierSelenium NetBeans Wednesday, August 13, 2008 52
  • 53. Book Programming Groovy (Venkat S.) Wednesday, August 13, 2008 53
  • 54. Links http://groovy.codehaus.org http://groovierselenium.googlecode.com http://www.pragprog.com/titles/vslg/ programming-groovy Wednesday, August 13, 2008 54