JSF Primefaces Automated Unit Testing using Selenium
Table of Contents
Today you’re going to see how to automate the unit testing of your PrimeFaces application.
The best part?
We will use the PageObject pattern.
So let’s get to work.
What is Selenium? #
Selenium is a software testing framework for web applications. Tests can be run against most modern web browsers and can be controlled by many programming languages.
Selenium is available as open-source software, released under the Apache 2.0 license.
The following example shows how to set up an automated unit test for your PrimeFaces web application using Selenium, Spring Boot, and Maven.
General Project Overview #
We will use the following tools/frameworks:
- PrimeFaces 6.2
- JoinFaces 3.3
- Spring Boot 2.1
- Selenium 3.14
- Maven 3.5
Our project has the following directory structure:
Maven Setup #
We start from a previous Spring Boot Primefaces Tutorial in which we created a greeting dialog based on a first and last name input form.
We add spring-boot-starter-test
to the existing Maven POM file. This will include the core dependencies for testing Spring Boot applications with libraries that include JUnit, Hamcrest and Mockito.
In order to create local Selenium scripts, we need to make use of language-specific client drivers. As this example is based on Java we include the selenium-java
client dependency.
The key interface against which tests should be written in Java is the WebDriver interface which has a number of implementing classes.
For this example, we will use HtmlUnitDriver which is a WebDriver compatible driver for the HtmlUnit headless browser. As such we also add the htmlunit-driver
dependency.
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.codenotfound</groupId>
<artifactId>jsf-primefaces-unit-testing-selenium</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jsf-primefaces-unit-testing-selenium</name>
<description>JSF Primefaces Automated Unit Testing using Selenium</description>
<url>https://codenotfound.com/jsf-primefaces-automated-unit-testing-selenium.html</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<joinfaces.version>3.3.0-rc2</joinfaces.version>
<selenium-htmlunit-driver.version>2.52.0</selenium-htmlunit-driver.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.joinfaces</groupId>
<artifactId>joinfaces-dependencies</artifactId>
<version>${joinfaces.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.joinfaces</groupId>
<artifactId>primefaces-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>htmlunit-driver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Creating a PrimeFaces Test Page #
To test the PrimeFaces Hello World page, we will use the PageObject pattern. Within our web app’s UI there are areas that our test will interact with (in this example input/output text fields and a button). A page object simply models these as objects within the test code.
The basic rule of thumb for a page object is that it should allow a software client to do anything and see anything that a human can. It should also provide an interface that’s easy to program to and hides the underlying widgetry in the window. In this example, the page object is the HelloWorldPage
class shown below.
In order to set/get the input/output field values we lookup the corresponding WebElement
s using the @FindBy
annotation. Matching is done using the id
of the HTML elements which are specified in the helloworld.xhtml
located under src/main/resources/META-INF/resources
. The submit button is also retrieved using its corresponding ID.
Note that @FindBy
is just an alternate way of finding elements. You could also use the findElement()
method on the WebDriver
.
The JSF framework prefixes HTML elements inside a
<form>
with the ID of the form. If no ID is present then one will be auto-generated.
Next, we represent our submit button as a submit()
method that takes a first and last name as an input parameter. These parameters are set on the corresponding input fields and the submit button is executed. The method finishes by reloading the page elements so that the greeting output field is set with the new value.
The getGreeting()
method allows to retrieve the greeting shown to the user. Note that we used getAttribute("textContent")
as using getText()
returns an empty string.
package com.codenotfound.primefaces;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import com.codenotfound.PageObject;
public class HelloWorldPage extends PageObject {
@FindBy(id = "hello-world-form:first-name")
private WebElement firstNameInput;
@FindBy(id = "hello-world-form:last-name")
private WebElement lastNameInput;
@FindBy(id = "hello-world-form:submit")
private WebElement submitButton;
@FindBy(id = "hello-world-form:greeting")
private WebElement greetingOutput;
public HelloWorldPage(WebDriver driver) {
super(driver);
}
public void submit(String firstName, String lastName) {
// set the input fields
firstNameInput.sendKeys(firstName);
lastNameInput.sendKeys(lastName);
// submit the form
submitButton.submit();
// refresh the output field
PageFactory.initElements(driver, this);
}
public String getGreeting() {
return greetingOutput.getAttribute("textContent");
}
}
Notice that the above HelloWorldPage
page object extends a PageObject
class. This is a small helper class that uses the PageFactory factory class provided by the WebDriver’s support library to help realize the PageObject pattern.
In the constructor, the PageFactory
is used to instantiate WebElements that we have annotated in the HelloWorldPage
class.
package com.codenotfound;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;
public class PageObject {
protected WebDriver driver;
public PageObject(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
}
In our test case, we navigate to the Hello World web page using the get()
method of the WebDriver
. We create a new instance of our HelloWorldPage
page object and call the submit()
method.
Finally, an assert is used to verify that the correct greeting is generated.
package com.codenotfound.primefaces;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.junit4.SpringRunner;
import com.codenotfound.WebDriverUtil;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class HelloWorldTest extends WebDriverUtil {
@Test
public void testSubmit() {
driver.get("http://localhost:8080/helloworld.xhtml");
HelloWorldPage page = new HelloWorldPage(driver);
page.submit("Jane", "Doe");
assertThat(page.getGreeting()).isEqualTo("Hello Jane Doe!");
}
}
Based on following example on writing functional tests using Selenium we have made above test case extend a WebDriverUtil
class.
This class holds all the WebDriver
lifecycle management code and assures correct setup and cleanup is done after each test case.
package com.codenotfound;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
public class WebDriverUtil {
protected static WebDriver driver;
@BeforeClass
public static void setUp() {
driver = new HtmlUnitDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
}
@After
public void cleanUp() {
driver.manage().deleteAllCookies();
}
@AfterClass
public static void tearDown() {
driver.close();
}
}
Let’s go ahead and run the HelloWorldTest
test case. Open a command prompt in the projects root folder and execute following Maven command:
mvn test
The result should be a successful build during which the test is executed.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.0.RELEASE)
18:15:51.168 [main] INFO c.c.primefaces.HelloWorldTest - Starting HelloWorldTest on DESKTOP-2RB3C1U with PID 18372 (started by Codenotfound in C:\Users\Codenotfound\repos\jsf-primefaces\jsf-primefaces-unit-testing-selenium)
18:15:51.168 [main] INFO c.c.primefaces.HelloWorldTest - No active profile set, falling back to default profiles: default
18:15:57.670 [main] INFO c.c.primefaces.HelloWorldTest - Started HelloWorldTest in 6.862 seconds (JVM running for 9.115)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.551 s - in com.codenotfound.primefaces.HelloWorldTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.001 s
[INFO] Finished at: 2018-12-08T18:16:00+01:00
[INFO] ------------------------------------------------------------------------
Using the PageObject pattern we were able to unit test a previous PrimeFaces Hello World example using Selenium, Spring Boot, and Maven.
Drop a line below in case you enjoyed reading.
Or to let me know how you are testing your JSF application.