Using Selenium to test JSF (myFaces impl) application

I have been looking for a good way to do integration testing of webapplications done in JSF, and decided to give Selenium a try.
First I had a look at the Selenium IDE, it is basically a Firefox “record” tool that remembers the actions you do in the webapp, and then saves these actions as a test-template for you to use later on. But I didn’t want to limit myself to any browser, and I would also like to keep an opening for some continous build processes and frameworks. I therefore decided to go for Selenium Remote Control (Selenium RC), which also allows me to write the tests in java.

I know that Selenium recommends using TestNG instead of JUnit (JUnit should be considered a unit testing tool, not for integration testing) , but since I had some (moderate) experience with JUnit from before I decided to go for this. So I downloaded Selenium RC, and created a new java-project in Eclipse with selenium-java-client-driver.jar, selenium-server.jar and junit.jar in my lib-folder. Both selenium-java-client-driver.jar and junit.jar should be added to the build path, but there’s no need to add selenium-server.jar.

Before you run your tests the selenium server needs to be running, and you can manually start this from command-line by running the command “java -jar selenium-server.jar”. But then, you might want to add some options or specify some properties, and perhaps you don’t want to start messing around with the cmd-window at all. So I created an ant-task for starting and stopping the server. This task I can then set as dependency in another task that runs my tests, but this is of course up to you.

The start task:

	<target name="selenium-start">
		<java jar="${lib.test.dir}/${selenium-jar}" fork="true" spawn="true">
			<arg  line="-port ${selenium.port}" />
			<arg  line="-debug" />
			<arg  line="-log ${build.dir}/selenium.log" /> 		
		</java>
	</target>

The stop task:

	<target name="selenium-stop" description="Stop the Selenium RC server on port ${selenium.port}">
		<get taskname="selenium-shutdown" dest="${build.dir}/selenium.log" src="http://localhost:${selenium.port}/selenium-server/driver/?cmd=shutDown" ignoreerrors="true" />
	</target>

So, what about the testing?

If you’re familiar with JUnit, you know that you need to define the setUp() and tearDown() method. Selenium have created a class SeleneseTestCase that extends TestCase of JUnit, and this class is giving you some selenium-utilities which can be nice to have. I decided to create my tests extending the SeleneseTestCase, but you don’t have to – you can extend TestCase directly. Also, to avoid having to specify the setup() and tearDown() method in all my test-cases I created an abstract class called DefaultTest which I would extend for my test-cases. The DefaultTest has this definition:

public abstract class DefaultTest extends SeleneseTestCase
{
	protected Selenium browser;
	public static final String DEFAULT_TIMEOUT = "20000";
	public static final String BROWSER_FIREFOX = "*firefox";
	public static final String BROWSER_CHROME = "*chrome";
	public static final String BROWSER_IE = "*iexplore";
	
	public void setUp() throws Exception
	{
		browser = new DefaultSelenium("localhost", 6666, BROWSER_CHROME, "http://localhost/Test/");
		browser.start();
	}

	public void tearDown() throws Exception
	{
		browser.stop();
	}
}

To create the “virtual” browser object in Selenium you need to specify the server-host, which port to use, which browser to simulate and so on. I’m doing my tests against a local jboss-server running on port 8080. Note that jboss uses the default port of Selenium (4444), so I specified 6666 for my selenium server to run.

To be able to simulate a user typing something or pressing links, it’s obvious that we in the java-code need some way of locating the different web-elements. In Selenium this is referred to as “locators,” in java you specify them as String objects. You can locate an element in different ways, I mention some of them here:

  • By id: String locator = “id=theIdOfMyComponent”;
  • By name: String locator = “name=theNameOfMyComponent”;
  • By xpath: String locator = “xpath=//table[@id=’myTable’]/tbody/tr/td[2]/span/a”;
  • By link content: String locator = “link=ClickMe”;
  • By table-cell-refernce (TableId.Row.Cell): String locator = “myTable.1.0”;
  • By DOM: String locator = “dom=document.forms[‘myForm’].myElement”;

To start with something simple, we can try a TestCase for logging on to an application. As a convention to follow all the methods in your test-cases should start with “test”, like testLogin():

public class LoginTestCase extends DefaultTest
{
	public final void testLogin()
	{
		String userNameLocator = "name=username";
		String passwordLocator = "name=password";
		String componentLocator = "id=idOfTheComponentIExpectToBeThereAfterSuccessfullyLoggingIn";
		
		browser.open("http://localhost:8080/MyWebbAppContextRoot/");
		browser.type(userNameLocator, "myUsername");
		browser.type(passwordLocator, "myPassword");
		browser.click("id=submitButton");
		browser.waitForPageToLoad(DEFAULT_TIMEOUT);
		assertEquals("The title I expect", browser.getTitle());
		assertTrue(browser.isElementPresent(componentLocator));
		
	}
	
}

You run the test by right-clicking the class (run all tests in the class) or individually on each method -> select Run As -> select JUnitTest. Of course this can also be done from an ant task.

The browser.open() specifies the initial page that should be loaded in the browser. Then you can see that we fill in a value for the username and password, and click a button. Whenever there is a click that requires a page reload, you have to follow that command by a “browser.waitForPageToLoad()”. I set my standard timeout to 20 sec (20000ms), but if the response is sent earlier the browser acts upon it immediately. Then we do an assertion on the title of the page we were sent to, as well as an assertion to check that an expected component on this page actually exist. So, this is a testCase that tests for a successful logon. Another test that could be done is for example to pass invalid username/password, to see that unsuccessful logons are handled correctly.

Testing your webapp like this is integration testing, not unit testing. It is therefore likely that to be able to do your test you need to other actions first, like you follow a chain of commands; you might have to log on, maybe click a link to go on the proper page, or you need to do some other actions before you can do your assertions. Thus, I created some static methods in a utility-class that handles the most common operations, so that I can reuse those from my TestCases. Consider a class like this:

public class InteractionUtil extends DefaultTest
{
	public static void login(Selenium browser)
	{
		String userNameLocator = "name=username";
		String passwordLocator = "name=password";
		
		browser.open("http://localhost:8080/MyWebbAppContextRoot/");
		browser.type(userNameLocator, "myUsername");
		browser.type(passwordLocator, "myPassword");
		browser.click("id=submitButton");
		browser.waitForPageToLoad(DEFAULT_TIMEOUT);
	}
}

Then, my testLogin() method would simply be like this:


	public final void testLogin()
	{
		String componentLocator = "id=idOfTheComponentIExpectToBeThereAfterSuccessfullyLoggingIn";
		InteractionUtil.login(browser);
		assertEquals("The title I expect", browser.getTitle());
		assertTrue(browser.isElementPresent(componentLocator));
	}

And it is very easy to also include this logon-action in other testCases where it is needed. All actions you need to do often can go into such a utility-class.

As a final example I will show how to use the JsCookMenu of tomahawk library from Selenium. I struggled quite a bit with this one before I was able to make the action trigger. Since triggering an element in a dropdown-menu like this can be considered a “generic” action I include it in my utility-class:


	public static void clickJsCookMenu(Selenium browser, String theme, String jsCookId, String itemValue)
	{
		String fieldRowLocator = "xpath=//div[@id='"+jsCookId+"']/div/table/tbody/tr[@class='"+theme+"MenuItem' and td = '"+itemValue+"']";
		String jsCookMenuTdLocator = "xpath=//div[@id='"+jsCookId+"']/table/tbody/tr/td[@class='"+theme+"MainItem']";
		String fieldRowHoverLocator = "xpath=//div[@id='"+jsCookId+"']/div/table/tbody/tr[@class='"+theme+"MenuItemHover']";
		String fieldRowActiveLocator = "xpath=//div[@id='"+jsCookId+"']/div/table/tbody/tr[@class='"+theme+"MenuItemActive']";
		
		browser.mouseOver(jsCookMenuTdLocator);
		browser.waitForCondition("selenium.isVisible(\""+ fieldRowLocator+"\");", DEFAULT_TIMEOUT);
		browser.mouseOver(fieldRowLocator);
		browser.mouseDown(fieldRowHoverLocator);
		browser.mouseUp(fieldRowActiveLocator);
	
		browser.waitForPageToLoad(DEFAULT_TIMEOUT);
	}

To explain a bit, I pass as parameters the browser object, the jsCook theme being used (office, ie and so on), the id I’ve given the jscookMenu component in my jsp and the value/text of the item I want to click. I first do a mouseOver to display the menu, and notice the “waitForCondition” command that follows. This means that the browser should wait for the menu to be visible. Then i figured out I needed to do a mouse-over on the row holding the item. This makes the css class change, so I do mouseDown on the “hovered” row. But the mouseDown-event in the jsCook javascript doesn’t trigger the link, actually a mouseUp does, so finally I do a mouseUp-event on the active row. And of course I then wait for the page to reload.

Then from my test-method I can simply do:


	public void testJsCookMenu()
	{
		InteractionUtil.login(browser);
		UserInteractionUtil.clickJsCookMenu(browser, "ThemeIE", "myJsCookId", "theElementIWantToClick");
		//--- assertions ---
	}

I hope this post will give you a brief understanding on how to use Selenium for testing your webapps!

Advertisements