PRACTICAL GUIDE / playwright multiple browser contexts
Model Multi-User Workflows with Isolated Playwright Browser Contexts
Model Playwright multi-user workflows with isolated browser contexts, actor-specific state, deterministic handoffs, parallel-safe data, and cleanup.
In this guide11 sections
- Know the Boundary Between Browser, Context, and Page
- Prepare Actor Identity and Shared Data Separately
- Create a Small Actor Helper
- Implement a Two-User Collaboration Test
- Synchronize Through Observable Handoffs
- Assert Both Actors' Views and Permissions
- Manage Context Lifecycle and Evidence
- Diagnose Multi-Context Failures
- Balance Realism Against Scenario Size
- Operational Checklist
- Action Plan
What you will learn
- Know the Boundary Between Browser, Context, and Page
- Prepare Actor Identity and Shared Data Separately
- Create a Small Actor Helper
- Implement a Two-User Collaboration Test
Multi-user tests are distributed-system tests inside one Playwright worker. Two pages may be visible on the same machine, but the important boundaries are identity, backend data, event delivery, and cleanup. Model each actor with a separate browser context and make every cross-user handoff observable.
A shared browser process is efficient; shared browser state is not. Contexts let an author and reviewer, buyer and seller, or two chat participants operate concurrently without carrying the same cookies. The test then coordinates their effects through the application, just as real users do.
Know the Boundary Between Browser, Context, and Page
The browser is the launched engine process. A browser context is an incognito-like profile with its own cookies and storage. A page is a tab or popup inside one context. Creating two pages in one context gives one identity two tabs, not two users.
The Playwright isolation guide describes contexts as clean-slate environments that isolate cookies, local storage, and session storage. Playwright normally creates one context per test. A multi-user scenario intentionally creates additional contexts within that test while preserving the same isolation principle between actors.
Animated field map
Two Users Sharing One Browser Process
Separate contexts preserve actor identity while a real backend event carries one user's action to the other user's independent page.
01 / shared browser
Shared browser
Reuse one engine process while keeping actor profiles independent.
02 / user a context
User A context
Load the first account state and perform the initiating action.
03 / cross user event
Cross-user event
Persist a backend change and deliver its notification or realtime update.
04 / user b context
User B context
Receive the event through a separate session and independent page.
05 / independent assertions
Independent assertions
Verify both actors' permissions, state, and visible outcomes.
Prepare Actor Identity and Shared Data Separately
Give each actor a named storage-state file or an authentication fixture. Identity setup should answer "who is this?" Shared scenario setup should answer "what room, document, ticket, or order can both users access?" Keeping those concerns separate makes failures easier to assign.
Create a unique shared record through an API before opening pages. A random identifier or server-generated ID prevents parallel tests from entering the same chat room or approving the same submission. Grant each dedicated account the intended access, and return the record to a neutral baseline in cleanup.
Avoid using the same mutable account in unrelated multi-user tests. Even with separate contexts, two tests can change that account's preferences, unread count, active session, or tenant membership at the server. Worker-owned account pools or immutable actor accounts reduce this interference.
Create a Small Actor Helper
Wrap context creation so every actor receives the same base URL and diagnostics while choosing a distinct storage state. Return both context and page because the caller must close the context and may need context-level events.
import type { Browser, BrowserContext, Page } from "@playwright/test";
type Actor = {
context: BrowserContext;
page: Page;
};
async function openActor(
browser: Browser,
baseURL: string,
storageState: string,
): Promise<Actor> {
const context = await browser.newContext({
baseURL,
storageState,
});
const page = await context.newPage();
return { context, page };
}Manual contexts do not automatically become the test's default context or page fixture. Pass every required option explicitly, including locale, permissions, or network routes when those belong to the scenario. Do not assume all project use settings were inherited by a direct browser.newContext() call.
Implement a Two-User Collaboration Test
The following scenario creates a unique room through the API, opens Alice and Bob in separate contexts, confirms both are ready, and then verifies delivery of Alice's message to Bob. The server, not a shared page variable, carries the event.
import { expect, test } from "@playwright/test";
test("a message from Alice appears for Bob", async ({
browser,
request,
}, testInfo) => {
const baseURL = testInfo.project.use.baseURL;
if (typeof baseURL !== "string") {
throw new Error("This scenario requires a configured baseURL.");
}
const createRoom = await request.post("/api/test-support/rooms", {
data: { purpose: "multi-user-message" },
});
expect(createRoom.ok()).toBeTruthy();
const { id: roomId } = (await createRoom.json()) as { id: string };
const alice = await openActor(
browser,
baseURL,
"playwright/.auth/alice.json",
);
const bob = await openActor(
browser,
baseURL,
"playwright/.auth/bob.json",
);
try {
await Promise.all([
alice.page.goto(`/rooms/${roomId}`),
bob.page.goto(`/rooms/${roomId}`),
]);
await expect(alice.page.getByText("Bob is online")).toBeVisible();
await expect(bob.page.getByText("Alice is online")).toBeVisible();
const message = `review-${testInfo.workerIndex}-${testInfo.retry}`;
await alice.page.getByLabel("Message").fill(message);
await alice.page.getByRole("button", { name: "Send" }).click();
await expect(
bob.page.getByTestId("message-list").getByText(message),
).toBeVisible();
await expect(alice.page.getByText(message)).toHaveAttribute(
"data-delivery",
"delivered",
);
} finally {
await Promise.all([
alice.context.close(),
bob.context.close(),
request.delete(`/api/test-support/rooms/${roomId}`),
]);
}
});In a real suite, use a server-generated unique message or record identifier rather than worker and retry numbers alone. The example's key property is that both actors reach a ready state before the send, and each assertion is scoped to the correct page.
Synchronize Through Observable Handoffs
Cross-user workflows have at least two asynchronous boundaries: persistence of the first actor's action and delivery to the second actor. A visible success toast for Alice proves only local acknowledgement. Bob's notification, API state, or realtime update proves propagation.
Use web-first assertions for UI delivery and response assertions for server handoffs. When delivery can legitimately be delayed, choose a timeout based on the system's service objective and report elapsed time as diagnostic context. Do not add waitForTimeout because it either wastes fast runs or still loses slow races.
For approval workflows, let the requester wait for "submitted," let the approver locate the specific submission ID, and let the requester observe "approved." For collaborative editing, assert revision identifiers or saved content rather than cursor animation. Precise handoffs keep the test from confusing eventual consistency with locator flakiness.
Assert Both Actors' Views and Permissions
One-sided assertions miss important defects. After Bob receives Alice's message, Alice should see the delivery state. After an administrator approves a request, the member should see the new status while losing any action that is no longer valid. The pair of views demonstrates convergence.
Also preserve permission boundaries. Bob should not acquire Alice's private controls simply because both can access the room. Use the request objects associated with each context when verifying actor-specific API permissions. Never use one global privileged request fixture for assertions that are supposed to represent a lower-privilege user.
Keep the scenario's assertion set focused. A multi-user test already has more possible failure locations than a single-page test. Verify the cross-user contract and move unrelated layout or form-validation checks into simpler tests.
Manage Context Lifecycle and Evidence
Close contexts even after an assertion fails. A finally block works for a few scenarios; custom fixtures are better when many tests use the same actors. Fixture teardown can close contexts, attach a filtered actor log, and release leased accounts consistently.
Name evidence by actor. A screenshot called failure.png is ambiguous when two pages are open. Attach alice-after-send.png and bob-delivery-timeout.png, and annotate traces or steps with the context role. Redact chat content or customer data according to the same policy used for production-like test environments.
Server cleanup should not depend on either user's browser still being authenticated. Use a dedicated test-support API or cleanup identity with the least required scope, and make deletion idempotent. If cleanup fails, report the record ID so an automated sweeper can remove it later.
Diagnose Multi-Context Failures
If both pages show the same user, confirm they were created from different contexts and state files. Two tabs from one context share identity. If one actor opens logged out, inspect that actor's state expiry, domain, and base URL rather than rerunning both logins blindly.
If the initiating action succeeds but the second page never updates, separate persistence from delivery. Query the shared record through a diagnostic API: missing state points to the first operation, present state points to notification, subscription, or client rendering. Check that the receiving page completed its subscription before the action.
Parallel-only failures usually indicate shared backend resources, not browser-context leakage. Look for reused room names, one-time invitations, account session limits, unread counters, and cleanup races. Context isolation cannot protect data stored under the same server identity.
Balance Realism Against Scenario Size
Two real contexts provide strong evidence for collaboration and role handoff, but they consume more memory and make traces harder to read. API setup plus one browser is better when the other actor's UI is irrelevant. Full multi-context coverage belongs on a small number of business-critical interactions.
Use direct API calls to create preconditions, not to replace the event under test. If the requirement is "reviewer sees an author's submission," create the document through API if authorship UI is irrelevant, then use two contexts only when both visible experiences matter. The test's shape should follow the claim.
Operational Checklist
- Represent every user with a separate browser context and named state.
- Pass required context options explicitly to manual contexts.
- Create unique shared records for each test execution.
- Confirm both actors are ready before triggering the handoff.
- Synchronize on persisted state, notifications, or visible updates.
- Scope every locator and API assertion to the intended actor.
- Verify convergence from both sides of the workflow.
- Close all contexts and delete server records after failures.
- Label screenshots, traces, and steps by actor.
- Investigate backend account and data collisions in parallel failures.
Action Plan
Choose one workflow where two visible users are essential. Prepare distinct state files, create a unique shared record through the API, and build a small actor helper that returns context and page. Add readiness checks, perform one cross-user action, and assert the result from both pages. Wrap context and record cleanup around the scenario, then run it repeatedly in parallel. Expand only after identity, handoff, and cleanup remain attributable under failure.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Playwright documentation
Microsoft
Canonical API, locator, fixture, browser, and test-runner behavior.
- 02Playwright best practices
Microsoft
Official guidance for resilient tests, isolation, and user-facing locators.
- 03
FAQ / QUICK ANSWERS
Questions testers ask
Why should Playwright multi-user tests use separate browser contexts?
Each context has isolated cookies, local storage, session storage, permissions, and pages. That lets two actors share one browser process without accidentally sharing an authenticated identity.
Are two Playwright pages enough to represent two users?
Only if the pages belong to different browser contexts. Two tabs in one context use the same browser storage and normally represent the same signed-in user.
How should two users synchronize in a Playwright test?
Wait on observable application handoffs such as a status change, message, API response, or notification. Avoid fixed delays; establish that both actors are ready before the first actor triggers the cross-user event.
Can multiple Playwright contexts run in parallel in one test?
Yes, but backend data and accounts still need isolation. Give the scenario unique room, document, or order identifiers, and avoid accounts whose preferences or sessions conflict under concurrent use.
How should manually created browser contexts be cleaned up?
Close every context in fixture teardown or a finally block, and clean server records through a reliable API path. Cleanup should run even when the interaction or assertion fails.
RELATED GUIDES
Continue the learning route
GUIDE 01
Multi-Role Authentication in Playwright with Separate storageState Files
Set up multi-role Playwright authentication with separate storageState files, isolated projects, safe credentials, and reliable permission checks.
GUIDE 02
Handle Popups and Multi-Page Journeys in Playwright Without Races
Capture Playwright popups before the click, coordinate several tabs, assert the right readiness signal, and diagnose multi-page test races.
GUIDE 03
20 Playwright Context, Page, Popup, and Frame Interview Scenarios
Solve 20 senior Playwright browser topology scenarios covering context isolation, multiple pages, popups, frames, permissions, events, and cleanup.
GUIDE 04
Build a Worker-Scoped Account Pool for Parallel Playwright Tests
Build a worker-scoped Playwright account pool with atomic leases, stable parallel identity, isolated browser contexts, and resilient cleanup.