QA How-To
Cypress cy.wait with alias: Examples
Use each cypress cy.wait with alias example for page loads, POST bodies, errors, GraphQL, pagination, retries, loading states, and duplicate request checks.
20 min read | 3,302 words
TL;DR
Start with cy.intercept(...).as('name'), trigger the browser request, and call cy.wait('@name'). The recipes cover initial loads, POST requests, errors, multiple calls, pagination, debounce, GraphQL, delays, 204 responses, history, refreshes, and retries with precise assertions.
Key Takeaways
- Intercept page-load requests before visiting and mutation requests before clicking submit.
- Assert outgoing bodies and response facts that protect the user behavior.
- Use static responses for deterministic error and loading-state scenarios.
- Wait repeatedly on one alias for pagination, refresh, and retry sequences.
- Combine controlled time with alias waits for debounce and delayed behavior.
- Use cy.get('@alias.all') after traffic settles to detect duplicate requests.
The basic cypress cy.wait with alias example is three lines of intent: intercept a precise route, name it, then wait after the user action that sends the request. The most useful examples go further by asserting the request, response, repeated-call sequence, or final UI state that actually protects the product.
This cookbook provides runnable TypeScript patterns for page loads, POST bodies, errors, parallel dashboard calls, repeated requests, debounced search, GraphQL, delayed stubs, 204 responses, and alias history. Each recipe uses current Cypress APIs and keeps fixed sleeps out of application synchronization.
TL;DR
cy.intercept('GET', '/api/projects/*').as('getProject')
cy.visit('/projects/prj-42')
cy.wait('@getProject').its('response.statusCode').should('eq', 200)
cy.contains('h1', 'Release Readiness').should('be.visible')
| Scenario | Alias pattern | Main evidence |
|---|---|---|
| Initial data load | Intercept before cy.visit() |
Response plus rendered content |
| Form submission | Intercept before click | Request body, status, success UI |
| Error handling | Stub error response | Error code plus recovery UI |
| Several startup calls | Wait on alias array | All required responses arrived |
| Repeated search or paging | Wait on the same alias repeatedly | Each request has correct parameters |
| GraphQL | Assign req.alias by operation |
Variables, errors, and data |
1. Page Load Cypress cy.wait with alias Example
Register page-load intercepts before cy.visit(). The page may request data as soon as its scripts initialize, so reversing these lines introduces a race.
type ProjectResponse = {
id: string
name: string
status: 'active' | 'archived'
}
describe('project details', () => {
it('renders the requested project', () => {
cy.intercept('GET', '/api/projects/prj-42').as('getProject')
cy.visit('/projects/prj-42')
cy.wait<never, ProjectResponse>('@getProject').then(({ request, response }) => {
expect(request.method).to.eq('GET')
expect(response, 'project response').to.exist
expect(response!.statusCode).to.eq(200)
expect(response!.body).to.include({
id: 'prj-42',
status: 'active',
})
})
cy.contains('h1', 'Release Readiness').should('be.visible')
cy.get('[data-testid=project-status]').should('have.text', 'Active')
})
})
The route is exact because the scenario knows the project. If the ID is generated during setup, create the intercept after receiving the ID but before visiting. A wildcard is acceptable when the route identity is asserted from request.url.
Keep the visible assertions. A successful response alone does not prove that state management consumed the payload or that the correct project rendered.
When the page can redirect on 401 or 404, cover those branches separately with controlled responses. One happy-path alias should not accept several statuses.
If the project route can be prefetched before navigation, confirm which request the alias consumes. Disable prefetch in the test setup or inspect request timing and identity so the wait does not resolve on data fetched for a different user action. Exact IDs reduce that risk.
2. POST Request and Response Assertions
For a mutation, assert the outgoing business input and the server result. This catches a UI that displays success even though it sent an incorrect value.
type InviteRequest = {
email: string
role: 'viewer' | 'editor'
}
type InviteResponse = {
id: string
status: 'pending'
}
it('invites an editor', () => {
cy.intercept('POST', '/api/team/invitations').as('createInvitation')
cy.visit('/team')
cy.contains('button', 'Invite member').click()
cy.get('[name=email]').type('avery@example.test')
cy.get('[name=role]').select('editor')
cy.contains('button', 'Send invitation').click()
cy.wait<InviteRequest, InviteResponse>('@createInvitation')
.then(({ request, response }) => {
expect(request.body).to.deep.equal({
email: 'avery@example.test',
role: 'editor',
})
expect(response?.statusCode).to.eq(201)
expect(response?.body.status).to.eq('pending')
expect(response?.body.id).to.be.a('string').and.not.be.empty
})
cy.contains('Invitation sent').should('be.visible')
})
Do not hardcode a generated invitation ID. Assert its shape, then capture it only if later cleanup or navigation requires it. If headers carry a required idempotency key, assert that specific header as part of the request contract.
Detailed schema and negative coverage can run more cheaply through Cypress cy.request examples. Keep this browser test focused on the UI integration.
Capture a generated response ID only when a later step needs it. Keep dependent Cypress commands inside the chain or return the value from the callback, rather than assigning it to a normal variable and reading it before the wait executes. If the POST is retried, assert an idempotency key and call count because two successful invitations would be a business defect.
Use called response fields only when the server contract guarantees them. A browser test should not normalize inconsistent success codes or accept several shapes for convenience.
3. Stub a Server Error and Wait on Its Alias
cy.intercept() can provide a static response and still produce an interception for cy.wait(). This makes error UI deterministic without requiring a real service outage.
it('keeps checkout recoverable after inventory rejects the order', () => {
cy.intercept('POST', '/api/orders', {
statusCode: 409,
headers: { 'content-type': 'application/json' },
body: {
code: 'OUT_OF_STOCK',
message: 'The selected item is unavailable',
},
}).as('createOrder')
cy.visit('/checkout')
cy.contains('button', 'Place order').click()
cy.wait('@createOrder').then(({ request, response }) => {
expect(request.body).to.include({ sku: 'KEYBOARD-1' })
expect(response?.statusCode).to.eq(409)
expect(response?.body.code).to.eq('OUT_OF_STOCK')
})
cy.contains('This item just sold out').should('be.visible')
cy.contains('button', 'Return to cart').should('be.enabled')
cy.contains('Order confirmed').should('not.exist')
})
This test proves the client response to a documented error, not that the real inventory service returns 409 correctly. Maintain separate integration or API contract checks against the service. Avoid returning an unrealistic body merely because it is easy to assert.
Use API error handling and negative testing to design a compact risk-based error matrix.
Keep the stubbed headers realistic. If the application parses JSON only when the content type is correct, omitting that header can test a different failure than intended. Add request delay or network error only in dedicated scenarios, because combining transport failure, error status, and malformed body makes the expected recovery unclear.
Also assert that the form retains or safely restores user input when retry is supported. Recovery is more than showing an alert.
4. Wait for Several Dashboard Requests
A dashboard may not be ready until account, permissions, and summary calls finish. Waiting on an array is concise when the next assertion needs all of them.
it('loads the authorized dashboard', () => {
cy.intercept('GET', '/api/account').as('getAccount')
cy.intercept('GET', '/api/permissions').as('getPermissions')
cy.intercept('GET', '/api/dashboard/summary').as('getSummary')
cy.visit('/dashboard')
cy.wait(['@getAccount', '@getPermissions', '@getSummary'])
.then((interceptions) => {
expect(interceptions).to.have.length(3)
interceptions.forEach(({ response }) => {
expect(response?.statusCode).to.eq(200)
})
})
cy.contains('h1', 'Dashboard').should('be.visible')
cy.get('[data-testid=summary-card]').should('have.length.at.least', 1)
})
The response array should not become a place for schema-specific assertions based on position. The requests can complete in different orders, and union types make detailed fields awkward. Use individual waits when each contract deserves different checks:
cy.wait('@getPermissions').its('response.body.canExport').should('eq', true)
cy.wait('@getSummary').its('response.body.openItems').should('be.a', 'number')
Wait only for calls that gate the behavior under test. Including every background request couples the test to implementation and can turn a harmless analytics delay into a dashboard failure.
If one required call fails, assert that the page presents a coherent partial or error state. Do not let the combined wait become the only oracle.
Consider whether the three requests are truly independent. If permissions must complete before the summary request is allowed, separate waits and assertions communicate that sequence better than one array. Array waiting is best when response order does not define product behavior and only collective readiness matters.
5. Reuse One Alias for Pagination
Repeated waits on one alias consume matching requests in sequence. This is ideal for pagination because each click should cause one next-page request.
it('requests the next two result pages', () => {
cy.intercept({
method: 'GET',
pathname: '/api/audit-events',
}).as('getAuditEvents')
cy.visit('/audit')
cy.wait('@getAuditEvents').then(({ request, response }) => {
expect(request.query.page).to.eq('1')
expect(response?.statusCode).to.eq(200)
})
cy.contains('button', 'Next').click()
cy.wait('@getAuditEvents').its('request.query.page').should('eq', '2')
cy.get('[aria-current=page]').should('have.text', '2')
cy.contains('button', 'Next').click()
cy.wait('@getAuditEvents').its('request.query.page').should('eq', '3')
cy.get('[aria-current=page]').should('have.text', '3')
})
This sequence proves ordering but does not by itself prove there were no extra refreshes. After expected requests settle, query the captured history with cy.get('@getAuditEvents.all') and assert count when duplicate traffic is a product risk.
Use a controlled dataset so the Next button remains available. A test that depends on whatever records happen to exist in a shared environment will fail for data reasons unrelated to waiting.
Also verify that changing pages preserves the active filter or sort when that state is contractual. Assert those parameters from each intercepted request rather than only reading the page number.
When the API uses cursor pagination, assert the returned cursor is passed into the next request instead of inventing numeric pages. Keep each wait paired with the click that causes it so a reviewer can see the sequence without tracing aliases across the whole spec.
6. Debounced Search Without Fixed Sleeps
Debounced search combines a controlled browser clock with an alias wait. Advance the debounce timer intentionally, then wait for the request. No real-time sleep is required.
it('searches once after the user stops typing', () => {
cy.clock()
cy.intercept({
method: 'GET',
pathname: '/api/search',
}).as('search')
cy.visit('/catalog')
cy.get('[type=search]').type('mechanical keyboard')
cy.tick(300)
cy.wait('@search').then(({ request, response }) => {
expect(request.query.q).to.eq('mechanical keyboard')
expect(response?.statusCode).to.eq(200)
})
cy.get('[data-testid=search-result]').should('have.length.at.least', 1)
cy.get('@search.all').should('have.length', 1)
})
Use the application's actual debounce interval. The illustrative 300 milliseconds belongs only if that is the product contract. cy.clock() controls timer APIs in the application window, while cy.wait('@search') observes the network event once the timer callback runs.
If results can arrive out of order, test cancellation or stale-result handling by returning controlled delayed responses. Do not merely increase the fixed wait until the race disappears.
Restore or advance the clock intentionally if the component also uses loading animations, toast expiry, or retry timers. A frozen clock can prevent unrelated UI from settling. Scope the scenario to the debounce contract and use network aliases for responses, because cy.tick() does not make a server reply complete.
The broader Cypress wait and retry patterns guide explains which Cypress operations retry and which need explicit control.
7. GraphQL Dynamic Alias Example
When every GraphQL operation uses /graphql, assign an alias inside the intercept callback based on the operation name. This keeps unrelated queries from releasing the wait.
type UpdatePreferencesResponse = {
data: {
updatePreferences: {
emailReports: boolean
}
}
errors?: Array<{ message: string }>
}
it('saves report preferences', () => {
cy.intercept('POST', '/graphql', (req) => {
if (req.body.operationName === 'UpdatePreferences') {
req.alias = 'gqlUpdatePreferences'
}
})
cy.visit('/settings/notifications')
cy.get('[name=emailReports]').check()
cy.contains('button', 'Save preferences').click()
cy.wait<unknown, UpdatePreferencesResponse>('@gqlUpdatePreferences')
.then(({ request, response }) => {
expect(request.body.variables.input).to.deep.equal({
emailReports: true,
})
expect(response?.statusCode).to.eq(200)
expect(response?.body.errors).to.be.undefined
expect(response?.body.data.updatePreferences.emailReports).to.eq(true)
})
cy.contains('Preferences saved').should('be.visible')
})
GraphQL errors can arrive with HTTP 200, so inspect the GraphQL body. For a deliberate validation failure, assert the documented error extension code and verify that stale success UI is absent.
Operation naming improves server traces as well as test matching. If the application uses persisted query hashes, match the documented hash or metadata field instead.
GraphQL clients may batch operations or answer from a normalized cache. For batching, inspect the array and alias the request only when it contains UpdatePreferences. For a cached query, decide whether the test needs a network exchange or only a rendered state. Do not wait for traffic the client is designed to avoid.
When testing errors, assert the documented extensions.code or equivalent stable field. Message text may be localized or revised independently of the client behavior.
8. Delayed Response and Loading-State Example
A static response with delay can create a controlled loading window. The test asserts that the application displays a progress state before the response completes and removes it afterward.
it('shows progress while the report loads', () => {
cy.intercept('GET', '/api/reports/monthly', {
statusCode: 200,
delay: 750,
headers: { 'content-type': 'application/json' },
body: {
period: '2026-06',
total: 42,
},
}).as('getMonthlyReport')
cy.visit('/reports')
cy.contains('button', 'Load monthly report').click()
cy.get('[role=status]').should('contain.text', 'Loading report')
cy.wait('@getMonthlyReport').its('response.statusCode').should('eq', 200)
cy.get('[role=status]').should('not.exist')
cy.get('[data-testid=report-total]').should('have.text', '42')
})
The delay is controlled test data, not a performance expectation. Keep it just long enough for Cypress to observe the loading state. Do not use large artificial delays across the suite, because they consume runner time and can mask a UI that reacts slowly after the response.
For real performance testing, measure with suitable service and browser instrumentation. A functional route stub only proves state transitions.
Keep the controlled payload small and representative so rendering assertions remain easy to diagnose.
Select the delay based on observability, not realism. It should create a deterministic window for the loading indicator without dominating suite time. If the indicator intentionally appears only after a threshold to prevent flicker, control application time separately and assert both the short and long request cases.
Always finish with the content state. A disappearing spinner alone can represent success, an empty response, or an unhandled error.
9. Assert 204 No Content and Request Headers
Successful DELETE operations often return 204 with no body. Assert the status and UI result rather than trying to parse JSON that should not exist.
it('deletes a saved filter', () => {
cy.intercept('DELETE', '/api/filters/filter-7').as('deleteFilter')
cy.visit('/filters')
cy.get('[data-filter-id=filter-7]').within(() => {
cy.contains('button', 'Delete').click()
})
cy.contains('button', 'Confirm delete').click()
cy.wait('@deleteFilter').then(({ request, response }) => {
expect(request.headers).to.have.property('x-csrf-token')
expect(response, 'delete response').to.exist
expect(response!.statusCode).to.eq(204)
})
cy.get('[data-filter-id=filter-7]').should('not.exist')
cy.contains('Filter deleted').should('be.visible')
})
Do not assert a secret header's exact value in test output. Confirm presence or safe structure. Authentication and CSRF tests should also cover the failure path through direct API or integration checks.
A 204 contract means the page must update from local state, a subsequent refresh, or another documented mechanism. The final DOM assertion proves that the UI completed that responsibility.
If deletion triggers a collection reload, alias that GET separately and wait for it after the DELETE. This identifies whether a stale row comes from failed mutation handling or failed refresh rendering.
Arrange an owned filter record before the test so deletion is isolated. Shared records create ordering failures when workers run in parallel. If the API treats repeated DELETE calls as idempotent, cover that service rule directly rather than sending a duplicate browser click.
Confirm that the confirmation dialog closes and keyboard focus returns to a sensible location when accessibility is part of the acceptance criteria.
10. Alias History and Duplicate Request Example
Current Cypress alias history supports .all and 1-based numeric access through cy.get(). Use a wait to synchronize, then inspect the full captured history to detect duplicate requests.
it('loads account settings exactly once', () => {
cy.intercept('GET', '/api/settings').as('getSettings')
cy.visit('/settings')
cy.wait('@getSettings').its('response.statusCode').should('eq', 200)
cy.contains('h1', 'Settings').should('be.visible')
cy.get('@getSettings.all').should('have.length', 1)
cy.get('@getSettings.1').then(({ request, response }) => {
expect(request.method).to.eq('GET')
expect(response.statusCode).to.eq(200)
})
})
Indices start at 1. These suffixes belong to cy.get(), so do not write cy.wait('@getSettings.all'). A negative count assertion still needs a closed observation window. If a second request could occur on a timer, control the clock or reach a state that stops polling before checking the final length.
This cypress cy.wait with alias example is valuable for accidental duplicate submissions, repeated initial loads, and polling shutdown. Do not assert one request merely because fewer calls seem cleaner. Some applications intentionally retry, prefetch, or refresh, and the expected count must come from product behavior.
For the complete mental model, see how to use Cypress cy.wait with alias. For low-level spy and stub decisions, see Cypress cy.stub and cy.spy examples.
11. Query Parameter and Header Example
Use a route matcher object when query values are part of request identity. Then assert required headers from the yielded request without logging sensitive values.
it('loads the second page with the selected sort', () => {
cy.intercept({
method: 'GET',
pathname: '/api/candidates',
query: {
page: '2',
sort: 'matchScore',
direction: 'desc',
},
}).as('getCandidatesPageTwo')
cy.visit('/candidates?page=1')
cy.get('[name=sort]').select('Best match')
cy.contains('button', 'Next').click()
cy.wait('@getCandidatesPageTwo').then(({ request, response }) => {
expect(request.headers['x-client-name']).to.eq('qajobfit-web')
expect(request.headers).to.have.property('authorization')
expect(response?.statusCode).to.eq(200)
expect(response?.body.page).to.eq(2)
})
cy.get('[aria-current=page]').should('have.text', '2')
})
Do not print or compare the full authorization header. Presence can prove that the client applied authentication, while authorization behavior belongs in security and API tests. Query values in a route matcher are strings because they represent URL search parameters.
If optional parameters vary, match the stable pathname and assert the query object afterward. An overly exact matcher can turn a useful payload failure into a confusing request-timeout failure. Exact matching is best when the parameters define which request may release the wait.
Header names are normalized for HTTP comparison, but application proxies can add, remove, or rewrite values. Assert only headers the browser client owns. Correlation and tracing headers are useful evidence when stable, while infrastructure-generated values usually belong in service observability rather than a UI regression.
12. Wait for a Mutation and Its Follow-up Refresh
Some applications save a change and then reload the collection. Give each phase its own alias so the test can distinguish a failed mutation from a stale refresh.
it('renames a saved view and refreshes the list', () => {
cy.intercept('PATCH', '/api/views/view-7').as('renameView')
cy.intercept('GET', '/api/views').as('getViews')
cy.visit('/views')
cy.wait('@getViews')
cy.get('[data-view-id=view-7]').within(() => {
cy.contains('button', 'Rename').click()
})
cy.get('[name=viewName]').clear().type('Release blockers')
cy.contains('button', 'Save name').click()
cy.wait('@renameView').then(({ request, response }) => {
expect(request.body).to.deep.equal({ name: 'Release blockers' })
expect(response?.statusCode).to.eq(200)
})
cy.wait('@getViews').then(({ response }) => {
expect(response?.statusCode).to.eq(200)
expect(response?.body.items).to.deep.include({
id: 'view-7',
name: 'Release blockers',
})
})
cy.get('[data-view-id=view-7]').should('contain.text', 'Release blockers')
})
The first @getViews wait consumes the initial load, and the second consumes the refresh. If the application updates optimistically and does not refetch, remove the second network expectation and assert local rendering plus persistence through an appropriate API test.
Keep both alias names distinct even when routes are close together. Clear names make the Command Log show exactly which phase stopped progressing.
If the refresh is triggered before the mutation response completes, do not impose an artificial serial order in the test. Register both routes, trigger the action, and assert the actual documented coordination. The UI may use optimistic data, invalidate a cache, or await persistence, and each design needs a different oracle.
Add a final direct read only when durable persistence is the risk. Rechecking every UI mutation through another API call can overcouple the scenario.
13. Retry the Same Alias After a Controlled Failure
A route callback can return different responses for consecutive calls. This recipe proves a user-controlled retry without depending on a flaky real service.
it('recovers when the user retries a failed summary request', () => {
let attempt = 0
cy.intercept('GET', '/api/dashboard/summary', (req) => {
attempt += 1
if (attempt === 1) {
req.reply({
statusCode: 503,
body: { code: 'SUMMARY_UNAVAILABLE' },
})
return
}
req.reply({
statusCode: 200,
body: { openItems: 4 },
})
}).as('getSummary')
cy.visit('/dashboard')
cy.wait('@getSummary').its('response.statusCode').should('eq', 503)
cy.contains('Summary is temporarily unavailable').should('be.visible')
cy.contains('button', 'Try again').click()
cy.wait('@getSummary').its('response.statusCode').should('eq', 200)
cy.get('[data-testid=open-items]').should('have.text', '4')
cy.get('@getSummary.all').should('have.length', 2)
})
The mutable counter belongs to this test and resets with the spec execution. For automatic retry logic, combine the route with controlled time and assert the documented backoff behavior. Avoid real-time delays or unlimited retries.
This recipe checks client recovery. Separate service tests must prove when 503 is returned and whether retry headers are correct.
Assert that the button becomes enabled only when a retry is allowed and that repeated clicks do not create uncontrolled concurrent calls. If the product limits attempts, extend the response sequence to the documented boundary and check the terminal guidance. Keep counters local to the test because module-level state can leak across retries or reruns.
For automatic retry, use cy.clock() and advance each documented backoff interval. Assert request history after the final interval so the test protects both timing and the maximum attempt count.
14. Assign an Alias From a Command Request Body
Some REST-style command endpoints use one URL for several actions. Assign a dynamic alias from a stable command field so the wait cannot resolve on a different operation.
it('publishes the selected release', () => {
cy.intercept('POST', '/api/release-commands', (req) => {
if (req.body.command === 'publish') {
req.alias = 'publishRelease'
}
})
cy.visit('/releases/rel-42')
cy.contains('button', 'Publish').click()
cy.contains('button', 'Confirm publish').click()
cy.wait('@publishRelease').then(({ request, response }) => {
expect(request.body).to.deep.equal({
command: 'publish',
releaseId: 'rel-42',
})
expect(response?.statusCode).to.eq(202)
expect(response?.body.jobId).to.be.a('string').and.not.be.empty
})
cy.contains('Publication started').should('be.visible')
})
The 202 response means the command was accepted, not completed. If the scenario requires completion, observe the application's documented status polling or event-driven update with a separate alias and final UI assertion. Do not reinterpret 202 as a finished release.
Only assign the alias when the body field is part of a stable public request contract. If the application moves the command to another field or header, the timeout should prompt a contract review rather than a broader matcher that accepts unrelated traffic.
Validate that unrelated command requests do not acquire the publish alias. When the page sends draft-save or audit commands in the background, dynamic aliasing keeps those exchanges visible in the Command Log without allowing them to satisfy the publication wait.
If completion is eventually consistent, use a second named status alias and a bounded product flow rather than sleeping after the 202 response.
15. Review Cypress cy.wait with alias Example Assumptions
Every copied example contains application-specific details even when the Cypress syntax is universal. Replace routes, statuses, content types, body fields, debounce values, and visible messages with the real contract. A runnable test against the wrong contract creates noise, not portability.
Control the records that page-load, pagination, deletion, and refresh examples depend on. Use unique IDs or a test-support API, and keep parallel workers from renaming or deleting the same record. When a response is stubbed, make its schema representative and cover the real integration elsewhere.
Run the spec repeatedly and review failures at each boundary: route registration, outgoing request, response, and rendering. A broad alias that happens to pass once may resolve on prefetch or background traffic in another run. Preserve the method and key parameters in assertion messages.
Finally, remove any fixed sleep that was added during debugging. Keep explicit alias waits for required network events, controlled clocks for timers, and retryable DOM assertions for rendered state. Each synchronization method should correspond to the event it observes.
Interview Questions and Answers
Q: Show a basic alias wait for a page request.
I call cy.intercept('GET', '/api/projects/*').as('getProject') before cy.visit(). After visiting, I call cy.wait('@getProject'), assert its response status or body, and then assert the rendered project. Registration must precede the request.
Q: How do you assert a POST body?
I wait on the POST route alias and inspect interception.request.body in .then(). I assert only business-critical fields, plus the documented response and UI result. This verifies what the browser actually submitted.
Q: Can you wait on a stubbed intercept?
Yes. An intercept with a static response can be aliased and waited on. The yielded interception contains the outgoing request and controlled response, which is useful for deterministic error and loading-state tests.
Q: How do you wait for repeated requests to one URL?
I call cy.wait('@alias') once for each expected request and assert its sequence-specific parameters. Afterward, I can use cy.get('@alias.all') to inspect the captured history and verify total count when duplicates matter.
Q: How do you test debounced search without sleeping?
I install cy.clock(), register the search intercept, type the query, and advance the configured debounce interval with cy.tick(). Then I wait on the route alias and assert the query and results. This makes time deterministic.
Q: Why use a dynamic alias for GraphQL?
Many GraphQL operations share one POST endpoint. Assigning req.alias from operationName ensures the wait corresponds to the mutation or query under test. I also assert GraphQL errors because HTTP 200 alone does not indicate success.
Q: How do you test a 204 response?
I assert that the response exists and its status is 204, without expecting a JSON body. Then I verify the user-visible deletion or update. If security headers matter, I assert safe presence rather than leaking their values.
Common Mistakes
- Visiting the page before registering the intercept for its startup call.
- Waiting on a route without checking that the page rendered the response.
- Hardcoding server-generated IDs that should only be shape-checked.
- Treating a static error response as proof that the real service honors the error contract.
- Assuming detailed array-wait results have a meaningful completion order.
- Using one alias wait for pagination requests with different expected parameters.
- Combining real-time sleeps with a controllable debounce timer.
- Checking only HTTP status for GraphQL and ignoring the
errorsfield. - Parsing a 204 response as JSON.
- Using
.allor numeric suffixes withcy.wait()instead ofcy.get(). - Asserting a duplicate-request count before delayed traffic has settled.
- Adding long artificial response delays to many functional tests.
Conclusion
A strong cypress cy.wait with alias example registers the route before the action, waits for the exact request, asserts the business-relevant exchange, and confirms the UI result. The same foundation supports initial loads, mutations, errors, parallel calls, pagination, debounced search, GraphQL, loading states, no-content responses, and request-count checks.
Start with the page-load or POST recipe closest to your scenario. Replace the route and payload with the real contract, run it against controlled data, and add complexity only when repeated traffic, caching, timing, or GraphQL makes it necessary.
Interview Questions and Answers
How would you test data loaded during page startup?
I register the GET intercept before cy.visit and give it a descriptive alias. I wait on that alias, assert the expected response contract, and then verify the page rendered the data. This removes the startup registration race.
How would you test a 409 error from a form submission?
I stub a faithful 409 response through cy.intercept, alias it, submit the form, and wait for the interception. I assert important outgoing input and the error code. Then I verify the recovery message, available next action, and absence of false success UI.
How do you test pagination requests?
I alias the collection GET, consume the initial request, then wait once after each Next action. Each interception must carry the expected page query, and the UI must update its current page. Alias history can detect unexpected duplicate loads.
How do you test a loading spinner without fixed waits?
I provide a controlled static response with a short delay, assert the loading state immediately after the action, and wait on the alias. After the response, I assert the spinner disappears and data renders. The delay is test control, not a performance target.
What do you assert for a 204 response?
I assert that the response exists and statusCode is 204, without expecting a JSON body. I may check safe header presence on the request. The final assertion proves the deleted or updated item changed in the UI.
How do you distinguish commands sent to one endpoint?
I inspect a stable request body field in the intercept callback and assign req.alias only for the target command. The wait then cannot resolve on another action using the same URL. I keep the alias tied to a documented public contract.
How do you prove exactly one request happened?
I first wait for the expected request and reach a state that closes delayed traffic. Then I use cy.get('@alias.all').should('have.length', 1). If a timer could create another call, I control and advance that timer before the count assertion.
Frequently Asked Questions
What is a basic Cypress cy.wait with alias example?
Register cy.intercept('GET', '/api/projects/*').as('getProject') before visiting, then call cy.wait('@getProject'). Assert the status or body and verify the rendered project. The intercept must exist before the request starts.
How do I assert a POST body after cy.wait?
Use .then on cy.wait('@alias') and inspect interception.request.body. Assert critical submitted fields, the documented response, and the resulting UI state. Avoid deep equality on unrelated generated metadata.
Can I alias a stubbed error response?
Yes. Provide a static response in cy.intercept, alias the route, and wait on it after the action. This gives a deterministic client error test, but it does not prove that the real service returns the same error contract.
How do I test debounced search with a route alias?
Install cy.clock, register the search intercept, type the query, and advance the product's debounce interval with cy.tick. Then wait on the alias, assert the query, and check the rendered results and request count.
How do I wait for a mutation followed by a refresh?
Give the mutation and collection GET separate aliases. Consume the initial GET wait, trigger the mutation, wait for its response, and then wait for the next GET. This distinguishes a failed save from stale refreshed data.
How do I check all requests captured by an alias?
After the required traffic settles, use cy.get('@alias.all') to retrieve history and assert its length or contents. Use cy.get('@alias.1') for the first captured request because indices are 1-based. Do not pass these suffixes to cy.wait.
How do I test retry behavior with one alias?
Use a route callback with a test-local attempt counter to return a controlled failure first and success next. Wait once for each request, assert the error UI before retry, and assert final success and total history. Keep automatic retry timing under cy.clock when necessary.