groovier selenium (djug)

54
Groovier Selenium Denver JUG 8/13/2008 Frederic Jean [email protected] [email protected] 1 Wednesday, August 13, 2008

Upload: frederic-jean

Post on 13-May-2015

13.291 views

Category:

Technology


0 download

DESCRIPTION

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

TRANSCRIPT

Page 2: Groovier Selenium (Djug)

who am i

2Wednesday, August 13, 2008

Page 3: Groovier Selenium (Djug)

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

Page 4: Groovier Selenium (Djug)

Selenium

UI testing tool

Runs in browser

Well suited for Ajax applications

4Wednesday, August 13, 2008

Page 5: Groovier Selenium (Djug)

Selenium IDE

Firefox plugin

Simplifies writing and testing Selenese test case

Can record and play back Selenese tests

5Wednesday, August 13, 2008

Page 6: Groovier Selenium (Djug)

SeleneseHTML Tables

Action

Target

Value

Intepreted by Selenium Core

Actions match JavaScript functions

6Wednesday, August 13, 2008

Page 7: Groovier Selenium (Djug)

Example

BlogTest

open /

clickAndWait link=Adoption

assertTitle Out of my mind... : category adoption

7Wednesday, August 13, 2008

Page 8: Groovier Selenium (Djug)

Selenese Locators

Allows an action to target a specific DOM element on the page

<type>=<locator>

8Wednesday, August 13, 2008

Page 9: Groovier Selenium (Djug)

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

Page 10: Groovier Selenium (Djug)

Sample Test

10Wednesday, August 13, 2008

Page 11: Groovier Selenium (Djug)

Selenese TestSuites

Groups and organizes individual Selenese tests

Can be run through ant

11Wednesday, August 13, 2008

Page 12: Groovier Selenium (Djug)

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

Page 13: Groovier Selenium (Djug)

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

Page 14: Groovier Selenium (Djug)

Generating Java Test

14Wednesday, August 13, 2008

Page 15: Groovier Selenium (Djug)

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

Page 16: Groovier Selenium (Djug)

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

Page 17: Groovier Selenium (Djug)

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

Page 18: Groovier Selenium (Djug)

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

Page 19: Groovier Selenium (Djug)

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

Page 20: Groovier Selenium (Djug)

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

Page 21: Groovier Selenium (Djug)

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

Page 22: Groovier Selenium (Djug)

Generated Java

Good start

Needs some work to be useful

Certainly faster than coding by hand

Noisy

22Wednesday, August 13, 2008

Page 23: Groovier Selenium (Djug)

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

Page 24: Groovier Selenium (Djug)

Groovy

Less noisy than Java

Still repetitive

waitForTextPresent is still missing

24Wednesday, August 13, 2008

Page 25: Groovier Selenium (Djug)

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

Page 26: Groovier Selenium (Djug)

Metaprogramming

Increases code expressiveness

Allows SMEs to understand the code

Domain Specific Languages

26Wednesday, August 13, 2008

Page 27: Groovier Selenium (Djug)

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

Page 28: Groovier Selenium (Djug)

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

Page 29: Groovier Selenium (Djug)

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

Page 30: Groovier Selenium (Djug)

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

Page 31: Groovier Selenium (Djug)

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

Page 32: Groovier Selenium (Djug)

Performance Hit

32Wednesday, August 13, 2008

Page 33: Groovier Selenium (Djug)

Performance Hit

33Wednesday, August 13, 2008

Page 34: Groovier Selenium (Djug)

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

Page 35: Groovier Selenium (Djug)

Performance Hit

35Wednesday, August 13, 2008

Page 36: Groovier Selenium (Djug)

Groovy Delegation

Results in cleaner test code

Almost trivial to implement in Groovy

Performance hit can be mitigated

36Wednesday, August 13, 2008

Page 37: Groovier Selenium (Djug)

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

Page 38: Groovier Selenium (Djug)

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

Page 39: Groovier Selenium (Djug)

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

Page 40: Groovier Selenium (Djug)

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

Page 41: Groovier Selenium (Djug)

Wait a minute...

JavaScript is a dynamic language...

Groovy is a dynamic language...

Why not synthesize these methods in Groovy?

41Wednesday, August 13, 2008

Page 42: Groovier Selenium (Djug)

Synthetic Methods

Methods that don't really exist

Grails finder methodsPerson.findByFirstNameAndAge(...)

42Wednesday, August 13, 2008

Page 43: Groovier Selenium (Djug)

Steps to Take

Identify synthetic methods

Implement behavior

Locate actual getter method

43Wednesday, August 13, 2008

Page 44: Groovier Selenium (Djug)

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

Page 45: Groovier Selenium (Djug)

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

Page 46: Groovier Selenium (Djug)

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

Page 47: Groovier Selenium (Djug)

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

Page 48: Groovier Selenium (Djug)

Refactor!

Move methods to super class

methodMissing

propertyMissing

Supporting methods

Group Groovy tests in one suite

48Wednesday, August 13, 2008

Page 49: Groovier Selenium (Djug)

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

Page 50: Groovier Selenium (Djug)

Near Future

JUnit 4.5 test runners

GroovierSeleniumRunner

GroovySuiteRunner

@Selenium annotation

50Wednesday, August 13, 2008

Page 51: Groovier Selenium (Djug)

NetBeans & Groovy

Grails and Groovy Plugin integrated with NetBeans 6.5

Adds Groovy Support to Java Projects

51Wednesday, August 13, 2008

Page 52: Groovier Selenium (Djug)

Looking BackTalked about Selenium

Leveraging Groovy metaprogramming

Delegating to another object

Creating synthetic methods

GroovierSelenium

NetBeans

52Wednesday, August 13, 2008

Page 53: Groovier Selenium (Djug)

Book

Programming Groovy (Venkat S.)

53Wednesday, August 13, 2008