Selenium and Maven are some of the quickest-acquired friends I have in the world of software development. They do the hard work of testing web applications while I sip my coffee navigating through the code to add fun new features. I'm sure you'll want them to be your friends too, so here's what I found working best in my projects to have them set-up and running together.
Organizing the Environment
Speaking in the language of Maven, Selenium tests fall into the category of
integration tests. As such they will be running in the
integration-test phase during Maven's
build lifecycle, which comes right after
package and just before
verify. These tests usually are time consuming and if you use the
install or
deploy goal to have your application deployed on a server for manual testing, you certainly don't want these tests to run each time.
The path to follow here is putting your Selenium tests in a separate Maven module, under one parent POM with the rest of the application modules, and creating a new profile to run them only when explicitly desired. Creating a new module goes as usual, by calling from the parent POM's directory:
mvn artifact:create -DgroupId=[group] -DartifactId=selenium-test-suite -Dpackaging=pom
The
artifactId may of course be changed to whatever you feel like. Notice that the packaging type is
pom. The reason for that is, that this very module will only contain test classes and no other sources, hence choosing
pom packaging will spare us a couple of unnecessary lifecycle phases. You should delete the
src/main directory in the newly created module - it won't be needed.
Having
pom as the packaging type disables compiling any kind of sources, including test classes, which we do need. To fix this you'll need to configure the
maven-compiler-plugin in the test module's POM to actually execute the
testCompile goal:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
We also won't have any unit tests in this module, so we need to tell Maven to skip the usual
test phase and have it run when the
integration-test goal is reached:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<skip>false</skip>
</configuration>
</execution>
</executions>
</plugin>
Last but not least, we need that separate profile for running Selenium tests, which requires editing the parent POM, deleting the
selenium-test-suite entry from the list of regular modules and putting the following in the POM:
<profiles>
<profile>
<id>integration</id>
<modules>
<module>selenium-test-suite</module>
</modules>
</profile>
</profiles>
Now whenever Selenium tests need to be run, the parent POM should be called with:
mvn verify -P integration
Where
verify may be any other goal including or coming after
integration-test.
The Eclipse Case
If you're like me, developing in Eclipse while not having the IDE's artifacts in the code repository and creating them with
mvn eclipse:eclipse after checkout, you'll notice that our newly created test suite will refuse to comply. The reason is that the
pom packaging type is usually used for multi-module Maven projects and hence only its modules would be imported into Eclipse's workspace. It has been recognized though, that very often
pom packaging is used for other purposes and a
feature request has already been filed, slated for inclusion in Maven 2.1, to have Eclipse's artifacts generated when no modules for a
pom-packaged project exist.
For now you'll have to use a simple workaround, by changing the packaging type of the test suite temporarily to say
jar, then running
mvn eclipse:eclipse and editing the packaging back to
pom. It's usually only required once, after checking out the project from the repository and later when the test suite's or any parent's POM changes, hence shouldn't be that much of a hassle.
Running the Selenium Server
Selenium needs its own server to run any tests and we'll obviously need it up and running when the
integration-test phase kicks in, as well as have it killed after testing finishes. Luckily Maven allows us to use two convenient goals -
pre-integration-test and
post-integration-test, one intuitively running before the test phase and one after.
Starting and stopping the server is conveniently done with the
selenium-maven-plugin. You'll need to put the following into your test-suite's POM:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>selenium-maven-plugin</artifactId>
<executions>
<execution>
<id>start-server</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start-server</goal>
</goals>
<configuration>
<background>true</background>
</configuration>
</execution>
<execution>
<id>stop-server</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop-server</goal>
</goals>
</execution>
</executions>
</plugin>
The tests will also need a session to be running, defining which browser should be used and what address is to be called to get to the application. It's doable from the POM but troublesome and not very flexible. We'll control the sessions in the test classes then later on when writing test code.
Creating Test Classes
To have our tests compiling and running we'll need to add a dependency to the test suite module on the Selenium Java Client drivers:
<dependency>
<groupId>org.openqa.selenium.client-drivers</groupId>
<artifactId>selenium-java-client-driver</artifactId>
<version>0.9.2</version>
<scope>test</scope>
</dependency>
Since this isn't a dependency available in the default repository, you'll also need to add a new one to whichever POM suits you best - parent one perhaps:
<repositories>
<repository>
<id>openqa</id>
<name>OpenQA Repository</name>
<url>http://maven.openqa.org</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
Our tests will be regular unit tests, with regular
assert* statements, with operations on Selenium provided objects controlling test behaviour. I assume you're using unit tests project-wide already, so a dependency on jUnit is present somewhere in the POM structure.
As stated previously, Selenium does require starting up a session before running tests, which includes opening a pre-defined browser. We want to have the browser run before each test and killed after, to have a clean session each time a test runs, which brings us to the
@Before and
@After constructs of jUnit. And since we don't want to put them in each test class separately, we'll create an abstract class containing those, which will become the base for all other test classes we'll write:
public abstract class AbstractSeleniumTestCase {
protected Selenium selenium;
@Before
public void setUp() {
selenium = new DefaultSelenium("localhost", SeleniumServer
.getDefaultPort(), "*firefox", "http://localhost:8080/");
selenium.start();
}
@After
public void tearDown() {
selenium.stop();
}
}
Now every time you create a new test class, make it extend the above. If the new test class requires some additional @Before and @After procedures, make sure they call the super-class's methods:
public class MyTest
extends AbstractSeleniumTestCase {
@Before
public void setUp() {
super.setUp();
[additional code here]
}
}
Designing a test is best now with the regular Selenium method - by using
Selenium IDE. It comes conveniently prepared as a Firefox plugin, opening a sidebar and registering all actions done on a website. Do the following:
- Go to the page under testing
- Start recording your actions
- Do your clicks, writes, selections etc.
- Stop recording
- Change the Selenium IDE display by choosing Options > Format > Java (you can also choose to export the whole test as a Java file, but since it doesn't create a pure jUnit test, I find my way to be more convenient)
- Copy the test procedure and paste it into your @Test-annotated method
Add
assert* statements wherever you find necessary, checking conditions is usually done with different
selenium.is* methods (my personal favourite:
isSomethingSelected). Make sure you know and understand how to
locate your element and value of choice.
Pages Requiring Authentication
It's fairly common that the system you are testing requires most tests to run in an authenticated environment. In that case you'll need a login procedure in place before any test starts (especially since we're restarting the browser for each test). In that case just create another abstract class, extending the base one we created earlier, performing authentication in a
@Before-annotated method:
public abstract class AbstractAuthenticatedAccessTest
extends AbstractSeleniumTestCase {
@Before
public void setUp() {
super.setUp();
[authentication procedures here]
}
}
The authentication procedure would be yet another recorded with Selenium IDE. You'll obviously need to have a user added to the system before anyone can login, which may also be done in a @Before method in any of the abstract classes. If different roles are needed for testing (ie. user, moderator, admin etc.) create separate abstract classes for each and extend them as required.
You're done. Running
mvn verify -P integration from the parent POM should get your tests running and checking what you ordered them to.
I left out the bit about automatically deploying the whole application for testing and starting/stopping the server, which I'd like to write about at a different time, possibly in connection with setting up Continuum. If you need that in the mean time, you should get your hands on
Cargo. As for the above, as usual I'm expecting comments on successes, failures and things I missed out on.