Skip to content

How to Write Browserless UI Tests with Vaadin 8

Tatu Lund edited this page Feb 6, 2026 · 1 revision

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 VaadinCreateUI with an in-memory H2 backend (e.g. BooksViewTest, AdminViewTest, AboutViewTest).
  • Lightweight component tests that use a mock UI and mock access control (e.g. LoginViewTest, TabNavigatorTest).

Both styles use the same tools:

  • UIUnitTest base class
  • mockVaadin(...) to create a headless Vaadin environment
  • $ query API to locate components
  • test(component) helpers to interact with components like a user

1. Core Testing Building Blocks

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.

2. Integration-Style Tests with Real VaadinCreateUI

These tests spin up the real application UI (VaadinCreateUI) including:

  • AppLayout application shell
  • Navigator and view registration
  • Access control and session handling
  • H2-based backend services and event bus

2.1 Bootstrapping the UI and Logging In

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 constructed VaadinCreateUI to 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.

2.2 Interacting with Views and Verifying Behavior

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.


3. Lightweight Component Tests with Mock UI

When you only need to test a single component or helper class, it’s faster and simpler to avoid VaadinCreateUI and backend services.

3.1 LoginView with MockAccessControl

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.

3.2 TabNavigator Without the Application Shell

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.


4. Choosing Between Integration and Unit-Style UI Tests

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.


5. Practical Tips

  • 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.

Clone this wiki locally