> Markdown version of [Test Multiple Users and Windows](https://vaadin.com/docs/next/building-apps/testing/browserless/test-multiple-users). Section index: [llms.txt](https://vaadin.com/docs/next/building-apps/llms.txt)

# Test Multiple Users and Windows

Use this guide when the behavior under test depends on more than one user or window. The examples cover the plain Java, Spring, and Quarkus setups; complete the [Spring](https://vaadin.com/docs/next/building-apps/testing/browserless/setup-spring-boot.md), [plain Java](https://vaadin.com/docs/next/building-apps/testing/browserless/setup-without-spring.md), or [Quarkus](https://vaadin.com/docs/next/building-apps/testing/browserless/setup-quarkus.md) setup first. The [Java EE/CDI setup](https://vaadin.com/docs/next/building-apps/testing/browserless/setup-cdi.md) supports one user per test, so these examples don’t apply to it.

The examples are patterns to adapt: `CartView`, `CheckoutView`, `SharedCounterView`, and `ChatView` represent your application views. For security scenarios, reuse the access-control configuration from [Test View Access Control](https://vaadin.com/docs/next/building-apps/testing/browserless/test-view-access.md).

## <a id="set-up-the-application-context"></a>Set Up the Application Context

Each test gets its own application context, created in one of two ways:

- In the test method, with try-with-resources, when only that test uses the context. The plain Java example below does this.

- In a `@BeforeEach` method, and closed with `close()` in an `@AfterEach` method, when every test in the class uses a context. The Spring and Quarkus examples do this.

Closing the application context cascades to every user and window it created.

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

Plain Java

```java
try (var app = BrowserlessApplicationContext.create(CartView.class)) {
    var user = app.newUser();
    var window = user.newWindow();
    window.navigate(CartView.class);
    // assertions...
}
```

For Spring and Quarkus, dedicated factories pre-wire the framework-specific servlet and lookup initialization:

Spring

```java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.vaadin.browserless.BrowserlessApplicationContext;
import com.vaadin.browserless.SpringBrowserlessApplicationContext;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ShopTestConfig.class)
class CartViewMultiUserTest {

    @Autowired
    private ApplicationContext applicationContext;

    private BrowserlessApplicationContext app;

    @BeforeEach
    void setUp() {
        app = SpringBrowserlessApplicationContext.create(applicationContext,
                CartView.class);
    }

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

Quarkus

```java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import com.vaadin.browserless.BrowserlessApplicationContext;
import com.vaadin.browserless.quarkus.QuarkusBrowserlessApplicationContext;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
class CartViewMultiUserTest {

    private BrowserlessApplicationContext app;

    @BeforeEach
    void setUp() {
        app = QuarkusBrowserlessApplicationContext.create(CartView.class);
    }

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

## <a id="create-users-and-windows"></a>Create Users and Windows

`newUser()` returns a fresh `BrowserlessUserContext` with its own `VaadinSession`. `newWindow()` creates a new `UI` for that user. Different users have independent sessions; different windows of the same user share a session but have independent `UI` instances.

Two Users, Independent Sessions

```java
var alice = app.newUser();
var aliceWindow = alice.newWindow();

var bob = app.newUser();
var bobWindow = bob.newWindow();

Assertions.assertNotSame(alice.getSession(), bob.getSession());
Assertions.assertNotSame(aliceWindow.getUI(), bobWindow.getUI());
```

Use the window instance to navigate, find components, and perform actions.

Two Users Sharing Application-Level State

```java
var w1 = app.newUser().newWindow();
w1.navigate(SharedCounterView.class);

var w2 = app.newUser().newWindow();
w2.navigate(SharedCounterView.class);

// w1 mutates a shared static counter
w1.findButton().withText("Increment").click();
Assertions.assertEquals("Count: 1", w1.findParagraph().getText());

// w2 still shows its own UI state until it refreshes
Assertions.assertEquals("Count: 0", w2.findParagraph().getText());

w2.findButton().withText("Refresh").click();
Assertions.assertEquals("Count: 1", w2.findParagraph().getText());
```

Same User, Two Windows, Independent UI State

```java
var user = app.newUser();
var w1 = user.newWindow();
var w2 = user.newWindow();

w1.navigate(CartView.class);
w2.navigate(CheckoutView.class);

// Each window holds its own current view
Assertions.assertInstanceOf(CartView.class, w1.getCurrentView());
Assertions.assertInstanceOf(CheckoutView.class, w2.getCurrentView());

// Session is the same; UIs are not
Assertions.assertSame(user.getSession(), w1.getUI().getSession());
Assertions.assertNotSame(w1.getUI(), w2.getUI());
```

## <a id="signals"></a>Share Signals between Users

The application context registers the test `SignalEnvironment`, so signal effects run deterministically instead of on a background thread pool. For single-user examples, see [Test Signal-Based Views](https://vaadin.com/docs/next/building-apps/testing/browserless/test-signals.md). 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:

Two Users Observing a Shared Signal

```java
var w1 = app.newUser().newWindow();
w1.navigate(ChatView.class);

var w2 = app.newUser().newWindow();
w2.navigate(ChatView.class);

// w1 updates a shared signal that both views are bound to
w1.findTextField().withLabel("Message").setValue("Hello!");
w1.findButton().withText("Send").click();

// Process the pending signal effects, then assert on the other window
w2.runPendingSignalsTasks();
Assertions.assertEquals("Hello!", w2.findParagraph().getText());
```

For background updates and write confirmation, see [Test Signal-Based Views](https://vaadin.com/docs/next/building-apps/testing/browserless/test-signals.md).

## <a id="test-authenticated-users-with-spring-security"></a>Test Authenticated Users with Spring Security

Create a secured application context, then interleave actions from an administrator and an anonymous user. Assert that the anonymous user is redirected while the administrator retains access. The example assumes a `ProtectedView` that requires a logged-in user, and an `AdminRoleView` that requires the `ADMIN` role. The context uses the `TestViewSecurityConfig` from [Test View Access Control](https://vaadin.com/docs/next/building-apps/testing/browserless/test-view-access.md#spring), which registers view access control with a login view. Keep `LoginView` and the protected views in a package that the secured context scans, such as the package of `ProtectedView`.

Multi-User Security Isolation

```java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.vaadin.browserless.SecuredBrowserlessApplicationContext;
import com.vaadin.browserless.SpringBrowserlessApplicationContext;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestViewSecurityConfig.class)
class MultiUserSecurityTest {

    @Autowired
    private ApplicationContext applicationContext;

    private SecuredBrowserlessApplicationContext<Authentication> app;

    @BeforeEach
    void setUp() {
        app = SpringBrowserlessApplicationContext.createSecured(
                applicationContext, ProtectedView.class);
    }

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

    @Test
    void switchingUsers_securityContextFollowsActiveWindow() {
        var admin = app.newUser("john", "ADMIN").newWindow();
        var anon = app.newUser().newWindow();

        admin.navigate(ProtectedView.class);
        Assertions.assertInstanceOf(ProtectedView.class,
                admin.getCurrentView());

        // Switching to the anonymous user restores their (empty) context;
        // the protected view redirects to login.
        Assertions.assertThrows(IllegalArgumentException.class,
                () -> anon.navigate(ProtectedView.class));
        Assertions.assertInstanceOf(LoginView.class, anon.getCurrentView());

        // Switching back restores admin's authentication, including the
        // ADMIN role that AdminRoleView requires.
        admin.navigate(AdminRoleView.class);
        Assertions.assertInstanceOf(AdminRoleView.class,
                admin.getCurrentView());
    }
}
```

For custom credentials, anonymous users, and logout behavior, see [Authenticated Users with Spring Security](https://vaadin.com/docs/next/flow/testing/browserless/multi-user.md#authenticated-users-with-spring-security).

## <a id="test-authenticated-users-with-quarkus-security"></a>Test Authenticated Users with Quarkus Security

The Quarkus factory follows the same pattern with `SecurityIdentity` as the credential type. The test uses the `ViewSecurityTestProfile` test profile from [Test View Access Control](https://vaadin.com/docs/next/building-apps/testing/browserless/test-view-access.md#quarkus), which registers view access control:

Quarkus Multi-User Test

```java
import java.util.Set;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.vaadin.browserless.SecuredBrowserlessApplicationContext;
import com.vaadin.browserless.quarkus.QuarkusBrowserlessApplicationContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;

@QuarkusTest
@TestProfile(ViewSecurityTestProfile.class)
class MultiUserSecurityTest {

    private SecuredBrowserlessApplicationContext<SecurityIdentity> app;

    @BeforeEach
    void setUp() {
        app = QuarkusBrowserlessApplicationContext
                .createSecured(ProtectedView.class);
    }

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

    @Test
    void authenticatedUser_byUsernameAndRoles_seesProtectedView() {
        var window = app.newUser("john", "USER").newWindow();

        window.navigate(ProtectedView.class);
        Assertions.assertInstanceOf(ProtectedView.class,
                window.getCurrentView());
    }

    @Test
    void authenticatedUser_byIdentity_seesProtectedView() {
        SecurityIdentity identity = QuarkusSecurityIdentity.builder()
                .setPrincipal(new QuarkusPrincipal("john"))
                .addRoles(Set.of("USER"))
                .setAnonymous(false)
                .build();

        var window = app.newUser(identity).newWindow();

        window.navigate(ProtectedView.class);
        Assertions.assertInstanceOf(ProtectedView.class,
                window.getCurrentView());
    }

    @Test
    void anonymousUser_protectedView_redirectToLogin() {
        var window = app.newUser().newWindow();

        Assertions.assertThrows(IllegalArgumentException.class,
                () -> window.navigate(ProtectedView.class));
        Assertions.assertInstanceOf(LoginView.class,
                window.getCurrentView());
    }
}
```

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

## <a id="keep-tests-independent"></a>Keep Tests Independent

Close the application context after each test and reset application-owned shared data. Create and use each context on the same test thread. See [context guarantees](https://vaadin.com/docs/next/flow/testing/browserless/multi-user.md#pitfalls-and-guarantees) for thread affinity, direct API access, and security-state ownership.

`B92B85CC-5CFD-4B22-8BA6-B61CC1903D7B`
