skip to content

How far should a Cypress suite go in throttling every response with `res.setThrottle()`?

level: principalimportance: should knowfreq 30%

answer

  1. A tax every spec pays forever
  2. Where does the slowness belong?
  3. Timeout headroom is a shared budget
  4. Uniform delay is pressure, not fidelity
  5. Local and CI must behave the same

basics

~20 s

Keep injected slowness out of the default path. A support-file route that throttles every response buys uniform timing pressure but charges wall-clock time to every spec forever, so put delay on the routes a specific test is about instead.

solid answer

~40 s

The pattern is a `beforeEach` in the support file registering a `{ middleware: true }` route that calls `res.setThrottle()` or `res.setDelay()` on every response. It is legitimate — it stops the suite from only ever testing an instantaneous network — but it is a suite-wide tax, paid on every spec, every shard and every retry, and it pushes every wait closer to `responseTimeout`. My default is: no global slowness; explicit `delay` on the route a test is actually about. I would consider a small global delay (a couple of hundred milliseconds) only where a team is repeatedly shipping render-before-data races, keep it uniform between local runs and CI so failures reproduce, and treat the added wall-clock time as a budget someone owns and reviews.

go deeper

for a junior

Know that a route in the support file can affect every spec, and that anything slowing responses down there is paid on every test run.

for a middle

Explain the mechanism — a middleware: true route shaping real responses in a beforeEach — and what it does to the suite's timeout headroom.

for a senior

Be able to diagnose a suite where global slowness has pushed waits near their limits, and to move the injection back to the specs that actually need it.

for a principal

Own the decision and its budget: what the throttle is for, what it costs per pipeline run, who reviews it, and what evidence would justify keeping or removing it.

This is a real fork in how a suite is set up, and both sides have a serious argument. The mechanism is small; the consequences are suite-wide and permanent, which is what makes it a lead's call rather than a spec author's. ## The pattern in question Cypress lets a route shape a **real** response without replacing it. A route declared with `{ middleware: true }` runs before the suite's other routes and passes the request on, so registering one in a support-file `beforeEach` reaches every request the application makes: ```js // cypress/support/e2e.js beforeEach(() => { cy.intercept({ url: '**/api/**', middleware: true }, (req) => { req.on('response', (res) => { res.setDelay(250).setThrottle(500) }) }) }) ``` Every forecast, station-list and alerts call now answers late and streams slowly, in every spec that loads that support file. ## What it genuinely buys - **Timing pressure everywhere, not just where someone remembered it.** Render-before-data races, components that read a response they no longer need, and double-submits are all invisible against an instantaneous stub. A uniform delay exposes them in whichever spec happens to touch the code. - **It removes a per-test decision.** Nobody has to remember to slow a route down to notice that the panel renders before its data arrives. - **It is closer to a user's experience than zero latency is.** Zero is not a realistic number for any deployed system. ## What it costs - **Wall clock, multiplied.** The added delay is paid per request, per spec, per shard, and again on every retried attempt. A 250 ms delay across forty requests per spec and three hundred specs is measured in hours of pipeline time per week, forever. - **Headroom against the timeouts.** `cy.wait()` allows `requestTimeout` (5000 ms by default) for the request and `responseTimeout` (30000 ms by default) for the response, and assertions are bounded by `defaultCommandTimeout`. Global slowness eats that headroom on every command, so a suite that is comfortable on a developer laptop starts timing out on a loaded CI machine — flake that looks like an application problem. - **Misattributed failures.** When a spec fails, the first question becomes "is this the app, or is this our global throttle?" That tax is paid by every person who ever debugs the suite. - **False realism.** A fixed delay and a fixed rate are not a network. There is no jitter, no packet loss, no slow first byte followed by a fast body, no difference between a cold and warm path. It is pressure, not fidelity, and reporting it as "we test on 3G" oversells it. ## Global default versus targeted injection | | Global support-file throttle | `delay` on the route under test | |---|---|---| | Who pays the time | every spec, always | the specs that need it | | Failure attribution | ambiguous | obvious from the spec | | Discoverability | one line far from the test | visible in the test | | Catches unplanned races | yes | only where applied | | Timeout headroom | reduced everywhere | reduced locally | ## Where I put the line 1. **Default to no injected slowness.** A suite's speed is the reason people run it before pushing; spend it deliberately. 2. **Put the slowness in the test that is about it.** A pending state, a cancelled request, an ordering guarantee — each gets an explicit `delay` on the route it concerns, where the next reader can see it. 3. **Consider a global delay only with evidence**, such as a run of production incidents caused by render-before-data races. Keep it small, keep it a delay rather than a throttle (a rate cap does almost nothing to a two-kilobyte stub), and keep it in one commented place. 4. **Keep it identical between local runs and CI.** A suite that is fast locally and throttled in the pipeline produces failures nobody can reproduce. 5. **Give the wall-clock cost an owner.** Whoever adds it should be able to state the added minutes per full run, and it should be revisited when the suite grows. 6. **Never tune it up to the point where waits sit near their timeouts.** If specs need raised timeouts to survive the global throttle, the throttle is too aggressive and the suite has lost its margin for genuinely slow machines. ## The question that usually settles it Ask what the throttle is *for*. If the answer is "to catch races", a handful of targeted specs plus a code review habit usually catch more, more cheaply, and with better failure messages. If the answer is "because our stubs are unrealistically fast", the honest fix is fewer stubs in the few journeys that should run against a real backend — a different decision, and not one a throttle can make for you.

  • If you did adopt a global delay, what would you watch afterwards?
    The added wall-clock time per full run, the number of specs that need a raised timeout to pass, and whether failures start being misattributed to the app. Any of those moving is the signal to scale it back. I would also keep it identical in local and pipeline runs, so a failure a developer sees is one they can reproduce.
  • Why prefer `delay` over `throttleKbps` for a global setting?
    A rate cap only slows the body stream, and most stubbed or real API payloads in a dashboard are a few kilobytes, so it barely registers while still adding machinery. A delay is predictable, applies to the whole response, and is easier to reason about when you are budgeting timeout headroom across a suite.

saying these in an interview costs you the question

  • Throttles everything globally with no wall-clock budget
  • Calls a fixed delay and rate a realistic network
  • Raises timeouts across the suite to absorb its own throttle
  • Enables the throttle in CI but not locally
  • Treats a global rate cap as covering small JSON payloads