skip to content

Why can a Cypress `cy.intercept()` route never fire even though the page loads that resource?

level: juniorimportance: should knowfreq 48%

answer

  1. Nothing was ever sent
  2. Check the browser's network panel
  3. The route sits at zero matches
  4. Caching headers on the second load
  5. cache-control: no-store in test mode

basics

~20 s

Because the browser answered from its own HTTP cache. cy.intercept() matches at the network layer, so a request that is never issued never reaches Cypress, the route stays at zero matches, and a wait on its alias fails.

solid answer

~50 s

`cy.intercept()` matches requests at the network layer, not inside your application's `fetch` or `XMLHttpRequest` calls. If the browser can satisfy a request from its own HTTP cache — a station-list response with a long `max-age`, a fingerprinted bundle, a radar image — it issues no request at all, so there is nothing for the route to match. The Routes panel in the Cypress Command Log shows the route at zero matches, and `cy.wait()` on its alias fails while still waiting for the request. Confirm it in the browser's network panel: the entries marked as served from cache are exactly the ones your route will never see. Every fix works by making the request actually happen — send `cache-control: no-store` from the dev server in test mode, or add a top-level Cypress route that strips the caching headers off the responses you care about.

code

javascript · 19 lines
javascript
// cypress/e2e/stations.cy.js
beforeEach(() => {
  // Make cacheable weather responses uncacheable so routes can match them
  cy.intercept({ url: '**/api/**', middleware: true }, (req) => {
    req.on('before:response', (res) => {
      res.headers['cache-control'] = 'no-store'
    })
  })
})

it('re-fetches the station list on every visit', () => {
  cy.intercept('GET', '**/api/stations').as('stations')

  cy.visit('/dashboard')
  cy.wait('@stations')

  cy.reload()
  cy.wait('@stations')
})

go deeper

for a junior

Recall that cy.intercept() only sees requests that actually leave the browser. If a route reports zero matches while the page renders, suspect the cache before you suspect the pattern.

for a middle

Be ready to explain where the route rule is applied and why a cache hit bypasses it entirely, and to name at least two ways to force the request to be issued.

for a senior

An interviewer expects you to spot the order-dependence — cold cache passes, warm cache fails — and to defeat caching narrowly so the suite still resembles the responses production serves.

for a principal

Own the environment call: which responses a test environment is allowed to make uncacheable, and how the team keeps that from quietly hiding a real caching regression.

## What `cy.intercept()` is watching A route registered with `cy.intercept()` is a **rule applied to requests at the network layer**. It is not a patch over the application's `fetch` or `XMLHttpRequest`, and it is not a subscription to the browser's resource loader. That distinction is invisible most of the time and decisive here: **if the browser never issues a request, no rule can match it.** The browser's HTTP cache is the most common reason a request is never issued. When a weather dashboard's station list comes back with `cache-control: max-age=86400`, or the radar legend image is a fingerprinted asset marked immutable, a later load is answered from memory or disk **inside the browser**. Cypress is never consulted, because there is nothing to consult it about. ## How the failure actually presents The symptom is easy to misread, because the page looks correct — the stations render, the radar draws — while the test insists nothing happened: - The **Routes** instrument panel in the Cypress Command Log shows your route with a match count of **0**. - `cy.wait('@stations')` fails while still waiting for the *request*, not for a slow response. - The failure is **order-dependent**. The first spec in a run passes on a cold cache; the second one fails because the first one warmed it. - The browser's own network panel labels the offending entries as served from cache. Those are precisely the entries an intercept cannot see. - A stub attached to the route (a fixture, a static body) silently has no effect, so the app renders **real** data and an assertion written against the fixture fails on content, not on timing. Raising a timeout never helps. There is no request in flight to wait longer for, so a bigger budget only makes the same failure slower. ## Making the request happen You cannot make `cy.intercept()` reach into the cache, so the fix is always to remove the cache's ability to answer. Three documented approaches, in rough order of preference: 1. **Turn caching off at the source.** Have the API or dev server send `cache-control: no-store` when it is running in test mode. This is the cleanest option because it applies to every browser and every spec, with nothing in the test code. 2. **Strip the caching headers with a top-level Cypress route.** Register a broad route in a `beforeEach` that rewrites `cache-control` on the responses you care about, so the browser has nothing worth storing. 3. **Disable the cache in the browser.** Chromium-family browsers expose this through the browser's remote debugging protocol; it is the heaviest hammer and the least portable across the browsers a suite runs. | Approach | Where it applies | What it costs | |---|---|---| | `no-store` from the dev server | Every browser, every spec | A test-mode branch in server config | | Top-level Cypress route | The specs that register it | An extra route in the matching order | | Disable the cache in the browser | Chromium-family browsers only | Least portable; least production-like | ## The judgement underneath Defeating the cache changes what you are testing. A suite that runs entirely with `no-store` never exercises the caching your users get, so the test environment drifts from production in a way nobody notices until a caching bug ships. Prefer defeating the cache **narrowly** — on the API responses whose content a test asserts — and leave static assets cached, because a test almost never needs to intercept the radar legend. The mirror-image trap is asserting that something *was* served from cache by checking that a route did **not** fire. Zero matches is a weak signal: it is equally consistent with a wrong URL pattern, a route registered too late, and a request the app never made. If caching behaviour itself is the thing under test, drive it with `cy.request()`, which goes through Cypress rather than the browser cache, and assert on the status your server actually returned. ## What to remember - `cy.intercept()` sees requests **at the network layer**; a cache hit never gets there. - Zero matches plus a rendered page is the fingerprint — check the browser's network panel before rewriting the pattern. - Fix it by making the request happen, not by widening the timeout or loosening the matcher. - Defeat the cache narrowly, so the suite still resembles what production serves.

  • How would you confirm from inside the test that the request never happened at all?
    The Routes panel in the Cypress Command Log reports a match count per route. A route stuck at 0 while the resource is visibly on the page means nothing reached the network layer. The browser's network panel confirms it by labelling those entries as served from cache rather than fetched.
  • Does a response stubbed by cy.intercept() get stored in the browser's cache?
    No. As of Cypress 16, a response answered inside Cypress — a fixture, a static body, or a body a handler rewrote — was never a network response, so the browser has nothing to store. A second navigation issues the request again and the route matches again, even if the stub declared cache-control headers.

saying these in an interview costs you the question

  • Assumes the URL pattern is wrong and rewrites the glob
  • Raises the Cypress timeout until the wait stops failing
  • Claims cy.intercept() patches fetch, so caching cannot matter
  • Deletes the assertion instead of making the request happen