Why does a long Cypress run accumulate browser memory a fresh tab would not?
answer
- Everything shares one browser tab
- What survives from test to test
- Detached nodes and listeners nobody removed
- A Cypress 16 default that samples usage
- Retention differs: open mode versus run
basics
~20 sEverything runs in one browser tab: the spec, the runner and the application share a heap and a main thread. No process boundary disposes of anything between tests, so the application's leaked nodes, listeners and state survive from test to test.
solid answer
~40 sCypress runs a whole spec file in one tab, so the application's leaked references -- detached DOM nodes, listeners never removed, module-level caches -- pile up across tests instead of being cleared by a fresh page or process. On top of that the runner retains snapshots and command data: `numTestsKeptInMemory` defaults to 50 during `cypress open` and 0 during `cypress run`, which is why interactive mode is heavier than a headless run of the same file. As of Cypress 16, `manageBrowserMemory` defaults to `true` and forces a collection when sampled usage crosses a threshold, but only in Chromium-based browsers -- it does nothing in Firefox or WebKit, and it cannot reclaim memory the application still references.
go deeper
Know that Cypress runs a whole spec file in one browser tab, so nothing is cleaned up by a fresh process between the tests in that file.
Explain what actually accumulates -- detached nodes, listeners and module state from earlier tests -- and which Cypress settings bound the runner's own share of it.
Diagnose it properly: separate the application's leaks from retained snapshots, test whether the failure is position-dependent, and recognise a memory death rather than re-running it as flake.
Decide what the team does about it -- a budget for spec file size, what a long suite may retain, and whether the browsers in the pipeline even support memory management.
## One tab holds everything The in-page model has a consequence that only shows up on long runs: **there is one browser tab, and everything is inside it.** The spec, the Cypress runner, the application under test, and the command data the runner retains all live in the same renderer process and the same JavaScript heap. Nothing is disposed of by a fresh process boundary between tests, because there is no process boundary between tests. Cypress does clear the page between tests. What it cannot do is reach into the application's own retained references and undo them. The documentation is explicit that in a long-running suite the browser accumulates memory across tests, and that references to DOM nodes, event listeners and application state from earlier tests can linger in the heap even after Cypress clears the page. ## What actually accumulates Two different piles grow, and separating them is most of the diagnosis: - **The application's own leaks.** A component that adds a `window` listener and never removes it, a module-level cache keyed by entity id, a detached subtree still referenced by a closure. In production a user reloads and the leak resets. In a Cypress spec file, forty tests run in one tab and every one of them adds to the pile. - **What the runner retains for you.** `numTestsKeptInMemory` sets how many tests keep their snapshots and command data. It defaults to **50 during `cypress open`** -- that retention is what makes an earlier test inspectable -- and to **0 during `cypress run`**. As of Cypress 16 it is always treated as 0 during `cypress run`, whatever the configuration says. This is why interactive mode feels heavier than a headless run of the same spec. There is a third, quieter effect that is not memory at all: the spec and the application share one main thread. Synchronous work inside a `.then()` callback is time the application is not rendering, so a heavy loop in a test can change the very timing the test is measuring. ## What Cypress 16 does about it As of Cypress 16, `manageBrowserMemory` defaults to `true`. Cypress samples the browser's memory usage on an interval while tests run and forces a garbage collection before the next test only when a sample has crossed a threshold. Two things about it matter in practice: - It applies to **Chromium-based browsers only** -- Chrome and Edge. It has **no effect** in Firefox or WebKit, so a suite pinned to Firefox is on its own. - It manages pressure; it does not remove the cause. An application that leaks on every render still leaks, and collection cannot reclaim what is still referenced. ## Diagnosing it in a real run When a spec file slows toward the end or the browser dies partway through a `cypress run`: 1. **Check whether it is position-dependent.** Run the failing test first in the file. If it passes alone and fails at position thirty, the cause is accumulation, not that test. 2. **Split the spec file and re-run.** Spec size is the most direct lever you have, because the browser's state is scoped to the file far more than to the individual test. 3. **Separate the two piles.** Compare `cypress open` against `cypress run` on the same file: if the headless run is healthy, retained command data was carrying the weight; if both degrade, the application is leaking. 4. **Take the heap profile in the browser you actually ship on.** Being in the page cuts both ways here: the browser's own developer tools profile the spec and the application together, because they are one page. 5. **Fix it in the application where you can.** A listener that is never removed is a defect the suite happened to find first, and it is usually cheaper to fix than to work around. ## What not to conclude A browser that dies mid-run is not automatically a flaky test, and re-running it is not a diagnosis. Equally, this is not an argument that the in-page model is unsound -- it is the predictable price of the same placement that gives the spec native access to the application in the first place. The teams that run large Cypress suites comfortably treat spec file size as a budget, keep an eye on which browsers their pipeline actually uses, and treat a memory failure as a signal about the application rather than noise from the runner.
- Why does cypress open feel heavier than cypress run on the same spec file?`numTestsKeptInMemory` defaults to 50 during `cypress open` and 0 during `cypress run`, so interactive mode keeps snapshots and command data for the last fifty tests in the browser's heap to make them inspectable. A headless run keeps none. As of Cypress 16 the value is always treated as 0 during `cypress run`, however it is configured.
- Does manageBrowserMemory help a Cypress suite running in Firefox?No. `manageBrowserMemory` applies to Chromium-based browsers only -- Chrome and Edge -- and has no effect in Firefox or WebKit. There the accumulation is unmanaged, so the levers left are the application's own leaks and the size of the spec file rather than any Cypress setting.
saying these in an interview costs you the question
- Blames the application alone for a runner-shaped memory profile
- Thinks each Cypress test gets a fresh browser process
- Assumes manageBrowserMemory helps in Firefox or WebKit
- Says open mode and cypress run retain the same snapshots
- Treats a mid-run browser crash as ordinary test flake