-
Notifications
You must be signed in to change notification settings - Fork 0
How to Write Browserless UI Tests with Vaadin 8
This guide shows how to test Vaadin 8 user interfaces without a real browser using the UIUnitTest framework. All examples are taken from this project and fall into two categories:
-
Integration-style tests that boot the real
VaadinCreateUIwith an in-memory H2 backend (e.g.BooksViewTest,AdminViewTest,AboutViewTest). -
Lightweight component tests that use a mock
UIand mock access control (e.g.LoginViewTest,TabNavigatorTest).
Both styles use the same tools:
-
UIUnitTestbase class -
mockVaadin(...)to create a headless Vaadin environment -
$query API to locate components -
test(component)helpers to interact with components like a user
All browserless UI tests rely on UIUnitTest:
public abstract class AbstractUITest extends UIUnitTest {
protected void login() {
test($(TextField.class).id("login-username-field")).setValue("Admin");
test($(PasswordField.class).id("login-password-field"))
.setValue("admin");
test($(LanguageSelect.class).first())
.clickItem(DefaultI18NProvider.LOCALE_EN);
test($(Button.class).id("login-button")).click();
}
protected void assertNotification(String message) {
boolean found = $(Notification.class).stream()
.anyMatch(n -> n.getCaption().equals(message));
assertTrue(found);
}
}Key concepts:
-
mockVaadin()/mockVaadin(ui)sets up UI, session, page, navigator, etc. -
$queries the component tree (by type, id, style name, or parent). -
test(component)returns a tester that performs user-like actions:-
click(),setValue(...),focus(),shortcut(KeyCode, ...), etc.
-
These tests spin up the real application UI (VaadinCreateUI) including:
-
AppLayoutapplication shell - Navigator and view registration
- Access control and session handling
- H2-based backend services and event bus
BooksViewTest, AdminViewTest, and AboutViewTest all follow this pattern:
public class BooksViewTest extends AbstractUITest {
private VaadinCreateUI ui;
private BooksView view;
@Before
public void setup() throws ServiceException {
ui = new VaadinCreateUI();
mockVaadin(ui); // Full VaadinCreateUI, including AppLayout
login(); // Uses the real login flow
view = navigate(BooksView.VIEW_NAME, BooksView.class);
}
@After
public void cleanUp() {
logout();
tearDown();
}
}Notes:
-
mockVaadin(ui)attaches the freshly constructedVaadinCreateUIto the mocked Vaadin environment. -
login()drives the real login view by filling fields and clicking the login button. -
navigate(viewName, ViewClass)uses UIUnitTest’s navigator helper to switch views.
Once the real UI is running, tests work against actual views and services.
Example: editing a product in BooksView
@Test
public void editing_product_and_saving_it_updates_grid_and_backend() {
var grid = $(BookGrid.class).single();
// Open first row
test(grid).click(1, 0);
var form = $(BooksView.class).single().getForm();
test($(form, TextField.class).id("product-name"))
.setValue("Edited book");
test($(form, Button.class).id("save-button")).click();
// UI feedback
assertNotification("\"Edited book\" updated");
assertFalse(form.isShown());
// Backend state
assertEquals("Edited book", test(grid).cell(1, 0));
}Example: AboutView reacting to backend events
@Test
public void posting_message_event_shows_notification_and_updates_label() {
var view = $(AboutView.class).single();
test($(Button.class).id("admin-edit")).click();
EventBus.get().post(new MessageEvent("Hello", LocalDateTime.now()));
waitWhile(view, v -> $(Notification.class).last() == null, 2);
var note = $(Label.class).id("admins-note");
assertEquals("Hello", note.getValue());
assertEquals("Hello", $(Notification.class).last().getDescription());
}These tests cover end-to-end flows inside a single JVM process: database updates, locking logic, event bus messages, responsive behavior, and assistive notifications—without a browser.
When you only need to test a single component or helper class, it’s faster and simpler to avoid VaadinCreateUI and backend services.
LoginViewTest extends UIUnitTest directly and creates a plain UI:
public class LoginViewTest extends UIUnitTest {
private UI ui;
@Before
public void setup() throws ServiceException {
ui = mockVaadin(); // Simple UI, no VaadinCreateUI
}
@After
public void cleanup() {
tearDown();
}
}The test injects a MockAccessControl to simulate authentication:
@Test
public void login_event_is_fired_on_success() {
var count = new AtomicInteger(0);
var accessControl = new MockAccessControl("Admin");
var login = new LoginView(accessControl, e -> count.incrementAndGet());
ui.setContent(login);
test(login.usernameField).setValue("Admin");
test(login.passwordField).setValue("Admin");
test($(login, LanguageSelect.class).single())
.clickItem(DefaultI18NProvider.LOCALE_EN);
test(login.login).click();
assertEquals(1, count.get());
assertTrue(accessControl.isUserSignedIn());
}This style is ideal when:
- You want to test validation, localization, or responsive behavior of a single view.
- Real database or event bus behavior isn’t relevant.
- You prefer deterministic tests without H2 test data.
TabNavigatorTest focuses purely on tab-based navigation. It builds the UI by hand:
public class TabNavigatorTest extends UIUnitTest {
private TabNavigator tabNavigator;
private UI ui;
@Before
public void setup() throws ServiceException {
ui = mockVaadin();
tabNavigator = new TabNavigator("");
tabNavigator.addTabView(new TestView1(), "View 1", VaadinIcons.HOME);
tabNavigator.addTabView(new TestView2(), "View 2", VaadinIcons.HOME);
ui.setContent(tabNavigator);
}
}Tests then assert fragment changes, listener callbacks, and error handling:
@Test
public void navigate_updates_uri_fragment_and_calls_listener() {
final boolean[] called = { false };
tabNavigator.addViewChangeListener(event -> {
called[0] = true;
assertEquals("test2", event.getNewView().getTabName());
assertEquals("test1", event.getOldView().getTabName());
});
tabNavigator.navigate("test2");
assertTrue(called[0]);
assertEquals("!/test2", ui.getPage().getUriFragment());
}Here, no VaadinCreateUI, no access control, and no backend are involved—only TabNavigator’s own logic.
Use integration-style tests with VaadinCreateUI when you want to:
- Verify complete user journeys (login → navigate → edit data → logout).
- Test interactions between views, application shell, access control, and backend.
- Assert behavior of locking, optimistic concurrency, or event-driven updates.
Use lightweight component tests with mock UI when you want to:
- Isolate one view or helper (e.g.
LoginView,TabNavigator). - Use mocks (
MockAccessControl) instead of real services. - Get very fast, deterministic feedback on UI logic and layout behavior.
Both approaches are fully browserless: tests run inside JUnit with no WebDriver, but still exercise Vaadin’s server-side component tree using the same APIs your application code uses.
- Prefer descriptive test names like
should_DoSomething_When_Condition(). - Use helpers (as in
AbstractUITest) to:- Log in, log out, and wait for grids or charts.
- Assert notifications and assistive announcements.
- Avoid sleeps; use
waitWhile(...)on components instead. - Keep tests focused on one behavior at a time, but don’t be afraid to run real backend logic where it adds value.