skip to content

In Cypress, how do you make a `cy.intercept()` route match only one of two same-path requests?

level: seniorimportance: nice to knowfreq 33%

answer

  1. Precision comes from more fields
  2. Everything you set must match
  3. Parameters and headers are matched by name
  4. A matcher cannot express an absent value
  5. Registration order can do part of the narrowing

basics

~10 s

Add more routeMatcher properties, because Cypress ANDs everything you set. A query or headers dictionary, or the method, hostname, port and https fields, separates two calls that share a path.

solid answer

~40 s

Precision in a Cypress `routeMatcher` comes from adding fields, not from a longer URL glob, because every property you set must match. Two calls to `/api/forecast` that differ only by a parameter are separated with `query: { units: 'metric' }`; a background poller is separated with `headers: { 'x-poll': 'true' }`, and Cypress lowercases header names for you since HTTP treats them case-insensitively. Each `query` and `headers` value is itself a glob or `RegExp`. The limitation to know is that a matcher can only say what a request **has**, never what it lacks — when absence is the difference, match on something the other request positively carries, or decide inside a request-handler function. Because matching runs in reverse definition order, registering the narrow route after the broad one makes the broad one a fallback.

go deeper

for a junior

Recall that a routeMatcher can match on query parameters and request headers, not just the URL, and that everything you set has to match.

for a middle

Explain that query and headers are dictionaries whose values are themselves globs or regular expressions, and that Cypress lowercases header names before comparing.

for a senior

Show the judgement call: when to narrow with an extra matcher field, when to let registration order make a broad route the fallback, and when a request handler is the honest answer.

for a principal

Own the risk that an over-broad route silently reshapes traffic nobody was looking at, and set an expectation for how narrowly shared routes in a suite must be scoped.

A weather dashboard often calls one endpoint several times with different intent: the map asks `/api/forecast?station=KSEA&units=metric`, the sidebar asks the same path with `units=imperial`, and a background poller re-asks it every thirty seconds with an `x-poll: true` header. A route matched on the path alone catches all three. Narrowing is a matter of adding properties to the `routeMatcher`, because everything you set has to match. ## Add a property, not a longer glob Every property on a `routeMatcher` is ANDed with the others, so precision comes from *more fields*, not from a cleverer URL pattern: - `query` — a dictionary matched parameter by parameter. `query: { units: 'metric' }` separates the map's call from the sidebar's without touching the path. - `headers` — a dictionary matched header by header. Cypress lowercases the names you give it, because HTTP header field names are case-insensitive, and rejects the same header supplied twice in different cases. - `hostname`, `port`, `https` — the origin. Useful for keeping a route off a third-party tile or telemetry host that happens to share a path shape. - `auth` — the username and password carried in HTTP Basic credentials. - `method` — often the cheapest discriminator of all, when the two calls differ by verb. Each value inside `query` and `headers` is itself a string matcher, so a glob or a `RegExp` works there too: ```js cy.intercept({ method: 'GET', pathname: '/api/forecast', query: { station: 'K*', units: 'metric' }, headers: { 'x-poll': 'true' }, }) ``` ## Absence is not a matcher The important limit: a `routeMatcher` says what a request must **have**, never what it must lack. There is no "no `x-poll` header" property. When the distinguishing feature is an absent value you have two honest options: 1. Match on something the other request positively has — a different `query` value, a different method, a different `pathname` — and use that instead. 2. Register a broad route and decide inside a request-handler function, which sees the real request and can leave anything it does not want to touch alone. ## Ordering as a narrowing tool Because Cypress consults matching routes in reverse order of definition, a narrow route registered *after* a broad one is consulted first, and the broad one becomes the fallback. That is often clearer than trying to make one pattern do both jobs: ```js cy.intercept('GET', '/api/forecast*') // catch-all, registered first cy.intercept({ method: 'GET', pathname: '/api/forecast', query: { units: 'metric' } }, handler) // narrow, consulted first ``` `times` narrows along a different axis — how many requests, rather than which ones. A route with `times: 1` takes only the dashboard's initial forecast call and lets every subsequent poll fall through to the broader route underneath. ## Checks before you reach for a header matcher Header and query matchers are precise but easy to get subtly wrong, so verify rather than assume: - Confirm the header is actually on the request — an `Authorization` header added by the app after a redirect, for example, may not be present on the call you are matching. - Remember that `query` compares each parameter to a **single** value and therefore cannot express a repeated, array-style parameter such as `?ids[]=1&ids[]=2`; use a `RegExp` `url` for that. - A string `hostname` is validated as a host or domain name, so patterns like a leading scheme are rejected; pass a `RegExp` when you need to span subdomains. - The Routes panel in the Cypress Command Log shows each registered route's matcher and how many requests it matched, which is the fastest way to confirm you narrowed to exactly one. Narrow deliberately. A route that is too broad silently changes requests you never meant to touch, and that is much harder to notice than a route that matches nothing at all.

  • Does the case of a header name matter in a Cypress `cy.intercept()` `headers` matcher?
    No. Cypress lowercases the field names in your matcher before comparing, because HTTP header field names are case-insensitive, so `X-Poll` and `x-poll` behave identically. It does reject the same header supplied twice in different cases within one matcher, since only one of the two could take effect and the intent would be ambiguous.
  • How would you match a Cypress route on the absence of a request header?
    You cannot express absence in a routeMatcher — every property states something the request must have. Either match on a positive feature the other request carries, such as a different method or query parameter, or register a broader route with a request-handler function and branch on the real request inside it, leaving anything you do not want to touch alone.

saying these in an interview costs you the question

  • Tries to express a missing header as a matcher property
  • Thinks header names must match the exact case sent
  • Assumes a longer URL glob is the only way to narrow
  • Believes routeMatcher properties are ORed together
  • Expects a query matcher to handle repeated parameters