Cypress can test subdomains and cross-origin flows, but it requires explicit configuration because it’s designed to isolate your tests to a single origin by default for security and stability.
Let’s see it in action. Imagine you have a login.example.com that redirects to app.example.com after a successful login. We want to test this flow.
First, we need to tell Cypress that app.example.com is an "allowed" origin. This is done in cypress.config.js:
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl: 'https://login.example.com', // Start here
chromeWebSecurity: false, // Important for cross-origin
experimentalSessionAndOrigin: true, // Essential for cross-origin
// Define allowed origins
allowedOrigins: ['https://app.example.com'],
},
});
The chromeWebSecurity: false is a big one. By default, Chrome (and thus Cypress, which uses Chromium) blocks cross-origin requests and navigations. Disabling this for Cypress only allows Cypress to bypass these restrictions within the test runner. It doesn’t make your application insecure; it’s a test environment setting.
experimentalSessionAndOrigin: true enables features specifically designed to handle cross-origin scenarios better, including managing cookies and local storage across different origins.
Now, in your test, you can navigate to your initial domain and then, after an action that causes a redirect, Cypress will follow it to the new origin.
// cypress/e2e/subdomain_login.cy.js
describe('Subdomain Login Flow', () => {
it('should log in and redirect to the app subdomain', () => {
cy.visit('/'); // This visits https://login.example.com
cy.get('input[name="username"]').type('testuser');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
// Cypress will automatically follow the redirect to app.example.com
// We can assert that we are now on the new origin
cy.url().should('include', 'https://app.example.com');
// Now we can interact with elements on the app subdomain
cy.contains('Welcome, testuser!').should('be.visible');
});
});
The magic here is that once the cy.click() on the submit button triggers a navigation event that lands on https://app.example.com, Cypress, because it’s been configured with allowedOrigins and experimentalSessionAndOrigin, will continue the test on this new origin. It will update its internal understanding of the "current origin" and allow subsequent commands to interact with elements on app.example.com.
What’s often misunderstood is how Cypress manages state (like cookies and local storage) across these origins. When experimentalSessionAndOrigin is enabled, Cypress attempts to synchronize these between login.example.com and app.example.com if they are part of the same root domain (e.g., .example.com). This means a cookie set on login.example.com might be available on app.example.com if its scope allows it, which is crucial for maintaining a logged-in state.
A common pitfall is forgetting chromeWebSecurity: false. Without it, Cypress will often hang or error out with a generic "timed out waiting for the command to complete" because the browser itself is blocking the navigation. Another is not listing all necessary subdomains in allowedOrigins. If your flow involves a third redirect to dashboard.app.example.com, you’d need to add that too.
If you encounter issues where cookies aren’t persisting across subdomains even with these settings, investigate your application’s cookie domain and path configurations. Cypress respects the browser’s cookie handling, so if your app isn’t setting cookies correctly for a shared domain, Cypress won’t be able to magically fix that.
The next challenge you’ll likely face is handling authentication that relies on third-party identity providers or complex OAuth flows that involve redirects to entirely different domains (auth.provider.com) before returning to your app.example.com.