Documentation

Documentation versions (currently viewingVaadin 23)
New Acceleration Kits: Observability Kit, SSO Kit, and Swing Kit. Read the blog post.

Testing Vaadin Applications with Selenium

How to set up testing automation for a Vaadin application using 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:

  1. How to set up Selenium;

  2. The making of a simple Selenium test;

  3. The basics of finding and interacting with elements in a Selenium test; and

  4. How a more complex Selenium test can be created.

Setting Up Selenium

To use Selenium in a Vaadin application, you need to perform the following steps:

  1. 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.

  2. 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.

    1. 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.2.3</version>
         <scope>test</scope>
      </dependency>

      The latest version of WebDriverManager can be found here.

    2. Import WebDriverManager into your test class.

      import io.github.bonigarcia.wdm.WebDriverManager;
    3. 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)
        }
    }
}
  1. Set up the driver for the test.

  2. Instantiate the ChromeDriver, which is used to interact with the Chrome web browser.

  3. Wrap the test in a try/finally block so that the session closes even when the test fails.

  4. Direct the ChromeDriver to load the URL.

  5. 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.

  6. Once you’re sure that the page title is available, try to retrieve a Button on this page.

  7. Check whether the Button text reads "Say Hello".

  8. Close the browser session after the test.

Running the 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 and 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();
  }
}
  1. You can find elements using the By matcher.

  2. Call click() to click on the WebElement.

  3. Wait for the "About" page to load first, before trying to get the URL. This reduces flakiness.

  4. 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();
  }
}
  1. You can use the By.id() matcher to find fields with a unique id. You can retrieve the id using your browser’s inspector.

  2. You must click on the field to simulate real behavior of an end user.

  3. You can send key strokes using the sendKeys() method.

  4. 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.

  5. 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;
    }
}

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