Testing Vaadin Apps Using Selenium
Selenium is a popular browser automation library that is often used to automate the testing of web apps. While it is more convenient to use TestBench, Selenium can be used for testing Vaadin apps as well.
This guide describes:
The differences between TestBench and Selenium.
How to setup Selenium.
The making of a simple Selenium test.
The basics of finding and interacting with elements in a Selenium test.
How a more complex Selenium test can be created.
Differences Between TestBench and Selenium
TestBench is a framework for testing that is itself based on Selenium. TestBench, however, is specifically designed for testing Vaadin apps. Compared to Selenium, TestBench is designed to:
Facilitate the handling of Vaadin components. Unlike Selenium which operates on the DOM of the web page and provides basic methods to interact with elements like "click" or "sendKeys", TestBench provides helper methods to interact with Vaadin components like "select" or "setValue". Moreover, TestBench provides a more convenient way to interact with elements in the shadow DOM of Vaadin components.
Take the nature of client-server communication in Vaadin apps into account. During the course of a test, TestBench will automatically suspend client-side execution if the server is busy and resume it when the server is ready. This makes it possible to write tests in TestBench without the need to explicitly set wait timeouts for various events like page load or waiting for long-running server calls to finish.
Provide an easy-to-use API for conducting screen-comparison tests.
Enable parallel testing of Vaadin apps.
Setting Up Selenium
To use Selenium in a Vaadin app, you need to perform the following steps:
Add the Selenium library dependency to your project’s
pom.xml
file.<!-- Selenium Library --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> </dependency>
In a Vaadin Start generated project, we can skip the version number here because it has already been declared by the
<selenium.version>
property in thepom.xml
file. You can find the latest version of Selenium here.Use WebDriverManager to manage Web Browser Drivers
Using WebDriverManager is the easiest way to manage Drivers because you don’t have to download and set up the driver manually by yourself.
Add the WebDriverManager dependency to your project’s
pom.xml
file.<!-- Web Driver Management Software --> <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>5.2.3</version> <scope>test</scope> </dependency>
The latest version of WebDriverManager can be found here.
Import WebDriverManager into your test class.
import io.github.bonigarcia.wdm.WebDriverManager;
Call
setup()
on the driver that you would like to use. We used the Chrome driver in the code snippet below, but drivers for other browsers can be found at the official Selenium guide.WebDriverManager.chromedriver().setup();
Note that apart from WebDriverManager, you can also install drivers by:
Create Your First Selenium Test
As an example, the code snippet below tests whether the loaded Button
text matches "Say Hello".
import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import static java.time.Duration.ofSeconds;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.openqa.selenium.support.ui.ExpectedConditions.titleIs;
import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated;
public class SimpleIT {
@Test
public void checkPageLoad() {
WebDriverManager.chromedriver().setup(); 1
var driver = new ChromeDriver(); 2
try { 3
driver.get("http://localhost:8080/hello"); 4
new WebDriverWait(driver, ofSeconds(30), ofSeconds(1))
.until(titleIs("Hello World")); 5
var button = driver.findElement(By.xpath("//vaadin-button[contains(.,'Say hello')]")); 6
assertEquals(button.getText(), "Say hello"); 7
} finally {
driver.quit(); 8
}
}
}
Setup the driver for the test.
Instantiate the
ChromeDriver
, which will be used to interact with the Chrome web browser.We wrap the test in a
try/finally
block so that the session will close even when the test fails.Direct the
ChromeDriver
to load the URL.Because Flow produces a single-page frontend, Selenium is not aware of DOM manipulation after the initial page has been loaded. So we can use Selenium’s WebDriverWait API to wait until the DOM has been compiled.
After we are sure that the page title is available, we attempt to retrieve a
Button
on this page.We check whether the
Button
text is "Say Hello".Finally, we close the browser session after the test.
Running the Tests
If you added your Selenium tests to a project that’s generated from Vaadin Start, you can run those by executing the following command from the terminal:
mvn verify -Pit,production
This will run the tests in the it
profile, which starts the Spring Boot server before the tests are run, and stops it afterwards.
If you are running the test this way, then your test classes must end with IT
.
The following lists the part of the pom.xml
file that is responsible for starting and stopping the Spring Boot server.
<profile>
<id>it</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>start-spring-boot</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-spring-boot</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
...
For a non-Spring Boot project, there are examples at GitHub of the it
profile for other technology stacks, including for a plain Java project and a projects.
Finding and Interacting With Elements
The following demonstrates a test that require finding and interacting with a web element. Specifically, it finds the link to the "About" page and clicks it. This action triggers a navigation to the “About” page. The test then waits until the "About" page is loaded and checks that the URL of the page is correct.
@Test
public void routeSwitch(){
//Set up the web driver
WebDriverManager.chromedriver().setup();
//Use this ChromeDriver to interact with Chrome
var driver = new ChromeDriver();
try {
//Loads the page
driver.get("http://localhost:8080");
//Have to explicitly wait because it takes time for compiled html to load
new WebDriverWait(driver, ofSeconds(30), ofSeconds(1))
.until(titleIs("Hello World"));
driver.findElement(By.cssSelector("vcf-nav-item:nth-child(2)")) 1
.click(); 2
new WebDriverWait(driver, ofSeconds(30), ofSeconds(1))
.until(titleIs("About")); 3
var url = driver.getCurrentUrl(); 4
//Checks whether the url matches
assertEquals("http://localhost:8080/about", url);
} finally {
//Ends the browser session
driver.quit();
}
}
You can find elements using the
By
matcher.We call
click()
to click on theWebElement
.We wait for the "About" page to load first before attempting to get the URL. This reduces flakiness.
We use the convenient method to get the full current URL.
Advanced Selenium Test
The test below demonstrates how a long Selenium test might look like. This test assumes a Master-Detail view of the kind that could be generated from Vaadin Start.
@Test
public void addUser(){
//Set up the web driver
WebDriverManager.chromedriver().setup();
//Use this ChromeDriver to interact with Chrome
var driver = new ChromeDriver();
try {
//Maximizes the screen
driver.manage().window().maximize();
//Loads the page
driver.get("http://localhost:8080/master-detail");
//Have to explicitly wait because it takes time for compiled html to load
new WebDriverWait(driver, ofSeconds(30), ofSeconds(1))
.until(titleIs("Master-Detail"));
//Test data
var firstName = "FirstName";
var lastName = "LastName";
var email = "first.last@example.com";
var phone = "(111) 111-1111";
//Cannot use simple String because the form and table display the dob differently
var dob = LocalDate.of(2000, Month.JANUARY, 1);
var occupation = "Forester";
//Adds First Name
var firstNameTextInput = driver.findElement(By.id("vaadin-text-field-0")); 1
firstNameTextInput.click(); 2
firstNameTextInput.sendKeys(firstName); 3
//Adds Last Name
var lastNameTextInput = driver.findElement(By.id("vaadin-text-field-1"));
lastNameTextInput.click();
lastNameTextInput.sendKeys(lastName);
//Adds Email
var emailTextInput = driver.findElement(By.id("vaadin-text-field-2"));
emailTextInput.click();
emailTextInput.sendKeys(email);
//Adds Phone
var phoneTextInput = driver.findElement(By.id("vaadin-text-field-3"));
phoneTextInput.click();
phoneTextInput.sendKeys(phone);
//Adds DOB
var dobTextInput = driver.findElement(By.id("vaadin-date-picker-4"));
dobTextInput.click();
dobTextInput.sendKeys(DateTimeFormatter.ofPattern("dd/MM/uuuu").format(dob));
dobTextInput.sendKeys(Keys.ENTER); //Closes the pop-up Date Picker
//Adds Occupation
var occupationTextInput = driver.findElement(By.id("vaadin-text-field-5"));
occupationTextInput.click();
occupationTextInput.sendKeys(occupation);
//Marks as Important
driver.findElement(By.id("vaadin-checkbox-6"))
.click();
//Clicks Save
driver.findElement(By.xpath("//vaadin-button[contains(.,'Save')]")).click(); 4
//Sorts by Phone number so the sample user is visible on the screen
driver.findElement(By.xpath("//vaadin-grid-sorter[contains(.,'Phone')]")).click();
//Reduces verbosity
var xPathStart = "//vaadin-grid-cell-content[contains(.,'";
var xPathEnd = "')]";
//Waits for the page to sort
new WebDriverWait(driver, ofSeconds(30), ofSeconds(1))
.until(visibilityOfElementLocated(By.xpath(xPathStart + firstName + xPathEnd)));
//Gets the cells in the table for the newly added user
var firstNameCell = driver.findElement(By.xpath(xPathStart + firstName + xPathEnd));
var lastNameCell = driver.findElement(By.xpath(xPathStart + lastName + xPathEnd));
var emailCell = driver.findElement(By.xpath(xPathStart + email + xPathEnd));
var phoneCell = driver.findElement(By.xpath(xPathStart + phone + xPathEnd));
var dobCell = driver.findElement(By.xpath(xPathStart + dob + xPathEnd));
var occupationCell = driver.findElement(By.xpath(xPathStart + occupation + xPathEnd));
//Assertions (5)
assertEquals(firstName, firstNameCell.getText());
assertEquals(lastName, lastNameCell.getText());
assertEquals(email, emailCell.getText());
assertEquals(phone, phoneCell.getText());
assertEquals(dob.toString(), dobCell.getText());
assertEquals(occupation, occupationCell.getText());
} finally {
//Ends the browser session
driver.quit();
}
}
We can use the
By.id()
matcher to find fields with a uniqueid
. You can retrieve theid
using your browser’s inspector.We must click on the field to simulate real behavior of an end user.
You can send key strokes using the
sendKeys()
method.For elements that don’t have an
id
, you can use xpath expression to find the element. The xpath can be generated by the Selenium IDE.Finally, we test whether all of the information in the table cells match our original data.
For more usage scenarios, you can check out the official Selenium doc
D341245B-909F-455A-B78B-AC8CF58356C5