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, 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. |
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
.
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);
}
}
-
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 helps 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 ("localhost" in development), the port the server uses (set to 8080 in application.properties), and information about the route to start from.
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.
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 |
Tip
|
Start the server separately to speed up tests
While developing tests, it isn’t very efficient to run the tests as |
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.
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
:
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 ( |
Adding the @Attribute(name = "class", contains = "login-view")
annotation allows you to find the LoginViewElement
using the TestBench query API, for example:
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:
@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:
@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.
0DDF0F9E-DCF0-4AEC-9DD4-C241699CC7F7