> Markdown version of [End-to-end testing](https://vaadin.com/docs/v23/tutorial/e2e-testing-with-testbench). Section index: [llms.txt](https://vaadin.com/docs/v23/tutorial/llms.txt)

# Testing Vaadin Applications in the Browser with End-To-End Tests

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

End-to-end tests are executed in a browser window, controlled by a WebDriver and run on the server where the application is deployed. Vaadin TestBench takes care of all this.

> **Note: Vaadin TestBench is a commercial product**
>
> The end-to-end tests use [Vaadin TestBench](https://vaadin.com/testbench), which is a commercial tool that’s a part of the Vaadin Pro Subscription. You can get a free trial at <https://vaadin.com/trial>. All Vaadin Pro tools and components are free for students through the [GitHub Student Developer Pack](https://education.github.com/pack).

## <a id="creating-the-base-test-class"></a>Creating the Base Test Class

To avoid repetition in each test class, it’s a good idea to put common logic in an abstract class and have all tests extend this class. Most of the heavy lifting relating to starting browsers, etc. is handled by `ParallelTest` in TestBench, but there are a couple of useful things you can add to the abstract class.

Create a new class, `AbstractTest` in the `com.example.application.it` package. Be sure to place it in `src/test/java` and not `src/main/java`.

`AbstractTest.java`

```java
package com.example.application.it;

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 AbstractTest 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);
        root.setLevel(Level.INFO);
    }

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

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

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

    protected AbstractTest(String route) {
        this.route = route;
    }

    private static String getURL(String route) {
        return String.format("https://%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 helps 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 ("localhost" in development), the port the server uses (set to 8080 in application.properties), and information about the route to start from.

## <a id="testing-the-login-view"></a>Testing 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 `AbstractTest`. The test validates that logging in with the correct user and password succeeds.

`LoginIT.java`

```java
package com.example.application.it;

import com.vaadin.flow.component.login.testbench.LoginFormElement;
import org.junit.Assert;
import org.junit.Test;

public class LoginIT extends AbstractTest {
    public LoginIT() {
        super("");
    }

    @Test
    public void loginAsValidUserSucceeds() {
        // Find the LoginForm used on the page
        LoginFormElement form = $(LoginFormElement.class).first();
        // Enter the credentials and log in
        form.getUsernameField().setValue("user");
        form.getPasswordField().setValue("userpass");
        form.getSubmitButton().click();
        // Ensure the login form is no longer visible
        Assert.assertFalse($(LoginFormElement.class).exists());
    }
}
```

> **Note: Make sure tests have the correct name**
>
> The name of the class should end in ”IT” for the test runner to pick it up as an integration test. If you name it `LoginTest`, it’s run as a unit test. Hence, the server isn’t started and the test fails.

> **Tip: Start the server separately to speed up tests**
>
> While developing tests, it isn’t very efficient to run the tests as `mvn -Pit verify`. Instead, you can start the server manually by launching the `Application` class or with `spring-boot:run`. You can then execute the selected test in your IDE and, hence, don’t have to wait for the server to start every time.

Start the application normally, then right-click `LoginIT.java` and select **Run 'LoginIT'**.

The first time you run the test, you’re asked to start a trial or to validate your existing license. Follow the instructions in the browser window that opens.

## <a id="creating-a-view-object"></a>Creating a View Object

You can now add a second test: validating 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 a view object (otherwise known as a call page object or element class) for each view. 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`:

`LoginViewElement.java`

```java
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;

@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();

        // Return true if we end up on another page
        return !$(LoginViewElement.class).onPage().exists();
    }

}
```

> **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 (`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, for example:

Finding a LoginViewElement using the TestBench query API

```java
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:

`LoginIT.java`

```java
@Test
public void loginAsValidUserSucceeds() {
    LoginViewElement loginView = $(LoginViewElement.class).onPage().first();
    Assert.assertTrue(loginView.login("user", "userpass"));
}
```

Add a test to use an invalid password as follows:

`LoginIT.java`

```java
@Test
public void loginAsInvalidUserFails() {
    LoginViewElement loginView = $(LoginViewElement.class).onPage().first();
    Assert.assertFalse(loginView.login("user", "invalid"));
}
```

Continue testing the other views by creating similar view objects and IT classes.

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

[Image: migration assistance]

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

https\://pages.vaadin.com/en/build-a-modern-web-app-with-spring-boot-vaadin-pdf

`render-banner.ts`

```html
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`
