Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The error has one underlying cause: an onRequest-style callback does not run in Cypress’s normal command queue. Cypress commands such as cy.wait(), cy.task(), cy.get(), and cy.request() are queued commands, so they cannot be executed from that callback. Keep the handler synchronous, use its request/response APIs, and hand any data you need to the test chain with an alias or a plain variable.

Some developers call a cy.intercept() route handler an “onRequest handler.” Others mean a Cypress.on() event listener. The names differ, but the practical rule is the same: do not put Cypress commands in either callback. Queue them later, after the interception has been yielded to the test.

The short fix

Move every cy.* command out of the callback. Inside the callback, inspect or change the supplied req object with ordinary JavaScript and synchronous Chai assertions. Set an alias or save plain data. Then use cy.wait(), cy.task(), or another Cypress command in the test chain after the application has made the request.

The pattern that causes the error

cy.intercept('POST', '/users', (req) => {
  cy.task('recordRequest', req.body)
  cy.wait(1000)
  cy.get('[data-cy=message]').should('be.visible')
}).as('createUser')

Those calls are not merely in the wrong order. The callback is running during network handling, outside the queue that executes commands in the test body. Adding await does not change that execution context.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A supported route-handler pattern

cy.intercept('POST', '/users', (req) => {
  expect(req.body).to.include('Acme Company')
  req.headers['x-test-mode'] = 'true'
  req.alias = 'createUser'
}).as('users')

cy.wait('@createUser')
  .its('request.body')
  .should('include', 'Acme Company')

The handler performs synchronous work on the request. The later cy.wait() runs in the test’s command chain, where Cypress commands are supported.

Understand which callback you are using

cy.intercept() route handler

The third argument to cy.intercept() is a route handler. Cypress invokes it when a matching browser request is intercepted. It receives a request object and can inspect or alter the request, provide a stubbed response, or attach response lifecycle listeners. It is not a place to start a second Cypress command chain.

Cypress.on() event listener

A listener registered with Cypress.on() is a Cypress event callback, not a test-body callback. Cypress documents that these callbacks run outside its normal command queue; Cypress commands, assertions, and cy.task() are not supported there. If an event callback must communicate with a test, capture simple data and consume it later in the test chain.

Execution-context comparison

Code location When it runs Use here Do not use here
cy.intercept() route handler During matching request/response processing Read or mutate req; synchronous JavaScript; req.reply(), req.continue(), req.destroy(), req.redirect(), and req.on() cy.get(), cy.wait(), cy.task(), cy.request(), or other queued commands
Cypress.on() listener When the Cypress event fires Small synchronous handlers; capture plain values for later Cypress commands, Cypress assertions, and cy.task()
Test body and .then() callback In Cypress’s command queue Assertions, aliases, cy.wait(), cy.task(), and follow-up commands Returning a conflicting value while also queuing commands

Use the request and response APIs inside the handler

Inspect or change a request synchronously

Route handlers can read the request body, headers, URL, and method. They can add a test header, set an alias, or make a synchronous assertion before the request is sent.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cy.intercept('PUT', '/profiles/*', (req) => {
  expect(req.method).to.equal('PUT')
  expect(req.url).to.match(//profiles//)
  expect(req.body).to.have.property('displayName')

  req.headers['x-test-mode'] = 'true'
  req.alias = 'updateProfile'
}).as('profileUpdate')

Use ordinary JavaScript for transformations. If a value must come from a Cypress command, do not try to obtain it inside this callback; arrange for that value before registering the intercept or use it after the interception has been yielded.

Stub, pass through, fail, or redirect

  • req.reply() supplies a stubbed response.
  • req.continue() allows the real request to reach the server and can receive a response callback.
  • req.destroy() forces a network error.
  • req.redirect() returns a redirect response.
  • req.on() attaches response-event handlers.
cy.intercept('GET', '/api/account', (req) => {
  req.continue((res) => {
    expect(res.statusCode).to.equal(200)
    res.headers['x-seen-by-test'] = 'true'
  })
})

The response object exposes body, headers, statusCode, and statusMessage. Changes to the first three can affect the response delivered to the browser in the supported response phases.

Choose the response phase deliberately

  • before:response runs before response handlers and before req.continue() response callbacks.
  • response runs after those handlers but before the response is sent to the browser.
  • after:response runs after delivery, so it cannot change what the browser receives.

These callbacks still use the interception lifecycle APIs, not Cypress commands.

Move asynchronous test work back into the command chain

Use an alias and cy.wait()

The most robust handoff is to assign an alias to the intercepted request and wait for it in the test body. The yielded interception contains the request and, when available, the response.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cy.intercept('POST', '/users', (req) => {
  req.alias = 'createUser'
}).as('users')

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

cy.wait('@createUser').then((interception) => {
  expect(interception.request.body).to.have.property('email')
  cy.task('recordRequest', interception.request.body)
})

Here, the callback only labels the request. The cy.wait(), the assertion in its .then(), and cy.task() all execute after Cypress has yielded the interception to the command queue.

Capture plain data when a later chain needs it

let capturedBody

cy.intercept('POST', '/users', (req) => {
  capturedBody = req.body
  req.alias = 'createUser'
})

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

cy.wait('@createUser').then((interception) => {
  expect(interception.request.body).to.deep.equal(capturedBody)
  cy.task('recordRequest', interception.request.body)
})

Only store ordinary values in the outer variable. Do not store a Cypress command or pretend that assigning a command’s return value gives you its eventual result.

Where each commonly attempted command belongs

cy.wait()

Use cy.wait('@alias') after the application action that triggers the request. A wait inside the route handler would deadlock the intended flow: the handler is already processing the request, while cy.wait() needs the test queue.

cy.task()

Use cy.task() in the test chain to perform Node-side work such as writing a file or recording a request. Put the data on an alias or in the yielded interception, then call the task in a later .then().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cy.request()

cy.request() is for making a direct HTTP call from Cypress’s Node process, commonly for setup, seeding, or API verification. It must be chained from cy, and it bypasses routes defined with cy.intercept(). It is therefore neither a substitute for a command inside the handler nor a way to intercept the browser request currently being processed.

await

Cypress commands are not Promises and cannot be awaited. Writing await cy.wait('@createUser') does not make the command run inside the callback, and marking the route handler async does not move it into Cypress’s queue. Use Cypress chaining and .then() instead.

A complete, reliable test flow

  1. Register the intercept in a supported test context, before the user action that sends the request.
  2. Keep the route handler synchronous. Inspect req, make immediate assertions, modify request properties, or choose a route response.
  3. Assign an alias when the test needs to wait for or inspect that request.
  4. Perform the application action, such as clicking Submit.
  5. Call cy.wait('@alias') in the test body.
  6. Use the yielded interception in .then() or with chained assertions, and run any cy.task() there.
it('submits a user and records the request', () => {
  cy.intercept('POST', '/users', (req) => {
    expect(req.body).to.have.property('company', 'Acme Company')
    req.headers['x-test-mode'] = 'true'
    req.alias = 'createUser'
  })

  cy.get('[data-cy=company]').type('Acme Company')
  cy.get('[data-cy=submit]').click()

  cy.wait('@createUser').then((interception) => {
    expect(interception.request.headers['x-test-mode']).to.equal('true')
    cy.task('recordRequest', interception.request.body)
  })
})

Fix common failures

“Cannot call cy.* outside a running test”

Cause: A command was called from a route handler or a Cypress.on() listener.

Fix: Replace the command with synchronous request logic, save the needed value, and consume it after cy.wait() or in a later test-chain callback.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The command appears to run too early or never runs

Cause: The callback’s execution is not part of the queue, so Cypress cannot schedule the command relative to the rest of the test.

Fix: Set an alias in the handler and wait for that alias after the browser action. Register the intercept before the action so the request cannot pass before the route exists.

cy.task() is needed to persist request data

Cause: The task was placed in the callback.

Fix: Yield the interception first, then call cy.task() from cy.wait('@alias').then(...). Pass a serializable body or selected fields rather than the live request object.

An assertion inside the handler fails unexpectedly

Cause: The assertion depends on a value that would require a Cypress command, or the handler is matching more requests than intended.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: Narrow the intercept method and URL pattern. Keep only immediate assertions on req in the handler; move DOM assertions and command-derived values to the test chain.

A response change is not visible in the browser

Cause: The response was changed in after:response, which runs after delivery, or the handler never called the appropriate continuation/stub API.

Fix: Make supported response edits in req.continue(), before:response, or response, and use req.reply() when supplying a stub.

“Cypress detected that you returned a promise…” or a conflicting return error

Cause: A callback queued a Cypress command and also returned a different value. Cypress commands execute later, so that return value conflicts with the queued work.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: Remove the explicit non-undefined return, or move the command into the test chain. Do not use a return statement to make a Cypress command synchronous.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Reliability and timing considerations

Keep handlers fast and deterministic

A route handler runs on the request path. Do parsing, validation, header changes, and response decisions there; defer file I/O, database work, DOM queries, and other queued activity until after the interception is yielded. This keeps the browser request lifecycle predictable.

Do not use arbitrary delays to repair queue problems

A delay such as cy.wait(1000) inside the callback cannot fix the context error. If the test needs to wait for a request, wait on its alias in the command chain. If the application needs time to render after the response, assert on the resulting UI after the network wait.

Separate API setup from browser interception

Use cy.request() for direct server setup or verification, understanding that it runs from Node and bypasses cy.intercept(). Use cy.intercept() when the browser request itself must be observed, modified, stubbed, or synchronized with the test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If your goal is to capture a page image for test evidence, documentation, or a visual check rather than exercise Cypress’s browser interception, ScreenshotNeo provides a single screenshot API request. Its cleanup step accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Only clean shots are billed, while bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are free, with the result identified by X-Page-Verdict and X-Billed headers. The same service also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

Use the API key and URL in the request below; the ScreenshotNeo documentation lists the request options.

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}`);

ScreenshotNeo supports PNG, JPEG, WebP, and PDF output, full-page and element captures, device and viewport settings, retina scale, dark mode, custom CSS and JavaScript, selector waits, network-idle waits, request blocking, custom headers and cookies, geolocation and timezone, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, caching with a chosen TTL, and a usage API. Every feature is included on every plan: 1,000 shots per month are free with no card; paid plans are $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000, and $249 for 1,000,000, with yearly billing providing two months free. Create a free ScreenshotNeo account to start with the 1,000 monthly shots.

Frequently Asked Questions

Should a route handler be declared with async?

It is unnecessary for Cypress command handling. Keep the callback synchronous and use the interception APIs; an async declaration does not put Cypress commands into the command queue.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can a response be changed after it reaches the browser?

No. Changes made in after:response occur after delivery and cannot affect the browser. Make supported edits in the earlier response phases instead.

Why might a direct API check miss an intercept?

cy.request() runs from Cypress’s Node process and bypasses routes defined with cy.intercept(). It verifies the endpoint directly rather than reproducing the browser request path.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.