Thought Forge
  • Home
  • Projects
  • The Team

Automated Testing of RichFaces Components with Selenium

Jun 21, 2011 ~ 1 Comment ~ Written by John Turner

It is pretty much accepted that Continuous Integration is an essential practice of any Agile software development. An essential part of Continuous Integration is automated testing.

When considering automated testing in a Continuous Integration environment one must consider the level of testing, the frequency and the degree of isolation.

Generally speaking, and from the perspective of a development team, I categorise testing as Unit testing, Integration testing and Acceptance testing. Acceptance testing takes the form of simulating the user interaction with the system and validating its behaviour.

In order to simulate the user interaction on a web interface, I often use Selenium. For the most part, I have found Selenium to provide the functionality I require from an automated testing framework. I use Maven to drive the acceptance tests and have found this works well.

The following maven pom will start a local instance of Tomcat, deploy the test subject (war), start an instance of the Selenium server, execute the Selenium tests, stop the Selenium server and stop the local instance of Tomcat.

Setting up The Web Application

This is a very simple web application that has a single page. This page renders a check box and a text description that indicates if the check box is checked or not.

First thing we need to do is to set up the dependencies in the pom.xml. I have skipped over this for brevity but the full project can beĀ downloaded so you are not missing out.

Next, create a web.xml file that includes the appropriate configuration for JSF and RichFaces.

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="

http://java.sun.com/xml/ns/javaee


http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

      id="WebApp_ID" version="2.5">

  <listener>
    <listener-class>com.sun.faces.config.ConfigureListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <context-param>
    <param-name>javax.faces.CONFIG_FILES</param-name>
    <param-value>/WEB-INF/faces-application.xml</param-value>
  </context-param>

  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
  </servlet-mapping>

  <context-param>
    <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
    <param-value>.xhtml</param-value>
  </context-param>

  <context-param>
    <param-name>com.sun.faces.expressionFactory</param-name>
    <param-value>org.jboss.el.ExpressionFactoryImpl</param-value>
  </context-param>

  <context-param>
    <param-name>org.richfaces.SKIN</param-name>
    <param-value>blueSky</param-value>
  </context-param>

  <context-param>
    <param-name>org.richfaces.CONTROL_SKINNING</param-name>
    <param-value>enable</param-value>
  </context-param>

  <context-param>
    <param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
    <param-value>com.sun.facelets.FaceletViewHandler</param-value>
  </context-param>

  <filter>
    <display-name>RichFaces Filter</display-name>
    <filter-name>richfaces</filter-name>
    <filter-class>org.ajax4jsf.Filter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>richfaces</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
  </filter-mapping>

  <filter-mapping>
    <filter-name>richfaces</filter-name>
    <servlet-name>Spring Dispatcher Servlet</servlet-name>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
  </filter-mapping>

  <context-param>
    <param-name>org.ajax4jsf.RESOURCE_URI_PREFIX</param-name>
    <param-value>resource/</param-value>
  </context-param>

  <context-param>
    <param-name>facelets.DEVELOPMENT</param-name>
    <param-value>true</param-value>
  </context-param>
</web-app>

Nothing very exciting so far. Next, we need to define a JSF managed bean to store the state of the check box.

<?xml version="1.0" encoding="UTF-8"?>

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="

http://java.sun.com/xml/ns/javaee


http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"

    version="1.2">

  <managed-bean>
    <managed-bean-name>checkboxState</managed-bean-name>
    <managed-bean-class>CheckboxState</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean>
</faces-config>

Finally, our one and only web page.

<rich:page
    xmlns:a4j="http://richfaces.org/a4j"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">

  <f:view>
    <a4j:form id="form">
      <h:selectBooleanCheckbox id="checkbox" label="Display Text Box:" value="#{checkboxState.checked}">

        <a4j:support event="onclick" reRender="form:textBoxPanel"/>
      </h:selectBooleanCheckbox>

      <a4j:outputPanel id="textBoxPanel">
        <h:outputText value="Checkbox is checked" rendered="#{checkboxState.checked}"/>
        <h:outputText value="Checkbox is not checked" rendered="#{!checkboxState.checked}"/>
      </a4j:outputPanel>
    </a4j:form>
  </f:view>
</rich:page>

You can see from the web page that we are rendering a check box that triggers an Ajax request when clicked. This Ajax request will update the model, synchronising the state of the check box user interface component and the corresponding model. On completion of the Ajax request, we re-render the output panel which will render one of the two messages depending on the state of the check box.

This all works very well so lets try to automate the testing of this user interface.

Setting up The Maven Build

Maven is a build management tool that supports management of dependencies and the build life cycle. It provides a plug-in framework for extensibility and there is a rich selection of plug-ins available.

For the purposes of this article, the plug-ins that we are interested in are:

  • cargo-maven2-plugin
  • selenium-maven-plugin
  • maven-surefire-plugin

Managing the Tomcat Server

The cargo-maven-plugin is used to manage the interaction with the container to which the artefact (war) is to be deployed. We are using a local instance of Tomcat (6.x) that is referred to via the CATALINA_HOME environment variable (i.e. the CATALINA_HOME environment variable should point to the root of your Tomcat installation).

The plug-in configuration below performs the following steps at the appropriate stage in the build life cycle:

  • Starts Tomcat
  • Deploys the web application.
  • Verifies the deployment of the web application.
  • Stops Tomcat.
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="

http://maven.apache.org/POM/4.0.0


http://maven.apache.org/maven-v4_0_0.xsd">

  ...

  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.codehaus.cargo</groupId>
        <artifactId>cargo-maven2-plugin</artifactId>
        <version>1.0.5</version>

        <executions>
          <execution>
            <id>start-container</id>
            <phase>pre-integration-test</phase>
            <goals>
              <goal>start</goal>
              <goal>deploy</goal>
            </goals>
          </execution>
          <execution>
            <id>stop-container</id>
            <phase>post-integration-test</phase>
            <goals>
              <goal>stop</goal>
            </goals>
          </execution>
        </executions>

        <configuration>
          <wait>false</wait>

          <container>
            <containerId>tomcat6x</containerId>
            <home>${CATALINA_HOME}</home>

            <log>${project.build.directory}/cargo.log</log>
            <output>${project.build.directory}/tomcat6x.log</output>
          </container>

          <configuration>
            <type>standalone</type>
            <home>target/tomcat6x</home>

            <properties>
              <cargo.logging>high</cargo.logging>
              <cargo.hostname>${deploy.target.hostname}</cargo.hostname>
              <cargo.servlet.port>${deploy.target.port}</cargo.servlet.port>
            </properties>
          </configuration>

          <deployer>
            <deployables>
              <deployable>
                <properties>
                  <context>${deploy.target.applicationContextName}</context>
                </properties>

                <pingURL>http://${deploy.target.hostname}:${deploy.target.port}/${deploy.target.applicationContextName}/index.jsf</pingURL>
                <pingTimeout>120000</pingTimeout>
              </deployable>
            </deployables>
          </deployer>
        </configuration>
      </plugin>
    </plugins>
    ...
  </build>

  <properties>
    <deploy.target.applicationContextName>richfaces-selenium</deploy.target.applicationContextName>
    <deploy.target.hostname>localhost</deploy.target.hostname>
    <deploy.target.port>8080</deploy.target.port>
  </properties>

  ...
</project>

Managing the Selenium Server

The selenium-maven-plugin is used to manage the Selenium Remote Control (RC) Server. The Selenium RC Server is used to simulate the user interaction with a web browser.

The plug-in configuration below performs the following steps at the appropriate stage in the build life cycle:

  • Starts the Selenium RC Server
  • Stops the Selenium RC Server
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="

http://maven.apache.org/POM/4.0.0


http://maven.apache.org/maven-v4_0_0.xsd">

  ...

  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>selenium-maven-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution>
            <id>start</id>
            <phase>pre-integration-test</phase>
            <goals>
              <goal>start-server</goal>
            </goals>
            <configuration>
              <background>true</background>
              <logOutput>true</logOutput>
              <multiWindow>true</multiWindow>
            </configuration>
          </execution>
          <execution>
            <id>stop</id>
            <phase>post-integration-test</phase>
            <goals>
              <goal>stop-server</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
    ...
  </build>

  ...
</project>

Executing the Acceptance Tests

The maven-surefire-plugin is used to manage the execution of the acceptance tests.

The plug-in configuration below performs the following steps at the appropriate stage in the build life cycle:

  • Skips the execution of unit tests.
  • Executes the acceptance tests.
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="

http://maven.apache.org/POM/4.0.0


http://maven.apache.org/maven-v4_0_0.xsd">

  ...

  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.8.1</version>
        <configuration>
          <skip>true</skip>
        </configuration>

        <executions>
          <execution>
            <id>surefire-it</id>
            <phase>integration-test</phase>
            <goals>
              <goal>test</goal>
            </goals>
            <configuration>
              <skip>false</skip>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
    ...
  </build>

  ...
</project>

Creating the Acceptance Test

So now we want to create a test that will:

  • Request the web page.
  • Verify the component ‘uncheckedMessage’ is present.
  • Click the check box.
  • Verify the component ‘checkedMessage’ is present.

Here is the first attempt.

public class IndexPageTest {

  private static final String browserStartCommand = "*googlechrome";

  private static final String hostname = "localhost";

  private static final int port = 8080;

  private static final String webApplicationContext = &quot;richfaces-selenium&quot;;

  private Selenium selenium;

  @Before
  public void before() throws Exception {

    String browserUrl = new StringBuffer("http://")
        .append(hostname)
        .append(":")
        .append(port)
        .append("/")
        .append(webApplicationContext)
        .append("/")
        .toString();

    selenium = new DefaultSelenium(hostname, 4444, browserStartCommand, browserUrl);
    selenium.start();
  }

  @After
  public void after() {

    selenium.stop();
  }

  @Test
  public void checkboxTrue() throws Exception {
    selenium.open("index.jsf");
    selenium.waitForPageToLoad("5000");

    Assert.assertTrue(selenium.isElementPresent("form:uncheckedMessage"));
    Assert.assertFalse(selenium.isElementPresent("form:checkedMessage"));

    selenium.click("form:checkbox");

    Assert.assertFalse(selenium.isElementPresent("form:uncheckedMessage"));
    Assert.assertTrue(selenium.isElementPresent("form:checkedMessage"));
  }
}

When I run this test it fails on the assertion:

‘assertTrue(selenium.isElementPresent(“form:checkedMessage”))’.

The cause of the failure is that the assertion is evaluated before the Ajax request is completed and the DOM is updated.

Of course, in the simple scenario I present, I might be tempted to wait for the checked message to appear. I might also be tempted to wait for a static period of time, say 5 seconds.

If I just wait for the checked message to appear (with a timeout of 10 seconds), when the message does not appear, how will I know if it is the performance of the Ajax request or the functionality that is causing the problem? The 10 second timeout can also add a significant amount of time to the execution of a large test suite.

If I wait for 5 seconds this adds 5 seconds to the execution of every such test. It also does not guarantee that the Ajax request has returned.

The bottom line is that we need to be able to verify definitively when the Ajax request has completed before making the assertion.

In order to verify that the Ajax request has completed, I use a hidden field that maintains the status of the Ajax request. I then use the oncomplete and onsubmit properties of the a4j:support component to update the status of the hidden field.

<rich:page
    xmlns:a4j="http://richfaces.org/a4j"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">

  <f:view>
    <a4j:form id="form">
      <h:inputHidden id="ajaxRequestStatus" value="AJAX REQUEST PROCESSOR INACTIVE"/>

      <h:selectBooleanCheckbox id="checkbox" label="Display Text Box:" value="#{checkboxState.checked}">

        <a4j:support event="onclick" reRender="form:textBoxPanel"
          oncomplete="document.getElementById('form:ajaxRequestStatus').value = 'AJAX REQUEST PROCESSOR INACTIVE"
          onsubmit="document.getElementById('form:ajaxRequestStatus').value = 'AJAX REQUEST PROCESSOR ACTIVE'"/>

      </h:selectBooleanCheckbox>

      <a4j:outputPanel id="textBoxPanel">
        <h:outputText id="checkedMessage" value="Checkbox is checked" rendered="#{checkboxState.checked}"/>
        <h:outputText id="uncheckedMessage" value="Checkbox is not checked" rendered="#{!checkboxState.checked}"/>
      </a4j:outputPanel>
    </a4j:form>
  </f:view>
</rich:page>

The test changes a little to evaluate the value of the hidden field.

public class IndexPageTest {

  private static final String browserStartCommand = "*googlechrome";

  private static final String hostname = "localhost";

  private static final int port = 8080;

  private static final String webApplicationContext = "richfaces-selenium";

  private Selenium selenium;

  @Before
  public void before() throws Exception {

    String browserUrl = new StringBuffer("http://")
      .append(hostname)
      .append(":")
      .append(port)
      .append("/")
      .append(webApplicationContext)
      .append("/")
      .toString();

    selenium = new DefaultSelenium(hostname, 4444, browserStartCommand, browserUrl);
    selenium.start();
  }

  @After
  public void after() {

    selenium.stop();
  }

  @Test
  public void checkboxTrue() throws Exception {
    String documentExpression = "selenium.browserbot.getCurrentWindow().document";
    String checkboxAjaxStatusValueExpression =
      documentExpression + ".getElementById('form:ajaxRequestStatus').value";

    selenium.open("index.jsf");
    selenium.waitForPageToLoad("5000");

    Assert.assertTrue(selenium.isElementPresent("form:uncheckedMessage"));
    Assert.assertFalse(selenium.isElementPresent("form:checkedMessage"));

    selenium.click("form:checkbox");

    selenium.waitForCondition(checkboxAjaxStatusValueExpression +
      " == 'AJAX REQUEST PROCESSOR INACTIVE'", "5000");

    Assert.assertFalse(selenium.isElementPresent("form:uncheckedMessage"));
    Assert.assertTrue(selenium.isElementPresent("form:checkedMessage"));
  }
}

The main amendment to the test is the waitForCondition statement. This statement will continually evaluate the expression until it evaluates to true. On execution of the click statement, the value of the hidden field will become ‘AJAX REQUEST PROCESSOR ACTIVE’ and the waitForCondition statement will only return when the value of the hidden field becomes ‘AJAX REQUEST PROCESSOR INACTIVE’.

If the timeout occurs, an exception will be thrown. As a result we are able to clearly distinguish between the Ajax request not returning in a reasonable amount of time and a functionality flaw.

I use this mechanism on a large suite of acceptance tests and it has proven valuable in that it removes the need to have static wait periods and clearly distinguishes between Ajax request not completing and functionality failures.

Posted in Rich Faces
←
→
Logging In...
Cancel Reply
  • 1 Reply
  • 0 Comments
  • 0 Tweets
  • 0 Facebook
  • 1 Pingback
Last reply was July 1, 2011
  1. A Smattering of Selenium #52 « Official Selenium Blog
    View July 1, 2011

    [...] Automated Testing of RichFaces Components with Selenium is a nice Maven example including how to deal with complex AJAX calls [...]

Tag Cloud

Agile Banking Book Review Cloud Computing Continuous Integration Enterprise Integration Groovy & Grails Hibernate JavaServer Faces NoSQL Object Relational Mapping Ramblings Representational State Transfer Rich Faces Software Design & Architecture Spring Framework Spring Integration Spring Security Spring Web Flow Training & Certification

Recent Posts

  • Continuous Delivery: A Maturity Assessment Model
  • Martin Fowler and No DBA
  • Marissa Mayer ends Work From Home
  • Lies, Damned Lies, and Statistics
  • 8th Light – The Principles of Craftsmanship

Top Posts & Pages

  • Starting out with Spring and Hibernate JPA
  • Creating a Logging Aspect with Spring AOP and AspectJ
  • Pipes and Filters, Routing Slips and Message Filters
  • Feature Toggle and Branch by Abstraction
  • Marshalling XML with Spring WS and JAXB

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 14 other subscribers

  1. I welcome any feedback, questions or comments

Blogroll

  • Harvard Business Review
  • High Scalability
  • Mountain Goat Software

Pure Line theme by Theme4Press  •  Powered by WordPress Thought Forge