skip to content

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

level: middleimportance: should knowfreq 52%

answer

  1. Waits are consumed in order
  2. Cypress counts per alias, per test
  3. The second call is not a replay
  4. cy.get with .all reads every one
  5. Numeric suffixes start at one

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.

solid answer

~40 s

Each `cy.wait()` on a route alias consumes the **next** matching request. Cypress keeps a counter per alias for the test: the first `cy.wait('@getForecast')` resolves against request 1, the second against request 2, and so on. It never re-resolves an earlier one and never skips ahead. If only one request was ever made, the second wait fails with `cy.wait() timed out waiting 5000ms for the 2nd request to the route: getForecast`. When you want to inspect requests rather than sequence on them, use `cy.get()`: `cy.get('@getForecast.all')` yields an array of every interception recorded, `cy.get('@getForecast.2')` yields the second (indices are **1-based**; `0` is an error), and bare `cy.get('@getForecast')` yields the most recent. Those suffixes belong to `cy.get()` only — `cy.wait('@getForecast.all')` is not supported.

code

javascript · 13 lines
javascript
it('refetches the forecast when the station changes', () => {
  cy.intercept('GET', '/api/forecast*').as('getForecast')

  cy.visit('/dashboard')
  cy.wait('@getForecast') // 1st: the initial load

  cy.get('[data-testid="station-select"]').select('KPDX')
  cy.wait('@getForecast') // 2nd: the refetch, not the initial load

  cy.get('@getForecast.all').should('have.length', 2)
  cy.get('@getForecast.1').its('request.url').should('contain', 'KSEA')
  cy.get('@getForecast.2').its('request.url').should('contain', 'KPDX')
})

go deeper

for a junior

Remember that two waits on one alias mean two separate requests. If the app only calls the endpoint once, the second wait will time out rather than resolve again.

for a middle

Explain the per-alias counter and the ordinal in the timeout message, and show cy.get('@alias.all'), cy.get('@alias.2') and bare cy.get('@alias') as the non-consuming reads.

for a senior

Show that you would count a route's real traffic before adding a wait, and explain why an extra wait added to quiet a failure silently shifts every later assertion onto a different cycle.

for a principal

Be ready to argue when a suite should sequence on network waits at all versus assert over the recorded set, and what convention keeps that consistent across many authors.

## An alias is a queue, not a variable The mental model that causes the most confusion is treating `@getForecast` as a variable holding *the* forecast request. It is not. A route alias names a `cy.intercept()` route, and Cypress records **every** request that route matches, in the order they happened. `cy.wait()` then walks that recording forward one entry at a time. Cypress keeps a small counter per alias for the duration of the test. The first `cy.wait('@getForecast')` claims recorded request 1, the second claims request 2, the third claims request 3. The counter only ever advances: - A wait **never** re-resolves a request an earlier wait already consumed. - A wait **never** skips ahead to the newest request just because it is available. - Waiting on a *different* alias does not move this alias's counter — each alias counts on its own. ## What happens when there is no next request If the second `cy.wait('@getForecast')` runs and only one matching request was ever made, the wait does not fall back to the first one. It waits, and then fails: ``` cy.wait() timed out waiting 5000ms for the 2nd request to the route: getForecast. No request ever occurred. ``` The ordinal in that message — `2nd` — is the counter telling you exactly which entry it wanted. It is one of the more useful error strings in the tool, because it distinguishes "the app never made this call" from "the app made fewer calls than my test assumed". ## cy.get is the other door When you want to *inspect* what a route saw rather than *sequence* on it, use `cy.get()` with the alias. It reads the recording instead of consuming from it, and it supports suffixes that `cy.wait()` does not. | Expression | Yields | Consumes a wait? | |---|---|---| | `cy.wait('@getForecast')` | The next unconsumed interception | Yes | | `cy.get('@getForecast')` | The **most recent** interception | No | | `cy.get('@getForecast.all')` | An array of every recorded interception | No | | `cy.get('@getForecast.2')` | The second interception (1-based) | No | | `cy.wait('@getForecast.all')` | Not supported — the suffix is a `cy.get()` feature | — | Two details to keep straight: - The numeric suffix is **1-based**. `cy.get('@getForecast.0')` is an error, not the first request. - `cy.get()` is a query, so it re-reads the recording on every attempt. That makes `cy.get('@getForecast.all').should('have.length', 2)` a retrying assertion — it keeps looking until the second request lands or `defaultCommandTimeout` runs out. ## Choosing between them 1. **Sequence with `cy.wait()` when the order is the point.** A test that changes station and expects a refetch genuinely wants "wait for the initial load, act, wait for the second call". 2. **Inspect with `cy.get('@alias.all')` when the count or the content is the point.** Asserting that the dashboard issued exactly two forecast calls, or that one of them carried the new station, reads far better as an assertion over the array than as a run of waits. 3. **Do not mix the two carelessly.** A `cy.wait()` you added for sequencing still advances the counter, so a later wait you added for a different reason is now off by one. ## The pattern that goes wrong most often A test fails on some runs, someone adds another `cy.wait('@getForecast')` and it goes green. What actually happened is that the extra wait absorbed a request the author did not know about, and the assertions that follow are now reading a different cycle than they were written for. The fix is to find out how many requests the route really sees — `cy.get('@getForecast.all').then((all) => cy.log(all.length))` answers that in one line — and then decide deliberately how many waits belong in the test. ## A worked sequence ```javascript cy.intercept('GET', '/api/forecast*').as('getForecast') cy.visit('/dashboard') cy.wait('@getForecast') // claims request 1: the initial load cy.get('[data-testid="station-select"]').select('KPDX') cy.wait('@getForecast') // claims request 2: the refetch cy.get('@getForecast.all').should('have.length', 2) ``` The last line is the assertion that documents the intent. It states outright that the dashboard made exactly two forecast calls, so if the app grows a third caller later, the test fails with a length mismatch rather than by silently shifting every wait one slot along. ## Worth remembering - The counter is per alias and advances over the course of one test. - The error message's ordinal (`1st`, `2nd`, `3rd`) is the counter's own position, and it tells you what the test believed about the app's traffic. - `cy.get('@alias.all')` is the read-only view; `cy.wait('@alias')` is the consuming one. - Two aliases pointing at the same underlying route keep two independent counters, because the counter is keyed on the alias name rather than on the request.

  • How do you assert on every request an aliased Cypress route saw, without counting waits?
    Use `cy.get('@getForecast.all')`, which yields an array of all interceptions recorded for that alias and consumes nothing. Because `cy.get()` re-reads the alias on every attempt, a chained assertion retries: `cy.get('@getForecast.all').should('have.length', 3)` keeps checking until the third request lands or `defaultCommandTimeout` expires.
  • Why does `cy.wait('@getForecast.all')` not work in Cypress?
    `.all` and the numeric suffixes are `cy.get()` features for reading interceptions Cypress has already recorded. `cy.wait()` accepts a bare route alias or an array of route aliases and nothing else, which is why the documentation says plainly to reach for `cy.get('@alias.all')` instead.

Each cy.wait on an alias is like taking the next ticket at a counter: the first call is served request one, the second request two. cy.get('@alias.all') is reading the whole day's ledger instead of taking a ticket.

saying these in an interview costs you the question

  • Thinks a second cy.wait re-resolves the first request
  • Uses cy.wait('@alias.all') to read every interception
  • Treats the numeric alias suffix as zero-based
  • Expects cy.get('@alias') to yield every recorded request
  • Adds extra cy.wait calls hoping one will match