QA How-To
How to Use Cypress cy.wait with alias (2026)
Learn cypress cy.wait with alias in 2026 using precise intercepts, typed request assertions, timeouts, GraphQL, repeated requests, caching, and reliable waits.
20 min read | 3,200 words
TL;DR
Register cy.intercept first, assign a descriptive alias with .as('name'), trigger the request, and call cy.wait('@name'). Assert critical request and response facts, then confirm the UI outcome. Use local request and response timeouts only when the operation's documented behavior requires them.
Key Takeaways
- Register cy.intercept before the page load or action that can send the request.
- Use a precise method and route matcher so unrelated traffic cannot release the wait.
- Wait on @alias and assert both the interception and the resulting browser state.
- Diagnose request-timeout and response-timeout phases separately.
- Use repeated waits, alias arrays, dynamic aliases, and alias history for their distinct purposes.
- Account for cache, service workers, and valid no-request scenarios before relying on a route wait.
The cypress cy.wait with alias pattern synchronizes a test with a request observed by cy.intercept(). Register the intercept first, assign it a route alias with .as('name'), trigger the browser action, then call cy.wait('@name'). Cypress waits for a matching request and its response, and yields the interception for precise assertions.
This is more reliable than sleeping for a fixed number of milliseconds because the test advances when the application reaches a meaningful network boundary. It can still be misused. A broad route, late registration, cache hit, duplicate request, or oversized timeout can make a test pass for the wrong reason or fail without useful evidence.
TL;DR
cy.intercept('POST', '/api/orders').as('createOrder')
cy.contains('button', 'Place order').click()
cy.wait('@createOrder').then(({ request, response }) => {
expect(request.body).to.include({ paymentMethod: 'card' })
expect(response?.statusCode).to.eq(201)
})
| Step | Rule |
|---|---|
| 1 | Register cy.intercept() before the request can start |
| 2 | Match the smallest stable method and route contract |
| 3 | Assign a descriptive alias without the @ in .as() |
| 4 | Use the @ prefix in cy.wait() |
| 5 | Assert request, response, and user-visible outcome |
Avoid cy.wait(5000) for application synchronization. A route alias follows the actual operation and usually finishes faster when the system is fast.
1. How Cypress cy.wait with alias Works
cy.intercept() registers a route matcher in Cypress's network layer. .as('createOrder') gives that route a name. When the application sends matching browser traffic, Cypress records an interception. cy.wait('@createOrder') waits for the request to begin and the response to finish, then yields an object containing request and response information.
The alias is not a promise and does not start the request. It is a reference to traffic that the application must generate. This ordering is fundamental:
cy.intercept('GET', '/api/account').as('getAccount')
cy.visit('/account')
cy.wait('@getAccount')
Registering after cy.visit() creates a race because the page may request the account before the intercept exists. Cypress cannot retroactively match traffic it did not observe through that route registration.
The alias name passed to .as() excludes @, while later references include it. Choose business-oriented names such as @createOrder, @loadPermissions, or @searchProducts. Names such as @request1 become difficult to diagnose when the Command Log contains many routes.
Aliases reset before each test. Define common intercepts in beforeEach, not in before when several tests need them. See Cypress cy.intercept fundamentals for route matching and stubbing behavior that underpins this waiting pattern.
2. Build the Basic Wait and Assertion Flow
A good flow has arrangement, action, network assertion, and visible outcome. The request proves the application collaboration. The page assertion proves what the user received.
type CreateOrderRequest = {
sku: string
quantity: number
}
type CreateOrderResponse = {
id: string
status: 'confirmed'
}
it('creates an order from the cart', () => {
cy.intercept('POST', '/api/orders').as('createOrder')
cy.visit('/cart')
cy.contains('button', 'Place order').click()
cy.wait<CreateOrderRequest, CreateOrderResponse>('@createOrder')
.then(({ request, response }) => {
expect(request.body).to.deep.equal({ sku: 'KEYBOARD-1', quantity: 1 })
expect(response?.statusCode).to.eq(201)
expect(response?.body.status).to.eq('confirmed')
})
cy.contains('Order confirmed').should('be.visible')
})
The optional access on response is useful because the interception type allows situations without a normal response, including a network error. If this scenario requires a response, an explicit expect(response, 'create order response').to.exist can improve the diagnostic before inspecting its fields.
Do not assert every header and generated field by default. Check the request inputs, status, and response facts that define the user behavior. Contract-heavy service validation belongs in focused API tests, while the browser test protects the UI integration.
Name the test around the business outcome, not the route. creates an order from the cart remains useful if the backend path changes, while waits for POST orders only repeats implementation. Keep the route assertion close enough that a contract change is still obvious during review.
If the action can send no request because client validation fails, cover that as a separate scenario. An expected alias wait should never double as a test for whether the button was actionable.
3. Match the Right Request Without Overmatching
Route matching determines what releases the wait. A broad pattern such as **/api/** can resolve on an unrelated background request. Match the HTTP method and the narrowest stable URL shape.
cy.intercept({
method: 'GET',
pathname: '/api/products',
query: {
category: 'keyboards',
},
}).as('loadKeyboards')
cy.visit('/products?category=keyboards')
cy.wait('@loadKeyboards').its('response.statusCode').should('eq', 200)
Use pathname when query ordering or encoding should not affect the match. Use a string glob for a stable path family, or a regular expression when the route grammar demands it. Avoid matching volatile hostnames unless verifying a particular origin is the requirement.
Method matching matters. An unqualified /api/orders/* intercept can catch GET, PATCH, or DELETE requests. A wait intended for a save might resolve on a background refresh. Add method: 'PATCH' or use the positional cy.intercept('PATCH', '/api/orders/*') form.
If two application features legitimately share the same endpoint and method, inspect body or headers in a route callback and assign a request-specific alias. This is common for GraphQL and command-style endpoints. A precise alias is part of the test oracle, not just a synchronization convenience.
4. Assert the Interception Object Correctly
Waiting on a route alias yields an interception with request and, for a completed normal response, response. Useful request fields include method, URL, headers, query, and body. Useful response fields include status code, headers, and body.
cy.wait('@updateProfile').then(({ request, response }) => {
expect(request.method).to.eq('PATCH')
expect(request.headers).to.have.property('content-type')
expect(request.body).to.include({ displayName: 'Avery QA' })
expect(response, 'profile response').to.exist
expect(response!.statusCode).to.eq(200)
expect(response!.body).to.include({ displayName: 'Avery QA' })
})
response.body is parsed as an object when the response declares JSON through its content type. Redirects, 304 responses, or incorrect server headers may produce a string or no useful body. Test the server contract rather than assuming every response is parsed JSON.
An alias wait is also a powerful diagnostic. Label assertions with the operation or field, especially for large bodies. Prefer expect(request.body.accountId, 'submitted account').to.eq('acct-42') to an unexplained deep equality failure on a large object.
After the interception assertion, verify the browser state. A 200 response does not guarantee that the application rendered the data, cleared its loading state, or handled a partial result correctly.
Avoid mutating the yielded request or response inside the assertion callback. Route behavior belongs in the intercept handler, while the wait callback should observe evidence and keep the cause of failures easy to follow.
5. Understand Request and Response Timeouts
For an alias, Cypress conceptually waits in two phases. It first waits for a matching request to leave the browser, governed by the request timeout. After the request is observed, it waits for the response, governed by the response timeout. These phases point to different failures.
| Failure phase | Likely cause | First evidence to inspect |
|---|---|---|
| No matching request starts | Late intercept, wrong matcher, blocked UI action, cache | Command Log, route badge, browser console |
| Request starts, no response completes | Slow or stalled service, connection issue, server timeout | Server logs, request ID, response timing |
| Response completes, UI assertion fails | Rendering, state management, parsing, product logic | Response body, DOM state, application errors |
Override timeouts locally only when the operation's expected service time supports it:
cy.wait('@generatePreview', {
requestTimeout: 5_000,
responseTimeout: 30_000,
})
The timeout option can set the timeout basis, while requestTimeout and responseTimeout express the phases explicitly. Do not increase global values to hide one slow endpoint. A long timeout slows every genuine failure and still does not explain why the operation is slow.
For background work that returns 202 before completion, wait for the submission request, then observe the documented completion mechanism. Do not keep one HTTP response open in the test model if production uses polling, events, or a status endpoint.
6. Wait for Multiple Aliases and Repeated Requests
Pass an array when the next UI state depends on several independent requests. Cypress resolves after every listed alias has produced a matching response.
cy.intercept('GET', '/api/account').as('getAccount')
cy.intercept('GET', '/api/permissions').as('getPermissions')
cy.intercept('GET', '/api/notifications*').as('getNotifications')
cy.visit('/dashboard')
cy.wait(['@getAccount', '@getPermissions', '@getNotifications'])
.then((interceptions) => {
expect(interceptions).to.have.length(3)
interceptions.forEach(({ response }) => {
expect(response?.statusCode).to.eq(200)
})
})
Array waiting does not promise a particular completion order. If each response has a distinct schema, separate waits give clearer types and assertions.
For repeated traffic on one route, call cy.wait('@search') once per request in the expected sequence. Each wait consumes the next matching request for that purpose:
cy.get('[type=search]').type('lap')
cy.wait('@search').its('request.url').should('include', 'q=lap')
cy.get('[type=search]').type('top')
cy.wait('@search').its('request.url').should('include', 'q=laptop')
Current Cypress alias history can also be queried with cy.get('@search.all'), and a 1-based request can be read with cy.get('@search.1'). Use those forms to assert total captured traffic after requests settle. They are cy.get() alias forms, not arguments for cy.wait().
Give each repeated action a visible postcondition as well as a request assertion. A correct second query is not enough if the page still displays results from the first. When calls can overlap, assert the product's stale-response or cancellation behavior rather than assuming network completion order equals rendering order.
7. Create Dynamic Aliases for GraphQL
GraphQL often sends many operations through one POST URL, so a route alias on /graphql alone is too broad. Inspect req.body.operationName in the intercept callback and set req.alias for the operation of interest.
cy.intercept('POST', '/graphql', (req) => {
if (req.body.operationName === 'UpdateProfile') {
req.alias = 'gqlUpdateProfile'
}
})
cy.get('[name=displayName]').clear().type('Avery QA')
cy.contains('button', 'Save').click()
cy.wait('@gqlUpdateProfile').then(({ request, response }) => {
expect(request.body.variables).to.deep.equal({
input: { displayName: 'Avery QA' },
})
expect(response?.body.errors).to.be.undefined
expect(response?.body.data.updateProfile.displayName).to.eq('Avery QA')
})
GraphQL can return HTTP 200 with an errors array, so status alone is not sufficient. Assert operation name, critical variables, error absence or expected errors, and relevant data.
Use operation names consistently in application documents. Anonymous operations make logging, server tracing, and test aliasing harder. If persisted queries omit a readable operation from the request body, match the documented identifier instead.
Do not dynamically assign the same alias to every GraphQL operation. That recreates the overmatching problem under a different name. The wait should identify the user action's specific network contract.
Subscriptions and WebSocket messages do not follow the ordinary request-response alias pattern. Test their observable UI effects or use purpose-built instrumentation. Do not force an HTTP alias around a transport it cannot represent.
For batched GraphQL requests, inspect the documented array shape and alias only when the target operation is present. Keep batch-specific logic explicit so one neighboring operation cannot satisfy the assertion.
8. Handle Cache, 304, and Service Worker Behavior
A wait can time out when the browser serves data from cache and no network request reaches Cypress's intercept layer. It can also yield a 304 response whose body is not the JSON object the assertion expected. Service workers add another request path that must be understood in the application architecture.
First decide what the test is proving. If it verifies UI behavior with a warm cache, synchronizing on a request is wrong because no request may be required. Assert the UI state. If it verifies a network refresh, arrange the application so the refresh actually occurs, using supported test configuration or a server response that disables caching in the test environment.
A route callback can remove conditional request headers for a targeted test when that manipulation matches the scenario:
cy.intercept('GET', '/api/catalog', (req) => {
delete req.headers['if-none-match']
}).as('getCatalog')
This encourages a full response instead of revalidation, but it changes browser traffic and should be documented. Do not add it globally merely to silence flaky assertions.
When a 304 is valid, assert the status and user behavior rather than a JSON response body. For service workers, verify whether the request is visible to Cypress in the actual setup. A product-level cache test may need separate instrumentation from an ordinary route wait.
9. Use TypeScript for Safer Alias Waits
cy.wait<RequestBody, ResponseBody>('@alias') provides explicit request and response types for a single alias. This improves autocomplete and makes payload assumptions visible.
type SearchRequest = {
query: string
page: number
}
type SearchResponse = {
items: Array<{ id: string; name: string }>
total: number
}
cy.intercept('POST', '/api/search').as('search')
cy.contains('button', 'Search').click()
cy.wait<SearchRequest, SearchResponse>('@search').then((interception) => {
expect(interception.request.body.page).to.eq(1)
expect(interception.response?.body.total).to.be.at.least(0)
expect(interception.response?.body.items).to.be.an('array')
})
For an array of aliases with different schemas, use the Interception type from cypress/types/net-stubbing and unions when needed. Separate waits are often clearer than a broad union when operations require different assertions.
Types do not validate runtime data. The server can still return malformed JSON, missing fields, or unexpected values. Keep runtime assertions for business-critical properties. Generate or share contract types only when the ownership and update process prevents stale types from creating false confidence.
Avoid any merely to silence an interception mismatch. If an endpoint can return success and documented error shapes, model a discriminated union and narrow it from status or a stable field. This keeps the test honest about optional response data.
Compile Cypress specs in CI so type drift fails before a browser run. Runtime assertions and static types protect different failure modes and should remain together.
The TypeScript for Cypress testing guide covers spec and support configuration beyond the wait itself.
10. Use Cypress cy.wait with alias in Production Suites
Treat every alias as a named synchronization contract. Register it close enough to the test to reveal intent, but extract repeated matchers when they are truly stable. Keep user action and wait adjacent so readers can see cause and effect.
Use this review checklist:
- Is the intercept registered before the action or page load?
- Does the matcher include the correct HTTP method?
- Can an unrelated request satisfy the route pattern?
- Does the test assert critical request and response facts?
- Is there a user-visible result after the network assertion?
- Are multiple or repeated requests handled intentionally?
- Does caching make a request optional?
- Are local timeout values tied to an expected service behavior?
- Does a negative assertion have a defined observation window?
- Would a direct API test cover detailed contract cases more cheaply?
Use Cypress wait and retry patterns to combine route waits with retryable DOM queries. A route wait should replace arbitrary sleeping, not every Cypress assertion. Cypress DOM queries already retry, and adding a wait for an unrelated request can make the test slower and more coupled.
The best cypress cy.wait with alias usage explains why the UI is allowed to proceed and leaves enough evidence to distinguish a missing request, stalled response, and rendering failure.
11. Prove a Request Was Not Sent
cy.wait('@alias') is designed for an expected request, so it is the wrong tool when success means no request. Register the intercept, perform the invalid action, reach a state that closes the observation window, then inspect alias history.
it('does not submit an invalid invitation', () => {
cy.intercept('POST', '/api/team/invitations').as('createInvitation')
cy.visit('/team')
cy.contains('button', 'Invite member').click()
cy.get('[name=email]').type('not-an-email')
cy.contains('button', 'Send invitation').click()
cy.contains('[role=alert]', 'Enter a valid email').should('be.visible')
cy.get('[data-testid=invite-dialog]').should('be.visible')
cy.get('@createInvitation.all').should('have.length', 0)
})
The validation alert and open dialog show that synchronous submission handling finished without leaving the form. If the request could be scheduled on a timer, control that timer with cy.clock() and advance through the relevant interval before asserting history. If another network event marks completion, wait for that specific event first.
Do not use cy.wait('@createInvitation', { timeout: 1000 }) and expect the timeout as success. Cypress reports the missing request as a command failure, which creates slow and noisy negative tests. Alias history expresses the backward-looking question directly.
Be precise about what closes the window. A validation message may be enough for synchronous form logic, while autosave, debounce, or background retry requires controlled time. Document that boundary in the scenario so a future timer change does not turn a fast negative assertion into a false pass.
12. Use Route Aliases in Component Tests
Component tests can use the same alias pattern when a mounted component sends browser HTTP traffic. Register the route before mounting if initialization triggers the call.
import { AccountSummary } from '../../src/AccountSummary'
it('renders a controlled account summary', () => {
cy.intercept('GET', '/api/accounts/acct-42/summary', {
statusCode: 200,
body: { openItems: 3, balance: 125 },
}).as('getAccountSummary')
cy.mount(<AccountSummary accountId="acct-42" />)
cy.wait('@getAccountSummary').then(({ request, response }) => {
expect(request.method).to.eq('GET')
expect(response?.statusCode).to.eq(200)
})
cy.contains('3 open items').should('be.visible')
})
This isolates the component's rendering contract from the deployed service. It does not replace an end-to-end test that verifies real authentication, routing, and backend integration. Keep the static response faithful to the documented schema, and share typed factories when several component specs need the same shape.
Mount timing has the same rule as visit timing: the intercept must exist before the component's effect can send the request. The wait then provides an explicit point between loading-state assertions and final rendering assertions.
Reset component providers and in-memory caches between tests. A query client can serve cached data and skip the request, producing the same ambiguity as a browser cache. Create a fresh provider instance or clear it through the component harness rather than weakening the alias expectation.
If the component receives a service callback as a public prop, a callback stub may be more focused than a route intercept. Choose the boundary the component contract actually exposes.
13. Diagnose Alias Wait Failures Systematically
Start with the Routes panel and Command Log. Confirm that the intercept was registered, then inspect whether a request displays a matching route badge. No request suggests the UI action did not occur, caching bypassed the network, or an earlier application error stopped execution. A request without the expected badge points to method, path, query, origin, or registration timing.
If the route matched but the response phase timed out, capture the server correlation ID and inspect service logs. Do not repeatedly add seconds to the Cypress timeout. A proxy, stalled connection, unhandled server promise, or overloaded environment requires different evidence than a missing browser request.
When cy.wait() succeeds but the next assertion fails, inspect response content type, status, and body before blaming synchronization. The application may have rejected malformed data, rendered an error boundary, or kept stale state. Preserve screenshots, browser console errors, and the interception's safe fields in CI artifacts.
Reproduce with the exact spec, browser family, application build, and route matcher. Simplifying the matcher temporarily can reveal a URL difference, but restore a precise production assertion once the cause is known.
Interview Questions and Answers
Q: How do you wait for an API call in Cypress?
I register cy.intercept() before the request can start, alias it with .as('name'), trigger the action, and call cy.wait('@name'). I then assert critical request and response fields and verify the resulting UI state.
Q: Why is cy.wait('@alias') better than cy.wait(5000)?
An alias wait follows a meaningful application event and completes as soon as that event finishes. A fixed sleep is always too long or potentially too short and gives weak diagnostics. Route waits also yield the interception for assertions.
Q: What happens if the intercept is registered after the action?
The application may send the request before Cypress registers the route. The wait then times out because it cannot match traffic retroactively. I always arrange the intercept before visit, click, or submission that can trigger the request.
Q: How do request and response timeouts differ?
The request timeout covers waiting for a matching request to begin. The response timeout covers waiting after that request is observed until its response completes. The distinction helps diagnose a missing action or matcher separately from a slow server.
Q: How do you wait for two requests?
I pass an array such as cy.wait(['@account', '@permissions']) when both responses gate the next state. If their schemas or assertions differ, I often use separate waits. Completion order should not be assumed from the array.
Q: How do you handle multiple GraphQL operations on one endpoint?
I inspect req.body.operationName in the intercept callback and assign req.alias only for the operation under test. I wait on that dynamic alias and assert variables, GraphQL errors, and relevant data rather than status alone.
Q: Why might an alias wait fail when the page has data?
The browser may have served the data from cache, a service worker, or state already in memory, so no matching request occurred. The route may also be too narrow or registered late. I decide whether the scenario requires network traffic before choosing the synchronization point.
Common Mistakes
- Calling
cy.intercept()aftercy.visit()or the action that starts the request. - Passing
@nameinto.as()or omitting@fromcy.wait(). - Matching every API request with one broad glob.
- Omitting the method when GET and mutation routes share a path.
- Checking only status and never verifying the visible outcome.
- Assuming array waits resolve in a meaningful response order.
- Expecting one wait to prove that no extra duplicate request occurred.
- Calling
cy.wait('@alias.all')instead of querying history withcy.get('@alias.all'). - Parsing a 304 or incorrectly labeled response as guaranteed JSON.
- Raising global timeouts to cover one slow operation.
- Using a route wait when cache makes the request legitimately optional.
- Replacing retryable DOM assertions with unrelated network waits.
Conclusion
Use cypress cy.wait with alias by registering a precise intercept before the request, naming it, triggering the user action, and waiting on @alias. Assert the important request and response contract, then confirm the browser outcome. This event-based sequence is faster, clearer, and more diagnostic than a fixed delay.
Review the matcher and timing before increasing any timeout. Once the basic wait is stable, add typed bodies, multi-request handling, GraphQL aliases, or history assertions only where the product behavior requires them.
Interview Questions and Answers
Explain the correct order for waiting on an aliased request.
I register a precise cy.intercept and alias it before the request can start. Then I perform the user action and call cy.wait with the @ alias. I assert the interception and the user-visible result after the response.
Why is an alias wait better than a fixed cy.wait time?
It follows a meaningful application event and completes as soon as the response finishes. A fixed delay can be too short on a slow run and always wastes time on a fast run. The alias wait also yields diagnostic request and response data.
How do you prevent an alias from matching the wrong request?
I include the HTTP method and the smallest stable pathname or URL grammar. I add query matching when it defines request identity. For shared command or GraphQL endpoints, I assign a dynamic alias from a stable body field.
How do you handle several calls to the same alias?
I call cy.wait('@alias') once per expected request and make sequence-specific assertions. After traffic settles, cy.get('@alias.all') can verify total history and numeric alias access can inspect a 1-based occurrence. The history suffixes are not cy.wait arguments.
How do you diagnose a request phase timeout?
I confirm the intercept registered and inspect the Command Log for the request and route badge. No request suggests the action, cache, or an earlier application error. An unmatched request points to method, path, query, origin, or registration timing.
How do you type an alias wait in TypeScript?
For one route I use cy.wait<RequestBody, ResponseBody>('@alias'). The types improve editor feedback but do not validate runtime data. I keep assertions on critical fields and often use separate waits instead of complex unions for different routes.
When should a test not wait on a route alias?
It should not when cache or local state legitimately makes the request optional, or when a retryable DOM condition is the true outcome. It also should not wait on unrelated background traffic merely to slow the test. Synchronization must observe the event the scenario requires.
Frequently Asked Questions
How do I use cy.wait with an alias?
Register cy.intercept before the request, chain .as('name'), trigger the application action, and call cy.wait('@name'). The wait yields the matching interception so you can assert request, response, and final UI behavior.
Why should cy.intercept come before cy.visit?
A page can send startup requests immediately. Cypress cannot retroactively match traffic that happened before the route registration. Putting the intercept first removes that race.
Can cy.wait wait for multiple aliases?
Yes. Pass an array such as cy.wait(['@account', '@permissions']) when all listed requests gate the next state. Use separate waits when different response schemas need detailed assertions, and do not assume meaningful completion order.
What is the difference between requestTimeout and responseTimeout?
requestTimeout covers waiting for a matching request to begin. responseTimeout covers waiting after the request is observed until its response completes. The two phases distinguish a missing action or matcher from a stalled service.
How do I wait for a GraphQL operation in Cypress?
Intercept the GraphQL endpoint, inspect req.body.operationName, and assign req.alias for the target operation. Wait on that dynamic alias and assert variables, the errors field, and relevant data. HTTP 200 alone is not enough for GraphQL success.
Why does cy.wait time out when the page already has data?
The browser may have used cache, a service worker, or in-memory state, so no matching request reached the intercept. The route may also be registered late or matched too narrowly. Decide whether network traffic is actually required by the scenario.
How do I assert that an aliased request did not happen?
Do not expect cy.wait to time out successfully. Reach a state that closes the observation window, then query cy.get('@alias.all') and assert an empty history. Control timers first if a delayed request remains possible.