The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


John Ferguson Smart

John is a freelance consultant specialising in Enterprise Java, Web Development, and Open Source technologies, currently based in Wellington, New Zealand. Well known in the Java community for his many published articles, John helps organisations to optimize their Java development processes and infrastructures and provides training and mentoring in open source technologies, SDLC tools, and agile development processes. John is principal consultant at Wakaleo Consulting, a company that provides consulting, training and mentoring services in Enterprise Java and Agile Development.

 

John Ferguson Smart's blog

Parameterized web tests with Maven and Selenium

Posted by johnsmart on November 12, 2009 at 4:07 PM PST

Selenium is a popular web testing framework, well known for the Selenium IDE, which lets you record and replay web tests in the form of HTML files. However, that is not my preferred way of using Selenium. In fact, I much prefer using tools like Selenium for Acceptance Test-Driven Development. The high-level Selenium API is great for writing executable acceptance tests. This approach also works well with easyb, but in this article, for simplicity's sake, we'll just be sticking to plain old JUnit-driven acceptance tests.

Suppose we want to implement a simple user login feature. Let's look as a simple acceptance test written using Selenium, based on some code that came out of a recent coding dojo, which verifies that a user can log on to the application by providing a username and password:

public class TestLoginStory extends SeleneseTestCase {

    public void setUp() throws Exception {
        setUp("http://localhost:8080/tweeter-web/", "*firefox");
    }
       
    public void testUserShouldLogInByEnteringUsername() throws Exception {
        selenium.open("/tweeter-web/home");
        assertThat(selenium.getTitle(), is("Tweeter - the art of meaningless utterances"));
        assertTrue(selenium.isTextPresent("Join the conversation"));
        assertFalse(selenium.isTextPresent("Logout"));
        
        selenium.type("username", "scott");
        selenium.type("password", "tiger");
        selenium.click("signin");
        
        selenium.waitForPageToLoad("5000");

        assertTrue(selenium.isTextPresent("Hi scott!"));
        assertTrue(selenium.isElementPresent("logout"));
    }
}

 

Now suppose you want to run this Selenium integration test in your Maven project. Let's go through the steps.

The first thing you need to do is to add a dependency on the Selenium Java client libraries:

<dependency>
    <groupId>org.seleniumhq.selenium.client-drivers</groupId>
    <artifactId>selenium-java-client-driver</artifactId>
    <version>1.0.1</version>
    <scope>test</scope>
</dependency>

 

Next, you need to do is to get your web application starting automatically just before your integration tests. That's pretty easy to do in Maven. A simple approach is to use the Maven Jetty plugin, as shown here:

<plugin>
  <groupId>org.mortbay.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>7.0.0.pre5</version>
  <configuration>
    <port>8080</port>
    <scanIntervalSeconds>5</scanIntervalSeconds>
    <stopPort>9966</stopPort>
    <stopKey>foo</stopKey>
  </configuration>
  <executions>
    <execution>
      <id>start-jetty</id>
      <phase>pre-integration-test</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <scanIntervalSeconds>0</scanIntervalSeconds>
        <daemon>true</daemon>
      </configuration>
    </execution>
  </executions>
</plugin>

 

So now you have your web application starting up before your unit tests. The next step is to ensure that your Selenium unit tests only run during the integration test phase. Well, nothing simpler with the maven-surefire-plugin:

<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
    <skip>true</skip>
  </configuration>
  <executions>
    <execution>
      <id>unit-tests</id>
      <phase>test</phase>
      <goals>
        <goal>test</goal>
      </goals>
      <configuration>
        <skip>false</skip>
        <excludes>
          <exclude>**/webtests/**</exclude>
        </excludes>
      </configuration>
    </execution>
    <execution>
      <id>integration-tests</id>
      <phase>integration-test</phase>
      <goals>
        <goal>test</goal>
      </goals>
      <configuration>
        <skip>false</skip>
        <includes>
          <include>**/webtests/**</include>
        </includes>
      </configuration>
    </execution>
  </executions>
</plugin>

OK, it's a bit verbose, but it's easy enough to understand if you know how the Maven lifecycle plugins work. What I'm doing here is excluding tests in the webtests package from the unit tests, and including them in the integration tests phase. Once you have this working, however, there is a third step. To run these Selenium tests, you need to start a Selenium RC server running on your local machine. The Selenium RC server opens a browser and runs your Selenium tests in the browser. This is easy to do in Maven, using the selenium-maven-plugin:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>selenium-maven-plugin</artifactId>
  <version>1.0</version>
  <executions>
    <execution>
      <phase>pre-integration-test</phase>
      <goals>
        <goal>start-server</goal>
      </goals>
      <configuration>
        <background>true</background>
      </configuration>
    </execution>
  </executions>
</plugin>

 

You can now run your Selenium tests in Maven. Now, when you run "mvn verify", Selenium will start up a browser and run your Selenium integration tests. "All too easy", you might say. "But what if you want to run these tests on a build server? What if there are several web tests running for different projects in parallel? Won't Jetty fail to start if there is another instance of Jetty already running on the same port"?

Well, you're dead right - we need to be able to get Jetty to start up on a different port. And we need to be able to provide the port number as a parameter.

<project>
 <build>
  ...
  <plugins>
    <plugin>
      <groupId>org.mortbay.jetty</groupId>
      <artifactId>jetty-maven-plugin</artifactId>
      <version>7.0.0.pre5</version>
      <configuration>
        <port>${jetty.port}</port>
        <scanIntervalSeconds>5</scanIntervalSeconds>
        <stopPort>9966</stopPort>
        <stopKey>foo</stopKey>
      </configuration>
      <executions>
        <execution>
          <id>start-jetty</id>
          <phase>pre-integration-test</phase>
          <goals>
            <goal>run</goal>
          </goals>
          <configuration>
            <scanIntervalSeconds>0</scanIntervalSeconds>
            <daemon>true</daemon>
          </configuration>
        </execution>
      </executions>
    </plugin>
    ...
  </plugins>
 </build>

 <properties>
   <jetty.port>8080</jetty.port>
 </properties>
</project>

 

That way, you can pass in the Jetty port from the command line.

mvn verify -Djetty.port=9090

 

Oh, but wait - we're not quite done yet. The Jetty port is still hard-coded in the Selenium test class:

    public void setUp() throws Exception {
        setUp("http://localhost:8080/tweeter-web/", "*firefox");
    }

 

Well, that shouldn't be too hard to fix. How about we just read the property from the system properties?

    public void setUp() throws Exception {
        String jettyPort = System.getProperty("jetty.port", "8080");
        setUp("http://localhost:" + jettyPort + "/tweeter-web/", *firefox");
    }

 

Don't bother trying, it won't work. You see, the unit tests are started in a forked Java process, so they don't have access to the original command line properties passed into Maven. The work-around is to pass the properties explicitly to the tests in the surefire plugin, as shown here:

<plugin>
   <artifactId>maven-surefire-plugin</artifactId>
   <configuration>
     <skip>true</skip>
     <systemProperties>
       <property>
         <name>jetty.port</name>
         <value>${jetty.port}</value>
       </property>
     </systemProperties>
   </configuration>
   ...
</plugin>

 

Now it will work as expected.

Also know that Selenium is not your only option for web testing. Doing this with JWebUnit involves essentially the same steps, without the need to set up a Selenium RC server.

Related Topics >> Blogs      
Comments
Comments are listed in date ascending order (oldest first)
Syndicate content