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=pomThe 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>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:
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<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:
<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>
<profiles>Now whenever Selenium tests need to be run, the parent POM should be called with:
<profile>
<id>integration</id>
<modules>
<module>selenium-test-suite</module>
</modules>
</profile>
</profiles>
mvn verify -P integrationWhere verify may be any other goal including or coming after integration-test.
The Eclipse Case
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
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>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.
<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>
Creating Test Classes
<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:
<groupId>org.openqa.selenium.client-drivers</groupId>
<artifactId>selenium-java-client-driver</artifactId>
<version>0.9.2</version>
<scope>test</scope>
</dependency>
<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.
<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>
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 {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:
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();
}
}
public class MyTestDesigning 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:
extends AbstractSeleniumTestCase {
@Before
public void setUp() {
super.setUp();
[additional code here]
}
}
- 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 AbstractAuthenticatedAccessTestThe 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.
extends AbstractSeleniumTestCase {
@Before
public void setUp() {
super.setUp();
[authentication procedures here]
}
}
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.