A test's independence is not a wish but the condition under which an E2E suite can be maintained at all. Playwright builds it on isolation: each test gets its own BrowserContext. A context is like a fast incognito profile - it has its own cookies, localStorage and sessionStorage that do not overlap with other tests. From this follows a strict requirement: a test must pass on its own, in any order and in parallel with the rest.
A BrowserContext is cheaper than it seems. It does not launch a whole new browser but creates an isolated state space inside an already running engine, so creating a context per test is almost free. This is exactly what makes isolation practical: there is no need to choose between cleanliness and speed - Playwright gives each test a fresh clean profile with no noticeable overhead, and this is the foundation of all parallelism.
A direct consequence of isolation is a ban on chains of tests. The temptation to write three scenarios in a row - "creates an order", "pays for the created order", "deletes this order" - is understandable: it seems the second simply uses the first's result. But under isolation the second test does not see the first's state, and in a parallel run they go simultaneously anyway. Such a chain either does not work or works by chance while the order happens to match.
The right form is self-contained scenarios, each with its own data. The test "pays for an order" does not rely on the previous one but gets its own order - via a fixture that creates it for this test. Then the scenario passes in any order and on any worker, because it does not depend on anyone's traces. Data comes into the test as its own input, not as a side effect of a neighboring scenario.
It helps to see the antipattern and the right form side by side once, to tell them apart at review. Below is a chain of dependent tests versus a self-contained scenario getting its own order via a fixture. You return to this comparison when the temptation arises to "save" and reuse in one test what another created - the saving turns into non-determinism.
There is a legitimate exception - serial mode. test.describe.serial runs tests in order and stops the rest when one fails. But it is a narrow tool for a truly indivisible process that cannot be broken into self-contained steps - for example, a multi-step wizard where each step physically continues the previous one in one session. Serial is justified only then, not as a convenient way to reuse state.
The danger of serial is that it masks an architectural problem. By moving related tests into serial mode so the second sees the first's data, you hide the absence of isolation and also block parallelism: the series runs in one worker and does not split. What looked like convenience becomes a suite bottleneck and a point of fragility - a failure of the first step ruins the whole series.
The typical isolation failures come down to attempts to bypass it. A chain of tests where the next relies on the previous one's state - green in order and red under parallelism or reordering. Serial mode as a way to reuse data instead of creating its own - a hidden architectural problem and lost parallelism. Give each test its own data and its own context, and save serial for truly indivisible processes.
// Antipattern: a chain where a test relies on the previous one's state
test('creates an order', ...)
test('pays for the created order', ...)
test('deletes this order', ...)
// Right: a self-contained scenario gets its own order via a fixture
test('pays for an order', async ({ page, order }) => {
// the fixture created the order for this test specifically
})