The problem often appears after a test suite starts running concurrently. A test creates an account, another test assumes the account does not exist, and only one of them can be right. Run either test by itself and it passes. Run both at the same time and the result depends on timing rather than the behavior under test.
This is why shared-state failures are so expensive to diagnose. The test code can be unchanged, the application code can be unchanged, and a retry can pass. The hidden input is data written by another actor that the test did not declare or control.
The same failure reaches beyond a single test suite when several pull requests are developed in parallel. Each preview can look healthy on its own while still reading a database, cache, or sandbox state that another PR changed. For validation before merge, a preview needs a declared and private starting state, not a shared baseline that cleanup may eventually restore.
That is why, at Autonoma, we built PreviewKit to operate the per-PR preview lifecycle with a routed environment and database boundary. It does not replace the application's isolation design, but it gives each pull request a place to validate that design before merge.
What is the shared-state problem?
Shared state is any mutable resource that independent test runs can read or change without a boundary between them. The shared database problem in testing is the most common form: two CI jobs, browser tests, or preview environments use the same rows and each assumes a baseline the other can alter.
For example, one end-to-end test signs up test@example.com and expects success. Another begins by checking that the address is unused. Neither assertion is wrong in isolation. Together, they are order-dependent. If the first write completes before the second check, the second test fails even though the application may be working correctly.
Cleanup helps remove data after a run, but cleanup is not isolation. It can be delayed, interrupted, or incomplete, and it cannot stop two active runs from observing each other's writes. Isolation makes the starting state private before a collision can happen.
Both tests touch the same application path, but their expectations conflict because the database is a shared baseline rather than a private test fixture.
Why does shared state make tests non-deterministic?
A deterministic test has a known starting condition and an outcome that follows from the code and declared fixture. A shared-state test has another variable: which run wrote first, which cleanup finished, or which process changed a row between the setup and the assertion. The same test can pass alone, fail in a full suite, and pass on retry without any code change.
Race conditions are one form of this failure. If one test reads before another commits, it observes one state. If it reads after the commit, it observes another. Leftover rows create a similar effect over a longer interval. Both problems make a failure look intermittent when the missing isolation boundary is the stable cause.
How to avoid shared state in test environments
There are three remedies. They differ by where the boundary sits and which part of the system can enforce it.
Per-run isolation
For a unit or integration test that controls its database connection, run the test inside a transaction and roll it back after the assertion. The test can create or update rows, but the writes disappear with the transaction. The next run returns to the same baseline.
This works only when the test owns the transaction context. A browser test against a deployed application normally does not own the server's database connection, so it cannot safely wrap every server-side write in its own transaction. In that setting, transaction rollback is not a complete end-to-end isolation boundary.
Tenant scoping
Tenant scoping keeps one physical database but gives each run a tenant, workspace, or account identifier. The run seeds rows for that identifier and every relevant query, worker, cache key, and side effect must remain in that scope. Parallel runs can share storage without seeing each other's tenant rows.
This approach is useful when the application already enforces tenant boundaries consistently. Its weakness is the same boundary: an unscoped query, global table, background job, or cache key can expose shared state again. Tenant scoping also does not isolate schema changes, because every tenant still uses the same database schema.
That is the gap between separating rows and validating a pull request independently. Tenant scoping helps only when every relevant query, worker, cache key, and side effect remains inside the boundary.
Per-preview databases
Per-preview databases give each preview environment, pull request, or job its own database instance or branch. The boundary sits below the application, so another run's rows are absent rather than filtered out. This is often the clearest option for deployed end-to-end tests, particularly when a change includes migrations or the application cannot guarantee complete tenant scoping.
The trade-off is lifecycle work. Provisioning must create the database, apply the expected schema, provide safe test data, connect the environment, and tear it down when the run ends. A separate database isolates data and schema, but any shared cache, object store, external sandbox account, or feature-flag configuration still needs its own boundary.
How Autonoma removes shared state from PR validation
The failure pattern here is not only a bad test fixture. It is an operational gap: every PR needs an environment, routing, secrets, services, and database state that another concurrent change cannot mutate. Without that boundary, a passing preview can still be passing against someone else's state.
Our managed preview environments provision, route, and tear down per-PR previews, including full-stack service replication, secrets propagation, environment routing, and database isolation. End-to-end testing runs in that same environment: Planner handles the database state setup needed by a test, and Diffs Agent maintains test cases as each PR changes. PreviewKit is our managed Layer 1 preview-environment product, while teams still need to isolate mutable dependencies such as external sandbox accounts and shared caches.
Rollback isolates one controlled connection, tenant scoping separates rows through application rules, and a separate database gives a deployed environment its own data and schema boundary.
How do you choose the right remedy?
Choose the smallest boundary that the test can actually enforce. Per-run isolation is efficient for tests that own their connection. Tenant scoping works when isolation is a reliable application invariant. Per-preview databases are the stronger choice when a deployed environment needs its own data and schema. Teams often use more than one: transactions for fast lower-level tests and tenant or database boundaries for deployed flows.
Validate the boundary by running two jobs concurrently with deliberately different data. Each job should read only its own state, and teardown should remove only the resources that job created. This check should include mutable dependencies beyond the database, because an isolated database does not protect a shared email inbox or cache key.
A managed preview-environment platform can take on the provisioning and teardown work when operating isolated databases internally costs more than maintaining the platform. For preview-specific context, see do Vercel previews share the same database and database branching vs tenant isolation.
Make Isolation a Property of the Pull Request
Modern engineering teams need to validate several changes before merge, not serialize work around one shared test environment. The goal is not simply to eliminate a flaky failure; it is to ensure that a PR can prove its behavior against the state it declares, while another PR does the same without either changing the other's result.
PreviewKit makes the environment part of that implementation by operating the per-PR preview lifecycle and its database boundary. The remaining design work still matters: identify shared caches, sandbox accounts, and data fixtures that need their own scope. Once those boundaries are explicit, parallel development and end-to-end validation can reinforce each other rather than create another source of uncertainty.
Frequently Asked Questions
It is the failure mode where independent test runs can read or change the same mutable state. Their outcomes then depend on timing, run order, or data left behind by another run.
No. Cleanup can restore a shared baseline after a run, but it does not prevent concurrent runs from observing or changing each other's state. Isolation creates the boundary before the collision happens.
A separate database is often the clearer choice for deployed end-to-end tests, schema changes, or applications that cannot enforce tenant scope consistently. Tenant scoping can work when the application already applies that boundary to every relevant query and service.




