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