Testing Vaadin Applications with Selenium
Selenium is a popular browser automation library that’s often used to automate the testing of web applications. Although it’s more convenient to use TestBench, Selenium can be used to test Vaadin applications, as well.
This guide describes:
-
How to set up Selenium;
-
The making of a simple Selenium test;
-
The basics of finding and interacting with elements in a Selenium test; and
-
How a more complex Selenium test can be created.
Setting Up Selenium
To use Selenium in a Vaadin application, you need to perform a few steps. First, 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, you can skip the version number here because it has already been declared by the <selenium.version>
property in the pom.xml
file. You can find the latest version of Selenium here.
Next, use WebDriverManager to manage web browser drivers. Using WebDriverManager is the easiest way to manage drivers because you don’t have to download and manually set up the driver.
Add the WebDriverManager dependency to your project’s pom.xml
file.
<!-- WebDriver Management Software -->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.3.1</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. The code snippet below uses the Chrome driver. Drivers for other browsers can be found at the official Selenium guide.
WebDriverManager.chromedriver().setup();
Apart from WebDriverManager, you can also install drivers by doing the following:
Create Your First Selenium Test
As an example, the following code snippet 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)
}
}
}
-
Set up the driver for the test.
-
Instantiate the
ChromeDriver
, which is used to interact with the Chrome web browser. -
Wrap the test in a
try/finally
block so that the session closes even when the test fails. -
Direct the
ChromeDriver
to load the URL. -
Because Flow produces a single-page frontend, Selenium isn’t aware of DOM manipulation after the initial page has been loaded. You can use Selenium’s WebDriverWait API to wait until the DOM has been compiled.
-
Once you’re sure that the page title is available, try to retrieve a
Button
on this page. -
Check whether the
Button
text reads "Say Hello". -
Close the browser session after the test.
Running Tests
If you added your Selenium tests to a project that was generated from Vaadin Start, you can run them by executing the following command from the terminal:
mvn verify -Pit,production
This runs the tests in the it
profile, which starts the Spring Boot server before the tests are run — and stops it afterwards. If you’re running the test this way, your test classes must end with IT
.
The following lists the part of the pom.xml
file that’s 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 on GitHub of the it
profile for other technology stacks, including for a plain Java project and a CDI project.
Finding & Interacting with Elements
The following demonstrates a test that requires finding and interacting with a web element. Specifically, it finds the link to the "About" page and clicks it. This action, of course, triggers 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 WebDriver
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. -
Call
click()
to click on theWebElement
. -
Wait for the "About" page to load first, before trying to get the URL. This reduces flakiness.
-
Use the convenient method to get the full current URL.
Advanced Selenium Test
The following test demonstrates how a long Selenium test might look. This test assumes a master-detail view of the kind that could be generated from Vaadin Start.
@Test
public void addUser(){
//Set up the WebDriver
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();
}
}
-
You can use the
By.id()
matcher to find fields with a uniqueid
. You can retrieve theid
using your browser’s inspector. -
You 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 usexpath
expression to find the element. Thexpath
can be generated by the Selenium IDE. -
Last, test whether all of the information in the table cells match the original data.
For more usage scenarios, see the official Selenium doc.
Selenium-Jupiter Extension
Selenium-Jupiter is a JUnit 5 extension that can be used to initialize, run and manage browser-based tests.
Combining TestBench with Selenium-Jupiter is shown in the following example:
@ExtendWith(SeleniumJupiter.class)
public class SimpleCaseSeleniumIT implements HasElementQuery {
private WebDriver driver;
@BeforeEach
public void beforeEach(ChromeDriver driver) { // driver injection by Selenium-Jupiter
this.driver = TestBench.createDriver(driver); // TestBench driver proxy
this.driver.get("http://" + IPAddress.findSiteLocalAddress() + ":8080");
}
@Test
public void calculateOnePlusTwo() {
$(ButtonElement.class).id("button_1").click();
$(ButtonElement.class).id("button_+").click();
$(ButtonElement.class).id("button_2").click();
$(ButtonElement.class).id("button_=").click();
Assertions.assertEquals("3.0",
$(TextFieldElement.class).first().getValue());
}
@Override
public SearchContext getContext() {
return driver;
}
}
D341245B-909F-455A-B78B-AC8CF58356C5