Resource library

QA How-To

How to Capture network traffic in Cypress (2026)

Learn cypress how to capture network traffic with cy.intercept, typed assertions, GraphQL aliases, redacted trace artifacts, simulation, and CI debugging.

24 min read | 3,115 words

TL;DR

Register cy.intercept before the action, alias the relevant route, trigger the UI, then use cy.wait to inspect the request and response. For a reusable trace, add a middleware intercept that records selected redacted fields in a plain array and write it after all required requests finish.

Key Takeaways

  • Register cy.intercept before the application action so the request cannot outrun the route handler.
  • Use narrow method and URL matchers, meaningful aliases, and cy.wait to connect traffic to user behavior.
  • Assert contract-critical request and response fields instead of storing entire sensitive payloads by default.
  • Use middleware observation plus plain serializable records to save a redacted application API trace.
  • Alias GraphQL operations from operationName because many requests share one endpoint.
  • Treat stubbing, delay, throttling, and forced errors as controlled failure tests, not passive capture.
  • Know the boundary: cy.intercept is not a packet sniffer, a universal HAR exporter, or an observer of cy.request traffic.

Cypress how to capture network traffic starts with cy.intercept(), the supported command for observing and controlling HTTP requests made by the application under test. Register the route before the action, give important requests aliases, and use cy.wait() to assert the request-response exchange.

Cypress is not a general packet sniffer. It does not expose raw TCP, TLS, DNS, or WebSocket frames through cy.intercept, and requests made by cy.request() are outside the application's intercepted browser traffic. This guide shows what Cypress can capture reliably, how to save a safe API trace, and when another diagnostic tool is required.

TL;DR

type CreateOrderRequest = { sku: string; quantity: number }
type CreateOrderResponse = { id: string; status: 'created' }

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

cy.get('[data-testid="buy-button"]').click()

cy.wait<CreateOrderRequest, CreateOrderResponse>('@createOrder')
  .then(({ request, response }) => {
    expect(request.body).to.deep.equal({ sku: 'BOOK-7', quantity: 1 })
    expect(response?.statusCode).to.equal(201)
    expect(response?.body.status).to.equal('created')
  })
Goal Cypress mechanism Main caution
Observe one call cy.intercept().as() plus cy.wait() Register before action
Inspect many calls Repeated waits or middleware recorder Wait for completion before writing
Stub a response req.reply() or static response Stubs do not prove backend integration
Modify real response timing req.continue() and response controls Use only for an intentional scenario
Save a trace Redacted plain objects plus cy.writeFile() Never dump secrets by default
Capture packets or frames External approved tooling Not provided by cy.intercept

1. Cypress How to Capture Network Traffic Within Its Real Scope

cy.intercept() can spy on or stub HTTP requests that the application makes while Cypress controls the browser. This includes common XHR and Fetch traffic and can include document or asset requests when the matcher selects them. It exposes request URL, method, headers, body, and a response object when a response exists.

The command works at Cypress's network proxy boundary, not as Chrome DevTools' full network panel. It does not give raw TLS-encrypted packets, DNS timing, TCP retransmissions, or a pcap file. It is not a built-in HAR recorder. It does not decode WebSocket message frames. If the investigation needs those artifacts, use an approved browser, proxy, packet, server, or observability tool and follow privacy policy.

Clarify the test purpose before capturing everything. For a checkout test, the contract may be one POST containing the selected SKU and a 201 response with an order identifier. For debugging, you may need a sequence of API methods, sanitized URLs, statuses, and durations. For a security test, collecting full headers and bodies could create more risk than value.

Prefer the minimum evidence that answers the question. Network capture is valuable because it separates UI symptoms from service behavior, but it should complement user-visible assertions. A successful API response does not prove that the page rendered or that the user can complete the journey.

2. Register cy.intercept Before the Application Action

The most common missed request is a timing error. Cypress cannot observe a request that completed before the intercept existed. Register routes before cy.visit() when the application calls an API during startup, or before the click that triggers a later call.

describe('account summary', () => {
  it('loads the signed-in account', () => {
    cy.intercept('GET', '**/api/account/summary').as('accountSummary')

    cy.visit('/account')

    cy.wait('@accountSummary').then(({ response }) => {
      expect(response?.statusCode).to.equal(200)
    })
    cy.contains('h1', 'Account summary').should('be.visible')
  })
})

Do not add cy.wait(2000) before inspecting the page. Waiting on the aliased business request provides an observable synchronization point. Follow it with a UI assertion because rendering can still fail after the response.

For a request triggered on page load, placing cy.intercept after cy.visit creates a race even when it appears to work locally. Fast CI caches, different network speed, and application changes can make it intermittent. The route declaration should be visible immediately before the action or in a well-named setup hook.

cy.intercept() yields null; it is not a command that returns captured traffic directly. The alias plus cy.wait() yields an interception object. This command-queue model is why assigning the return value to a normal JavaScript variable does not produce the request.

3. Use Narrow Route Matchers and Meaningful Aliases

A string matcher can be exact or use glob patterns. A RouteMatcher object can constrain method, URL, path, query, hostname, headers, and other supported properties. Narrow matching reduces false waits when an application calls similar endpoints.

cy.intercept({
  method: 'GET',
  pathname: '/api/search',
  query: {
    category: 'books',
  },
}).as('bookSearch')

cy.visit('/search?category=books&q=testing')
cy.wait('@bookSearch').its('response.statusCode').should('eq', 200)

Use aliases that describe the business exchange, such as createOrder, loadEntitlements, or saveProfile. Names such as api1 make failures harder to read. Register separate intercepts when different calls protect different risks, even if a broad recorder also observes them.

Avoid an overly broad **/* route for everyday assertions. It can match assets, analytics, health calls, and third-party traffic, increasing noise and accidental data collection. A broad middleware recorder can be appropriate for a deliberately scoped debugging artifact, but filter it to owned API hosts or paths.

If query order is not stable, use the matcher query object for known values or assert request.query after capture. Do not hard-code transient cache-busting values. When a path contains a dynamic ID, use a glob or regular expression narrow enough to exclude unrelated resources.

4. Assert Typed Request and Response Contracts

The object yielded by cy.wait('@alias') contains the captured request and, for a completed HTTP exchange, its response. Cypress supports request and response body generics, which make TypeScript assertions easier to review.

type UpdateProfileRequest = {
  displayName: string
  locale: string
}

type UpdateProfileResponse = {
  id: string
  displayName: string
  locale: string
}

cy.intercept('PATCH', '**/api/profile').as('updateProfile')

cy.get('input[name="displayName"]').clear().type('Ada Tester')
cy.get('select[name="locale"]').select('en-IN')
cy.contains('button', 'Save profile').click()

cy.wait<UpdateProfileRequest, UpdateProfileResponse>('@updateProfile')
  .then(({ request, response }) => {
    expect(request.headers).to.have.property('content-type')
    expect(request.body).to.deep.equal({
      displayName: 'Ada Tester',
      locale: 'en-IN',
    })
    expect(response?.statusCode).to.equal(200)
    expect(response?.body).to.include({
      displayName: 'Ada Tester',
      locale: 'en-IN',
    })
  })

cy.get('[role="status"]').should('contain.text', 'Profile saved')

The example uses core Cypress commands and application-owned attributes. Prefer accessible labels and roles in the product markup, then choose selectors that remain stable when layout changes. Testing Library queries are a useful option only after that separate integration is installed and configured.

Assert contract-critical fields and ignore server-generated values unless their shape matters. Avoid snapshots of whole payloads that include timestamps, request IDs, experiments, or optional fields. The API response validation guide covers schema and semantic assertion strategies in more depth.

5. Capture Multiple Requests Into a Redacted Trace

For a focused diagnostic artifact, a middleware route can observe owned API traffic and allow the request to continue. Store plain serializable values in a closure, redact sensitive fields recursively, and write the array only after the required activity completes.

type TrafficRecord = {
  method: string
  url: string
  statusCode: number
  durationMs: number
  requestBody: unknown
  responseBody: unknown
}

const secretKey = /authorization|cookie|password|token|secret/i

function redact(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(redact)
  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>).map(([key, item]) => [
        key,
        secretKey.test(key) ? '[REDACTED]' : redact(item),
      ])
    )
  }
  if (typeof value === 'string' && value.length > 2000) {
    return value.slice(0, 2000) + '[TRUNCATED]'
  }
  return value
}

it('saves a sanitized checkout API trace', () => {
  const traffic: TrafficRecord[] = []

  cy.intercept({ url: '**/api/**', middleware: true }, (req) => {
    const startedAt = Date.now()
    req.on('response', (res) => {
      traffic.push({
        method: req.method,
        url: new URL(req.url).pathname,
        statusCode: res.statusCode,
        durationMs: Date.now() - startedAt,
        requestBody: redact(req.body),
        responseBody: redact(res.body),
      })
    })
  })

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

  cy.visit('/cart')
  cy.get('[data-testid="checkout"]').click()
  cy.wait('@createOrder').its('response.statusCode').should('eq', 201)
  cy.wait('@loadOrder').its('response.statusCode').should('eq', 200)

  cy.then(() => {
    cy.writeFile('cypress/results/checkout-traffic.json', traffic)
  })
})

middleware: true lets this observer run in middleware order. The handler registers a response listener and returns without ending the request phase, so later matching handlers can still assign aliases or stub. The listener records status and duration after the response returns. The URL keeps only the path so query secrets are not written. Adapt redaction to your data classification, including nested arrays, headers, GraphQL variables, and binary bodies.

The two explicit waits define completion. Without them, the test might write while requests are still in flight. Do not call Cypress commands such as cy.writeFile from inside the route callback. Route handlers run outside the Cypress command queue pattern expected by tests. Accumulate plain data, then enqueue the write from cy.then.

6. Capture GraphQL Operations by operationName

GraphQL clients often send many operations to one POST endpoint, so URL aliases alone are not descriptive. Inspect the request body and set req.alias dynamically from a known operationName.

cy.intercept('POST', '**/graphql', (req) => {
  const operationName = req.body?.operationName

  if (operationName === 'CreateProject') {
    req.alias = 'gqlCreateProject'
  }
  if (operationName === 'ProjectSummary') {
    req.alias = 'gqlProjectSummary'
  }
})

cy.visit('/projects/new')
cy.get('[name="projectName"]').type('Network observability')
cy.contains('button', 'Create project').click()

cy.wait('@gqlCreateProject').then(({ request, response }) => {
  expect(request.body.variables.input.name).to.equal('Network observability')
  expect(response?.body.errors).to.be.undefined
  expect(response?.body.data.createProject.id).to.be.a('string')
})

Operation names should be stable and present in non-minified client requests according to your GraphQL conventions. If persisted queries omit the full document, match the operation identifier or documented request shape. Avoid searching raw query text with a fragile substring when an explicit operation name exists.

An HTTP 200 response can still contain GraphQL errors. Assert the relevant errors and data semantics. For partial data, follow the product contract rather than universally requiring errors to be absent.

Subscriptions and WebSocket frames are outside cy.intercept HTTP response capture after the upgrade. Test subscription effects through observable UI or service state, instrument the client through an approved test seam, or use a protocol-aware tool. Be explicit about this limitation in framework designs.

7. Simulate Errors, Delays, and Controlled Responses

Spying observes the real exchange. Stubbing controls it. Cypress supports static responses, req.reply(), req.continue(), forced network errors, response delay, and throttling. Use them to test deterministic failure states that are difficult or unsafe to create through a real dependency.

it('shows a retry action after a network failure', () => {
  cy.intercept(
    { method: 'GET', url: '**/api/activity', times: 1 },
    { forceNetworkError: true }
  ).as('failedActivity')

  cy.visit('/activity')

  cy.wait('@failedActivity').should('have.property', 'error')
  cy.contains('Unable to load activity').should('be.visible')
  cy.contains('button', 'Retry').should('be.enabled')
})

The times: 1 matcher limits the forced error to the first match so a subsequent retry can reach another handler or the real service, depending on route setup. Keep the behavior intentional and readable.

To exercise a slow response while preserving the real body, call req.continue((res) => res.setDelay(1500)). res.setThrottle() can constrain response transfer in kilobits per second. These controls model HTTP response behavior, not every characteristic of a mobile network. They do not simulate DNS delay, uplink loss, packet reordering, or WebSocket degradation.

Every stub creates a test boundary. A passing error-state test proves client behavior for the supplied response, not that the real service generates that error correctly. Pair client stubs with service and integration tests. The Cypress intercept guide provides additional request and response control examples.

8. Understand Cache, Service Workers, and Request Sources

An intercept cannot capture a network request that the browser satisfies without making one. Browser cache can therefore make a route appear missing. Prefer application test configuration that disables or controls caching for scenarios that need network observation. Verify headers and the Network behavior before changing waits.

Cypress route handlers can remove cache headers from test-environment responses through a middleware before:response event, but this changes behavior and should be scoped. It proves the non-cached path, not the production caching contract. Keep separate tests for cache semantics when they matter.

Service workers can serve responses from their own cache or generate them, which changes what reaches the network proxy. Decide whether the test is about the application under service-worker control or the underlying endpoint. In a controlled test environment, unregistering the worker may simplify API assertions, but it removes an important production layer. Retain dedicated offline and update coverage for the service worker.

cy.request() originates from Cypress rather than the application browser request path and is not observed by cy.intercept(). Assert its returned response directly. Likewise, server-side rendering requests made by an application server will not necessarily appear as browser API calls. Use service logs, traces, or a test proxy at the correct boundary.

Redirects, preflight OPTIONS, and document requests may create more exchanges than expected. Match the intended method and URL, and understand whether the browser exposes the final response or a redirect hop through your route. Avoid counting all network calls as a stable business oracle unless the contract controls the count.

9. Assert Headers, Authentication, and Privacy Safely

Headers can prove content negotiation, correlation, conditional requests, and application-specific authorization behavior. Do not print full request headers. Cookies, bearer tokens, API keys, CSRF values, and trace baggage may contain secrets or identifiers.

Assert presence or safe shape instead of the credential value:

cy.intercept('POST', '**/api/payments').as('createPayment')

cy.get('[data-testid="pay-now"]').click()

cy.wait('@createPayment').then(({ request, response }) => {
  expect(request.headers.authorization).to.match(/^Bearer\s+\S+$/)
  expect(request.headers).to.have.property('x-request-id')
  expect(response?.headers).to.have.property('content-type')
  expect(response?.statusCode).to.equal(202)
})

Even a shape assertion can expose the value in an assertion failure if the framework prints the subject. For highly sensitive headers, assert through a boolean derived in the route handler or validate behavior server-side. Never save authorization or cookie headers in a trace artifact.

Request and response bodies may contain personal, payment, health, message, or file content. Default the recorder to metadata and a small allowlist of business fields. Redaction by key is necessary but not sufficient because secrets can appear inside free text, URLs, arrays, or binary encodings. Apply data classification and test only synthetic records.

Access to CI artifacts should be least privilege with a short retention appropriate to the content. A network trace that makes diagnosis easy can also become a secondary data leak.

10. Measure Timing Without Turning It Into a Brittle Performance Test

The middleware example measures elapsed time around the intercepted exchange with Date.now(). That is useful diagnostic context, but it includes Cypress proxy behavior, test environment load, network, and service time. It is not a precise browser performance metric or a substitute for service telemetry.

Avoid a hard assertion such as "response under 500 ms" in a shared functional CI environment unless the environment and service objective are designed for it. Such checks create noisy failures and may miss percentile or load-dependent problems. Use a controlled performance environment and representative workload for service-level objectives.

Functional tests can assert a generous user-facing deadline when timeout behavior is a requirement. For example, the page must show a recoverable error before its documented client timeout. Keep the error path deterministic with a stub or controlled service. Measure production percentiles through observability rather than scraping one Cypress duration.

When investigating slowness, record the request start, response completion, UI-ready time, route, status, response size if safely available, and correlation ID. Compare a passing build under the same environment. The gap between response and UI-ready state can reveal client rendering or state work that an API timer alone hides.

11. Save Useful CI Traffic Artifacts Only on Failure

Capturing every body from every passing test creates storage cost, review noise, and security risk. Prefer a concise trace with method, sanitized path, status, approximate duration, correlation identifier, and allowlisted fields. Save it when the test fails or when a diagnostic mode is explicitly enabled.

Cypress has after-test and Node event hooks, but design carefully because browser closure data and Node hook data live in different processes. A straightforward pattern writes a temporary redacted trace during the test, then lets CI upload the file only if the spec fails. Another pattern sends sanitized records through a cy.task to a Node-side store keyed by the runnable and test identity.

Include build and environment metadata beside the trace. Keep the first-attempt artifact when retries are enabled. A second pass can have a different request sequence and erase the evidence of the intermittent failure. Name artifacts with a safe test identifier and CI attempt number.

Validate the recorder. Unit test redaction with nested objects, arrays, query strings, case variants, long text, and unexpected values. Cap body and record sizes so a file upload or streaming response cannot exhaust runner memory. Skip binary bodies by default.

Do not call an application trace a HAR unless it follows the HAR schema and captures the required semantics. Accurate naming helps future investigators understand limitations.

12. Build a Reusable Cypress How to Capture Network Traffic Strategy

Create three explicit modes rather than one global catch-all. Contract mode uses narrow aliases and direct assertions for requests that define the behavior. Failure mode uses controlled stubs, delays, and network errors for client resilience. Diagnostic mode records a redacted sequence for selected owned APIs.

Keep route definitions close to the scenario or in small domain helpers. A global support-file intercept can surprise tests and create ordering conflicts. If a recorder must be global, document matcher precedence, register a passive response listener, filter the scope, and provide an opt-in switch.

Use typed models for stable application contracts, but do not duplicate every server type by hand. Generate or share types from an approved schema where practical, and still assert business semantics. A type checker does not validate runtime data from the server.

Review the strategy with security and service owners. Define which headers and fields may be retained, how correlation IDs connect to server traces, how stubs are versioned, and who owns failures. Monitor false failures, missed requests, trace size, and time to diagnosis.

Finally, keep user-facing assertions. Network traffic explains what happened at an interface. The customer experiences the rendered state, accessibility behavior, and completed workflow. A trustworthy Cypress test connects both.

Interview Questions and Answers

Q: What is the difference between cy.intercept and cy.request?

cy.intercept observes or controls HTTP traffic made by the application under test. cy.request initiates a request from Cypress and yields its own response. A cy.request call is not captured by cy.intercept, so I assert it directly. I choose based on whether I am testing browser wiring or an endpoint.

Q: Why does cy.wait report that no request occurred?

I first confirm the intercept was registered before the triggering action and that method, host, path, and query match. Then I check browser cache, service-worker handling, redirects, server-side requests, and whether the application actually took the expected branch. Increasing the timeout is useful only when a matching request is genuinely slow.

Q: How do you capture multiple requests safely?

I use a scoped middleware intercept that records plain metadata and allowlisted, recursively redacted fields. Required business aliases define when activity is complete, after which cy.then writes the trace. I cap sizes and exclude credentials, cookies, sensitive queries, and binary content.

Q: How do you intercept GraphQL requests?

I match the GraphQL HTTP endpoint, inspect request.body.operationName, and assign req.alias for relevant operations. Assertions cover variables and the documented data and errors semantics. WebSocket subscription frames require another observation approach.

Q: Can cy.intercept capture WebSocket traffic?

It can observe the initial HTTP upgrade only in limited HTTP terms, not individual WebSocket frames as normal intercept responses. I test visible subscription effects, add an approved client test seam, inspect service telemetry, or use a protocol-aware tool. I state that limitation clearly.

Q: How do you test a network error response?

I use a route with forceNetworkError: true, usually limited with times: 1, and assert the client error plus retry behavior. This proves the client handles the simulated failure. Separate integration tests prove real service behavior.

Q: Should every Cypress test save network logs?

No. Most tests need a few narrow assertions and safe failure evidence. Full payload capture increases noise, memory, storage, and exposure risk. I enable a redacted diagnostic trace only for selected tests or failures.

Q: How do you avoid brittle network assertions?

I assert methods, meaningful fields, status semantics, and stable headers while ignoring transient IDs and timestamps unless their shape matters. I use schemas for structure and targeted checks for business meaning. User-visible outcomes remain part of the test.

Common Mistakes

  • Registering cy.intercept after the request-triggering action.
  • Using a matcher so broad that unrelated assets or analytics satisfy the alias.
  • Assuming cy.intercept() returns captured traffic directly.
  • Calling Cypress commands from inside a route handler instead of accumulating plain data.
  • Writing the trace before required responses complete.
  • Saving authorization, cookies, tokens, query secrets, or personal data.
  • Treating a stubbed pass as proof of backend integration.
  • Expecting cy.intercept to capture cy.request, server-side calls, or WebSocket frames.
  • Adding arbitrary waits instead of waiting on a meaningful alias and UI state.
  • Ignoring cache and service-worker behavior when a request is absent.
  • Using one functional request duration as a performance service-level result.
  • Calling a custom JSON trace a complete HAR file.

Conclusion

Cypress how to capture network traffic is reliable when the scope is explicit. Register a narrow cy.intercept before the action, use aliases and cy.wait for important exchanges, and connect network assertions to the user-visible result. For multi-request diagnostics, save only redacted, serializable records after all required calls finish.

Start with one critical API call in an existing Cypress test. Add request and response assertions, then introduce the scoped trace recorder only if it materially improves diagnosis. This keeps the suite fast, readable, and safe while giving engineers the evidence they need.

Interview Questions and Answers

What is the difference between cy.intercept and cy.request?

cy.intercept observes or controls application HTTP traffic, while cy.request creates a request from Cypress and yields its response. cy.request traffic is not captured by cy.intercept. I choose based on whether the test protects browser wiring or the endpoint contract.

Why might an aliased request never appear?

The intercept may be registered late, the matcher may be wrong, or the browser may use cache or a service worker. The call could also be server-side or come from cy.request. I confirm the actual boundary and branch before changing the timeout.

How do you record multiple requests safely?

I use scoped middleware, store plain serializable metadata and allowlisted redacted fields, and wait for explicit business aliases before writing. I exclude credentials, sensitive URLs, personal data, and binary bodies and cap trace size.

How do you intercept GraphQL operations?

I match the GraphQL endpoint and assign req.alias from a known operationName. I assert variables plus documented data and errors behavior. WebSocket subscription frames need a protocol-aware or application-level observation point.

Can cy.intercept capture WebSocket frames?

No, it is not a WebSocket frame recorder. I verify visible effects, use an approved client seam or service telemetry, or use a protocol-aware tool. I do not describe an HTTP intercept trace as complete socket evidence.

How do you test network failure handling?

I use forceNetworkError for a controlled request, often limited to one match, then assert error communication, retry, and final state. The stub proves client handling, while service and integration tests protect real failure contracts.

Should every response body be stored?

No. Full capture creates exposure, memory, and maintenance risk. I store minimal metadata and selected redacted business fields only when they improve diagnosis, with access and retention controls.

How do you keep network assertions stable?

I use narrow matchers and assert contract-critical semantics while excluding transient timestamps and identifiers unless their shape matters. Schema checks cover structure, targeted assertions cover meaning, and UI assertions cover the customer outcome.

Frequently Asked Questions

How do I capture an API request in Cypress?

Register cy.intercept with the method and URL before the action, assign an alias, then call cy.wait on that alias. The yielded interception contains request data and the completed response when available.

Can Cypress save all network traffic as a HAR file?

Core cy.intercept is not a complete HAR exporter. You can build a scoped redacted JSON trace for application HTTP calls, or use an approved browser or proxy tool when genuine HAR semantics are required.

Why is Cypress not intercepting my request?

Common causes are late registration, a method or URL mismatch, browser cache, service-worker handling, a server-side request, or use of cy.request. Inspect the real request boundary before increasing timeouts.

Can cy.intercept capture Fetch and XHR calls?

Yes, it is designed to observe and control application HTTP traffic including common Fetch and XHR calls when they reach Cypress matching. Cache and service workers can change whether a network request occurs.

How do I intercept multiple GraphQL operations?

Match the shared GraphQL endpoint, inspect request.body.operationName, and assign a distinct req.alias for each relevant operation. Then wait on and assert each business operation separately.

Does cy.intercept capture cy.request calls?

No. cy.request originates from Cypress rather than the application browser traffic observed by cy.intercept. Assert the response returned directly by cy.request.

How should I redact Cypress network logs?

Default to metadata and allowlisted fields. Remove authorization, cookies, tokens, passwords, secrets, sensitive queries, personal data, and binary bodies recursively, then cap record size and artifact retention.

Related Guides