Docs

Application, User, and Window Contexts

Browserless application, user, and window contexts — the main test API — with thread affinity, window activation, security isolation, single-component tests, factories, and configuration.

The BrowserlessApplicationContext API is the main way to write browserless tests. A test creates an application context, creates users from it, and opens windows for the users. Each window provides the testing methods — navigation, component queries, testers, and locators — and drives its own UI.

A typical test needs one user with one window:

Source code
Java
try (var app = BrowserlessApplicationContext.create(CartView.class)) {
    var window = app.newUser().newWindow();
    window.navigate(CartView.class);
    window.findButton().withText("Add to cart").click();
    // assertions...
}

The same API also covers tests that need several participants: application-wide state observed by two users, per-window UI state of the same user, or security context isolation when switching between authenticated users.

Contexts and Base Classes

The base classes — BrowserlessTest, SpringBrowserlessTest, and QuarkusBrowserlessTest — and the JUnit 6 extensions create one user with one window for each test method, or for each test class with BrowserlessClassExtension, and provide the same testing methods on the test class or the extension. They differ from an application context as follows:

  • A base class or an extension supports one user and one window. An application context creates as many as the test needs.

  • A base class reads its configuration from annotations, such as @ViewPackages, @ComponentTesterPackages, and @BrowserlessTestConfig. An application context takes its configuration from the code that creates it, and ignores those annotations.

  • A base class applies Spring Security and Quarkus test annotations, such as @WithMockUser and @TestSecurity, to its user. A secured application context gives each user the credentials passed to newUser().

  • A base class always recreates the environment before each test method. The test decides how long an application context lives.

The CDI integration extends a base class; the application context API doesn’t apply its custom setup.

Setting Up an Application Context

A test class that uses an application context creates it, opens the users and windows it needs, and closes it afterwards. The application context scans the package of the class passed to it, and its sub-packages, for routes. Closing the application context also closes every user and window created from it.

In Spring Boot, a test configuration in the package of the @SpringBootApplication class defines the application context as a bean. Every test that uses the same Spring application context shares the bean, and Spring closes it; each test closes only its own user.

Source code
Java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.vaadin.browserless.BrowserlessApplicationContext;
import com.vaadin.browserless.BrowserlessUIContext;

class MyViewTest {

    private BrowserlessApplicationContext app;
    private BrowserlessUIContext window;

    @BeforeEach
    void setUp() {
        app = BrowserlessApplicationContext.create(MyView.class);
        window = app.newUser().newWindow();
    }

    @AfterEach
    void tearDown() {
        app.close();
    }

    @Test
    void myTest() {
        MyView view = window.navigate(MyView.class);
        // Find components and simulate user actions through the window
    }
}
Java
Java
Java
Java
Java

Application, User, and Window

The API exposes three nested contexts that mirror Vaadin’s runtime hierarchy. The single-user Weld/CDI recipe does not automatically map these users to independent CDI session contexts.

The context types are:

BrowserlessApplicationContext

One VaadinService and one servlet, shared across every user and window the test creates.

BrowserlessUserContext

One VaadinSession and the security state of a single logical user. A user can own many windows.

BrowserlessUIContext

One UI instance — a single browser window. Provides the testing DSL (navigate, find, test, …​).

Note
All three contexts are thread-affine: within a single test, each context must be created, used, and closed on the same thread, and cannot be shared with other threads. Tests can still run in parallel — each test owns its own contexts.

When the test calls a DSL method on any BrowserlessUIContext, the API automatically switches the thread-local Vaadin state — VaadinService, VaadinSession, UI, request, response, and the security context — to that window’s user. Interleaving calls on different windows is therefore safe without any explicit context switch.

Application Context Lifecycle

The application context owns the users and windows it creates. It implements explicit resource cleanup through close(): closing the application context cascades to every user and window it created.

A test creates the context in a @BeforeEach method and closes it in an @AfterEach method, or creates it with try-with-resources in the test method itself. Creating it in a static @BeforeAll method shares it across the tests of a class; see Sharing the Vaadin Environment.

In Spring Boot, a test configuration can define the context as a bean, which every test that uses the same Spring application context shares, and which Spring closes together with the Spring application context. Each test then opens a user of its own, and closes it after the test; see Set Up Browserless Tests with Spring Boot. Closing a user ends its session and clears the Vaadin and security state from the thread.

create() accepts the packages that contain @Route-annotated views, either as package names or as classes whose packages should be scanned. Passing classes plays well with IDE refactoring and is the preferred form.

The SpringBrowserlessApplicationContext and QuarkusBrowserlessApplicationContext factories pre-wire the framework-specific servlet and lookup initialization. The Spring factory also accepts the Spring application context.

Creating Users and Windows

newUser() returns a fresh BrowserlessUserContext with its own VaadinSession. newWindow() creates a new UI for that user and navigates it to the root route, if one is registered. Different users have independent sessions; different windows of the same user share a session but have independent UI instances.

Every window exposes the testing DSL, scoped to that window — no explicit activation is required:

  • window.navigate(…​) — navigate the window to a view.

  • window.findButton(), window.findTextField(), window.findGrid(Class<V>), …​ — typed locator entry points that combine a filter chain with the component’s tester actions in one fluent expression; see Component Locators.

  • window.find(Class) and window.findInView(Class) — locate components in this window. Returns a ComponentQuery; see Querying Components.

  • window.test(component) — wrap a component in a tester that simulates user actions like click() and setValue(). Returns a typed tester matched to the component’s type; see Component Testers. To request a specific tester type explicitly — for example, a custom tester — use window.test(MyTester.class, component).

  • window.fireShortcut(Key, KeyModifier…​) — simulate a keyboard shortcut in the window.

  • window.runPendingSignalsTasks() — process pending signal effects before asserting; see Signals below.

  • window.getCurrentView() and window.roundTrip() — get the view the window shows, and simulate a server round trip.

Testing a Single Component

A component such as a form or a custom field can be tested on its own, without an @Route view that wraps it. The forComponent() factories create an application context without routes: no view packages are scanned, and the component is the only content of the window’s UI.

Factory Returns

BrowserlessUIContext.forComponent(Component)

A single window with the given instance attached. The component is constructed before any UI exists.

BrowserlessUIContext.forComponent(Supplier<Component>)

A single window with the component produced by the factory attached.

BrowserlessApplicationContext.forComponent(Supplier<Component>)

The application context. Every window created with newUser().newWindow() gets a component from the factory.

The factory runs from UI.init(), after the window’s UI, session, and security context are installed, so a constructor that calls UI.getCurrent() sees the live environment. Use a factory instead of an instance when the constructor needs the UI or the session.

Source code
Java
try (var window = BrowserlessUIContext.forComponent(MyForm::new)) {
    window.findTextField().withLabel("Name").setValue("Ada");
}

The BrowserlessUIContext variants are single-window shorthands. Use BrowserlessApplicationContext.forComponent() when the test needs several windows or users. Make its factory return a new instance on each call: attaching the same instance to a second window moves it there and leaves the first window empty.

Source code
Java
try (var app = BrowserlessApplicationContext.forComponent(MyForm::new)) {
    var ada = app.newUser().newWindow();
    var bob = app.newUser().newWindow();
    // Each window holds its own MyForm instance
}

The context created by forComponent() closes itself when a window’s UI is detached, so closing either the returned window or the returned application context tears down the whole environment.

Configuring the Environment

Each factory has an overload that takes a BrowserlessConfiguration as the first argument. The configuration sets application properties, feature flags, and Lookup services the same way as withConfiguration() on the builder, and it is already in effect when the component factory runs:

Source code
Java
var configuration = BrowserlessConfiguration.builder()
        .withFeatureFlags("defaultAutoResponsiveFormLayout")
        .withApplicationProperty("devmode.sessionSerialization.enabled", "true")
        .build();
try (var window = BrowserlessUIContext.forComponent(configuration,
        MyForm::new)) {
    // ...
}

Like any application context, these factories don’t read @BrowserlessTestConfig. Pass BrowserlessConfiguration.from(getClass()) to reuse the configuration that the test class declares. See Configuring without Annotations.

Signals

The application context registers the test SignalEnvironment, so signal effects run deterministically instead of on a background thread pool. For signal testing in single-user tests — and a fuller treatment of synchronous propagation — see Signal Test Environment. When one window mutates a signal that other windows observe — the typical pattern for collaborative features built on shared signals — call runPendingSignalsTasks() to process the pending effects before asserting on the observing window.

See Signal Task Processing for waiting, return values, and session-lock handling.

The same call also confirms shared-signal writes made through the window: the SignalOperation returned by a write completes only after the queue has been drained. Write the signal while the window’s thread-locals are active — either from within a DSL call or after window.activate() — so that the confirmation is dispatched through the window’s UI. See Confirming a Write for details.

Authenticated Users with Spring Security

When Spring Security is on the classpath, SpringBrowserlessApplicationContext.createSecured() returns a SecuredBrowserlessApplicationContext<Authentication> that exposes credential-aware newUser(…​) overloads. The handler installs the user’s Authentication on the calling thread before SessionInit listeners fire, mirroring the order of operations in a real Vaadin + Spring Security request.

When the test switches between windows belonging to different users, the outgoing user’s SecurityContext is saved and the incoming user’s snapshot is restored automatically.

newUser(String username, String…​ roles) is a convenience that produces an Authentication with the conventions of @WithMockUser. To install a custom Authentication directly, pass it to newUser(Authentication). Calling newUser() without arguments creates an anonymous user; the handler installs Spring’s AnonymousAuthenticationToken. Spring Security test annotations, such as @WithMockUser, don’t affect the users of a secured context.

A logout performed in one window leaves the user logged out in all of that user’s windows, both already open and opened afterwards. To authenticate again, create a fresh user context with newUser(…​) rather than reusing the logged-out one.

Authenticated Users with Quarkus Security

The Quarkus factory follows the same pattern with SecurityIdentity as the credential type.

As with the Spring factory, newUser() without arguments creates an anonymous user, and cross-user window switches save and restore the active SecurityIdentity automatically.

Customizing the Application Context

For advanced setups, BrowserlessApplicationContext.Builder exposes the same configuration knobs as the framework-specific factories. Construct one directly and call build() for an unsecured context, or chain withSecurityContextHandler(…​) to transition to a typed SecuredBrowserlessApplicationContext.Builder<C> whose build() returns the credential-aware variant.

Table 1. Builder Configuration
Method Purpose

withViewPackages(String…​) / withViewPackages(Class<?>…​)

Adds packages to scan for @Route-annotated views. Successive calls accumulate.

withoutRoutes()

Disables @Route discovery, so building the context skips the classpath scan. View packages are ignored once this is set. The forComponent() factories use it.

withComponentTesterPackages(String…​) / withComponentTesterPackages(Class<?>…​)

Adds packages to scan for custom ComponentTester implementations.

withSecurityContextHandler(SecurityContextHandler<C>)

Enables credential-aware newUser(…​) overloads. Transitions to the typed SecuredBrowserlessApplicationContext.Builder<C>.

withServletFactory(…​)

Replaces the default mock servlet — for example, to plug in a framework-specific servlet.

withUIFactory(UIFactory)

Provides a custom UI subclass for every window created by the context.

withLookupServices(Class<?>…​)

Registers Vaadin Lookup service implementations.

withCloseHook(Runnable)

Registers a callback to run after the context tears down — intended for releasing framework-specific state.

withApplicationProperty(String, String) / withApplicationProperties(Map)

Sets Vaadin application properties for the environment the context creates.

withFeatureFlags(String…​) / withFeatureFlag(String, boolean)

Enables or disables feature flags for the environment the context creates. Both methods also have a Feature overload.

withConfiguration(BrowserlessConfiguration)

Applies a configuration built elsewhere as the baseline that the other methods add to, such as BrowserlessConfiguration.from(getClass()) to reuse what the test class declares with @BrowserlessTestConfig.

The property, feature flag, and configuration methods are the programmatic form of @BrowserlessTestConfig, which the context itself does not read; see Configuring without Annotations. SecuredBrowserlessApplicationContext.Builder exposes the same methods.

For one-off tweaks without holding on to a builder reference, BrowserlessApplicationContext.create(UnaryOperator<Builder>) and the corresponding createSecured(Function<Builder, SecuredBuilder<C>>) accept a configurer:

Source code
Java
try (var app = BrowserlessApplicationContext.create(b -> b
        .withViewPackages(CartView.class)
        .withCloseHook(() -> testFixtures.reset()))) {
    // ...
}

The Spring and Quarkus factories expose builder(…​) overloads that return a pre-wired builder for projects that need to add further customizations on top of the framework defaults.

Pitfalls and Guarantees

Common gotchas worth keeping in mind:

  • Always close the application context. Use try-with-resources or call close() in @AfterEach. Closing the application cascades to every user and window. Leaking a context across tests leaves Vaadin thread-locals pointing at torn-down state.

  • No parallel access. Every context is thread-affine. Driving the same BrowserlessApplicationContext from multiple threads in the same test is unsupported.

  • Security snapshot is per user, not per window. Two windows of the same user share one snapshot; a security mutation made while one window is active — including a logout — is visible to the user’s other windows, both already open and opened afterwards. The snapshot is re-captured only on cross-user switches.

  • Calling Vaadin APIs directly. DSL methods on BrowserlessUIContext activate the window automatically. If the test reaches for UI.getCurrent(), VaadinSession.getCurrent(), or SecurityContextHolder between DSL calls, call window.activate() first to make sure the thread-locals reflect the intended window. A window stays active after it’s created and after each DSL call, so in a test with a single window these APIs already reflect that window.

  • Spring request and session scopes follow the user. A @SessionScope or @RequestScope bean is resolved against the active user’s own request, so two users never share one instance. @VaadinSessionScope has always behaved this way, because it resolves against VaadinSession.getCurrent(). See Session-Scoped Beans for why such a bean can’t be autowired into a test class field. Resolving it from the test method instead gives the active user’s instance, so activate the intended user’s window first.

  • Anonymous users still go through the handler. On a secured context, newUser() with no arguments delegates to the handler, which installs its anonymous-equivalent state (for example, Spring’s AnonymousAuthenticationToken).

For worked examples, see the setup guides for a single user, and Test Multiple Users and Windows for several.

9C4E8A2D-6F31-4B72-9A8F-5D7E1C3B4F60

Updated –