skip to content

Waiting on Aliases

Naming an intercept with .as and blocking on it with cy.wait, then asserting on the request and response that wait hands back. This is how a test synchronises on the network instead of sleeping.

on this pageshow

explore

questions

5

In Cypress, what does `cy.wait('@getForecast')` yield to the next command?

level: juniorimportance: must knowfreq 78%

answer

  1. What comes back once the route resolves
  2. One object per request-response cycle
  3. Two halves: what went out, what returned
  4. Also carries id, routeId and error
  5. Chain .its('response.statusCode') to assert

basics

~20 s

cy.wait on an intercept alias yields one interception object for that request-response cycle: request and response, plus id, routeId, and an error property when the request failed. Chain .its() or .then() to assert on it.

solid answer

~40 s

`cy.wait('@getForecast')` resolves once a request matched by the aliased `cy.intercept()` route has completed, and it yields an **interception object** describing that one cycle. The properties you use are `request` (`method`, `url`, `headers`, `body`) and `response` (`statusCode`, `headers`, `body`), alongside `id` and `routeId`; a request that ended in a network error yields `error` and no `response`. Cypress parses `response.body` into an object only when the response carries a `Content-Type: application/json` header — otherwise it stays a string. From there you assert as you would on any subject: `cy.wait('@getForecast').its('response.statusCode').should('eq', 200)`, or `.then(({ request, response }) => { … })` for anything that needs several properties at once. The millisecond form, `cy.wait(ms)`, is different — it yields back whatever subject the chain already had.

code

javascript · 15 lines
javascript
describe('forecast panel', () => {
  it('renders what the forecast API returned', () => {
    cy.intercept('GET', '/api/forecast').as('getForecast')

    cy.visit('/dashboard')

    cy.wait('@getForecast').then(({ request, response }) => {
      expect(request.method).to.equal('GET')
      expect(response.statusCode).to.equal(200)
      expect(response.body.periods).to.have.length(7)
    })

    cy.get('[data-testid="forecast-day"]').should('have.length', 7)
  })
})

go deeper

for a junior

Be ready to name the two halves of what comes back — request and response — and to show one assertion off each, such as .its('response.statusCode').should('eq', 200).

for a middle

Explain why .as() names a route while cy.wait consumes a request, and why response.body is sometimes a string: Cypress parses it only for a JSON content type.

for a senior

Show that you read the interception when a failure is ambiguous, using status, url and headers to say whether the app asked wrongly or the server answered wrongly.

for a principal

Be ready to say how much of a suite should assert on interception internals at all, and what a team convention for that looks like across many specs.

## Two commands, one contract `cy.intercept()` registers a **route**: it tells Cypress which requests to watch and, optionally, what to reply with. The command yields nothing itself, which is why you chain `.as()` onto it — `.as()` names the *route*. `cy.wait('@getForecast')` then blocks the command queue until a request that route matched has completed, and hands the next command a single **interception object** describing that one request-response cycle. The split matters. `.as()` names a route, not a request; `cy.wait()` consumes one request the route matched. Nothing about the alias is a readable value until a request has actually gone through it. ## What is on the interception object | Property | Present when | What it holds | |---|---|---| | `request` | always | The outgoing request: `method`, `url`, `headers`, `body` | | `response` | the server or the stub answered | `statusCode`, `headers`, `body` | | `error` | the request failed at the network level | The captured error; `response` is absent | | `id` | always | Cypress's identifier for this one request-response cycle | | `routeId` | always | The identifier of the `cy.intercept()` route that matched it | A few details about the payload itself are worth carrying into an interview: - `response.body` is parsed into an object **only** when the response carries a `Content-Type: application/json` header. A plain-text body, a `301` redirect or a `304 Not Modified` gives you `response.body` as a **string**. - `request.url` is the fully-qualified URL the browser actually asked for, query string included — not the pattern you wrote in `cy.intercept()`. - The object is a settled snapshot. By the time `cy.wait()` releases the queue, the cycle it describes is finished, so nothing on it changes afterwards. ## Asserting on what came back The interception is an ordinary JavaScript object, so you assert on it the way you assert on any Cypress subject: 1. `.its('response.statusCode').should('eq', 200)` — read one nested value and assert on it. `.its()` is a query, so it starts a fresh chain. 2. `.then((interception) => { … })` — take the whole object and use Chai's `expect` inside the callback. This is what you want when several properties have to be checked together, or when a value has to be derived from the body. 3. `.should('have.property', 'error')` — assert on the interception itself. Useful for the failure case, where there is no `response` to read at all. Destructuring reads well inside `.then()`, because the two properties you almost always want are the top-level ones: `.then(({ request, response }) => …)`. ## The millisecond form yields something else entirely `cy.wait()` has a second signature that takes a number of milliseconds. Given a number, it yields back **the same subject the chain already had** — there is no interception, because no route was involved. That is precisely why the alias form is the one you can chain `.its('response.statusCode')` onto: only the alias form produces a network-shaped subject. ## Where it goes wrong - **Waiting on an alias that is not a route.** A DOM alias or a spy alias fails with `cy.wait() only accepts aliases for routes. The alias: 'x' did not match a route.` - **Treating the yielded object as the body.** `cy.wait('@getForecast').should('have.length', 7)` does not check the forecast array; it checks the interception object, which has no length. - **Expecting an array from one alias.** A single alias yields a single interception. An array comes from passing `cy.wait()` an array of aliases, or from `cy.get('@getForecast.all')`. - **Reading `response` on a failed request.** When the request errored, `response` is `undefined` and `error` is set, so `.its('response.statusCode')` fails with an unhelpful "cannot read property of undefined" instead of surfacing the network error you actually hit. ## In practice A test for a weather dashboard usually wants two things out of one wait: confirmation that the app asked for the right resource, and a look at what came back. ```javascript cy.intercept('GET', '/api/forecast').as('getForecast') cy.visit('/dashboard') cy.wait('@getForecast').then(({ request, response }) => { expect(request.method).to.equal('GET') expect(response.statusCode).to.equal(200) }) ``` The interception is the half of the picture the rendered page cannot show you: it tells you exactly what crossed the wire, with the status and headers attached, which is why a failure here points at a specific request rather than at a missing element.

  • When does `interception.response.body` come back as a string rather than an object?
    Cypress parses the body only when the response carries a `Content-Type: application/json` header. Anything else — a plain-text payload, a `301` redirect, a `304 Not Modified` — yields `response.body` as a string, so `.its('response.body.periods')` will not resolve. Assert on the string, or parse it yourself inside `.then()`.
  • What does `cy.wait('@getForecast')` yield when the request never received a response?
    The interception still comes back, but with an `error` property set and no `response`. That is why Cypress documents `cy.wait('@alias').should('have.property', 'error')` as the way to assert a route ended in a network failure rather than in a status code.

saying these in an interview costs you the question

  • Says cy.wait yields the response body directly
  • Expects an array of interceptions from one alias
  • Thinks cy.wait('@alias') yields a DOM element
  • Assumes response.body is always a parsed object
  • Waits on an alias never attached to cy.intercept
open as a page

A Cypress wait fails: 'timed out waiting 5000ms for the 1st request to the route'. What does that mean?

level: seniorimportance: must knowfreq 55%

basics

~20 s

The request phase timed out: no request matching that aliased route left the browser inside requestTimeout, which defaults to 5000 ms. The app never made the call, so widening responseTimeout changes nothing about this failure.

open as a page

In Cypress, what does `cy.wait(['@getStations', '@getAlerts'])` resolve on and yield?

level: middleimportance: should knowfreq 42%

basics

~20 s

It blocks until every listed route alias has completed a request-response cycle, each within the usual requestTimeout and responseTimeout budgets, then yields an array of interceptions ordered by the aliases as you passed them, not by arrival.

open as a page

In Cypress, what does a second `cy.wait('@getForecast')` on the same alias wait for?

level: middleimportance: should knowfreq 52%

basics

~20 s

The second matching request, not the first one again. Cypress keeps a per-alias counter for the test, and each cy.wait on that alias claims the next recorded request in order, so a third call waits for the third.

open as a page

In Cypress, a background poll keeps satisfying your `cy.wait('@getForecast')` calls. Why, and what do you change?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Because a route alias records every request the route matches, whoever caused it. Each cy.wait consumes the next recorded request, so a timer's refresh takes a slot and your second wait resolves against it rather than against the fetch you triggered.

open as a page