Test Automation

Test Isolation: Browser Contexts vs Backend Data and Shared State

Test Isolation: Browser Contexts vs Backend Data and Shared State

A checkout test opens a fresh browser context, signs in, and adds a product to its cart. Another test opens a different context and signs in with the same saved account. Both browsers are isolated, but both requests can still reach the same server-side cart. When the second test empties it, the first test loses the item it expected to purchase.

The browser reset worked. The test-data boundary did not cover the cart. A shared feature flag can create a similar collision even after the tests use different accounts: changing the checkout variant globally can change what both tests see.

Effective test isolation requires control over every mutable input that can affect the assertion. Browser contexts, isolated test data, and scoped flag evaluation address different inputs. Combine them according to where the application stores state and which other tests or processes can change it.

Table of Contents

Compare the Boundaries Before Choosing a Reset

An independent test can establish its own prerequisites and reach the same expected result without depending on another test's leftover state. Independence also needs to hold while neighboring tests run, not only after cleanup has finished.

The comparison below separates three boundaries. Backend-data isolation means assigning records and identities to a test or other explicit owner, rather than assuming the browser framework provisions a separate database. Feature-flag isolation means controlling the evaluation inputs or configuration used by that test, rather than repeatedly changing an environment-wide value.

Criterion

Browser-Context Isolation

Backend-Data Isolation

Feature-Flag Isolation

Owned Boundary

Browser-managed state in the configured context

Records, accounts and tenant data assigned to the test

Flag evaluation inputs or configuration assigned to the test

Starting State

New context or documented framework reset, with deliberate auth preload

Owned records provisioned to known values

Declared variation through supported targeting or isolated evaluation

What Still Leaks

Shared accounts, server records and global settings

Unscoped queries, shared queues and global configuration

Global flag changes, shared evaluation identities and unscoped rules

Prerequisites

Correct runner settings and explicit storage assumptions

Application-supported ownership and scoped reads and writes

Provider and application support for the chosen evaluation scope

Cleanup Responsibility

Dispose owned context; handle stores outside the framework reset

Delete only owned records and track incomplete cleanup

Remove only owned targets or dispose the isolated flag environment

These boundaries usually complement one another. A fresh browser prevents a previous test's browser session from becoming an accidental prerequisite. An owned account and cart prevent another test from editing the same records. A supported flag target or isolated evaluator keeps the chosen checkout variant stable. None of those controls automatically supplies the others.

There is also a limit to identity-based isolation. Separate users can still query the same shared inventory, update a tenant-wide setting, or consume messages from a shared queue. Trace the state used by the assertion beyond the login identity before declaring the tests independent.

What a Fresh Browser Does and Does Not Reset

Start with the framework's documented behavior and the configuration the suite uses. The phrase browser test isolation does not describe an identical storage-reset contract in every tool, and it says nothing by itself about the application database.

Playwright Contexts and Saved Authentication

Playwright Test creates a separate browser context for each test by default. Its Isolation documentation describes independent cookies, local storage, and session storage, using an incognito-like profile as the analogy. When using the Playwright library directly, creating and disposing the necessary contexts is an explicit responsibility.

Loading saved authentication deliberately changes the starting point. A context initialized from storageState can begin signed in instead of showing a login page. Separate contexts can load authentication for the same backend account, so private browser storage does not imply a private user, cart, preferences record, or order history.

Saved authentication is not a database snapshot. Restoring it does not undo purchases, restore an account preference, or remove records created by an earlier test. Playwright's authentication guide recommends shared-account reuse only when tests can run together without affecting one another. For tests that modify shared server-side state, it describes a separate account per parallel worker.

That worker-account pattern still needs application-specific data design. Sequential tests using the same worker account may inherit its modified preferences unless setup restores the required values. Concurrent jobs must also avoid allocating the same account pool slot without coordination. Scope the account allocation to the runners that can overlap, not merely to a worker number that may recur elsewhere.

Cypress Reset Depends on the Test Mode

With end-to-end test isolation enabled, current Cypress documentation says it clears the DOM by visiting about:blank and clears cookies, localStorage, and sessionStorage across domains before each test. The test must then visit the application and establish its required state.

With end-to-end isolation disabled, Cypress leaves the page and those browser stores available across tests in the suite. Component testing has a different contract: Cypress always unmounts the component and clears the listed stores, and that isolation behavior is not configurable in the same way.

The current guide explicitly states that IndexedDB and other browser storage mechanisms are not cleared, even with test isolation enabled. It also limits cy.session caching and restoration to cookies, localStorage, and sessionStorage. An application that keeps relevant state in IndexedDB needs a deliberate strategy for that state; a passing cookie-reset check is insufficient.

Avoid expanding either framework's short reset description into an assumed inventory of every browser facility. If an assertion depends on a service worker or another persistence mechanism, verify that mechanism's lifecycle under the actual framework, browser, and settings. Keep that check separate from backend test isolation.

A Parallel Checkout Collision, Then an Owned Fixture

Consider this fictional test application. Carts are stored on the server by user account, and a feature flag selects the classic or redesigned checkout. The following sequence is illustrative, not a captured execution trace:

  1. The classic-checkout test opens its own browser context, loads the shared shopper's authentication, and adds a product to that shopper's cart.
  2. The redesigned-checkout test opens another context with the same shopper, clears the cart as setup, and enables the redesigned checkout through a global test-environment flag.
  3. The classic test requests its cart again. The server returns the cart after the other test's changes, and the global flag may also select the wrong checkout variant.
  4. A retry happens to run without the competing mutation and passes. That result does not establish that the prerequisites were isolated.

Giving each test another browser context does not repair either shared backend resource. Waiting longer is also unrelated to ownership: it changes timing without preventing the other test from editing the same cart or flag.

A stronger design allocates a test identity within a unique run namespace, then provisions a cart and any required order records for that test. The fixture returns their actual identifiers, and the test reads and changes only those records. For the flag, assume the provider and application support targeting distinct evaluation contexts. Configure the classic test's context for the classic variation and the redesigned test's context for the new variation without switching a global value between them.

Now the tests own separate mutable inputs. Their browsers remain separate too, but the useful change happened at the server-data and flag-evaluation boundaries. If the application cannot distinguish those flag targets, the proposed flag design is unavailable; use an isolated evaluator or environment instead.

Check queries as well as names. An order lookup that selects the latest order across every test user can still pick the neighbor's order. A cart cleanup helper that deletes all test carts can still remove a neighbor's fixture. Unique record names help identify ownership, but the application's reads, writes, and cleanup must enforce that scope.

Match Fixture Lifetime to the State It Owns

Test fixtures organize provisioning and teardown; their lifetime determines how long supplied state is shared. Playwright documents test-scoped fixtures that are torn down after each test and worker-scoped fixtures that remain until the worker is torn down. Neither lifetime automatically isolates an external database.

Reuse Authentication Without Reusing Mutable Inputs

Worker-scoped authentication can be useful when creating accounts or signing in is expensive. Reuse it only where the tests can safely share that identity within the worker. Put mutable scenario records, such as carts and draft orders, in test-scoped fixtures when each test changes them.

Define the fixture contract before implementing the helper. It should identify the account or tenant, the records it creates, the initial values it establishes, and the identifiers the test must use. A fixture that returns a convenient page while silently relying on a shared default cart leaves the important dependency hidden.

Include retry behavior in that contract. A retry may encounter records or pending work left by the previous attempt. Either allocate fresh owned records for the attempt or verify and restore the exact owned starting state before reuse. Reusing an authentication file does not perform that restoration, and an expired login state needs its own renewal path.

Avoid using a broad reset endpoint as a substitute for owned data on shared infrastructure. Resetting the database before a test can destroy the prerequisites of another test already running. Such a reset belongs only within an environment the resetting job exclusively owns, with an explicit boundary around every affected consumer.

Clean Up Owned Records and Retain Failures

Record created identifiers as provisioning succeeds, including partial setup. Cleanup should target those identifiers and their ownership scope, rather than delete every record matching a broad test label. When fixtures depend on one another, teardown should remove dependent resources before their parent account or tenant; Playwright's fixture dependency ordering supports that lifecycle.

Cleanup does not replace isolation during execution. Deleting a shared cart after the assertion cannot prevent a neighbor from reading its temporary contents while the test is running. Establish private mutable inputs first, then use cleanup to keep subsequent runs manageable.

Treat cleanup failure as evidence. Preserve the original test failure and separately report the records that could not be removed. An orphan register with the run identity, created identifiers, and cleanup error gives a later cleanup task a bounded target. Do not assume teardown will always run after an abrupt process termination.

Asynchronous work needs an ownership rule too. A checkout request may enqueue an order-processing job that continues after the browser closes. Before reusing or removing its data, define how the test observes completion, cancels owned work where supported, or lets it expire safely. If a background job drops the test namespace and writes a shared record, browser disposal cannot contain the effect.

Feature Flags Need an Evaluation Boundary

A browser context and a flag evaluation context are different concepts. The first separates browser-managed state. The second supplies an identity and attributes to a flag evaluator. They align only if the application passes the intended test-specific identity and attributes to the relevant evaluation path.

LaunchDarkly's testing guide documents a dedicated test-environment approach with targeting: provide a distinct, predictable context for each test and configure the flags to return the required variations for those contexts. That design can avoid reconfiguring flags during concurrent runs. It requires both correct rules and application support for supplying the context; creating a new login name alone is insufficient.

For any provider, establish the expected variation before navigating through the behavior under test and retain enough evidence to diagnose the evaluated state. If the application evaluates flags on the server, controlling a browser-side response does not prove control over the server's decision. Check where evaluation occurs rather than assuming every flag travels through a browser SDK.

A global flag remains shared even when its mutation sits inside a test-scoped fixture. LaunchDarkly explicitly warns that concurrent test runners changing the same environment configuration can conflict. Reading the old value, changing it, and restoring it afterward also creates a restoration race: another test may legitimately change the value between those operations.

A local mutex cannot coordinate another worker process or an independent CI job unless all relevant writers participate in a shared coordination mechanism. Prefer supported scoped targeting, an isolated evaluator, or a separate test environment. Where a global configuration cannot be partitioned, an environment owner must coordinate every writer and consumer affected by the change.

Controlled flag evaluation has its own limits. A mock can test application behavior for a supplied variation without validating live provider targeting or delivery. LaunchDarkly's documented file-based approach is limited to server-side SDKs, and its test data sources are not supported by every SDK. Choose a supported mechanism for the installed SDK and retain a separate integration check where real evaluation is part of the requirement.

Choose the Smallest Boundary That Covers Every Writer

Before parallelizing a state-changing test, make these checks explicit:

  • Browser state: which stores and page state does this runner reset under the selected mode, and what authentication is deliberately restored?
  • Identity: can another worker, retry, developer, or simultaneous job use the same mutable account or tenant?
  • Data: do provisioning, application queries, assertions, and cleanup all use the owned records rather than global or latest-record lookups?
  • Flags: is the required variation scoped to the test at the actual evaluation point, or does a fixture still change a global value?
  • Lifecycle: how are partial setup, failed cleanup, retries, and background jobs handled before ownership is reused?

Read-only tests may safely share immutable reference data and an account when their behavior truly does not interfere. Tests intended to verify interaction between users may deliberately share a backend resource within one controlled scenario. Document those choices instead of applying isolation so broadly that the behavior being tested disappears.

For the next intermittent checkout failure, enumerate the cart, account, records, flag evaluator, and background work before adding another retry. Assign each mutable resource an owner and a known starting state. Keep the browser boundary, but add the backend and flag boundaries the assertion needs.

Related

More on Test Automation

Create traceable test cases, prepare release-specific test runs, and keep approved coverage current as the product changes.