Continuous integration on Liferay: running your Selenium 2 tests on the Tomcat 6 bundle
In this article we are going to write a Selenium 2 test for the Liferay portlet we developed in Getting started with portals. Then we are going to add a profile to our Maven pom that, when activated, starts the Liferay server and runs the selenium 2 test after which the Liferay server is stopped again.
This is ideal for Liferay integration testing, especially when the process needs to be fully automated(e.g. no manual stop/start of server), such as by a continuous integration server like Hudson or Jenkins.
The Selenium 2 test
We implement a Selenium 2 test similar to the one we introduced in Converting Selenium 1 to Selenium 2 tests. We are using System.getProperty calls now. A System.getProperty call fetches a certain system property. These system properties will be set by Maven first, as we will see later.
import java.io.File; import org.junit.Test; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxBinary; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.support.PageFactory; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; public class PortalPageTest { private FirefoxDriver createFirefoxDriver() { File firefoxBin = new File(System.getProperty("selenium.pathToFirefoxBinary")); FirefoxBinary firefoxBinary = new FirefoxBinary(firefoxBin); FirefoxDriver driver = new FirefoxDriver(firefoxBinary, null); return driver; } @Test public void testPortalPage() { final FirefoxDriver firefoxDriver = createFirefoxDriver(); firefoxDriver.get(System.getProperty("selenium.baseURL")); final PortalPage portalPage = new PortalPage(); WebDriverWait wait = new WebDriverWait(firefoxDriver, 20 * 1000, 200); wait.until(new ExpectedCondition() { @Override public Boolean apply(WebDriver driver) { PageFactory.initElements(firefoxDriver, portalPage); return portalPage.testPortlet.getText().equals("This is our test portlet."); } }); } }
And the related Page object:
import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; public class PortalPage { @FindBy(id = "testPortlet") public WebElement testPortlet; }
The Maven profile
In summary, our Maven profile will:
- Clean the Liferay server directories during the clean phase.
- Start the Liferay server, deploy the application and wait 5 minutes for the application to finish deploying during the pre-integration-test phase.
- Execute the Selenium 2 tests. It will not fail if any of the tests fails but instead, it will write the results away.
- Stop the Liferay server during the post-integration-test phase.
Profile breakup
We build up the profile part by part.
1. Activation
<activation> <property> <name>liferay.home.dir</name> </property> </activation>
The profile is getting activated when we supply a liferay.home.dir. However, this is not the only property we are going to need for a successful run. We need to give all the necessary info about the browser we are going to run the Selenium tests with and the server we are going to run the tests on. So, next to the liferay.home.dir, we will also need to supply the selenium.firefox.binary and the liferay.port properties.
Hence, to do an install with the profile enabled, we will need to run Maven with a goal like this:
clean install –Dliferay.home.dir=/development/Liferay –Dselenium.firefox.binary=/opt/firefox/firefox.sh –Dliferay.port=8080
2. Cargo plugin
<plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <version>1.0.6</version> <executions> <execution> <id>start-liferay</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop-liferay</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> <configuration> <skip>false</skip> <wait>false</wait> <container> <containerId>tomcat6x</containerId> <home>${liferay.home.dir}/tomcat-6.0.23</home> <timeout>30000</timeout> <type>installed</type> <systemProperties> <file.encoding>UTF8</file.encoding> <external-properties>${liferay.home.dir}/portal-ext.properties </external-properties> </systemProperties> </container> <configuration> <home>${liferay.home.dir}/tomcat-6.0.23</home> <type>existing</type> <properties> <cargo.jvmargs>-Xmx1024m -XX:MaxPermSize=512m</cargo.jvmargs> <cargo.servlet.port>${liferay.port}</cargo.servlet.port> <cargo.logging>high</cargo.logging> </properties> </configuration> </configuration> </plugin>
The Cargo plugin is going to start and stop Liferay in a talled application server container. The server is started before integration testing begins and stopped after integration testing. In the configuration part of the plugin, we configure our server: where it is located, the vmargs we want to start it with, etc..
The <skip> part of the configuration means we want Cargo to run and start the server. If set to true, the execution of this plugin would be skipped and no container would be started.
The <wait> part of the configuration determines whether we want to wait after the Cargo container is started or not. If set to true, we would need to manually intervene(as in pressing some buttons) to continue the Maven process. For automated integration testing, this obviously needs to be set to false.
For more information about configuring the cargo plugin, visit the Cargo maven plugin reference.
One thing to keep in mind is that the cargo.servlet.port should be the port Liferay runs on(8080 by default). If this port is not configured correctly, Cargo will timeout because it cannot determine whether the server started or not. It is the port Cargo listens on, not the port the server starts on. The server starts on the default port listed in its own configuration files.
3. Liferay plugin
<plugin> <groupId>com.liferay.maven.plugins</groupId> <artifactId>liferay-maven-plugin</artifactId> <version>6.0.5</version> <executions> <execution> <id>liferay-deploy</id> <phase>pre-integration-test</phase> <goals> <goal>deploy</goal> </goals> </execution> </executions> <configuration> <autoDeployDir>${liferay.home.dir}/deploy</autoDeployDir> </configuration> </plugin>
The above plugin deploys our war(the product of the pom) to the Liferay server. It also executes before integration testing begins. Note that in the default case, Maven plugins execute in the order in which they are defined. So in this case, the Liferay server is started first, after which the application(the portlets) is deployed to the started server.
Note that this plugin deploys the application, but it does NOT wait for the app to be deployed. Maven execution continues right away, so we will need to build something in to wait for the app to be deployed, before we actually start running the Selenium 2 tests.
4. Antrun plugin
<plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>wait-for-liferay</id> <phase>pre-integration-test</phase> <configuration> <tasks> <echo>Wait 300 seconds for Liferay..</echo> <waitfor maxwait="300" maxwaitunit="second"> <available file="errors.log" /> </waitfor> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> <execution> <id>clean-liferay</id> <phase>clean</phase> <configuration> <tasks> <echo>Cleaning Liferay..</echo> <delete dir="${liferay.home.dir}/tomcat-6.0.23/webapps/${project.build.finalName}" quiet="true" /> <delete dir="${liferay.home.dir}/tomcat-6.0.23/work" quiet="true" /> <delete dir="${liferay.home.dir}/tomcat-6.0.23/temp" quiet="true" /> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin>
The antrun plugin enables all Ant functionality within a Maven context.
This the dirty part of our profile. If there is something to improve, it is this part.
We are essentially doing two things here.
Before integration testing begins, we are waiting 300 seconds. This gives the application enough time to deploy, so it is ready once the selenium tests start running. Note that you could use something more intelligent here than plain waiting, such as polling the server to check whether a certain page is loading or even building a Liferay hook that when deployed, can be used to ask Liferay through a webservice whether it has finished deploying or not. However, that would require building something custom and is outside of the scope of this article.
The second ant execution configured for the plugin, cleans the Liferay server in the clean phase, so we start with a clean Liferay server every run. We had to add this because we were having issues with the new deploy conflicting with certain parts of an old deploy from time to time. Among other things, this makes sure the old version is not deployed first when the server starts. Same as with the other execution, one could come up with more sophisticated ways to do this, but it should be enough to illustrate the idea.
5. Surefire plugin
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.7.2</version> <configuration> <skip>true</skip> </configuration> </plugin>
The surefire plugin is the default plugin to run unit tests with. Because we want to use the failsafe plugin in the next section, the execution of this plugin should be skipped. Therefore, we are setting <skip> to true.
6. Failsafe plugin
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.9</version> <executions> <execution> <id>selenium2-firefox-test</id> <phase>integration-test</phase> <goals> <goal>integration-test</goal> </goals> <configuration> <includes> <include>**/SeleniumIntegrationTestSuite.java </include> </includes> <systemPropertyVariables> <selenium.baseURL>http://localhost:${liferay.port}/web/guest/testing </selenium.baseURL> <selenium.pathToFirefoxBinary>${selenium.firefox.binary} </selenium.pathToFirefoxBinary> </systemPropertyVariables> <reportsDirectory>${project.build.directory}/failsafe-reports/firefox </reportsDirectory> </configuration> </execution> </executions> </plugin>
With the failsafe plugin, we are going to run our Selenium 2 test. The main difference with the surefire plugin is that this plugin will make sure the Maven execution keeps executing is some integration tests fail.
There is also some additional configuration required.
Basically, we make a Suite class to which we add our test:
import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ PortalPageTest.class }) public class SeleniumIntegrationTestSuite{ }
All tests defined in this suite will be run using the supplied firefox executable. Note that the path to the firefox bin is not part of the failsafe plugin, but is a system property that we are setting. This system property is then fetched within the running Selenium 2 test. Same for the Selenium base url. Note that in order to run the test (suite) successfully, you will need to add a page “testing” to your Liferay portal and make sure the portlet is deployed on that page first.
Finally, the outcome of all tests will be written to htmlfiles in the supplied reportsDirectory.
The complete profile
To end, we are showing the entire Maven profile here for reference:
<profile> <id>liferay</id> <activation> <property> <name>liferay.home.dir</name> </property> </activation> <build> <plugins> <plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <version>1.0.6</version> <executions> <execution> <id>start-liferay</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop-liferay</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> <configuration> <skip>false</skip> <wait>false</wait> <container> <containerId>tomcat6x</containerId> <home>${liferay.home.dir}/tomcat-6.0.23 </home> <timeout>30000</timeout> <type>installed</type> <systemProperties> <file.encoding>UTF8</file.encoding> <external-properties>${liferay.home.dir}/portal-ext.properties </external-properties> </systemProperties> </container> <configuration> <home>${liferay.home.dir}/tomcat-6.0.23</home> <type>existing</type> <properties> <cargo.jvmargs>-Xmx1024m -XX:MaxPermSize=512m </cargo.jvmargs> <cargo.servlet.port>${liferay.port}</cargo.servlet.port> <cargo.logging>high</cargo.logging> </properties> </configuration> </configuration> </plugin> <plugin> <groupId>com.liferay.maven.plugins</groupId> <artifactId>liferay-maven-plugin</artifactId> <version>6.0.5</version> <executions> <execution> <id>liferay-deploy</id> <phase>pre-integration-test</phase> <goals> <goal>deploy</goal> </goals> </execution> </executions> <configuration> <autoDeployDir>${liferay.home.dir}/deploy</autoDeployDir> </configuration> </plugin> <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>wait-for-liferay</id> <phase>pre-integration-test</phase> <configuration> <tasks> <echo>Wait 300 seconds for Liferay..</echo> <waitfor maxwait="300" maxwaitunit="second"> <available file="errors.log" /> </waitfor> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> <execution> <id>clean-liferay</id> <phase>clean</phase> <configuration> <tasks> <echo>Cleaning Liferay..</echo> <delete dir="${liferay.home.dir}/tomcat-6.0.23/webapps/${project.build.finalName}" quiet="true" /> <delete dir="${liferay.home.dir}/tomcat-6.0.23/work" quiet="true" /> <delete dir="${liferay.home.dir}/tomcat-6.0.23/temp" quiet="true" /> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.9</version> <executions> <execution> <id>selenium2-firefox-test</id> <phase>integration-test</phase> <goals> <goal>integration-test</goal> </goals> <configuration> <includes> <include>**/SeleniumIntegrationTestSuite.java </include> </includes> <systemPropertyVariables> <selenium.baseURL>http://localhost:${liferay.port}/web/guest/testing </selenium.baseURL> <selenium.pathToFirefoxBinary>${selenium.firefox.binary} </selenium.pathToFirefoxBinary> </systemPropertyVariables> <reportsDirectory>${project.build.directory}/failsafe-reports/firefox </reportsDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.7.2</version> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build> </profile>
Hi Steffen, thanks for the great post. Is it possible to access the whole source code of this project as a zip somewhere? I’m probably having some version issues with the Selenium dependency, it would help a lot to resolve it. Thanks in advance!
Was this answer helpful?
LikeDislikeThanks for the great post..I was able to use in my project.