Why would a Cypress spec re-run in an endless loop after every save in cypress open?
answer
- Only open mode watches anything
- The watched set is the import graph
- A build that writes into what it watches
- Generated declarations and hot-update chunks
- Ignore globs belong on the preprocessor
basics
~20 sSomething inside the spec's own import graph is regenerated by each build. The Cypress watcher sees the new file, recompiles, reloads the browser, and the rebuild regenerates the file again. Exclude those generated files from the watcher.
solid answer
~40 sIn `cypress open` the Node half watches the spec graph — the open spec, the support file and everything they import — and recompiles through the preprocessor and reloads the browser on any change. A loop forms when the build itself **writes files that are inside that graph**: emitted `.d.ts` declarations, `*.hot-update.js` chunks, generated fixtures. Each rebuild produces new files, the watcher sees them, and it rebuilds again. The fix is to stop watching those specific files, by passing webpack's `watchOptions.ignored` glob to the preprocessor Cypress bundles. Note that `excludeSpecPattern` will not help: it only hides files from the spec list in the UI and has no effect on what the preprocessor watches. `watchForFileChanges: false` stops the loop but costs you the whole feedback loop.
go deeper
Know that cypress open re-runs a spec when you save it, and that cypress run does not watch anything. The loop itself is not junior material.
Be able to describe what the watched set is — the spec, the support file and their imports — and why a build artefact landing inside that set causes a rebuild.
This is a diagnosis question. Show how you confirm the cause rather than guessing, and how you pick between ignoring the file at the watcher and moving the generator's output out of the tree.
Own the developer experience: decide whether generated files may live beside sources at all, and what the team's local test loop is expected to feel like before anyone starts silencing watchers.
## What Cypress actually watches File watching lives in the **Node half** of a Cypress run, and it exists only for `cypress open`. When something it watches changes, Node recompiles the affected files through the preprocessor and tells the browser to reload and re-run the spec. What is watched: - Your Cypress configuration file and `cypress.env.json`. - Every file matching `specPattern`, anywhere under the project root. - The support file, and **anything the open spec or the support file imports** — the spec's whole module graph, because the bundler has to rebuild it to serve the spec. What is deliberately not watched: - Your application's own source. If you are developing the bank statement app with a dev server, that server already does hot reloading, and Cypress does not duplicate the work. - `node_modules`. - `cypress/fixtures`. | Change while `cypress open` has a spec running | Effect | |---|---| | Edit `cypress/e2e/statements.cy.js` | Recompile, reload, re-run the spec | | Edit `cypress/support/e2e.js` | Recompile, reload, re-run the spec | | Edit a helper that spec imports | Recompile, reload, re-run the spec | | Edit the statement app's own component | Nothing from Cypress; your dev server handles it | | Edit a JSON file under `cypress/fixtures` | Nothing | | Edit the Cypress config file | Cypress picks the change up and reloads the project | ## How the loop forms The cycle needs only one ingredient: a file that is **both** produced by the build **and** inside the watched graph. 1. You save a spec. Cypress recompiles it. 2. A loader in that build emits a side-effect file — a `.d.ts` from a CSS-modules typing loader, a `*.hot-update.js` chunk, a generated constants file. 3. The watcher sees a new or modified file in the graph and treats it as your edit. 4. It recompiles, reloads the browser, re-runs the spec — and step 2 happens again. From the outside it looks like Cypress is re-running by itself, forever, and the tests may even flicker between passing and failing because each reload interrupts the previous one mid-run. ## Breaking the cycle - **Ignore the generated files at the watcher.** Pass webpack's `watchOptions.ignored` — a glob, an array of globs, a regular expression or a function — to the preprocessor Cypress ships (`@cypress/webpack-preprocessor` and the batteries-included wrapper that is registered by default). `'**/*.d.ts'` and `'**/*.hot-update.js'` cover the two common cases. - **Stop emitting them into the watched tree.** If the generator can write to a build output folder outside your spec graph, that removes the problem instead of masking it. - **Do not reach for `excludeSpecPattern`.** It controls which files appear in the spec list in the Cypress UI and has no effect on watching, so it looks like the right knob and changes nothing. - **`watchForFileChanges: false` is a blunt last resort.** It disables watching for the whole project, so every edit then needs a manual re-run — you have traded the loop for the feedback loop. ## Why `cypress run` never shows this Nothing is watched during `cypress run`. Each spec is compiled once, executed, and the run moves on, so `watchForFileChanges` has no effect there at all. That asymmetry is worth remembering when triaging: a reload loop is by definition a local, open-mode problem, and a CI failure that looks similar has a different cause. ## Recognising it quickly The tell is that the re-runs continue when you are not typing. If a spec re-runs on a rhythm rather than on your keystrokes, look at what the last build wrote: - Check `git status` immediately after a rebuild — files that appear there are candidates. - Watch the file modification times under `cypress/` and your source tree. - Temporarily set `watchForFileChanges` to `false` to confirm the diagnosis; if the loop stops, the cause is a watched file, not a test that re-triggers itself. ## Which half does the work It is worth being precise about who does what during a reload, because the fix lives in only one of the two halves: - The **Node process** owns the watcher and the bundler. It notices the change, runs the preprocessor over the affected module graph and produces a fresh bundle. - The **browser** is only told to reload and re-run. It has no idea a file changed; it receives new code and starts the spec again. So an ignore rule is configuration for the Node half — it is attached to the preprocessor, not to anything in the spec — and no amount of skipping tests or restructuring the spec itself will stop the cycle. By default that preprocessor is the batteries-included webpack build Cypress registers for you when you have not bound `file:preprocessor` yourself, which is why TypeScript and JSX specs simply work with no bundler configuration of your own.
- In cypress open, does editing the application's own source re-run the spec?No. Cypress watches the spec graph, not your application code, on the assumption that your dev server already reloads the app. If you want a spec to re-run after an app change, save the spec as well or trigger the re-run yourself.
- Why does excludeSpecPattern not stop a file from triggering a rebuild?It is a display and selection filter: it decides which files Cypress lists and runs as specs. Watching is done by the preprocessor over the module graph it bundles, which is a different mechanism, so an excluded file can still be watched.
saying these in an interview costs you the question
- Blames flaky tests rather than the file watcher
- Reaches for excludeSpecPattern to stop a rebuild
- Disables watching entirely as the first move
- Thinks cypress run watches files too
- Assumes Cypress watches the application source as well