Test Vaadin Applications in Browser with End-To-End Tests
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.
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 https://vaadin.com/trial. 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 com.example.application.it
package. Be sure to place it in src/test/java
and not src/main/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 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);
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 LoginE2ETest(String route) {
this.route = route;
}
private static String getURL(String route) {
return String.format("http://%s:%d/%s", SERVER_HOST, SERVER_PORT, route);
}
}
-
Start by invoking the Chrome
WebDriverManager
before any test method is invoked. TestBench doesn’t invoke the WebDriver manager. -
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. -
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 application.properties, 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.
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;
//@RunLocally(Browser.FIREFOX) // (1)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class LoginE2ETest extends BrowserTestBase { // (2)
@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") + "/"); // (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
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());
}
}
-
This optional annotation specifies the test to be run on the local machine and using Firefox. The default is Chrome.
-
The super class
BrowserTestBase
provides handy helper methods and configures TestBench. -
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. -
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 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 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, 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., |
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:
@BrowserTest
public void loginAsValidUserSucceeds() {
LoginViewElement loginView = $(LoginViewElement.class).onPage().first();
assertTrue(loginView.login("user", "password"));
}
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 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.
0DDF0F9E-DCF0-4AEC-9DD4-C241699CC7F7