ScreenshotNeo

BlogHow-to

How to Stub Network Requests with cy.intercept() in Cypress

Stub Cypress requests with cy.intercept(): match routes, return fixtures or dynamic responses, wait and assert reliably, and know when to use the real backend.

By the ScreenshotNeo team4 October 20269 min read

cy.intercept() stubs a browser request by matching its method and URL, returning a static response or fixture, and optionally assigning an alias so the test can wait for and inspect the request. Register the intercept before the request happens; for requests made during app startup, define it before cy.visit().

cy.intercept('GET', '/api/users', {
  statusCode: 200,
  body: [{ id: 1, name: 'Ada' }],
}).as('getUsers')

cy.visit('/users')
cy.wait('@getUsers')
  .its('response.statusCode')
  .should('eq', 200)

This guide covers route matching, fixtures, dynamic handlers, waiting and assertions, errors, and the choice between stubbing and using a real backend. See the Cypress cy.intercept() API reference and the network requests guide for the complete command contract.

1. Set up the intercept before the request

Use cy.intercept(method, url, response) for a simple stub. The method narrows the route to a specific HTTP verb, the URL identifies the request, and the response is the data Cypress should return. Alias the route with .as(), then wait for it after the action that should trigger the request.

describe('users page', () => {
  it('shows users returned by the API', () => {
    cy.intercept('GET', '/api/users', {
      statusCode: 200,
      body: [{ id: 1, name: 'Ada' }],
    }).as('getUsers')

    // Register before visit when page initialization requests the data.
    cy.visit('/users')

    cy.wait('@getUsers').then(({ request, response }) => {
      expect(request.method).to.equal('GET')
      expect(response.statusCode).to.equal(200)
      expect(response.body).to.deep.equal([{ id: 1, name: 'Ada' }])
    })

    cy.contains('Ada').should('be.visible')
  })
})

cy.intercept() itself yields null; it does not yield the request. The alias and cy.wait('@getUsers') provide an interception object with request and response details. If no request matches, waiting will time out. Cypress clears intercepts before each test, so define them per test or in that test’s setup hook.

2. Match the request you mean

Choose the narrowest matcher that still covers the request. A string can match a URL, Cypress supports minimatch-style glob patterns, and a regular expression can match variable URL portions. A RouteMatcher lets you constrain multiple request attributes; every supplied field must match.

Exact URL and method

cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')

Always specify the method when only one method should be intercepted. If omitted, the matcher can match all HTTP methods, which may catch unrelated traffic.

Query string with a RouteMatcher

cy.intercept({
  method: 'GET',
  pathname: '/api/users',
  query: { active: 'true' },
}, { fixture: 'active-users.json' }).as('activeUsers')

Use pathname when you want to match the path independently of the query string, then describe query constraints in query. Other useful matcher fields include hostname, path, and headers. If any configured field differs from the actual request, the route will not match.

Glob or regular expression

// Match a URL with a variable user ID.
cy.intercept('GET', '**/api/users/*').as('getUser')

// Or constrain the URL with a regular expression.
cy.intercept('GET', /\/api\/users\/\d+$/).as('getUserById')

Prefer a precise matcher over a broad wildcard if multiple endpoints share a prefix. A broad route can make a test pass with the wrong response or make an unrelated request consume the alias wait.

3. Return static data or a fixture

Pass an object as the response to return a deterministic status, headers, and body. Or store the body in a fixture file under Cypress’s fixtures directory and reference it by name.

cy.intercept('GET', '/api/users', {
  statusCode: 200,
  headers: { 'content-type': 'application/json' },
  body: [{ id: 1, name: 'Ada' }],
}).as('getUsers')
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')

Fixtures are useful when the response is reused, large, or clearer as a separate data file. Keep the fixture shaped like the API response the app expects; a missing property can exercise a different code path than intended.

Response controls

A Cypress StaticResponse can include a body, status code, headers, a fixture, a delay, a throttle rate, or a forced network error. For example, a delayed response can help exercise a loading state:

cy.intercept('GET', '/api/users', {
  statusCode: 200,
  body: [{ id: 1, name: 'Ada' }],
  delay: 500,
}).as('getUsers')

Use response controls to model a condition the test needs to verify, such as a server error or a slow response. Avoid arbitrary delays as a synchronization strategy: wait on the aliased request instead.

Binary fixture data

For binary files such as PDFs or MP4s, Cypress warns that the default UTF-8 fixture handling can alter raw bytes. Use the null encoding suffix when the response must preserve binary data:

cy.intercept('GET', '/reports/summary.pdf', {
  fixture: 'media/summary.pdf,null',
  headers: { 'content-type': 'application/pdf' },
}).as('getReport')

See the Cypress fixture documentation for fixture encoding details.

4. Use a handler for request-dependent behavior

When the response should depend on the incoming request, use a route handler. It can inspect the request, assert on it, modify it, reply with a stub, or continue it to the destination.

cy.intercept('POST', '/api/users', (req) => {
  expect(req.body).to.have.property('name')
  req.reply({
    statusCode: 201,
    body: { id: 42, ...req.body },
  })
}).as('createUser')

cy.visit('/users/new')
cy.get('[name="name"]').type('Ada')
cy.get('button[type="submit"]').click()

cy.wait('@createUser').then(({ request, response }) => {
  expect(request.body.name).to.equal('Ada')
  expect(response.statusCode).to.equal(201)
  expect(response.body.id).to.equal(42)
})

Call req.reply() to stub a response. Call req.continue() to forward the request to the server, optionally handling the response. A reply ends the request phase, so later matching handlers do not receive that request. Matching routes run in reverse definition order, except middleware routes, which run first. Account for ordering when several intercepts match the same request.

5. Spy on a real request instead of stubbing it

An intercept without a response handler can observe a request while letting it continue normally:

cy.intercept('POST', '/api/orders').as('createOrder')

cy.get('[data-cy=place-order]').click()
cy.wait('@createOrder').then(({ request, response }) => {
  expect(request.body.items).to.have.length.greaterThan(0)
  expect(response.statusCode).to.equal(201)
})

Use this when the real server behavior is part of what the test needs to cover. Choose a stub when you need deterministic data or a condition that is difficult to arrange through the application. Cypress’s Real World App tests predominantly use server responses and stub only on a few occasions for convenient edge cases; its guide also discusses the work involved in seeding data and following application business rules.

Approach What it verifies Best fit Tradeoff
Stub response Frontend behavior against the specified response Controlled error states, empty results, unusual payloads, repeatable UI tests Does not verify backend behavior; fixtures and stubs need maintenance
Spy or real backend Request details and integrated server behavior End-to-end flows where backend logic matters Requires dependable server state, test data setup, and business-rule-aware data seeding

A suite can use both strategies. Keep stubs for targeted frontend cases and retain tests that exercise the integrated backend where that behavior is the subject.

6. Wait for the cycle and assert useful details

Place cy.wait('@alias') after the action that causes the request. Assert on the actual request and response rather than waiting a fixed number of milliseconds.

cy.get('[data-cy=refresh-users]').click()

cy.wait('@getUsers').then(({ request, response }) => {
  expect(request.method).to.equal('GET')
  expect(request.url).to.include('/api/users')
  expect(response.statusCode).to.equal(200)
  expect(response.body).to.have.length(1)
})

You can also chain assertions on a specific property, such as cy.wait('@getUsers').its('response.statusCode').should('eq', 200). For an intentionally forced network failure, wait on the alias and assert the interception’s error details rather than expecting a normal response.

7. Troubleshoot common failures

Symptom Likely cause Fix
cy.wait('@alias') times out The intercept was registered after the request, often after cy.visit(); or the matcher does not match method, URL, path, query, or hostname. Register before the triggering action. For startup requests, register before visiting. Check the actual method and URL, then narrow or correct the matcher fields.
The wrong request satisfies the wait The route is too broad, or the method was omitted and all methods can match. Add the explicit HTTP method and constrain the route with pathname, query, hostname, or a tighter pattern.
Assertions see no interception data from cy.intercept() The command yields null. Assign an alias and inspect the object yielded by cy.wait('@alias').
The route works in one test but not the next Intercepts are cleared before each test. Register the route in each test or in a setup hook that runs for each test.
The stubbed response never reaches the app A different route matched first, or a handler called req.continue() instead of replying. Review all matching routes and their order. Remember that req.reply() ends the request phase and matching routes use reverse definition order except middleware.
PDF or media bytes are corrupted The fixture was served with default text encoding. Use fixture: 'path/to/file.pdf,null' (or the relevant binary file path) and the correct content type.
Stubbed UI differs from the real app The fixture omits fields, uses the wrong shape, or represents a response the app would not receive. Align the stub with the API contract and add an integrated test when backend behavior must also be verified.

8. Reliability, speed, and maintenance

  • Reliability: Stubs make selected response states repeatable and avoid dependence on server data for that test. They only provide confidence about the behavior the test actually covers; keep real-backend coverage for integrated behavior.
  • Synchronization: Alias and wait for the request-response cycle. Fixed sleeps can be too short on a slow run and waste time on a fast one.
  • Performance: A stub avoids waiting for the destination server for that request, while a real request includes server and network time. No fixed speedup is guaranteed; the main practical benefit of stubbing is control over the response.
  • Maintenance: Fixtures and route matchers are test code. Keep them specific and representative, and update them when the API response contract changes.
  • Cost: Cypress documentation does not establish a monetary cost for this pattern. Consider the engineering cost of maintaining test data and fixtures against the coverage each strategy provides.

9. FAQ

Can one intercept match every HTTP method?

Yes. If you omit the method, the route matcher can match all methods. Specify a method when the test should catch only one verb.

Can I wait for more than one request with the same alias?

Yes. Cypress supports waiting on an aliased route as requests occur; make each wait correspond to the action and request cycle you intend to verify.

Does a stub prove the server endpoint works?

No. A stub verifies the application’s behavior with the response you supplied. Use a real request in tests intended to cover server integration.

Are intercepts shared between tests?

No. Cypress clears them before each test, so register the needed routes in each test or per-test setup.

10. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. It captures a URL as PNG, JPEG, WebP, or PDF with one GET request. It is for capturing pages, rather than stubbing Cypress application requests; use the DIY Cypress flow above when the test must control a request made by your app.

With ScreenshotNeo’s API, a simple capture looks like this:

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', new Uint8Array(await res.arrayBuffer()))

Before capture, it accepts cookie and consent banners and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. All features are on every plan.

Sign up free for 1,000 screenshots a month, with no card required.