Introduction
Although older versions of Cypress did not support testing across domains, with the addition of cy.origin()
in version
9.6, tests can now be created that span multiple domains.
In this article, we will cover how to create cross-domain tests with Cypress using the new cy.origin()
and
cy.session()
functions, and identify some limitations with these new commands that you may run into in your own
testing.
What is Cypress?
Cypress is a JavaScript-based end-to-end testing framework for browser-based applications. Due to its tight integration with the browser, it is possible to use browser-based tools like Chrome Dev Tools and popular browser extensions like React Dev Tools to debug Cypress tests. The fact that you can use familiar tools when building tests is one of the reasons that Cypress is popular with developers. Cypress offers several testing features including: automatic waiting (to avoid timing issues), integrated debugging and stack tracing, snapshot support, network traffic control with the possibility to see DOM elements, cookies, local storage items and more.
Historical Cross-Origin Limitations in Cypress
A long-standing limitation of Cypress was that single tests could not span multiple domains. This is due to how Cypress is architected. Other testing tools like Selenium and Playwright operate outside of the browser runtime, and communicate with the browser through a predefined protocol (the WebDriver API for Selenium, and the Chrome DevTools Protocol for Playwright).
Cypress does not operate outside of the browser runtime, instead it runs within the browser event loop itself. While this can provide some advantages, such as being able to have more guarantees around when triggered browser actions will occur, it does come at a cost. The reason why Cypress historically has not been able to test across domains is because since it operates inside the browser runtime, it needs to follow the browser’s Same Origin Policy which normally restricts JavaScript execution across domains.
Starting in Cypress version 9.6.0, Cypress now includes “experimental” support for testing across multiple domains in a
single test via the new cy.origin()
command. cy.origin()
works by injecting the test runtime into a secondary
domain, sending the written callback function, executing the function in the secondary domain, and returning control to
the first origin.
Example usage of cy.origin() and cy.session()
Here’s an example which makes Cypress work on any number of superdomains. We will start by demonstrating a simple example that demonstrates a cross-domain error in Cypress. We will then update that code so that this single test can access multiple domains.
First, create a new folder for our project and cd into it. Then install Cypress via npm:
|
|
Now open Cypress from your project folder:
|
|
From here, you can create an E2E testing environment using the Cypress user interface and choosing a browser to test:
This will create the required folders in your project folder: fixtures
, support
, e2e
, etc.
The e2e
folder will contain your specs (testing workflows) which can be modified using your favorite editor.
The example below is a trivial example which demonstrates a cross-origin failure:
|
|
If you run this test, you’ll find that the following error occurs:
|
|
To successfully navigate across multiple domains, you can modify this test to use the new cy.origin()
command:
|
|
Note that for this to work, you’ll need to first set the experimentalSessionAndOrigin
flag to true in your
cypress.config.js
file.
The following example is a more complete example that demonstrates how to use cy.origin()
to navigate between two
different domains:
|
|
Here the cy.session()
command is used to cache session information between tests, instead of having to pass login
information before each test. This command pairs well with cy.origin()
.
Often it’s useful to set up common actions like login so that the code is shared across tests. This can be accomplished by adding a custom command (“login” in this case) to your “commands.js” file:
|
|
Current Limitations
There are several limitations to be aware of with the current implementation of Cypress’s cross-domain support,
particularly around the types of logic that can and cannot be included inside a cy.origin()
callback.
cy.origin()
’s model requires data to be serialized when transmitting from one instance to another.
Looking back at our previous examples, the following syntax is considered best practice:
|
|
Since Cypress uses the structured clone algorithm (as defined by
MDN’s docs on the Web Workers API) to transmit the
args
option, there are also restrictions for the callback. This includes functions being prohibited from being
duplicated, cloning DOM nodes, and the exclusion of specific object properties such as lastIndex and descriptors.
For callback restrictions, these commands will return errors when including them in the callback:
- cy.origin()
- cy.intercept()
- cy.session()
- cy.route()
- cy.server(),
- Cypress.Cookies.preserveOnce(),
- require(),
- dynamic import(),
Since require() and import() can’t be used in the callback, npm packages or third-party libraries can’t be referenced in
the cy.origin()
block either; however, code can be reused between callbacks using a before
block:
|
|
cy.origin()
is by no means the perfect Cypress command for cross-domain testing support, as several bugs are currently
being ironed out including handling cross-origin document.cookie, and unresponsive login pages when using a third-party
identity management system like Keycloak. There are also issues when using cy.visit()
inside a cy.origin()
block;
the test hangs for an undetermined time for some domains.
For example, the following test case hangs when visiting a cross-origin page with cy.origin()
:
|
|
Cross-domain testing made easy with Reflect
Cross-domain testing is critical, especially when it comes to multi-tenancy architectures or web apps involving
operations on different sites. You can create such a test in Cypress with cy.origin()
, but that is still an
experimental feature subject to instability and limitations.
For example, suppose you want to test the purchase process on your e-commerce site. After adding some items to your shopping cart and checking out, you might be temporarily redirected to the payment provider. Once you made the payment, you will return to the e-commerce platform, which should now display the outcome of the transaction and delivery information. This common task involves pages on different domains.
Now, assume you want to test that flow in Cypress or similar technologies. It would result in a long, complex, and unstable script, taking you a lot of time and effort. Instead, imagine that you can create the same test by running the operations to be tested directly in a browser, without any code involved, letting an advanced testing tool record what you do and convert it into a repeatable test for you. This is exactly what Reflect is all about!
Reflect is a code-free, cloud-based testing tool with native support for cross-domain testing. It also supports complex interactions such as hovers, file uploads, and more. In detail, Reflect has no restrictions on writing tests that span multiple domains since is capable of recording virtually any action that can be taken in a browser. Cross-domain testing has never been easier, Try Reflect for free now!
Conclusion
The experimental inclusion of cy.origin()
in Cypress makes multi-domain testing possible for single test workflows.
This is especially important when testing workflows that require using a third party login solution. Additionally,
cy.session() makes cross-testing tokenization a possibility. Being a new feature however, new bugs and existing
limitations need to be addressed in order to provide a more complete testing experience. Fortunately, cross-domain
testing is natively supported by more advanced testing tools, such as Reflect.