Docs

Documentation versions (currently viewingVaadin 24)

End-To-End Test Applications in Browser

Explains how to do full-stack tests on a Vaadin Flow application using Vaadin TestBench.
Important
Deprecated
This tutorial is no longer maintained. It’s accessible, though, until the new tutorial is completed, which involves adding all of the topics in this tutorial to the new one.

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, simulating user interactions. Vaadin TestBench controls the browser window using Selenium WebDriver.

Note
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 Vaadin Commercial Trial. All Vaadin Pro tools and components are free for students through the GitHub Student Developer Pack. For an open source alternative to TestBench, you can get similar results with Selenium WebDriver or Playwright.

Test the Login View

In this tutorial, you’ll build a test that ensures a user can log in. For this test, you’ll need to open the base URL, fill in the user name and password, then click the login button, and make certain the actual application view opens.

First, create a new class, LoginE2ETest in the src/test/java/com/example/application/it directory. The test validates that logging in with the correct user and password succeeds.

package com.example.application.it;

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;

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

    @Autowired
    Environment env;

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

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

    @BrowserTest 5
    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
        form.getUsernameField().setValue("user");
        form.getPasswordField().setValue("password");
        form.getSubmitButton().click();

        // 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
        getDriver().findElement(By.tagName("vaadin-app-layout"));

        // Ensure the login form is no longer visible
        assertFalse($(LoginFormElement.class).exists());
    }

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

  2. This annotation instructs Spring Boot test helpers to start an actual web server for this test. A random port is assigned so that running this test doesn’t potentially conflict with a running development server. Alternatively, you can package the whole application before the end-to-end tests and use Maven integration test phases together with Maven Failsafe plugin to ensure the application server is running during the test.

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

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

  5. BrowserTest annotation is a TestBench extension of the better known Test annotation. This is useful if you decide to extend your end-to-end tests to cover multiple browsers at some point.

Right-click LoginE2ETest.java 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’ll need to use the same code for accessing the components in the view as you did for the first test. To make your tests more maintainable, you can create a view object for each view — 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, com.example.application.it.elements:

package com.example.application.it.elements;

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();
        form.getUsernameField().setValue(username);
        form.getPasswordField().setValue(password);
        form.getSubmitButton().click();

        try {
            getDriver().manage().timeouts().implicitlyWait(Duration.of(1, ChronoUnit.SECONDS));
            getDriver().findElement(By.tagName("vaadin-app-layout"));
            return true;
        } catch (Exception e) {
            return false;
        }
    }

}
Caution
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 a specific LoginViewElement using the TestBench query API, in this case by CSS class name. The following is an example of this:

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

The annotation searches for the login-view CSS class name, which is set for the LoginView 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:

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

You might also add a test to use an invalid password as follows:

@BrowserTest
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 execute 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() {
    this.renderBanner();
  }

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

    if (bannerWrapper) {
      return;
    }

    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');
      tocEl.classList.add('toc');

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

    // Prepare banner container
    bannerWrapper = document.createElement('div');
    bannerWrapper.id = 'tocBanner';
    tocEl?.appendChild(bannerWrapper);

    // 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>
          </a>
        </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) {
      imgTarget.appendChild(imgSource);
    }
  }
}

0DDF0F9E-DCF0-4AEC-9DD4-C241699CC7F7