groovier selenium (djug)
DESCRIPTION
Presentation on using Groovy's metaprogramming capabilities to simplify writing Selenium tests.TRANSCRIPT
who am i
2Wednesday, August 13, 2008
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
3Wednesday, August 13, 2008
Selenium
UI testing tool
Runs in browser
Well suited for Ajax applications
4Wednesday, August 13, 2008
Selenium IDE
Firefox plugin
Simplifies writing and testing Selenese test case
Can record and play back Selenese tests
5Wednesday, August 13, 2008
SeleneseHTML Tables
Action
Target
Value
Intepreted by Selenium Core
Actions match JavaScript functions
6Wednesday, August 13, 2008
Example
BlogTest
open /
clickAndWait link=Adoption
assertTitle Out of my mind... : category adoption
7Wednesday, August 13, 2008
Selenese Locators
Allows an action to target a specific DOM element on the page
<type>=<locator>
8Wednesday, August 13, 2008
Selenese LocatorsLocator Type Description
name The name of an input element on a form
id The id associated with an element on a page
link The text contained within an anchor element (<a/>)
dom A JavaScript expression that returns an element
xpath An XPath expression pointing to an element on the page
9Wednesday, August 13, 2008
Sample Test
10Wednesday, August 13, 2008
Selenese TestSuites
Groups and organizes individual Selenese tests
Can be run through ant
11Wednesday, August 13, 2008
Selenium RC
Runs as a process on a system
Listens to requests on a specific port
Has drivers for different languages
12Wednesday, August 13, 2008
Selenium RC DriversDrives the Selenium RC Server programatically
Allows integration with xUnit frameworks
Flow control and conditionals
Java driver provides a SeleneseTestCase class
13Wednesday, August 13, 2008
Generating Java Test
14Wednesday, August 13, 2008
Generated Javapackage com.example.tests;
import com.thoughtworks.selenium.*;import java.util.regex.Pattern;
public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp("http://fredjean.net/", "*chrome"); } public void testNew() throws Exception { selenium.open("/"); selenium.click("link=Adoption"); selenium.waitForPageToLoad("30000"); assertEquals("Out of my mind... : category adoption", selenium.getTitle()); selenium.click("link=We Are Out!"); for (int second = 0;; second++) { if (second >= 60) fail("timeout"); try { if (selenium.isTextPresent("Comments")) break; } catch (Exception e) {} Thread.sleep(1000); }
assertTrue(selenium.isTextPresent("Trackbacks")); }}
15Wednesday, August 13, 2008
Generated Javapackage com.example.tests;
import com.thoughtworks.selenium.*;import java.util.regex.Pattern;
public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp("http://fredjean.net/", "*chrome"); } public void testNew() throws Exception { selenium.open("/"); selenium.click("link=Adoption"); selenium.waitForPageToLoad("30000"); assertEquals("Out of my mind... : category adoption", selenium.getTitle()); selenium.click("link=We Are Out!"); for (int second = 0;; second++) { if (second >= 60) fail("timeout"); try { if (selenium.isTextPresent("Comments")) break; } catch (Exception e) {} Thread.sleep(1000); }
assertTrue(selenium.isTextPresent("Trackbacks")); }}
Where is it used?
16Wednesday, August 13, 2008
Generated Javapackage com.example.tests;
import com.thoughtworks.selenium.*;import java.util.regex.Pattern;
public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp("http://fredjean.net/", "*chrome"); } public void testNew() throws Exception { selenium.open("/"); selenium.click("link=Adoption"); selenium.waitForPageToLoad("30000"); assertEquals("Out of my mind... : category adoption", selenium.getTitle()); selenium.click("link=We Are Out!"); for (int second = 0;; second++) { if (second >= 60) fail("timeout"); try { if (selenium.isTextPresent("Comments")) break; } catch (Exception e) {} Thread.sleep(1000); }
assertTrue(selenium.isTextPresent("Trackbacks")); }}
Need to rename class.
17Wednesday, August 13, 2008
Generated Javapackage com.example.tests;
import com.thoughtworks.selenium.*;import java.util.regex.Pattern;
public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp("http://fredjean.net/", "*chrome"); } public void testNew() throws Exception { selenium.open("/"); selenium.click("link=Adoption"); selenium.waitForPageToLoad("30000"); assertEquals("Out of my mind... : category adoption", selenium.getTitle()); selenium.click("link=We Are Out!"); for (int second = 0;; second++) { if (second >= 60) fail("timeout"); try { if (selenium.isTextPresent("Comments")) break; } catch (Exception e) {} Thread.sleep(1000); }
assertTrue(selenium.isTextPresent("Trackbacks")); }}
Must inherit from SeleneseTestCase
18Wednesday, August 13, 2008
Generated Javapackage com.example.tests;
import com.thoughtworks.selenium.*;import java.util.regex.Pattern;
public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp("http://fredjean.net/", "*chrome"); } public void testNew() throws Exception { selenium.open("/"); selenium.click("link=Adoption"); selenium.waitForPageToLoad("30000"); assertEquals("Out of my mind... : category adoption", selenium.getTitle()); selenium.click("link=We Are Out!"); for (int second = 0;; second++) { if (second >= 60) fail("timeout"); try { if (selenium.isTextPresent("Comments")) break; } catch (Exception e) {} Thread.sleep(1000); }
assertTrue(selenium.isTextPresent("Trackbacks")); }}
Should rename method
19Wednesday, August 13, 2008
Generated Javapackage com.example.tests;
import com.thoughtworks.selenium.*;import java.util.regex.Pattern;
public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp("http://fredjean.net/", "*chrome"); } public void testNew() throws Exception { selenium.open("/"); selenium.click("link=Adoption"); selenium.waitForPageToLoad("30000"); assertEquals("Out of my mind... : category adoption", selenium.getTitle()); selenium.click("link=We Are Out!"); for (int second = 0;; second++) { if (second >= 60) fail("timeout"); try { if (selenium.isTextPresent("Comments")) break; } catch (Exception e) {} Thread.sleep(1000); }
assertTrue(selenium.isTextPresent("Trackbacks")); }}
selenium.thisselenium.that
selenium.thisandthat
20Wednesday, August 13, 2008
Generated Javapackage com.example.tests;
import com.thoughtworks.selenium.*;import java.util.regex.Pattern;
public class NewTest extends SeleneseTestCase { public void setUp() throws Exception { setUp("http://fredjean.net/", "*chrome"); } public void testNew() throws Exception { selenium.open("/"); selenium.click("link=Adoption"); selenium.waitForPageToLoad("30000"); assertEquals("Out of my mind... : category adoption", selenium.getTitle()); selenium.click("link=We Are Out!"); for (int second = 0;; second++) { if (second >= 60) fail("timeout"); try { if (selenium.isTextPresent("Comments")) break; } catch (Exception e) {} Thread.sleep(1000); }
assertTrue(selenium.isTextPresent("Trackbacks")); }}
Where did waitForTextPresent go?
21Wednesday, August 13, 2008
Generated Java
Good start
Needs some work to be useful
Certainly faster than coding by hand
Noisy
22Wednesday, August 13, 2008
Groovypackage com.example.tests
import com.thoughtworks.selenium.*
class NewGroovyTest extends SeleneseTestCase { void setUp() { setUp "http://fredjean.net/", "*chrome" } void testNew() { selenium.open "/" selenium.click "link=Adoption" selenium.waitForPageToLoad "30000" assert "Out of my mind... : category adoption" == selenium.title selenium.click "link=We Are Out!" for (second in 0..60) { if (second == 60) fail "timeout" if (selenium.isTextPresent("Comments")) break; sleep(1000) }
assert selenium.isTextPresent("Trackbacks") }}
23Wednesday, August 13, 2008
Groovy
Less noisy than Java
Still repetitive
waitForTextPresent is still missing
24Wednesday, August 13, 2008
Metaprogramming
Writing of computer programs that write or manipulate other programs (or themselves) as
their data.
http://en.wikipedia.org/wiki/Metaprogramming25Wednesday, August 13, 2008
Metaprogramming
Increases code expressiveness
Allows SMEs to understand the code
Domain Specific Languages
26Wednesday, August 13, 2008
Meta Object ProtocolEstablishes the rules behind method calling in Groovy
Provides the hooks to modify your program's behavior
invokeMethod
propertyMissing
methodMissing
27Wednesday, August 13, 2008
Metaclass
All Groovy objects have one
Can be defined for Java objects
Per class vs per instance
Allows developers to "mutate" a class
28Wednesday, August 13, 2008
DelegationForward method calls to another object
Tedious to do in Java
Extend delegate
Manually code delegation code
Almost trivial in Groovy
ExpandoMetaClass
29Wednesday, August 13, 2008
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."$name"(* 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."$name" }
30Wednesday, August 13, 2008
Goodbye Repetition
void testDelegation() { open "/" click "link=Adoption" waitForPageToLoad "30000" assert "Out of my mind... : category adoption" == title click "link=We Are Out!" for (second in 0..60) { if (second == 60) fail "timeout" if (isTextPresent("Comments")) break; sleep(1000) }
assert isTextPresent("Trackbacks") }
31Wednesday, August 13, 2008
Performance Hit
32Wednesday, August 13, 2008
Performance Hit
33Wednesday, August 13, 2008
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."$name" = { Object varArgs -> delegate.selenium.metaClass.invokeMethod(delegate.selenium, name, varArgs) } selenium."$name"(* args) }
34Wednesday, August 13, 2008
Performance Hit
35Wednesday, August 13, 2008
Groovy Delegation
Results in cleaner test code
Almost trivial to implement in Groovy
Performance hit can be mitigated
36Wednesday, August 13, 2008
waitForTextPresent?
void testDelegation() { open "/" click "link=Adoption" waitForPageToLoad "30000" assert "Out of my mind... : category adoption" == title click "link=We Are Out!" for (second in 0..60) { if (second == 60) fail "timeout" if (isTextPresent("Comments")) break; sleep(1000) }
assert isTextPresent("Trackbacks") }
37Wednesday, August 13, 2008
waitForTextPresent?
void testDelegation() { open "/" click "link=Adoption" waitForPageToLoad "30000" assert "Out of my mind... : category adoption" == title click "link=We Are Out!" for (second in 0..60) { if (second == 60) fail "timeout" if (isTextPresent("Comments")) break; sleep(1000) }
assert isTextPresent("Trackbacks") }
Replaces waitFor... with a loop
38Wednesday, August 13, 2008
waitForTextPresent?
void testDelegation() { open "/" click "link=Adoption" waitForPageToLoad "30000" assert "Out of my mind... : category adoption" == title click "link=We Are Out!" for (second in 0..60) { if (second == 60) fail "timeout" if (isTextPresent("Comments")) break; sleep(1000) }
assert isTextPresent("Trackbacks") }
How about assertTextPresent?
39Wednesday, August 13, 2008
waitForTextPresent?
Selenese generates waitFor, verify, and assert methods
Java driver doesn't provide them
Java -> Explicitly typed language
JavaScript -> What's a type?
40Wednesday, August 13, 2008
Wait a minute...
JavaScript is a dynamic language...
Groovy is a dynamic language...
Why not synthesize these methods in Groovy?
41Wednesday, August 13, 2008
Synthetic Methods
Methods that don't really exist
Grails finder methodsPerson.findByFirstNameAndAge(...)
42Wednesday, August 13, 2008
Steps to Take
Identify synthetic methods
Implement behavior
Locate actual getter method
43Wednesday, August 13, 2008
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) } }
44Wednesday, August 13, 2008
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("waitFor", name); for (i in 0..(timeout / 1000)) { if ("$methodName"(* args)) { return true; } sleep(1000) } fail("Timeout occured in $name for $args") }
45Wednesday, August 13, 2008
Locating Getter
def getMethodName(prefix, name) { ["is", "get"].collect { name.replaceFirst(prefix, it) }.find { delegate.selenium.metaClass.respondsTo(delegate.selenium, it) } }
46Wednesday, August 13, 2008
Loop Begone!
void testDelegation() { open "/" click "link=Adoption" waitForPageToLoad "30000" assert "Out of my mind... : category adoption" == title click "link=We Are Out!" waitForTextPresent "Comments" assertTextPresent "Trackbacks" }
47Wednesday, August 13, 2008
Refactor!
Move methods to super class
methodMissing
propertyMissing
Supporting methods
Group Groovy tests in one suite
48Wednesday, August 13, 2008
GroovierSelenium
Extends SeleneseTestCase with methodMissing
Allows Groovy users to write tests that almost look like Selenese
http://groovierselenium.googlecode.com
Licensed under ASLv2.0
49Wednesday, August 13, 2008
Near Future
JUnit 4.5 test runners
GroovierSeleniumRunner
GroovySuiteRunner
@Selenium annotation
50Wednesday, August 13, 2008
NetBeans & Groovy
Grails and Groovy Plugin integrated with NetBeans 6.5
Adds Groovy Support to Java Projects
51Wednesday, August 13, 2008
Looking BackTalked about Selenium
Leveraging Groovy metaprogramming
Delegating to another object
Creating synthetic methods
GroovierSelenium
NetBeans
52Wednesday, August 13, 2008
Book
Programming Groovy (Venkat S.)
53Wednesday, August 13, 2008
Links
http://groovy.codehaus.org
http://groovierselenium.googlecode.com
http://www.pragprog.com/titles/vslg/programming-groovy
54Wednesday, August 13, 2008