Test Multiple Users and Windows
- Create Users and Windows
- Share Signals between Users
- Test Authenticated Users with Spring Security
- Test Authenticated Users with Quarkus Security
- Keep Tests Independent
Use this guide when the behavior under test depends on more than one user or window.
Every setup guide creates an application context and opens one window for one user; a multi-user test creates more users and windows from the same application context.
Complete the Spring, plain Java, or Quarkus setup first.
The examples use an app field for an application context that each test creates in a @BeforeEach method and closes in an @AfterEach method, which closes all of its users and windows at once.
In Spring Boot, create it from the Spring application context as described in Create an Application Context for Each Test, instead of using the shared bean.
The Java EE/CDI setup 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.
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.
Source code
Two Users, Independent Sessions
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.
Source code
Two Users Sharing Application-Level State
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());Source code
Same User, Two Windows, Independent UI State
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());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. 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:
Source code
Two Users Observing a Shared Signal
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.
Test Authenticated Users with Spring Security
Create a secured application context as in Test View Access Control, 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, 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.
Source code
Multi-User Security Isolation
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.
Test Authenticated Users with Quarkus Security
The Quarkus factory follows the same pattern with SecurityIdentity as the credential type.
Create the secured context and its users as in Test View Access Control, then interleave actions from the users' windows as in the Spring example.
As with the Spring factory, cross-user window switches save and restore the active SecurityIdentity automatically.
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 for thread affinity, direct API access, and security-state ownership.
B92B85CC-5CFD-4B22-8BA6-B61CC1903D7B