How to Assert Network Calls in Cypress Tests
Use cy.intercept() and cy.wait() to verify requests, responses, and user-visible results in Cypress tests, without relying on arbitrary delays.
Use cy.intercept() to observe or stub the request, give it an alias, trigger the UI action, and wait with cy.wait('@alias'). Assert on the yielded request or response, then check the rendered UI when the response should change what the user sees. Register the intercept before the page visit or action that sends the request.
This pattern tests the browser application’s network behavior. A spy lets the request reach the real server; a stub supplies a controlled response. Choose based on whether the test needs to exercise the real backend or isolate the UI.
1. Observe a request and assert its result
Here is a complete Cypress test using a real backend response:
describe('user creation', () => {
it('sends the expected user data and shows success', () => {
cy.intercept('POST', '/api/users').as('createUser')
cy.visit('/users/new')
cy.get('[name="name"]').type('Ada Lovelace')
cy.get('form').submit()
cy.wait('@createUser').then(({ request, response }) => {
expect(request.method).to.equal('POST')
expect(request.url).to.include('/api/users')
expect(request.body).to.have.property('name', 'Ada Lovelace')
expect(response.statusCode).to.equal(201)
})
cy.contains('User created').should('be.visible')
})
})
The route is registered before cy.visit(), so it can observe requests sent during page startup as well as after the form submission. The alias is the synchronization point: Cypress continues when the matching request completes, rather than after an estimated delay.
What the interception contains
cy.wait('@createUser') yields the completed interception. Its request and response objects let you check the outgoing call and the result. For example:
cy.wait('@createUser').then(({ request, response }) => {
expect(request.url).to.include('/api/users')
expect(request.headers).to.have.property('content-type')
expect(request.body).to.deep.equal({ name: 'Ada Lovelace' })
expect(response.statusCode).to.equal(201)
expect(response.body).to.have.property('id')
})
Use fields that are part of the behavior you want to protect. Request headers, response bodies, and transport metadata can vary with the application, browser, or Cypress version; avoid asserting incidental details without a reason.
2. Match only the request you mean to test
cy.intercept() supports URL strings, glob patterns, regular expressions, and route matcher objects. Include the HTTP method when it matters: if you omit it, the intercept can match requests using any method.
// Match a method and exact path
cy.intercept('GET', '/api/products').as('getProducts')
// Match a glob pattern
cy.intercept('GET', '/api/products?*').as('searchProducts')
// Match with a route matcher
cy.intercept({
method: 'POST',
pathname: '/api/users',
headers: { 'content-type': 'application/json' }
}).as('createUser')
Choose the narrowest stable matcher that identifies the intended call. A broad intercept can accidentally match unrelated traffic such as analytics, images, feature flags, or monitoring requests. Cypress cautions that intercepting traffic a test does not need adds work to the test run; see its test performance guidance.
For query parameters, prefer matching the pathname and inspecting the query or URL when parameter ordering may vary. If an exact URL is stable and meaningful, matching it directly is also reasonable.
3. Spy on the server or stub a response
A spy observes a call while it proceeds to the real server. A stub gives the browser a response you define. These options answer different questions:
| Approach | What it establishes | Trade-off |
|---|---|---|
| Spy, real server | The application emits the request and participates in the real request/response path. | Needs a suitable backend and test data; backend variability can affect the test. |
| Stubbed response | The application constructs a request and handles a controlled response. | Does not establish that the real backend returns that response. |
Use a spy for integration behavior that depends on the server. Use a stub to isolate the UI, make data deterministic, or exercise a response edge case. Cypress’s Real World App predominantly uses server responses in its end-to-end tests and stubs on a few occasions for convenient edge cases; that is an example, not a universal rule. See Cypress’s network request guide.
// Spy: request continues to the real server
cy.intercept('GET', '/api/products').as('getProducts')
cy.visit('/products')
cy.wait('@getProducts').its('response.statusCode').should('eq', 200)
// Stub: supply a controlled response
cy.intercept('GET', '/api/products', {
statusCode: 200,
body: [{ id: 1, name: 'Notebook' }]
}).as('getProducts')
cy.visit('/products')
cy.wait('@getProducts')
cy.contains('Notebook').should('be.visible')
A stubbed test should still assert the visible behavior it is meant to protect. A request assertion alone does not prove that the interface rendered the result correctly.
4. Assert errors and deliberate edge cases
Stub an error when you need to verify how the interface handles a known failure. To test a network error, use the forceNetworkError option and assert the application’s error state:
cy.intercept('GET', '/api/products', {
forceNetworkError: true
}).as('getProducts')
cy.visit('/products')
cy.wait('@getProducts').should('have.property', 'error')
cy.contains('Could not load products').should('be.visible')
For an HTTP error response, return a status and body instead. An HTTP 500 response is still a response, whereas a forced network error has no normal response status to assert:
cy.intercept('GET', '/api/products', {
statusCode: 500,
body: { message: 'Temporary failure' }
}).as('getProducts')
cy.visit('/products')
cy.wait('@getProducts').its('response.statusCode').should('eq', 500)
cy.contains('Could not load products').should('be.visible')
Use response.statusCode and response.body when a response exists. Use the interception’s error field for a deliberately simulated network failure. For current route handler and stubbing options, refer to the official cy.intercept() API.
5. Handle several matching requests
An alias tracks each matching request. Repeated waits consume matching calls in order, which is useful when a user action intentionally sends a sequence:
cy.intercept('GET', '/api/status').as('getStatus')
cy.get('[data-cy="refresh"]').click()
cy.wait('@getStatus').its('response.statusCode').should('eq', 200)
cy.get('[data-cy="refresh"]').click()
cy.wait('@getStatus').its('response.statusCode').should('eq', 200)
If you need to inspect the calls captured by an alias after they have occurred, use cy.get('@getStatus.all'). The .all suffix is supported by cy.get(), not cy.wait(); indexes into the history are one-based.
cy.get('@getStatus.all').should('have.length', 2)
cy.get('@getStatus.all').then((calls) => {
expect(calls[0].request.method).to.equal('GET')
expect(calls[1].request.method).to.equal('GET')
})
One successful wait proves that a matching call completed; it does not prove that no extra call occurred. To assert an exact count, wait until the intended activity has settled and then inspect the history. Avoid using a fixed sleep as a substitute for a clear completion condition.
6. Alias GraphQL operations by name
GraphQL clients often send different operations to the same endpoint, so matching only /graphql can wait for the wrong request. Inspect the request body and assign a per-request alias based on the operation name. This example assumes the client sends JSON with an operationName field:
cy.intercept('POST', '/graphql', (req) => {
if (req.body.operationName === 'GetProducts') {
req.alias = 'getProducts'
}
})
cy.visit('/products')
cy.wait('@getProducts').then(({ request, response }) => {
expect(request.body.operationName).to.equal('GetProducts')
expect(response.statusCode).to.equal(200)
})
The request format depends on the GraphQL client and transport. Some clients batch operations or use a different payload shape, so inspect the application’s actual request before choosing the matching condition. Cypress documents dynamic aliases in its network requests guide.
7. Set a useful wait timeout
cy.wait('@alias') waits for the request and response cycle for a matching call. If a slow endpoint needs a longer bound, pass a timeout explicitly:
cy.wait('@createUser', { timeout: 15000 })
.its('response.statusCode')
.should('eq', 201)
Set the bound to fit the test environment and endpoint. A larger timeout can accommodate a legitimately slower response, but it can also make a missing request take longer to fail. Cypress notes that response handlers are not governed by responseTimeout and recommends setting a timeout on cy.wait() when bounding that wait matters. Consult the cy.wait() documentation and the native network interception guide for behavior applicable to your installed version.
8. Keep the assertion tied to the user behavior
A strong end-to-end test connects three things: the user action, the network call it should cause, and the resulting interface state. For example:
- Register a specific intercept before the action.
- Perform the action through the UI.
- Wait for the alias and assert the meaningful request fields.
- Assert the important response fields when the test needs them.
- Check the user-visible result.
Do not use a network assertion as a proxy for every product behavior. If a UI can render optimistically before the server responds, decide whether the test should assert the optimistic state, the settled state, or both. If retry behavior is part of the requirement, assert the relevant sequence and final state rather than assuming one wait covers retries.
9. Understand Cypress version and cache behavior
Cypress’s native network interception guide describes changes to interception behavior and caveats for protocol metadata, browser-rejected responses, caching, request/response fields, and timing. In the native interception path, Cypress is no longer the connection between browser and server. A cached resource that does not result in a network request is not visible to cy.intercept().
Check the installed Cypress version and use the documentation for that version when a test depends on transport details or timing. For a direct test of caching behavior, Cypress recommends cy.request(); that command runs from Cypress’s Node process and bypasses cy.intercept(), so it does not prove that the browser application sent the request. See native interception notes and the cy.request() API.
10. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
cy.wait('@alias') times out |
The route was registered after the call, the matcher does not match, or the action did not trigger a request. | Register before the visit/action; check method, path, query, and request timing; confirm the UI action actually ran. |
| The wait completes on an unexpected call | The matcher is too broad or an alias covers multiple calls. | Add the method and narrow the route matcher. For GraphQL, inspect the operation body and assign a dynamic alias. |
response is missing |
The call failed as a network error, or the browser rejected the response. | For deliberate network error tests, assert error. Check the browser console and version-specific interception notes for rejected responses. |
| The intercept never sees a cached resource | No network request was made for the cached resource. | Test cache behavior using the appropriate direct API or application setup; do not expect an intercept for a request the browser never sends. |
| The request assertion passes but the UI is wrong | The test checks traffic but not the user-visible result. | Assert the page state after the wait, including success, error, or loading behavior as appropriate. |
| The test is flaky with a fixed delay | A delay guesses when traffic will finish and is unrelated to the actual request lifecycle. | Wait on the aliased request, then assert UI state. Use a bounded timeout if the endpoint needs one. |
| Too many requests slow the test | The intercept matches irrelevant traffic. | Match only the needed method and endpoint. Avoid intercepting all requests without a test-specific reason. |
A test using cy.request() cannot find the intercept alias |
cy.request() is issued by Cypress’s Node process, not the browser application. |
Use cy.intercept() with a browser action to verify browser traffic; use cy.request() when directly exercising an API endpoint. |
11. Performance, reliability, and cost
Network assertions add little value when they observe unrelated traffic, and broad interception makes Cypress process requests the test does not need. Keep matchers focused, avoid arbitrary sleeps, and use deterministic stubs for UI cases that do not require a real backend. Use real-server spies where backend integration is the behavior under test.
Reliability comes from installing the intercept before the trigger, matching the intended call, waiting on its alias, and asserting the rendered outcome. A timeout should reflect the expected environment, not hide a request that never happens. Cypress test execution has no per-intercept fee described in the cited material; operational cost is generally the test runtime and any backend or test infrastructure your suite uses. Avoid unsupported numeric performance claims.
Or skip the browser setup
If your task is to capture how a page looks, ScreenshotNeo can return a screenshot from one GET request; it is a website screenshot API and MCP server from ScreenshotNeo. It complements Cypress network assertions: Cypress verifies application requests and behavior, while ScreenshotNeo captures a page image or PDF. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Its MCP server offers take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, no card required.
FAQ
Does cy.wait('@alias') retry my assertions?
The wait yields the completed interception. Assertions chained from it inspect that interception; they do not poll a changing request object. Cypress’s normal retry behavior applies to retryable queries and assertions as documented for the commands you use.
Can one alias match more than one request?
Yes. Each matching request is tracked, and repeated waits consume matching calls in order. Inspect cy.get('@alias.all') when you need the captured history or an exact count.
Should every test stub its API calls?
No. Stub when controlled data or a specific edge case helps isolate the UI; spy on the real server when integration with that backend is part of the test.
Can I use an intercept to test caching?
Not for a resource that produces no network request. Cypress’s native interception guide recommends cy.request() for testing caching behavior itself, while noting that it bypasses browser interception.


