In a headless Cypress `cypress run`, what happens when a spec calls cy.pause()?
answer
- It does not hang the pipeline
- Cypress checks the mode first
- Two conditions, not one
- A visible browser that will not exit
- --headed --no-exit together
basics
~20 sNothing. Cypress skips the pause entirely: it yields its subject unchanged and does not even add a Command Log entry, so a forgotten cy.pause() cannot hang a pipeline. To make it stop during a run you need a visible browser that will not exit: cypress run --headed --no-exit.
solid answer
~50 sIn a headless `cypress run` the pause is a pass-through. Cypress checks whether stopping would be useful before it does anything, and if the session is not interactive and either the browser is not headed or the run is going to exit when the spec finishes, `cy.pause()` becomes an identity step: same subject out as in, no Command Log entry, no wait. That is deliberate — a `cy.pause()` accidentally committed to `master` cannot deadlock CI. It stops in exactly two situations: `cypress open`, and `cypress run --headed --no-exit`, where there is a real browser window that will still be there when the spec ends. `--headed` on its own is not enough, because the run exits as soon as the spec finishes; `--no-exit` on its own is not enough headless, because there is no window to look at.
go deeper
Recall that cy.pause() is an open-mode tool and that leaving one in a spec does not break a headless run, because Cypress simply skips it.
Name both conditions Cypress checks — a headed browser and a run that will not exit — and why passing only one of the two flags leaves the pause inert.
Be ready to argue why a silent skip beats an error for an interactive-only command, and why a headed --no-exit reproduction is not the same environment as the CI run you are chasing.
Set the standard: debugging affordances do not belong in committed specs even when harmless, and the team needs a check for them since the pipeline will never complain.
## The rule Before `cy.pause()` does anything, Cypress asks whether pausing could possibly help. If the session is **not interactive** — that is, it is a `cypress run` rather than `cypress open` — and **either** the browser is not headed **or** the run is configured to exit when it finishes, the command degrades to a pass-through. It yields whatever subject it was given, adds no Command Log entry, and the queue keeps draining as though the line were not there. Laid out as a table: | How the spec was started | Does `cy.pause()` stop? | |---|---| | `cypress open` | yes | | `cypress run` (headless, the default) | no | | `cypress run --headed` | no — the run still exits when the spec ends | | `cypress run --headed --no-exit` | yes | | `cypress run --no-exit` (still headless) | no — there is no window to look at | The two conditions are doing different work. **Headed** means there is a browser you can actually see and interact with. **`--no-exit`** means Cypress will not tear that browser down the moment the spec finishes, so the paused state survives long enough to be useful. ## Why it is a pass-through rather than an error Two designs were possible: throw when `cy.pause()` runs in a context that cannot pause, or silently skip it. Cypress skips it, and the reason is the one that matters in practice: - A `cy.pause()` accidentally committed to a shared branch would otherwise **hang the pipeline** — a job burning its whole timeout because a machine with no display is waiting for a human to click Resume. - The same spec is expected to run in both modes. Open mode is where you debug; run mode is where CI executes the identical file. A command whose only purpose is interactive has nothing to do in the non-interactive half, and failing the run over it would punish the wrong thing. The trade-off is that the mistake is **silent**. Nothing in the run output says "a pause was skipped here", so a leftover pause is invisible until someone reads the spec. Treat it as a lint-level concern rather than something CI will catch for you: a debugging aid does not belong in a committed spec, even a harmless one. ## Getting an actual pause during a run When a support-ticket suite misbehaves only under `cypress run`, the way to keep `cy.pause()` working is to give the run the two properties it checks for: ```bash npx cypress run --headed --no-exit --spec cypress/e2e/ticket-queue.cy.js ``` This is documented as the way to reach the Command Log and DevTools after a spec has run, and it is the same switch that makes a pause meaningful. Things to expect: - **It runs one spec's worth of work and then sits there.** `--no-exit` means you close it yourself; do not put this combination in a pipeline. - **It needs a display.** On a headless CI container it needs a virtual framebuffer, which is usually more trouble than reproducing locally. - **It is not the same environment as CI.** A headed run on your laptop differs from the containerised headless run in viewport, machine speed, and often browser build — so a failure that disappears under `--headed --no-exit` is itself information. ## Related behaviour worth not confusing - **`.debug()` is not gated this way.** It always hits its `debugger` statement; whether anything catches it depends on DevTools being open, which in a headless run they are not. Same practical outcome, entirely different mechanism. - **`cy.log()` is not gated either.** It writes its Command Log entry in both modes; the entry simply has no interactive log to be read in. - **`--headless` is the default for `cypress run`**, so you do not need to pass it; `--headed` is what changes the behaviour. ## What to take from it in an interview The interesting part is not the trivia — it is the reasoning. The command was given a defined, safe behaviour in the mode where its whole purpose is unavailable, rather than being made an error. That is a reasonable pattern for interactive-only affordances generally: degrade to a no-op in the non-interactive mode, and keep the failure mode "this did nothing" rather than "this blocked the pipeline". The residual cost — silence — is real, and it is what makes a debugging command in committed spec code worth removing on its own account.
- Why does Cypress skip cy.pause() in a headless run instead of throwing an error?Because the alternative is worse. A pause left in a shared spec would otherwise hang a CI job until its timeout, on a machine with nobody to click Resume. The same spec file is expected to run in both open and run modes, so an interactive-only command degrades to a no-op in the non-interactive one. The cost is silence: nothing announces that a pause was skipped.
- Does .debug() behave the same way as cy.pause() in a headless Cypress run?Not by the same mechanism, though the outcome looks similar. `.debug()` is not mode-gated: it always executes its `debugger` statement. In a headless run there is no debugger attached, so the engine steps straight past it and the run continues. `cy.pause()` is genuinely skipped by a mode check before it does anything, including before it would have added a Command Log entry.
saying these in an interview costs you the question
- Thinks a leftover cy.pause() hangs the CI job
- Says cypress run --headed alone restores the pause
- Expects Cypress to warn that a pause was skipped
- Believes --no-exit works without a headed browser