Documentation versions (currently viewingVaadin 24)

Test Vaadin Applications in Browser with End-To-End Tests

This article explains how to do full-stack tests on a Flow application using Vaadin TestBench.

End-to-end (e2e) tests are used to test an entire application. They’re much more coarse-grained than unit or integration tests. This makes them well suited to check that the application works as a whole, and catch any regressions that may be missed by more specific tests.

End-to-end tests are executed in a browser window. Vaadin TestBench controls the browser window using Selenium WebDriver.

Vaadin TestBench is a commercial product.

The end-to-end tests use Vaadin TestBench, which is a commercial tool that’s part of the Vaadin Pro Subscription. You can get a free trial at All Vaadin Pro tools and components are free for students through the GitHub Student Developer Pack. For an open source alternative for TestBench, you can get similar results with plain Selenium WebDriver or Playwright.

The Base Test Class

Vaadin TestBench contains handy base classes that you can use as a basis for your e2e tests. The JUnit5 version is called BrowserTestBase. It can be used alone, if you orchestrate starting and stopping your server. For example, it can be used with Maven executions in pre-integration-test and post-integration-test, and to execute the actual test in integration test phase.

In Spring Boot applications, it’s easier to use the same @SpringBootTest annotation that you already used in the previous phase to ensure a running server during the browser test execution.

First, create a new class, LoginE2ETest in the package. Be sure to place it in src/test/java and not src/main/java.


import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import com.vaadin.testbench.IPAddress;
import com.vaadin.testbench.ScreenshotOnFailureRule;
import com.vaadin.testbench.parallel.ParallelTest;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.slf4j.LoggerFactory;

public abstract class LoginE2ETest extends ParallelTest {
    private static final String SERVER_HOST = IPAddress.findSiteLocalAddress();
    private static final int SERVER_PORT = 8080;
    private final String route;

    static {
        // Prevent debug logging from Apache HTTP client
        Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

    public static void setupClass() {
        WebDriverManager.chromedriver().setup(); // (1)

    @Rule // (2)
    public ScreenshotOnFailureRule rule = new ScreenshotOnFailureRule(this, true);

    public void setup() throws Exception {
        getDriver().get(getURL(route)); // (3)

    protected LoginE2ETest(String route) {
        this.route = route;

    private static String getURL(String route) {
        return String.format("http://%s:%d/%s", SERVER_HOST, SERVER_PORT, route);
  1. Start by invoking the Chrome WebDriverManager before any test method is invoked. TestBench doesn’t invoke the WebDriver manager.

  2. ScreenshotOnFailureRule tells TestBench to grab a screenshot before exiting, if a test fails. This can help you understand what went wrong when tests don’t pass.

  3. Open the browser to the correct URL before each test. For this, you need the host name where the application runs (i.e., "localhost" in development), the port the server uses, which is set to 8080 in, and information about the route from which to start.

Test the Login View

Now that your setup is complete, you can start developing your first test: ensuring that a user can log in. For this test, you need to open the base URL.

Create a new class, LoginIT, in the same package as LoginE2ETest. The test validates that logging in with the correct user and password succeeds.


import com.vaadin.flow.component.login.testbench.LoginFormElement;
import com.vaadin.testbench.BrowserTest;
import com.vaadin.testbench.BrowserTestBase;
import com.vaadin.testbench.annotations.RunLocally;
import com.vaadin.testbench.parallel.Browser;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;

import static org.junit.jupiter.api.Assertions.assertFalse;

//@RunLocally(Browser.FIREFOX) // (1)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class LoginE2ETest extends BrowserTestBase { // (2)

    Environment env;

    static {
        // Prevent Vaadin Development mode to launch browser window
        System.setProperty("vaadin.launch-browser", "false");

    void openBrowser() {
        getDriver().get("http://localhost:" +
            env.getProperty("local.server.port") + "/"); // (3)

    @BrowserTest // (4)
    public void loginAsValidUserSucceeds() {
        // Find the LoginForm used on the page, using a
        // typed selector API provided by TestBench
        LoginFormElement form = $(LoginFormElement.class).first();
        // Enter the credentials and log in

        // Behind the scenes TestBench uses lower level WebDriver API
        // Here we can configure it on the fly
        getDriver().manage().timeouts().implicitlyWait(Duration.of(1, ChronoUnit.SECONDS));
        // Here finding an element on the actual main layout (after login),
        // using pure WebDriver API, BTW. There is also AppLayoutElement for TB

        // Ensure the login form is no longer visible

  1. This optional annotation specifies the test to be run on the local machine and using Firefox. The default is Chrome.

  2. The super class BrowserTestBase provides handy helper methods and configures TestBench.

  3. The openBrowser method is annotated to be executed before each actual tests. The URL points to local test server with the random port SpringBootTest has selected. The browser should be automatically redirected to the login screen.

  4. BrowserTest annotation is a TestBench extension of the better known Test annotation, that is handy if you decide to extend your end-to-end tests to cover multiple browsers at some point.

Right-click and select Run 'LoginE2ETest'.

Create a View Object

You can now add a second test, one to validate that you can’t log in with an invalid password.

For this test, you need to write the same code to access the components in the view as you did for the first test. To make your tests more maintainable, you can create for each view a view object — otherwise known as a call page object or element class. A view object provides a high-level API to interact with the view and hides the implementation details.

For the login view, create the LoginViewElement class in a new package,


import com.vaadin.flow.component.login.testbench.LoginFormElement;
import com.vaadin.flow.component.orderedlayout.testbench.VerticalLayoutElement;
import com.vaadin.testbench.annotations.Attribute;
import org.openqa.selenium.By;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

@Attribute(name = "class", contains = "login-view")
public class LoginViewElement extends VerticalLayoutElement {

    public boolean login(String username, String password) {
        LoginFormElement form = $(LoginFormElement.class).first();

        try {
            getDriver().manage().timeouts().implicitlyWait(Duration.of(1, ChronoUnit.SECONDS));
            return true;
        } catch (Exception e) {
            return false;

Class hierarchies must match.

To make the correct functionality available from superclasses, the hierarchy of the view object should match the hierarchy of the view (i.e., public class LoginView extends VerticalLayout vs public class LoginViewElement extends VerticalLayoutElement).

Adding the @Attribute(name = "class", contains = "login-view") annotation allows you to find the LoginViewElement using the TestBench query API. The following is an example of this:

LoginViewElement loginView = $(LoginViewElement.class).onPage().first();

The annotation searches for the login-view class name, which is set for the login view in the constructor. The onPage() call ensures that the whole page is searched. By default, a $ query starts from the active element.

Now that you have the LoginViewElement class, you can simplify your loginAsValidUserSucceeds() test to be this:

public void loginAsValidUserSucceeds() {
    LoginViewElement loginView = $(LoginViewElement.class).onPage().first();
    assertTrue(loginView.login("user", "password"));

Add a test to use an invalid password as follows:

public void loginAsInvalidUserFails() {
    LoginViewElement loginView = $(LoginViewElement.class).onPage().first();
    assertFalse(loginView.login("user", "invalid"));

You can continue testing the other views by creating similar view objects and IT classes.

If you’re building a large application, it’s probably better to make slower end-to-end tests executed only when requested separately. You can do this by using Maven Failsafe plugin or using the tagging feature in JUnit 5.

The next part covers how to make a production build of the application and deploy it to a cloud platform.

migration assistance

Download free e-book.
The complete guide is also available in an easy-to-follow PDF format.

Open in a
new tab
export class RenderBanner extends HTMLElement {
  connectedCallback() {

  renderBanner() {
    let bannerWrapper = document.getElementById('tocBanner');

    if (bannerWrapper) {

    let tocEl = document.getElementById('toc');

    // Add an empty ToC div in case page doesn't have one.
    if (!tocEl) {
      const pageTitle = document.querySelector(
        'main > article > header[class^=PageHeader-module--pageHeader]'
      tocEl = document.createElement('div');

      pageTitle?.insertAdjacentElement('afterend', tocEl);

    // Prepare banner container
    bannerWrapper = document.createElement('div'); = 'tocBanner';

    // Banner elements
    const text = document.querySelector('.toc-banner-source-text')?.innerHTML;
    const link = document.querySelector('.toc-banner-source-link')?.textContent;

    const bannerHtml = `<div class='toc-banner'>
          <a href='${link}'>
            <div class="toc-banner--img"></div>
            <div class='toc-banner--content'>${text}</div>

    bannerWrapper.innerHTML = bannerHtml;

    // Add banner image
    const imgSource = document.querySelector('.toc-banner-source .image');
    const imgTarget = bannerWrapper.querySelector('.toc-banner--img');

    if (imgSource && imgTarget) {