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.

By The Testing AcademyUpdated July 11, 20269 min read
All field guides
In this guide11 sections
  1. Know the Boundary Between Browser, Context, and Page
  2. Prepare Actor Identity and Shared Data Separately
  3. Create a Small Actor Helper
  4. Implement a Two-User Collaboration Test
  5. Synchronize Through Observable Handoffs
  6. Assert Both Actors' Views and Permissions
  7. Manage Context Lifecycle and Evidence
  8. Diagnose Multi-Context Failures
  9. Balance Realism Against Scenario Size
  10. Operational Checklist
  11. 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.

  1. 01 / shared browser

    Shared browser

    Reuse one engine process while keeping actor profiles independent.

  2. 02 / user a context

    User A context

    Load the first account state and perform the initiating action.

  3. 03 / cross user event

    Cross-user event

    Persist a backend change and deliver its notification or realtime update.

  4. 04 / user b context

    User B context

    Receive the event through a separate session and independent page.

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

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

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

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 11, 2026 / Reviewed July 11, 2026

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.

  1. 01
    Playwright documentation

    Microsoft

    Canonical API, locator, fixture, browser, and test-runner behavior.

  2. 02
    Playwright best practices

    Microsoft

    Official guidance for resilient tests, isolation, and user-facing locators.

  3. 03
    WebDriver standard

    W3C

    The browser automation protocol specification used by major automation stacks.

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.