---
title: "Making cross-origin work: The Journey Behind cy.origin()"
url: "https://www.cypress.io/blog/the-journey-behind-cy-origin"
description: "Soon Cypress 12 will be released, making cross-origin testing generally available to everyone! We are excited to bring you this feature, and we hope you are as…"
---

# Making cross-origin work: The Journey Behind cy.origin()

December 2, 2022

By The Cypress Team

Soon Cypress 12 will be released, making cross-origin testing generally available to everyone! We are excited to bring you this feature, and we hope you are as excited about it as we are.

Cross-origin testing has been in the works for a while, with multiple developers building it over the last year and [releasing](https://www.cypress.io/blog/2022/04/25/cypress-9-6-0-easily-test-multi-domain-workflows-with-cy-origin/?ref=cypress-io.ghost.io) the initial [experimental version](https://www.cypress.io/blog/2022/04/25/cypress-9-6-0-easily-test-multi-domain-workflows-with-cy-origin/?ref=cypress-io.ghost.io) in 9.6! Cross-origin testing allows you to seamlessly switch between multiple origins to test syndicated login services, integrated systems, and more.

This seems like a long time to get something as simple as cross-origin testing available. But deriving a practical solution was not easy, and we want to share that journey with you.

## Understanding the Problem

One [key difference](https://docs.cypress.io/guides/overview/key-differences?ref=cypress-io.ghost.io#Native-access) about Cypress is it runs natively in the browser as an application while running your web app inside of an iframe. This offers several benefits, including improved flake resistance, [direct access](https://docs.cypress.io/guides/overview/key-differences?ref=cypress-io.ghost.io#Native-access) to your application, and improved [debuggability](https://docs.cypress.io/guides/overview/key-differences?ref=cypress-io.ghost.io#Debuggability). However, this also subjects your E2E tests to [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy?ref=cypress-io.ghost.io), which means that if cross-origin content is loaded into the page, Cypress is unable to interact or verify without browser restrictions. Cypress is limited to working only with the domain of the first URL that is loaded via `cy.visit()`. For example:

```
cy.visit('https://docs.cypress.io')
```

  
Before Cypress 12, if your web app visits another origin, Cypress cannot interact with it without violating the [same-origin](https://docs.cypress.io/guides/guides/web-security?ref=cypress-io.ghost.io#Same-superdomain-per-test) policy in the browser. The main instance of Cypress can no longer communicate directly with your web app.

```
cy.visit('https://docs.cypress.io');
cy.visit("https://www.npmjs.com/package/cypress");
```

  
In the past, this has made testing some scenarios difficult, such as testing third-party authentication. We have previously recommended users leverage [programmatic login](https://docs.cypress.io/guides/end-to-end-testing/auth0-authentication?ref=cypress-io.ghost.io#Custom-Command-for-Auth0-Authentication) to sidestep the same-origin policy issues, which avoids interacting with third-party login screens altogether. Some users would also break their tests into [multiple tests](https://www.cypress.io/blog/2022/04/25/cypress-9-6-0-easily-test-multi-domain-workflows-with-cy-origin/?ref=cypress-io.ghost.io#the-problem) to overcome these restrictions since each test could visit a new origin. Others would disable [chromeWebSecurity](https://docs.cypress.io/guides/guides/web-security?ref=cypress-io.ghost.io#Disabling-Web-Security). However, many of these solutions have common pitfalls, inducing common frustrations for everyone like complexity, hard-to-debug failures, weird edge cases, and test flake.

## Deriving the Implementation

At Cypress, our mission is to enable users to write faster, easier, and more reliable tests. We wanted a solution where users could initiate a cross-origin navigation without drastically changing how their tests look today. Keeping a low barrier to entry for users was important to us. This meant the implementation under the hood likely needed to be complex. To accomplish this, we introduced the `cy.origin()` command.

#### Instance Communication

When the `cy.origin()` command is used, a new iframe is created within the Cypress application. This iframe’s source origin is the origin of the URL passed into the command. This iframe, once created, sets up a new Cypress instance that is cross-origin to the main Cypress instance. To work around the same-origin policy restrictions, these two instances communicate via [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage?ref=cypress-io.ghost.io), which is a safe way to enable cross-origin communication between window objects. Through this mechanism, data communicated between these frames must be [serializable](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm?ref=cypress-io.ghost.io). To keep some of our valued debuggability functionality working (such as DOM snapshots and contextual error messages), we needed to rework some of their internals.

All of your test code is also serialized and then evaled in the iframe for execution. Because of this, outside variables [cannot be used](https://docs.cypress.io/api/commands/origin?ref=cypress-io.ghost.io#Serialization) with `cy.origin()` and must be passed into the [args option](https://docs.cypress.io/api/commands/origin?ref=cypress-io.ghost.io#Arguments) of `cy.origin()`. Any dependencies you rely on inside of the call to `cy.origin()` must all be [required](https://docs.cypress.io/api/commands/origin?ref=cypress-io.ghost.io#Dependencies-Sharing-Code) separately.

When you visit a new origin, the [proxy under the hood](https://docs.cypress.io/guides/guides/web-security?ref=cypress-io.ghost.io#Examples-of-what-Cypress-does-under-the-hood) injects HTML into the destination, allowing this new Cypress instance to be discovered. Since both frames fit the [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy?ref=cypress-io.ghost.io), the new Cypress instance is linked to the newly navigated origin. This allows us to execute the `cy.origin()` serialized function in the new origin and communicate it back to the primary instance of Cypress.

#### Stability

We can now execute our serialized `cy.origin()` function, but how do we know _when_ to execute it? Cypress has a mechanism built in known as stability which is why Cypress is so [flake resistant](https://docs.cypress.io/guides/overview/key-differences?ref=cypress-io.ghost.io#Flake-resistant) when testing. Cypress knows when your web app page loads and is ready for Cypress commands to execute against it. Cypress also knows when your page has unloaded, and commands should no longer run. This concept has applied to a single origin until now.

When cross-origin navigation occurs, our new Cypress instance inside our expected same-origin iframe knows if and when the page has loaded and executes your Cypress commands. Since the page can load before our new Cypress instance is created and vice versa, our linking behavior needs to be bi-directional. Once this link is established, and the page has loaded, your commands start executing inside `cy.origin()` and stop executing when the page has either navigated or is unloaded. This mechanism allows users to navigate to any origin at any time seamlessly. You can also define `cy.origin()` commands that bind to Cypress events even before the origin is visited!

But what if no `cy.origin()` command for that origin exists, or the origin declared inside `cy.origin()` does not match? What if navigation occurs mid-execution? Since Cypress tracks stability, it can notify users when something unexpected has occurred and present the user with an actionable error message to remedy the problem.  

```
it("fails because it's cross origin", () => {
	cy.visit("https://example.cypress.io");
	cy.visit("https://www.npmjs.com/package/cypress");
	cy.get("#homePage-link").should("have.text", "github.com/cypress-io/cypress");
});
```

  

In this scenario, a user can simply fix their test by wrapping this `cy.get()` inside the appropriate `cy.origin()` call.

```
it("passes with cy.origin()", () => {
	cy.visit("https://example.cypress.io");
	cy.visit("https://www.npmjs.com/package/cypress");
	cy.origin("https://www.npmjs.com", () => {
		cy.get("#homePage-link").should("have.text", "github.com/cypress-io/cypress");
	});
});
```

  
There you have it. Cross-origin testing now works! This allows users to write tests similar to how they look today, with commands to test cross-origin content living inside a `cy.origin()` command. Very cool, right? The architecture of `cy.origin()` helped solve our main communication issue, but there were a few other things to consider.

#### Frame Busting

To prevent [clickjacking](https://developer.mozilla.org/en-US/docs/Glossary/Clickjacking?ref=cypress-io.ghost.io) and iframe hosting, many popular sites implement a technique called [framebusting](https://en.wikipedia.org/wiki/Framekiller?ref=cypress-io.ghost.io). Some of these techniques prevent Cypress from working properly. This is why the [modifyObstruciveCode](https://docs.cypress.io/guides/references/legacy-configuration?ref=cypress-io.ghost.io#modifyObstructiveCode) option exists today and is enabled by default.

As an extension of this, to support Google, Facebook, and Microsoft OAuth authentication services, the [experimentalModifyObstructiveThirdPartyCode](https://docs.cypress.io/guides/references/experiments?ref=cypress-io.ghost.io#Configuration) option exists. The main goal of the flag is to apply [modifyObstructiveCode](https://docs.cypress.io/guides/references/legacy-configuration?ref=cypress-io.ghost.io#modifyObstructiveCode) to third-party .html and .js files, as well as strip out [SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity?ref=cypress-io.ghost.io) attributes from modified framebusting code. Not every user will wish to do this, but may be necessary for getting a specific authentication provider to work within Cypress. The [experimentalModifyObstructiveThirdPartyCode](https://docs.cypress.io/guides/references/experiments?ref=cypress-io.ghost.io#Configuration) is disabled by default and can be enabled/disabled at your convenience.

#### Cookies

When an iframe is considered cross-origin to the top URL of the browser, the cookie context changes. In the context of your web app running within Cypress, Cypress needs to treat your web app URL as the top URL. Our [proxy under the hood](https://docs.cypress.io/guides/guides/web-security?ref=cypress-io.ghost.io#Examples-of-what-Cypress-does-under-the-hood) handles this for you, providing a seamless experience for attaching and setting cookies appropriately, including _document.cookie_.

All of these changes we’ve implemented and iterated on for the past few months allow us to bring you a great experience using `cy.origin()`.  

## Examples

### Logging into Microsoft Azure Active Directory

## Tell us what you think

We’ll be posting updated guides and examples in our [documentation](https://docs.cypress.io/guides/overview/why-cypress?ref=cypress-io.ghost.io) when Cypress 12 releases. You can also stay tuned to our blog or follow us on [Twitter](https://twitter.com/Cypress_io?ref=cypress-io.ghost.io).

At Cypress, we always want to hear your feedback to continue providing an improved testing experience. If something isn’t working correctly, or there are issues in our updated guides, please open a [Github](https://github.com/cypress-io/cypress/issues/new/choose?ref=cypress-io.ghost.io) issue or chat with us on [Discord](https://discord.gg/Cd4CdSx?ref=cypress-io.ghost.io). Thank you all for your patience with this feature, and as always, happy testing!