Groovier Selenium (Djug)

Preview:

DESCRIPTION

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

Citation preview

Groovier SeleniumDenver JUG 8/13/2008

Frederic Jeanfred@fredjean.net

frederic.jean@sun.com

1Wednesday, August 13, 2008

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

Recommended