Você está na página 1de 46

ThoughtWorks

Advanced Selenium
NEAL FORD thoughtworker / meme wrangler

ThoughtWorks
14 Wall St, Suite 2019, nford@thoughtworks.com www.nealford.com www.thoughtworks.com memeagora.blogspot.com New York, NY 10005

Questions, Slides, and Samples

ThoughtWorks

Please feel free to ask questions anytime The slides and samples will be available at www.nealford.com Ill show that address again at the end Samples denoted via => document_name

What This Session Covers


Seleniums scope TestRunner techniques Remote Control Ajax IDE Extensions Best practices

ThoughtWorks

Seleniums Scope

ThoughtWorks

Selenium is an acceptance testing tool for web applications

Selenium tests are fragile by design

Selenium Test Runner Mode

ThoughtWorks

Selenium Core

Selenium Test Runner

Application Under Test

The Selenium IDE


FireFox extension Not just a recorder Intelligent field selection Autocomplete for all common Selenium commands Walk through tests Debug and set breakpoints

ThoughtWorks

String Matching Patterns


glob:pattern
Match a string against a "glob" (aka "wildmat") pattern.
"*" represents any sequence of characters "?" represents any single character.

ThoughtWorks

Glob patterns match against the entire string.

regexp:regexp
Match a string using a regular-expression. The full power of JavaScript regular-expressions is available.

exact:string
Match a string exactly, verbatim, without any of that fancy wildcard stuff.

If no pattern prefix is specified, Selenium assumes that it's a "glob" pattern. => End 2 End Test

ThoughtWorks

TestRunner Techniques

End-to-end Testing

ThoughtWorks

Example of end to end testing => End 2 end Test

Always make sure you leave your application in a known good state

Generating Unique Values


The problem: you need unique user names

ThoughtWorks

Solution: create user names based on time values


'fred ' + (new Date().getTime())

Data Driven Tests

ThoughtWorks

Test cases (and suites) dont have to be static HTML


<% for (int row = 0; row < 6; row++) { resultSet.next(); Product p = new Product(); p.setId(resultSet.getInt("ID")); p.setName(resultSet.getString("NAME")); p.setPrice(resultSet.getDouble("PRICE")); %> <tr> <td>assertTable</td> <td>//html/body/table.<%= row + 1 %>.0</td> <td><%= p.getId() %></td> </tr> <tr></tr> <td>assertTable</td> <td>//html/body/table.<%= row + 1 %>.1</td> <td><%= p.getName() %></td> </tr> <tr> <td>assertTable</td> <td>//html/body/table.<%= row + 1 %>.2</td> <td><%= p.getPriceAsCurrency() %></td> </tr> <% } %>

Verifying # of Rows in a Table

ThoughtWorks

Assert that nth row exists and that nth+1 does not
assertElementPresent //table[@id='mytable']/tr[10] assertElementNotPresent //table[@id='mytable']/tr[11]

Use the assertXPath extension to count columns


assertXpath count(//table[@id='mytable']/tr)
10

Parameters and Variables

ThoughtWorks

Parameter and variable declarations range from simple values to Javascript evaluation store, storeValue, and storeText store values for later access Internally, Selenium uses a map called storedVars, keyed to variable names

store, storeValue
store( valueToStore, variableName )
Stores a value into a variable.

ThoughtWorks

Variable substitution or javascript evaluation

storeValue( inputLocator, variableName )


Stores the value of an input field into a variable.

storeText, storeAttribute

ThoughtWorks

storeText(elementLocator, variableName )
Stores the text of an element into a variable.

storeAttribute (elementLocator@attributeName, variableName)


Stores the value of an element attribute into a variable.

Variable Substitution

ThoughtWorks

Provides a simple way to access variables using ${xxx} syntax

JavaScript Evaluation
You can use JavaScript to construct whatever values you want

ThoughtWorks

The entire parameter value is prefixed with javascript{ with a trailing } Text inside the braces is a pure JavaScript expression

Harvesting Values

ThoughtWorks

Variables allow you to harvest information from an information only page Steps:
Build a page with values On startup of the test, harvest those values Use the variables for assertions in subsequent tests

=> harvesting values

Selenium Remote Control

ThoughtWorks

Selenium Core

JUnit Test

Proxy Server

Application Under Test

19

Interactive Selenium
Start the proxy Create an instance of the browser

ThoughtWorks

The proxy will provide a unique ID that must be used in all subsequent commands Youll see a separate instance of the browser launch

Issue Selenium commands When done, kill the browser instance => Interactive Selenium

Test Code Reuse

ThoughtWorks

One of the advantages of remote control Create helper methods for common tasks
Login helper => DecisionDemo

When designing tests


Keep them modular Refactor frequently

Multiple Language Support

ThoughtWorks

The Selenium IDE generates tests in a variety of languages All your tests dont have to be in the same language Java == Ruby example =>
Selenium Remote Control, Selenium Remote Control Ruby

Use the language that makes sense for the situation

Decisions

ThoughtWorks

TestRunner tests cant make deciscions


The Selenium language was designed to be strictly declarative, not imperative

In remote control, you write unit tests Allows you to make testing decisions
Firefox makes you logon, IE doesnt Different roles for different users

Remote control gives you imperative tests in Selenium => Decision Demo

The Selenium IDE


More than just a pretty face! Supports multiple languages

ThoughtWorks

TestRunner (declarative) Java, Ruby, C#, Python, etc. (imperative)

A round-trip tool but only for declarative tests Dont make the imperative plunge lightly

Ajax

ThoughtWorks

Because Selenium works directly with the DOM, testing Ajax is easy Testing XmlHttpRequest => Testing collapsable divs => End 2 End Test Testing absence of controls =>

Extending Selenium

ThoughtWorks

Selenium allows you to add your own actions, checks, and locator strategies Selenium uses naming patterns to discover extensions at run-time

Actions

ThoughtWorks

All methods in the form of doFoo are added as actions For each foo, and fooAndWait is created
Selenium.prototype.doTypeRepeated = function(locator, text) { // All locator-strategies are automatically // handled by "findElement" var element = this.page().findElement(locator); // Create the text to type var valueToType = text + text; // Replace the element text with the new text this.page().replaceText(element, valueToType);

};

Checks

ThoughtWorks

All assertFoo methods are added as checks For each foo, you get assertFoo & verifyFoo
Selenium.prototype.assertValueRepeated = function(locator, text) { var element = this.page().findElement(locator); // Create the text to verify var expectedValue = text + text; // Get the actual element value var actualValue = element.value; // Make sure actual value matches the expected this.assertMatches(expectedValue, actualValue);

};

Locator Strategies

ThoughtWorks

All locateElementByFoo methods on PageBot are added as locator strategies Locators take 2 parameters
Locator string (minus the prefix) Document in which to search

Add a "valuerepeated=" locator, that finds the first element a value attribute equal to the the supplied value repeated.

Custom Locator

ThoughtWorks

// The "inDocument" is a the document you are searching. PageBot.prototype.locateElementByValueRepeated = function(text, inDocument) { // Create the text to search for var expectedValue = text + text; // Loop through elements, looking for ones that have // a value === our expected value var allElements = inDocument.getElementsByTagName("*"); for (var i = 0; i < allElements.length; i++) { var testElement = allElements[i]; if (testElement.value && testElement.value === expectedValue) { return testElement; } } return null;

};

User Extensions

ThoughtWorks

By default, Selenium looks for a file called "user-extensions.js", and loads the javascript code found in that file. A convenient location for adding features to Selenium, without needing to modify the core Selenium sources. This file doesnt exist by default (youll have to add it)

Handy User Extensions


Alert => Interactive debugger => GUI_Map =>

ThoughtWorks

Allows you to handle auto-generated fields names more gracefully

Include => Check out


http://wiki.openqa.org/display/SEL/Contributed+User-Extensions

Uploading Files

ThoughtWorks

Normally, JavaScript permissions block you from filling in an input path for a file upload Workaround:
Set the browser property signed.applets.codebase_principal_support to true Add netscape.security.PrivilegeManager.enablePrivilege(UniversalFileRead); to selenium-api.js in function Selenium.prototype.doType

Currently only works in Firefox

Selenium Best Practices


Allows for test code reuse Easier to refactor Not as fragile

ThoughtWorks

Create modular test methods for remote control

Record acceptance tests


End users/business analysts only have to verify it once Regression tests catch errors

Selenium Best Practices


Write selenium tests late in the development cycle
User acceptance tests are fragile Require lots of refactoring The danger is that youll stop running regression tests if they break too much

ThoughtWorks

ThoughtWorks

Questions?
Please fill out the session evaluations Samples & slides at www.nealford.com
NEAL FORD thoughtworker / meme wrangler

ThoughtWorks
14 Wall St, Suite 2019, New York, NY 10005

This work is licensed under the Creative Commons AttributionNoncommercial-Share Alike 2.5 License. http://creativecommons.org/licenses/by-nc-sa/2.5/

nford@thoughtworks.com www.nealford.com www.thoughtworks.com memeagora.blogspot.com

End 2 End Test

file://localhost/Users/jNf/bin/apache-tomcat-5.5.17/webapps/art_emo...

End 2 End Test open type clickAndWait assertLocation assertTitle assertTextPresent assertElementPresent assertTable type clickAndWait assertLocation assertTitle assertElementPresent assertTextPresent clickAndWait assertLocation type clickAndWait click assertConfirmation type select type clickAndWait assertTextPresent assertTextPresent

http://localhost:8080/art_emotherearth_memento/welcome user //input[@id='submitButton'] glob:*art_emotherearth_memento/catalog CatalogView Catalog of Items //html/body/table/ //html/body/table/.1.1 document.forms[1].quantity //input[@id='submit2'] *art_emotherearth_memento/showcart ShowCart link=Click here for more shopping *, here is your cart: link=Click here for more shopping */art_emotherearth_memento/catalog document.forms[3].quantity //input[@id='submit4'] //html/body/input[1] Do you * want to check out? ccNum ccType ccExp //input[@value='Check out'] *, Thank you for shopping at eMotherEarth.com regexp:Your confirmation number is \d?

Homer

Ocean 3

444444444444 label=Amex 12/10

1 of 1

2/22/07 11:57 AM

DataTest.jsp <%@ <%@ <%@ <%@ <%@ <%@ page page page page page page

2007-02-22

import="java.sql.Connection "%> import="com.nealford.art.memento.emotherearth.util.DBPool"%> import="java.sql.ResultSet"%> import="java.sql.Statement"%> import="java.sql.DriverManager"%> import="com.nealford.art.memento.emotherearth.entity.Product"%>

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String driverClass = getServletContext().getInitParameter("driverClass"); String dbUrl = getServletContext().getInitParameter("dbUrl"); Class.forName(driverClass).newInstance(); Connection connection = DriverManager.getConnection(dbUrl); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT * FROM PRODUCTS"); %> <html> <head><title>Data Test</title></head> <body> <table border="1" cellpadding="1" cellspacing="1"> <thead> <tr> <td rowspan="1" colspan="3">Data Test</td> </tr> </thead><tbody> <tr> <td>open</td> <td>/art_emotherearth_memento/welcome</td> <td></td> </tr> <tr>

- 1/3 -

DataTest.jsp <td>type</td> <td>user</td> <td>Homer</td> </tr> <tr> <td>clickAndWait</td> <td>//input[@id='submitButton']</td> <td></td> </tr> <tr> <td>verifyTitle</td> <td>CatalogView</td> <td></td> </tr> <% for (int row = 0; row < 6; row++) { resultSet.next(); Product p = new Product(); p.setId(resultSet.getInt("ID")); p.setName(resultSet.getString("NAME")); p.setPrice(resultSet.getDouble("PRICE")); %> <tr> <td>assertTable</td> <td>//html/body/table.<%= row + 1 %>.0</td> <td><%= p.getId() %></td> </tr> <tr></tr> <td>assertTable</td> <td>//html/body/table.<%= row + 1 %>.1</td> <td><%= p.getName() %></td> </tr> <tr> <td>assertTable</td> <td>//html/body/table.<%= row + 1 %>.2</td> - 2/3 -

2007-02-22

DataTest.jsp <td><%= p.getPriceAsCurrency() %></td> </tr> <% } %> </tr> </tbody> </table>

2007-02-22

</body> </html>

- 3/3 -

Data Test

http://localhost:8080/art_emotherearth_memento/selenium/tests/Dat...

Data Test open type clickAndWait verifyTitle assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable assertTable

/art_emotherearth_memento/welcome user //input[@id='submitButton'] CatalogView //html/body/table.1.0 //html/body/table.1.1 //html/body/table.1.2 //html/body/table.2.0 //html/body/table.2.1 //html/body/table.2.2 //html/body/table.3.0 //html/body/table.3.1 //html/body/table.3.2 //html/body/table.4.0 //html/body/table.4.1 //html/body/table.4.2 //html/body/table.5.0 //html/body/table.5.1 //html/body/table.5.2 //html/body/table.6.0 //html/body/table.6.1 //html/body/table.6.2

Homer

1 Ocean $1,393,456,200.00 2 Leaves (green) $3.50 3 Leaves (brown) $0.01 4 Mountain $2,694,381.34 5 Lake $34,563.12 6 Snow $2.45

1 of 1

2/22/07 12:04 PM

RawDataTest

file:///Users/jNf/Documents/dev/java/intellij/art_emotherearth_meme...

RawDataTest open storeValue storeValue storeValue storeValue storeValue open assertTitle type clickAndWait assertTitle type clickAndWait assertTitle click assertConfirmation type select type clickAndWait assertTitle

/art_emotherearth_memento/RawData.jsp user_name ocean_quantity cc_num cc_type cc_exp_date /art_emotherearth_memento/welcome WelcomeView userName submitButton CatalogView qty1 submit1 ShowCart //input[@value='Show Checkout'] Do you really want to check out? ccNum ccType ccExp //input[@value='Check out'] CheckOutView

userName oceanQuantity ccNum ccType ccExpDate

Homer

${oceanQuantity}

${ccNum} label=${ccType} ${ccExpDate}

1 of 1

2/23/07 11:26 AM

Interactive Selenium Script.txt Script for Interactive Selenium

2007-02-23

'java -jar /Users/jNf/bin/selenium-remote-control-0.8.1/server/selenium-server.jar -interactive' cmd=getNewBrowserSession&1=*firefox&2=http://localhost:8080 cmd=open&1=/art_emotherearth_memento/welcome&sessionId=<ID#> cmd=type&1=userName&2=Homer&sessionId=<ID#> cmd=clickAndWait&1=submitButton&sessionId=<ID#> cmd=testComplete&sessionId=<ID#>

- 1/1 -

File - /Users/jNf/Documents/dev/java/intellij/art_emotherearth_memento/src/com/nealford/art/memento/emotherearth/test/DecisionDemoTest.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

package com.nealford.art.memento.emotherearth.test; import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium; import junit.framework.TestCase; /** * User: Neal Ford * Date: Feb 7, 2007 * Time: 9:55:54 PM * <cite>Incidentally, created by IntelliJ IDEA.</cite> */ public class DecisionDemoTest extends TestCase { private Selenium s; public void setUp() { s = new DefaultSelenium("localhost" , 4444, "*firefox" , "http://localhost:8080/" ); s.start(); } private void login() { s.open("/art_emotherearth_memento/welcome" ); s.type("user" , "Homer" ); s.click("//input[@id='submitButton']" ); s.waitForPageToLoad("30000" ); assertTrue(s.getLocation().matches(".*art_emotherearth_memento/catalog" )); } private void verifyLastPageContent() { assertTrue(s.isTextPresent("*, Thank you for shopping at eMotherEarth.com" )); assertTrue(s.isTextPresent("regexp:Your confirmation number is \\d?" )); } public void test_Simple_transaction_runs_from_first_to_last_page() { login(); assertEquals("CatalogView" , s.getTitle()); assertTrue(s.isTextPresent("Catalog of Items" )); assertTrue(s.isElementPresent("//html/body/table/" )); assertEquals("Ocean" , s.getTable("//html/body/table/.1.1" )); s.type("document.forms[1].quantity" , "3" ); s.click("//input[@id='submit2']" ); s.waitForPageToLoad("30000" ); assertTrue(s.getLocation().matches(".*art_emotherearth_memento/showcart" )); assertEquals("ShowCart" , s.getTitle()); assertTrue(s.isElementPresent("link=Click here for more shopping" )); assertTrue(s.isTextPresent("*, here is your cart:" )); s.click("link=Click here for more shopping" ); s.waitForPageToLoad("30000" ); assertTrue(s.getLocation().matches(".*art_emotherearth_memento/catalog" )); s.type("document.forms[3].quantity" , "2" ); s.click("//input[@id='submit4']" ); s.waitForPageToLoad("30000" ); s.type("ccNum" , "444444444444" ); s.select("ccType" , "label=Amex" ); s.type("ccExp" , "12/10" ); s.click("//input[@value='Check out']" ); s.waitForPageToLoad("30000" ); verifyLastPageContent(); } public void test_Undo_operation_restores_button_state_and_shopping_cart_correctly() { login(); assertFalse(s.isElementPresent("restoreButton" )); s.type("id=qty1" , "1" ); s.click("//input[@id='submit1']" ); s.waitForPageToLoad("30000" );
Page 1

File - /Users/jNf/Documents/dev/java/intellij/art_emotherearth_memento/src/com/nealford/art/memento/emotherearth/test/DecisionDemoTest.java

67 assertEquals("Ocean" , s.getTable("//html/body/p[1]/table.1.1" )); 68 s.click("link=Click here for more shopping" ); 69 s.waitForPageToLoad("30000" ); 70 s.type("id=qty2" , "2" ); 71 s.click("//input[@id='submit2']" ); 72 s.waitForPageToLoad("30000" ); 73 assertEquals("Ocean" , s.getTable("//html/body/p[1]/table.1.1" )); 74 assertEquals("Leaves (green)" , s.getTable("//html/body/p[1]/table.2.1" )); 75 s.click("bookmark" ); 76 s.waitForPageToLoad("30000" ); 77 assertTrue(s.isElementPresent("restoreButton" )); 78 s.click("link=Click here for more shopping" ); 79 s.waitForPageToLoad("30000" ); 80 s.type("id=qty3" , "1" ); 81 s.click("//input[@id='submit3']" ); 82 s.waitForPageToLoad("30000" ); 83 assertEquals("Ocean" , s.getTable("//html/body/p[1]/table.1.1" )); 84 assertEquals("Leaves (green)" , s.getTable("//html/body/p[1]/table.2.1" )); 85 assertEquals("Leaves (brown)" , s.getTable("//html/body/p[1]/table.3.1" )); 86 s.click("link=Click here for more shopping" ); 87 s.waitForPageToLoad("30000" ); 88 s.type("id=qty4" , "2" ); 89 s.click("//input[@id='submit4']" ); 90 s.waitForPageToLoad("30000" ); 91 assertEquals("Ocean" , s.getTable("//html/body/p[1]/table.1.1" )); 92 assertEquals("Leaves (green)" , s.getTable("//html/body/p[1]/table.2.1" )); 93 assertEquals("Leaves (brown)" , s.getTable("//html/body/p[1]/table.3.1" )); 94 assertEquals("Mountain" , s.getTable("//html/body/p[1]/table.4.1" )); 95 s.click("bookmark" ); 96 s.waitForPageToLoad("30000" ); 97 s.click("link=Click here for more shopping" ); 98 s.waitForPageToLoad("30000" ); 99 s.type("id=qty5" , "1" ); 100 s.click("//input[@id='submit5']" ); 101 s.waitForPageToLoad("30000" ); 102 assertEquals("Ocean" , s.getTable("//html/body/p[1]/table.1.1" )); 103 assertEquals("Leaves (green)" , s.getTable("//html/body/p[1]/table.2.1" )); 104 assertEquals("Leaves (brown)" , s.getTable("//html/body/p[1]/table.3.1" )); 105 assertEquals("Mountain" , s.getTable("//html/body/p[1]/table.4.1" )); 106 assertEquals("Lake" , s.getTable("//html/body/p[1]/table.5.1" )); 107 s.click("restore" ); 108 s.waitForPageToLoad("30000" ); 109 assertTrue(s.isElementPresent("restoreButton" )); 110 s.click("restore" ); 111 s.waitForPageToLoad("30000" ); 112 assertFalse(s.isElementPresent("restoreButton" )); 113 s.type("ccNum" , "444444444444" ); 114 s.select("ccType" , "label=Amex" ); 115 s.type("ccExp" , "12/10" ); 116 s.click("//input[@value='Check out']" ); 117 s.waitForPageToLoad("30000" ); 118 verifyLastPageContent(); 119 } 120 121 122 protected void tearDown() throws Exception { 123 s.stop(); 124 } 125 } 126

Page 2

File - /Users/jNf/Documents/dev/java/intellij/art_emotherearth_memento/src/com/nealford/art/memento/emotherearth/test/SeleniumRemoteControlTest.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

package com.nealford.art.memento.emotherearth.test; /** * User: Neal Ford * Date: Sep 27, 2006 * Time: 1:50:51 PM * <cite>Incidentally, created by IntelliJ IDEA.</cite> */ import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium; import junit.framework.TestCase; public class SeleniumRemoteControlTest extends TestCase { private Selenium s; public void setUp() { s = new DefaultSelenium("localhost" , 4444, "*firefox" , "http://localhost:8080/" ); s.start(); } public void testEMotherEarthEnd2End() { s.open("/art_emotherearth_memento/welcome" ); s.type("user" , "Homer" ); s.click("//input[@id='submitButton']" ); s.waitForPageToLoad("30000" ); assertTrue(s.getLocation().matches(".*art_emotherearth_memento/catalog" )); assertEquals("CatalogView" , s.getTitle()); assertTrue(s.isTextPresent("Catalog of Items" )); assertTrue(s.isElementPresent("//html/body/table/" )); assertEquals("Ocean" , s.getTable("//html/body/table/.1.1" )); s.type("document.forms[1].quantity" , "3" ); s.click("//input[@id='submit2']" ); s.waitForPageToLoad("30000" ); assertTrue(s.getLocation().matches(".*art_emotherearth_memento/showcart" )); assertEquals("ShowCart" , s.getTitle()); assertTrue(s.isElementPresent("link=Click here for more shopping" )); assertTrue(s.isTextPresent("*, here is your cart:" )); s.click("link=Click here for more shopping" ); s.waitForPageToLoad("30000" ); assertTrue(s.getLocation().matches(".*art_emotherearth_memento/catalog" )); s.type("document.forms[3].quantity" , "2" ); s.click("//input[@id='submit4']" ); s.waitForPageToLoad("30000" ); s.type("ccNum" , "444444444444" ); s.select("ccType" , "label=Amex" ); s.type("ccExp" , "12/10" ); s.click("//input[@value='Check out']" ); s.waitForPageToLoad("30000" ); assertTrue(s.isTextPresent("*, Thank you for shopping at eMotherEarth.com" )); assertTrue(s.isTextPresent("regexp:Your confirmation number is \\d?" )); } public void tearDown() { s.stop(); } }

Page 1

File - /Users/jNf/Documents/dev/java/intellij/art_emotherearth_memento/src/com/nealford/art/memento/emotherearth/test/SeleniumRemoteControl.rb

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

require 'test/unit' require 'selenium' class SeleniumRemoteControlTest < Test::Unit::TestCase def setup @s = Selenium::SeleneseInterpreter.new("localhost", 4444, "*chrome", "http://locahost:8080/", 15000) @s.start end def test_emotherearth_end_to_end @s.open "http://localhost:8080/art_emotherearth_memento/welcome" @s.type "user", "Homer" @s.click "//input[@id='submitButton']" @s.wait_for_page_to_load "30000" assert @s.get_location =~ /.*art_emotherearth_memento\/catalog/ assert_equal "CatalogView", @s.get_title assert @s.is_text_present("Catalog of Items") assert @s.is_element_present("//html/body/table/") assert_equal "Ocean", @s.get_table("//html/body/table/.1.1") @s.type "document.forms[1].quantity", "3" @s.click "//input[@id='submit2']" @s.wait_for_page_to_load "30000" assert @s.get_location =~ /.*art_emotherearth_memento\/showcart/ assert_equal "ShowCart", @s.get_title assert @s.is_element_present("link=Click here for more shopping") assert @s.is_text_present("*, here is your cart:") @s.click "link=Click here for more shopping" @s.wait_for_page_to_load "30000" assert @s.get_location =~ /.*art_emotherearth_memento\/catalog/ @s.type "document.forms[3].quantity", "2" @s.click "//input[@id='submit4']" @s.wait_for_page_to_load "30000" @s.type "ccNum", "444444444444" @s.select "ccType", "label=Amex" @s.type "ccExp", "12/10" @s.click "//input[@value='Check out']" @s.wait_for_page_to_load "30000" assert @s.is_text_present("*, Thank you for shopping at eMotherEarth.com") assert @s.is_text_present("regexp:Your confirmation number is \d?") end def teardown @s.stop end end

Page 1

File - /Users/jNf/Documents/dev/java/intellij/art_emotherearth_memento/src/com/nealford/art/memento/emotherearth/test/RandomQuantitiesTest.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

package com.nealford.art.memento.emotherearth.test; import com.thoughtworks.selenium.SeleneseTestCase; import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium; import java.util.Random; /** * User: Neal Ford * Date: Feb 23, 2007 * Time: 9:33:12 AM * <cite>Incidentally, created by IntelliJ IDEA.</cite> */ public class RandomQuantitiesTest extends SeleneseTestCase { private Random random; private Selenium s; protected void setUp() throws Exception { super.setUp(); random = new Random(); s = new DefaultSelenium("localhost" , 4444, "*firefox" , "http://localhost:8080/" ); s.start(); } public void test_Random_number_of_quantities() throws Exception { s.open("/art_emotherearth_memento/RawData.jsp" ); String userName = s.getValue("user_name" ); String ccNum = s.getValue("cc_num" ); String ccType = s.getValue("cc_type" ); String ccExpDate = s.getValue("cc_exp_date" ); s.open("/art_emotherearth_memento/welcome" ); assertEquals("WelcomeView" , s.getTitle()); s.type("userName" , userName); s.click("submitButton" ); s.waitForPageToLoad("30000" ); assertEquals("CatalogView" , s.getTitle()); int randomNumberOfInvocations = random.nextInt(5) + 1; for (int invocations = 0; invocations < randomNumberOfInvocations; invocations++) { int randomQuantity = random.nextInt(3) + 1; int randomProductId = random.nextInt(5) + 1; s.type("qty" + randomProductId , String.valueOf(randomQuantity)); s.click("submit" + randomProductId); s.waitForPageToLoad("30000" ); assertEquals("ShowCart" , s.getTitle()); if (invocations < randomNumberOfInvocations - 1) { s.click("link=Click here for more shopping" ); s.waitForPageToLoad("30000" ); } } s.click("//input[@value='Show Checkout']" ); assertTrue(s.getConfirmation().matches("^Do you really want to check out[\\s\\S]$" )); s.type("ccNum" , ccNum); s.select("ccType" , "label=" + ccType); s.type("ccExp" , ccExpDate); s.click("//input[@value='Check out']" ); s.waitForPageToLoad("30000" ); assertEquals("CheckOutView" , s.getTitle()); checkForVerificationErrors(); } public void tearDown() { s.stop(); }
Page 1

File - /Users/jNf/Documents/dev/java/intellij/art_emotherearth_memento/src/com/nealford/art/memento/emotherearth/test/RandomQuantitiesTest.java

67 } 68 69 70

Page 2

Ajax XmlHttp

file:///Users/jNf/Documents/Conferences/current/talks/advanced%20s...

Ajax XmlHttp setTimeout 3000 open /conf_selenium_pragajax_crm/ajaxlibs/figure_ed_screen_prototype-updater.html assertTitle Customer Data Screen type name Homer 123 type address Springfield Dr type zip 30329 fireEvent zip blur waitForValue city Atlanta assertValue state GA

1 of 1

2/23/07 11:37 AM

Ajax and absence test

file:///Users/jNf/Documents/Conferences/current/talks/advanced%20s...

Ajax and absence test open /conf_selenium_googlemaps/step7-1.html assertElementNotPresent //html/body/div[@id='outerDiv']/div[@id='innerDiv']/div[@id='pinDialog']/table/tbody/tr/td click togglePushPinDiv assertElementPresent //html/body/div[@id='outerDiv']/div[@id='innerDiv']/div[@id='pinDialog']/table/tbody/tr/td

1 of 1

2/23/07 11:43 AM

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

/* * By default, Selenium looks for a file called "user-extensions.js", and loads and javascript * code found in that file. This file is a sample of what that file could look like. * * user-extensions.js provides a convenient location for adding extensions to Selenium, like * new actions, checks and locator-strategies. * By default, this file does not exist. Users can create this file and place their extension code * in this common location, removing the need to modify the Selenium sources, and hopefully assisting * with the upgrade process. * * You can find contributed extensions at http://wiki.openqa.org/display/SEL/Contributed%20User-Extensions */ // The following examples try to give an indication of how Selenium can be extended with javascript. // All do* methods on the Selenium prototype are added as actions. // Eg add a typeRepeated action to Selenium, which types the text twice into a text box. // The typeTwiceAndWait command will be available automatically Selenium.prototype.doTypeRepeated = function (locator, text) { // All locator-strategies are automatically handled by "findElement" var element = this.page().findElement(locator); // Create the text to type var valueToType = text + text; // Replace the element text with the new text this.page().replaceText(element, valueToType); }; // All assert* methods on the Selenium prototype are added as checks. // Eg add a assertValueRepeated check, that makes sure that the element value // consists of the supplied text repeated. // The verify version will be available automatically. Selenium.prototype.assertValueRepeated = function (locator, text) { // All locator-strategies are automatically handled by "findElement" var element = this.page().findElement(locator); // Create the text to verify var expectedValue = text + text; // Get the actual element value var actualValue = element.value; // Make sure the actual value matches the expected Assert.matches(expectedValue, actualValue); }; // All get* methods on the Selenium prototype result in
Page 1

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

// store, assert, assertNot, verify, verifyNot, waitFor, and waitForNot commands. // E.g. add a getTextLength method that returns the length of the text // of a specified element. // Will result in support for storeTextLength, assertTextLength, etc. Selenium.prototype.getTextLength = function (locator) { return this.getText(locator).length; }; // All locateElementBy* methods are added as locator-strategies. // Eg add a "valuerepeated=" locator, that finds the first element with the supplied value, repeated. // The "inDocument" is a the document you are searching. PageBot.prototype.locateElementByValueRepeated = function (text, inDocument) { // Create the text to search for var expectedValue = text + text; // Loop through all elements, looking for ones that have a value === our expected value var allElements = inDocument.getElementsByTagName("*" ); for (var i = 0; i < allElements.length; i++) { var testElement = allElements[i]; if (testElement.value && testElement.value === expectedValue) { return testElement; } } return null; }; // Extensions // This extension adds command 'alert' that pops-up a alert message with a given parameter.This helps a lot // in script debuging. // Note: the undocuments <code>break</code> command does the same thing Selenium.prototype.doDisplayAlert = function (value, varName) { alert(value); }; // GUI-Map //////////////////////////////////////////////////////////////////// /* 1. many control ids in you application-under-test are 'autogenerated' and look like _ctl0_page_header__page_header__tbp_window_items_list__toolbar_print_view, but you prefer to see something more meaningful, like uimap=customers.ee.view.lnk_print_view. 2.control ids change occasionally and you want to have a single place to modify changed id. Steps: 1. create file 'gui_map.js' in the selenium's folder with content
Page 2

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

var gui_map = {}; 2. include it in the 'TestRunner.html' like: <script language="JavaScript" type="text/javascript" src="gui_map.js"></script> 3. add following code into the 'users-extensions.js': */ PageBot.prototype.locateElementByUimap = function (identifier, inDocument) { var element_id = eval("gui_map." + identifier); if( !element_id ) { throw "no such entry in UI Map: " + identifier; } var res = this.findElement( element_id, inDocument); return res; } // // Sample GUI Map (gui_map.js): /* var gui_map = { main: { lnk_logout: "link=Logout" , lnk_customer_section: "link=Customers" , lnk_jobs_section: "link=Jobs" , lnk_my_items_section: "link=My Items" , quicksearch: { p_topic: "_ctl0:_ctl3:topics_" , keyword: "_ctl0:_ctl3:keyword_" , btn_go: "_ctl0:_ctl3:go_" } , recents: { lnk_clear: "link=[clear]" , lnk_fn_link_id: function( x ) { return "_ctl0__ctl7_rpt_recents__ctl" + (x + 1) + "__ctl0_v_jump" } } } // etc....................... }; */ // Eval //////////////////////////////////////////////////////////////////// /* Evaluates any arbitrary JavaScript, as needed Sample usage:
Page 3

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

+--------------------------------------------------------------------------------+ | eval | selenium.page(); selenium.browserbot.getCurrentWindow().alert('Hello');| +-------------------------------------------------------------------------------| assertAlert | Hello | +---------------------+ */ Selenium.prototype.doEval = function (script) { try { return eval(script); } catch (e) { throw new SeleniumError("Threw an exception: " + e.message); } }; /** * add the content of another test to the current test * target receives the page address (from selenium tests root) * text receives vars names and their values for this test * as a comma separated list of var_name=value * * nested include works * * Take a look at the supplied includeCommand TestSuite.html * more examples * * example of use * in the test : * +------------------+----------------------+----------------------+ * |include | testpiece.html | name=joe,userId=3445 | * +------------------+----------------------+----------------------+ * where * testpiece.html contains * +---------------------------------------------+ * | this is a piece of test | * +------------------+-----------------------+--+ * |open | myurl?userId=${userId}| | * +------------------+-----------------------+--+ * |verifyTextPresent | ${name} | | * +------------------+-----------------------+--+ * as selenium reach the include commande, it will load * seleniumRoot/tests/testpiece.html into you current test, replacing ${name} with joe and ${userId} with 3445 * and your test wil become * +------------------+----------------------+----------------------+ * |includeExpanded | testpiece.html | name=joe,userId=3445 | * +------------------+----------------------+----------------------+ * |open | myurl?userId=3445 | |
Page 4

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240

* +------------------+----------------------+----------------------+ * |verifyTextPresent | joe | | * +------------------+----------------------+----------------------+ * moreover if you click on the line with "includeExpanded", it will show/hide included lines ! * * @author Alexandre Garel * @author Robert Zimmermann * * Note from Robert Zimmermann: * One thing to the variable handling (that's "name=joe,userId=3445" in the expamle above) * I recomend to use selenium build-in variables instead of the those of includeCommand. * Why?: There are escaping issues. The variables are substituted on inclusion of the document, * selenium substitutes it's variables internally on execution of each command. * Though includeCommand variable-like substitution should work and I didn't remove it for backward compatibility * * Version: 2.1 */ // The real include selenium-command is placed at the end of this file as // jslint complains about undefined functions otherwise Selenium.prototype.doIncludeCollapsed = function (locator, paramString) { // do nothing, as rows are already included }; Selenium.prototype.doIncludeExpanded = function (locator, paramString) { // do nothing, as rows are already included }; function IncludeCommand() { // TODO targetRow is needed for fold/unfold, isn't there a better way without this member? this.targetRow = null; } IncludeCommand.EXPANDED_COMMAND_NAME = "includeExpanded" ; IncludeCommand.COLLAPSED_COMMAND_NAME = "includeCollapsed" ; IncludeCommand.LOG_PREFIX = "IncludeCommand: " ; // use a closure here to keep each row actions by them self // TODO think about example: http://www.jibbering.com/faq/faq_notes/closures.html#clObjI IncludeCommand.prototype.onClickFactory = function (inclCmdRow, lastInclCmdRow) { return (function (e) { // change the trailing "Expanded" "Collapsed" of include // and choose the display style var cmdCol = (inclCmdRow.getElementsByTagName("td" ))[0].firstChild; var displayMode; if ( cmdCol.nodeValue == IncludeCommand.EXPANDED_COMMAND_NAME ) { cmdCol.nodeValue = IncludeCommand.COLLAPSED_COMMAND_NAME;
Page 5

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288

displayMode = "none" ; } else { cmdCol.nodeValue = IncludeCommand.EXPANDED_COMMAND_NAME; displayMode = inclCmdRow.style.display; } var ptrRow = inclCmdRow.nextSibling; while (ptrRow != lastInclCmdRow.nextSibling) { // when I unfold i shall unfold all nested include (no way to know which row they concern if (displayMode != "none" ) { cmdCol = (ptrRow.getElementsByTagName("td" ))[0].firstChild; if (cmdCol.nodeValue == IncludeCommand.COLLAPSED_COMMAND_NAME) { cmdCol.nodeValue = IncludeCommand.EXPANDED_COMMAND_NAME; } } // set display mode for rows if (ptrRow.style) { ptrRow.style.display = displayMode; } ptrRow = ptrRow.nextSibling; }

}; IncludeCommand.prototype.postProcessIncludeCommandRow = function (includeCmdRow) { /** * Alter the original include command row to add fold, unfold magic * * @param includeCmdRow TR DOM-element, the source of the current execution */ // TODO names should be class-constants var foldUnfoldToolTipp = "click to fold/unfold included rows" ; var lastInclRow = this.targetRow; // command name is changed from 'include' to 'include<TAIL>' to avoid another inclusion during a second pass (includeCmdRow.getElementsByTagName("td" ))[0].firstChild.nodeValue = IncludeCommand.EXPANDED_COMMAND_NAME; includeCmdRow.title = foldUnfoldToolTipp; includeCmdRow.alt = foldUnfoldToolTipp; // adding the fold/unfold trick includeCmdRow.onclick = this.onClickFactory(includeCmdRow, lastInclRow); }; IncludeCommand.extendSeleniumExecutionStack = function (newRows) { /** * Put the new commands into the current position of the selenium execution stack * * @param newRows Array of HtmlTestCaseRows to be inserted in seleniums' execution stack */
Page 6

});

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

289 290 291 292 293

294 295 296 297 298 299 300 301 302 IncludeCommand.prototype.injectIncludeTestrows = function (includeCmdRow, testDocument, testRows) { 303 /** 304 * Insert new (included) commad rows into current testcase (inject them) 305 * 306 * @param includeCmdRow TR Element of the include commad row wich called this include extension (from here the included rows have to be inse rted) 307 * @param testDocument DOM-document of the current testcase (needed to copy included command rows) 308 * @param testRows prepared testrows to be included 309 * @return newRows Array of HtmlTestCaseRow objects ready to be used by selenium 310 */ 311 this.targetRow = includeCmdRow; 312 var newRows = new Array(); 313 314 // TODO: use selenium methods to get to the inner test-rows (tr-elements) of an testcase. 315 // here it is the testcase to be included 316 //LOG.debug(IncludeCommand.LOG_PREFIX + "start with some table magic"); 317 // first element is empty and first row is the title => let's begin at i=2 318 for (var i = 2 ; i < testRows.length; i++) { 319 var newRow = testDocument.createElement("tr" ); 320 var newText = testRows[i]; 321 // inserting 322 this.targetRow = this.targetRow.parentNode.insertBefore(newRow, this.targetRow.nextSibling); 323 // innerHTML permits us not to interpret the rest of html code 324 // note: innerHTML is to be filled after insertion of the element in the document 325 // note2 : does not work with internet explorer 326 try { 327 this.targetRow.innerHTML = newText; 328 } catch (e) { 329 // doing it the hard way for ie 330 // parsing column, doing column per column insertion 331 // LOG.debug(newText); 332 // remove < td> 333 newText = newText.replace(/<\s*td[^>]*>/ig, "" );
Page 7

try { //(rz WEB.DE) changed to work with selenium 0.8.0 // Leave previously run commands as they are var seleniumCmdRowsPrev = htmlTestRunner.currentTest.htmlTestCase.commandRows.slice(0, htmlTestRunner.currentTest.htmlTestCase.next CommandRowIndex); var seleniumCmdRowsNext = htmlTestRunner.currentTest.htmlTestCase.commandRows.slice(htmlTestRunner.currentTest.htmlTestCase.nextCo mmandRowIndex); var newCommandRows = seleniumCmdRowsPrev.concat(newRows); htmlTestRunner.currentTest.htmlTestCase.commandRows = newCommandRows.concat(seleniumCmdRowsNext); } catch (e) { LOG.error(IncludeCommand.LOG_PREFIX + "Error adding included commandRows. exception=" + e); throw new Error("Error adding included commandRows. exception=" + e); } };

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381

};

} //LOG.debug(IncludeCommand.LOG_PREFIX + "end with some table magic"); return newRows;

} // TODO try to use original HtmlTestCase class instead copying parts of it if (newRow.cells.length >= 3) { var seleniumRow = new HtmlTestCaseRow(newRow); seleniumRow.addBreakpointSupport(); newRows.push(seleniumRow); }

//Lance: remove </tbody> newText = newText.replace(/<\/*tbody*>|<br>/ig, "" ); // split on < td> var testCols = newText.split(/<\/\s*td[^>]*>/i); // first element is empty -> j=1 for (var j = 0 ; j < testCols.length; j++) { var newCol = testDocument.createElement("td" ); var colText = testCols[j]; newCol = this.targetRow.appendChild(newCol); newCol.innerHTML = colText; }

IncludeCommand.getCurrentTestDocument = function () { /** * Get the current test-case document from selenium * * @return testDocument the document object of the testcase-frame */ var testDocument; try { // rz (WEB.DE): changed to work with selenium 0.8.0 //testDocument = getIframeDocument(getTestFrame()) testDocument = testFrame.getDocument(); } catch (e) { throw new Error("testDocument not avalaible. Selenium API changed?" ); } return testDocument; }; IncludeCommand.prepareTestCaseAsText = function (responseAsText, paramsArray) { /** * Prepare the HTML to be included in as text into the current testcase-HTML * Strip all but the testrows (tr) * Stripped will be: * - whitespace (also new lines and tabs, so be careful wirt parameters relying on this), * - comments (xml comments)
Page 8

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

382 * Replace variable according to include-parameters 383 * note: the include-variables are replaced literally. selenium does it at execution time 384 * also note: all selenium-variables are available to the included commands, so mostly no include-parameters are necessary 385 * 386 * @param responseAsText table to be included as text (string) 387 * @return testRows array of tr elements (as string!) containing the commands to be included 388 * 389 * TODO: 390 * - selenium already can handle testcase-html. use selenium methods or functions instead 391 * - find better name for requester 392 */ 393 // LOG.debug(IncludeCommand.LOG_PREFIX + 394 // "removing new lines, carret return and tabs from response in order to work with regexp"); 395 // removing new lines, carret return and tabs from response in order to work with regexp 396 var pageText = responseAsText.replace(/\r|\n|\t/g, "" ); 397 // remove comments 398 // begin comment, not a dash or if it's a dash it may not be followed by -> repeated, end comment 399 pageText = pageText.replace(/<!--(?:[^-]|-(?!->))*-->/g, "" ); 400 // find the content of the test table = <[spaces]table[char but not >]>....< /[spaces]table[chars but not >]> 401 var testText = pageText.match(/<\s*table[^>]*>(.*)<\/\s*table[^>]*>/i)[1]; 402 403 // LOG.debug(IncludeCommand.LOG_PREFIX + "replace vars with their values in testText"); 404 // replace vars with their values in testText 405 for ( var k = 0 ; k < paramsArray.length ; k++ ) { 406 var pair = paramsArray[k]; 407 testText = testText.replace(pair[0],pair[1]); 408 } 409 410 // removes all < /tr> 411 // in order to split on < tr> 412 testText = testText.replace(/<\/\s*tr[^>]*>/ig, "" ); 413 // split on <tr> 414 var testRows = testText.split(/<\s*tr[^>]*>/i); 415 // LOG.debug(IncludeCommand.LOG_PREFIX + "about to return testRows"); 416 return testRows; 417 }; 418 419 IncludeCommand.getIncludeDocumentBySynchronRequest = function (includeUrl) { 420 /** 421 * Prepare and do the XMLHttp Request synchronous as selenium should not continue execution meanwhile 422 * 423 * note: the XMLHttp requester is returned (instead of e.g. its text) to let the caller decide to use xml or text 424 * 425 * selenium-dependency: uses extended String from htmlutils 426 * 427 * TODO: 428 * - renamen includeUrl to includeUri as it is a more precise name 429 * - use a URL object for parameter and url handling instead of custom regexes
Page 9

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

430 * //there is discussion about getting rid of prototype.js in developer forum. 431 * //the ajax impl in xmlutils.js is not active by default in 0.8.0 due tue no script-tag in TestRunner.html 432 * //TODO use Ajax from prototype like this: 433 * var sjaxRequest = new Ajax.Request(url, {asynchronous:false}); 434 * 435 * @param includeUrl URI to the include-document (document has to be from the same domain) 436 * @return XMLHttp requester after receiving the response 437 */ 438 var url = IncludeCommand.prepareUrl(includeUrl); 439 // the xml http requester to fetch the page to include 440 var requester = IncludeCommand.newXMLHttpRequest(); 441 if (!requester) { 442 throw new Error("XMLHttp requester object not initialized" ); 443 } 444 requester.open("GET" , url, false); // synchron mode ! (we don't want selenium to go ahead) 445 requester.send(null); 446 447 // handle HTTP-response status better. Are 200 and 0 the only sucessful states? 448 if ( requester.status != 200 && requester.status !== 0 ) { 449 throw new Error("Error while fetching " + url + " server response has status = " + requester.status + ", " + requester.statusText ); 450 } 451 return requester; 452 }; 453 454 IncludeCommand.prepareUrl = function (includeUrl) { 455 // use composition instead of inheritance to keep dependency minimal 456 var urlConfig = Class.create(); 457 Object.extend(urlConfig.prototype, URLConfiguration.prototype); 458 Object.extend(urlConfig.prototype, { 459 initialize: function () { 460 this.queryString = document.location.search.substring(1, document.location.search.length); 461 }, 462 463 getQueryParameter: function (searchKey) { 464 return this._getQueryParameter(searchKey); 465 } 466 }); 467 var uc = new urlConfig(); 468 var baseUrl = "" ; 469 // TODO get this function unittestable. extract document.location to be mockable 470 //LOG.debug(IncludeCommand.LOG_PREFIX + "document.location='" + document.location + "'"); 471 if (!includeUrl.match(/^\//) && !includeUrl.match(/^http:/)) { 472 var subdir = uc.getQueryParameter("test" ); 473 474 if (subdir && subdir.indexOf("/" ) > -1) { 475 var idx = subdir.lastIndexOf("/" ); 476 subdir = subdir.substring(0, idx + 1); 477 }
Page 10

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

478 baseUrl = document.URL.match(new RegExp("^([^?\n]+/).+$" ))[1]; // base uri = char - / - chars other than / 479 LOG.debug(IncludeCommand.LOG_PREFIX + 480 "include URL seems to be relative determined baseUrl='" + baseUrl + "'" ); 481 baseUrl = baseUrl + subdir; 482 } 483 if (document.URL.match(/^chrome:/)) { 484 // special case of selenium IDE 485 // takes the baseUrl parameter as base url 486 baseUrl = uc.getQueryParameter("baseURL" ); 487 // adds a special seleniumRoot variable if exists 488 // TODO look for selenium-methods to get this array 489 // this array beeing global may change in any next selenium release 490 if (storedVars["seleniumRoot" ]) { 491 baseUrl += storedVars["seleniumRoot" ]; 492 } 493 } 494 //LOG.debug(IncludeCommand.LOG_PREFIX + "using baseUrl to get include document='" + baseUrl + "'"); 495 LOG.debug(IncludeCommand.LOG_PREFIX + "using url to get include document='" + baseUrl + includeUrl + "'" ); 496 return baseUrl + includeUrl; 497 }; 498 499 IncludeCommand.newXMLHttpRequest = function () { 500 // TODO should be replaced by impl. in prototype.js or xmlextras.js 501 // but: there is discussion of getting rid of prototype.js 502 // and: currently xmlextras.js is not activated in 0.8.0 release 503 var requester = 0; 504 var exception = ''; 505 // see http://developer.apple.com/internet/webcontent/xmlhttpreq.html 506 // native XMLHttpRequest object 507 try { 508 if(window.XMLHttpRequest) { 509 requester= new XMLHttpRequest(); 510 } 511 // for IE/ActiveX 512 else if(window.ActiveXObject) { 513 try { 514 requester = new ActiveXObject("Msxml2.XMLHTTP" ); 515 } 516 catch (e) { 517 requester = new ActiveXObject("Microsoft.XMLHTTP" ); 518 } 519 } 520 } 521 catch (e) { 522 throw new Error("Your browser has to support XMLHttpRequest in order to use include \n" + e); 523 } 524 return requester; 525 };
Page 11

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

526 527 IncludeCommand.splitParamStrIntoVariables = function (paramString) { 528 /** 529 * Split include Parameters-String into Variable-Name and -Value into an 2-dim array 530 * 531 * selenium-dependency: uses extended String from htmlutils 532 * 533 * TODO: write jsunit tests - this could be easy (if there were not the new RegExp) 534 * 535 * @param includeParameters string the parameters from include call 536 * @return new 2-dim Array containing regExpName (to find a matching variablename) and value to be substituted for 537 */ 538 var newParamsArray = new Array(); 539 // paramString shall contains a list of var_name=value 540 var paramListPattern = /([^=,]+=[^=,]*,)*([^=,]+=[^=,]*)/; 541 if (! paramString || paramString === "" ) { 542 return newParamsArray; 543 } else if (paramString.match( paramListPattern )) { 544 // parse parameters to fill newParamsArray 545 var pairs = paramString.split("," ); 546 for ( var i = 0 ; i < pairs.length ; i++ ) { 547 var pair = pairs[i]; 548 var nameValue = pair.split("=" ); 549 //rz: use String.trim from htmlutils.js of selenium to get rid of whitespace in variable-name(s) 550 var trimmedNameValue = new String(nameValue[0]).trim(); 551 // the pattern to substitute is ${var_name} 552 var regExpName = new RegExp("\\$\\{" + trimmedNameValue + "\\}" , "g" ); 553 554 if (nameValue.length < 3) { 555 newParamsArray.push(new Array(regExpName,nameValue[1])); 556 } else { 557 var varValue = new String(nameValue[1]); 558 for (var j = 2; j < nameValue.length; j++) { 559 varValue=varValue.concat("=" +nameValue[j]); 560 } 561 newParamsArray.push(new Array(regExpName,varValue)); 562 } 563 } 564 } else { 565 throw new Error("Bad format for parameters list : '" + paramString + "'" ); 566 } 567 return newParamsArray; 568 }; 569 570 IncludeCommand.prototype.doInclude = function (locator, paramString) { 571 // TODO check if reordering of these calls can help to "fail fast/early" 572 573 // ask selenium for the current row (<tr> Element of the include command)
Page 12

File - /Users/jNf/Documents/Conferences/current/talks/advanced selenium/user-extensions.js

574 // TODO maybe there is an api-way to get this element instead of digging in members 575 var currentSelHtmlTestcase = htmlTestRunner.currentTest.htmlTestCase; 576 var includeCmdRow = currentSelHtmlTestcase.commandRows[currentSelHtmlTestcase.nextCommandRowIndex - 1].trElement; 577 578 if (!includeCmdRow) { 579 throw new Error("includeCommand: failed to find include-row in source testtable" ); 580 } 581 582 var paramsArray = IncludeCommand.splitParamStrIntoVariables(paramString); 583 584 // rz: TODO use selenium timeout 585 var inclDoc = IncludeCommand.getIncludeDocumentBySynchronRequest(locator); 586 587 var includedTestCaseHtml = IncludeCommand.prepareTestCaseAsText(inclDoc.responseText, paramsArray); 588 589 var testDocument = IncludeCommand.getCurrentTestDocument(); 590 591 // only member method because targetRow member is set 592 var newRows = this.injectIncludeTestrows(includeCmdRow, testDocument, includedTestCaseHtml); 593 594 IncludeCommand.extendSeleniumExecutionStack(newRows); 595 596 // only member method because targetRow member is accessed 597 this.postProcessIncludeCommandRow(includeCmdRow); 598 }; 599 600 601 Selenium.prototype.doInclude = function (locator, paramString) { 602 LOG.warn(IncludeCommand.LOG_PREFIX + "refactoring work in progress (rz: 2006-11-23)" ); 603 var includeCommand = new IncludeCommand(); 604 includeCommand.doInclude(locator, paramString); 605 }; 606 607 608 609 610 611 612

Page 13

Você também pode gostar