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

When Cypress appears to reach a different URL than your external application, first capture the browser’s actual final location with cy.url() or cy.location(). Then determine whether the change was an HTTP redirect, a form submission, a link, or client-side JavaScript. Only after that should you diagnose Cypress’s origin boundary: a change in scheme, hostname, or port can require cy.origin() even when the redirect itself is correct.

This order separates an application defect from a test-scope problem. Cypress normally follows redirects during cy.visit(); a later command can still fail because the destination is on another origin or is an external site your team does not control.

What Cypress actually does during a redirect

cy.visit() follows redirects and resolves after the remote page fires its load event. Cypress documents that the resulting response must be HTML, must end with a 2xx status after redirect following, and must eventually fire load. See the cy.visit() API.

That means the URL in your test may already be the final URL, not the URL you originally requested. A redirect that looks “different” can therefore be one of three separate observations:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • The server returned a redirect response and the browser followed it.
  • A form or anchor caused a new navigation.
  • Application JavaScript changed the location after the initial document loaded.

Cypress does not generally rewrite your application’s destination. Its hosted runner and network interception have documented limitations, but application code executes as it does outside Cypress. Record the location first instead of assuming Cypress changed the redirect.

A repeatable debugging sequence

1. Record the test context

Write down the Cypress version, browser, configured baseUrl, requested URL, and whether the test uses the legacy network path or Cypress’s native network path. Network behavior and diagnostics can differ by version. Cypress’s native interception guide describes behavior for Cypress 16; do not apply those details automatically to earlier versions. Start with the native network interception guide when that path is enabled.

2. Capture the final browser location immediately

Assert the URL directly after the visit or action that should navigate. The cy.location() API documents location assertions and normalization; Cypress’s visit examples also demonstrate checking the resulting route.

cy.visit('/login')
cy.get('#continue').click()

cy.url().then((url) => {
  cy.log(`Final browser URL: ${url}`)
})

cy.location().should((location) => {
  expect(location.protocol).to.match(/^https?:$/)
  expect(location.hostname).to.equal('identity.example.test')
  expect(location.pathname).to.equal('/authorize')
})

For a simpler assertion, use cy.url().should('include', '/authorize'). Capture the value before any command that might time out, so the failure shows where the browser actually landed.

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

3. Identify who initiated the navigation

Inspect the application flow and browser network log to classify the transition:

  • Server redirect: an HTTP response points to a Location URL.
  • Form submission: a form action and method send the browser to another route or origin.
  • Anchor navigation: an <a> element supplies the destination in href.
  • JavaScript navigation: code assigns window.location, calls a router, or otherwise changes the document location.

The cross-origin guide covers these navigation paths separately: Cypress cross-origin testing. A server response and a client-side route change can produce similar final URLs but require different fixes.

4. Compare origins exactly

An origin is the combination of scheme, hostname, and port. These are different origins:

  • http://app.example.test and https://app.example.test (scheme differs)
  • https://app.example.test and https://login.example.test (hostname differs)
  • https://app.example.test and https://app.example.test:8443 (port differs)

A subdomain change is still a cross-origin navigation. Cypress’s documented rule is concise: “Different origins per test require cy.origin().”

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

5. Check Cypress’s version behavior

In Cypress 14, document.domain injection is no longer the default. Tests that previously crossed subdomains through that compatibility behavior may now need cy.origin(). The injectDocumentDomain option is transitional and deprecated; use the documented origin model instead. See both the cross-origin guide and cy.origin() API.

Choose the assertion that matches what you own

What you are testing Recommended method Evidence obtained Dependency and scope
Your application’s outbound destination Assert the link’s href The exact URL string emitted by your app Deterministic; does not contact the third-party site
HTTP redirect behavior cy.request() Response status, headers, body and redirectedToUrl HTTP-level check, not proof of browser rendering
Rendered behavior on a controlled secondary origin Navigate, then use cy.origin() DOM and interaction after the destination loads Requires control of, or an explicit test arrangement for, that origin
Third-party page your team does not control Assert the outbound href That your app points to the intended partner URL Avoids partner uptime, content and policy changes

Controlled cross-origin navigation

When the destination belongs to your team and you need to inspect it, put all commands that interact with that origin inside cy.origin(). The origin argument must match scheme, hostname and port exactly.

cy.visit('/login')
cy.get('#continue').click()

cy.origin('https://identity.example.test', () => {
  cy.url().should('include', '/authorize')
  cy.get('[data-cy=consent]').should('be.visible')
})

The callback is a separate Cypress command context for the secondary origin. Without it, commands may time out or fail with a cross-origin error. See the cy.origin() documentation.

External navigation you do not control

If a button or link sends users to a partner, verify the destination without visiting it:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cy.visit('/')
cy.get('a.external')
  .should('have.attr', 'href', 'https://partner.example/path')

Cypress recommends this approach for external destinations because it tests your contract without coupling the suite to a third party’s availability or behavior. The guidance appears in the common error messages and cross-origin testing guide.

Inspect an HTTP redirect without rendering it

cy.request() is useful when the question is “What did the server return?” It is not a substitute for proving that a browser rendered and interacted with the final page. Cypress’s response object includes redirectedToUrl, and the command is not constrained by browser CORS in the same way as page JavaScript.

cy.request('/start').then((response) => {
  cy.log(`Status: ${response.status}`)
  cy.log(`Redirect target: ${response.redirectedToUrl || 'none'}`)
})

Be precise about the URL base. A relative request after a visit uses the visited host. Before any visit, Cypress resolves a relative URL against the configured baseUrl. Details are in the cy.request() API.

For a redirect chain that must remain at the HTTP layer, request the starting endpoint and record the final metadata, then separately use a browser test if the rendered destination matters. Do not treat a successful cy.request() as evidence that cookies, scripts, CSP, a login flow or the final DOM worked in a browser.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Install intercepts before navigation

Application startup requests can occur before cy.visit() resolves. Register routes first so the request is not missed:

cy.intercept('/api/session', { fixture: 'session.json' })
cy.visit('/app')

This is especially important when authentication state determines whether the app redirects to a login, consent or error page. The timing requirement is documented in the cy.visit() API.

When the apparent discrepancy is not a redirect

Authentication and state

An external application may receive a different cookie, token, locale, user-agent condition or session state in Cypress. Compare request headers and cookies, then compare the final location. Do not “fix” the test with cy.origin() until you know the server selected the intended route.

HTTPS-to-HTTP transitions

A secure-to-insecure hop can fail because of browser security behavior even when the server’s redirect is valid. Compare the schemes explicitly and consult Cypress’s cross-origin documentation for HTTPS-to-HTTP navigation errors.

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

Top-level page versus iframe

cy.origin() addresses top-level navigation. It does not make a cross-origin iframe’s DOM accessible. If the differing URL is inside an iframe, treat iframe access as a separate constraint; Cypress’s FAQ documents this distinction.

Troubleshooting common failures

“Cypress redirected to the wrong URL”

Log cy.url() immediately after the triggering command and inspect the network response. If the response contains a server redirect, fix the application or test data. If JavaScript changes the location later, wait for the specific route or state that causes that assignment rather than asserting too early.

Commands time out after a successful external redirect

The navigation may be correct but the next command is running outside the current origin. Move destination commands into cy.origin('scheme://host:port', () => { ... }), or replace the navigation with an href assertion when the site is not yours.

cy.request() shows a target that the browser does not render

That is an expected scope difference: cy.request() reports HTTP behavior, while the browser must load HTML, execute scripts and satisfy security policies. Compare response metadata with the browser’s final location and load errors instead of treating either result as definitive alone.

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.

An intercept never matches

Move cy.intercept() before cy.visit() and before the action that initializes the app. Startup requests may already have been sent by the time a later command runs.

A subdomain test worked on an older Cypress release

Check whether the test relied on implicit document.domain injection. Under Cypress 14’s default, use explicit cy.origin(); retain the compatibility option only as a temporary migration aid.

The result changes only in Cypress 16 native network mode

Capture the version and network path in the failure report. Consult the Cypress 16 native network guide and avoid generalizing its behavior to earlier versions.

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

Or skip the browser setup

If your immediate need is a visual record of the destination page rather than a Cypress interaction test, ScreenshotNeo can capture a URL with one request. It is a website screenshot API and MCP server for developers; it does not replace redirect assertions, but it can provide a repeatable image or PDF of a reachable final URL.

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

Use the API examples in the ScreenshotNeo documentation and replace the URL with the destination you want to inspect:

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-app.example/landing -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://your-app.example/landing"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://your-app.example/landing' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed as clean shots, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server includes take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Every feature is included on every plan. Create a free ScreenshotNeo account to try it without a card.

Practical decision checklist

  • Did you record the requested URL and the final cy.url() or cy.location()?
  • Did you classify the transition as HTTP, form, link or JavaScript?
  • Do scheme, hostname or port differ between the two locations?
  • Is the secondary origin controlled by your team?
  • Are cross-origin commands inside a matching cy.origin() callback?
  • Would an href assertion test the contract more reliably than visiting a partner?
  • Did you register intercepts before the visit or startup action?
  • Did you record Cypress version, browser and network path?
  • If using cy.request(), did you treat redirectedToUrl as HTTP evidence rather than browser-rendering evidence?
  • Is the apparent destination actually an iframe or a secure-to-insecure transition?

Frequently Asked Questions

Does a redirect to a different subdomain always require cy.origin()?

Under Cypress 14’s default behavior, a subdomain is a different origin because the hostname changes. Use cy.origin() when the test must execute commands on that destination; an href assertion may be sufficient when you only need to verify the outbound URL.

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

Can I verify a redirect chain without allowing the browser to follow it?

Use an HTTP client or application-side server test that records each 3xx response and Location header. Cypress’s cy.request() is suitable for HTTP-level inspection, but its normal redirect handling and redirectedToUrl value should not be interpreted as a rendered-page test.

Why can a page screenshot differ even when the URL is identical?

Rendering can vary with cookies, consent state, viewport, user agent, timing, blocked resources and application data. Keep visual capture separate from URL and origin assertions, and record those inputs when comparing screenshots.

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.