Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

max 1 site... support visiting multiple superdomains in one test #944

Closed
jukefr opened this issue Nov 21, 2017 · 277 comments · Fixed by #18075
Closed

max 1 site... support visiting multiple superdomains in one test #944

jukefr opened this issue Nov 21, 2017 · 277 comments · Fixed by #18075
Labels
topic: cross-origin ⤭ Topics related to cross-origin navigation in tests type: feature New feature that does not currently exist

Comments

@jukefr
Copy link

jukefr commented Nov 21, 2017

"One thing you may notice though is that Cypress still enforces
visiting a single superdomain with cy.visit().
This is an artificial limitation (and one that can be removed).
You should open an issue and tell us what you’re trying to do!"
- https://docs.cypress.io/guides/guides/web-security.html#Disabling-Web-Security
I was trying to get a quick script to press an update button on 500 of our domains.

Maybe you could document a way for us to remove this limit without having to open an issue to have the core functioning of the app changed ? Maybe allow passing an argument when running the app.

@brian-mann
Copy link
Member

Yeah, it can be done. Quick question - is there any reason you need a browser to load up 500 different domains and click a button?

Wouldn't it be much much easier to just use cy.request or another programatic means to accomplish this?

For instance, what does clicking the button do? Send an HTTP request is my guess. So instead of using the UI, just send the HTTP request directly using cy.request. Same result, 100x faster, and no domain issues.

Any additional information about your use case would be helpful.

@jennifer-shehane jennifer-shehane added the stage: needs information Not enough info to reproduce the issue label Nov 27, 2017
@ejoubaud
Copy link

Just jumping in to say that I have a use-case where I need to load several sites in the same test (not 500, 2 will do for a test).

I'm testing a browser extension that will show a modal (via a content script) on several sites that you can whitelist in its settings. The extension uses a global timer (via its background tab) to synchronise the extension's behaviour across different sites/tabs/link clicks/refreshes (it persists a countdown as you browse those various whitelisted sites, among other things). Because of this restriction, I can't test that the synchronisation works when the sites I visit are on different domains.

I can't just make a cy.request because I need Chrome to load the page, then load the extension's contentscript on it, then assert that the contentscript shows the modal and that its content is coherent with the cross-tab synchronisation I expect to happen.

@MaxwellGBrown
Copy link

Wouldn't it be much much easier to just use cy.request or another programatic means to accomplish this?

To me the issue is that it is more work to simulate the requests than it is to have Cypress fill in a form.

That and it deviates too far from the User flow that my users would be experiencing.

@alovato88
Copy link

This is an applicable issue for my organization's test code. We recently implemented OKTA, which requires you to go to a super domain to authenticate then route to the super domain that is going to be tested. When I use "cy.visit()" after authenticating, all authentication data will be wiped out and Cypress will attempt to authenticate the same exact way again, causing either a sid or cross domain error. Due to this issue, we are about to drop Cypress all together and move back to Selenium.

@brian-mann
Copy link
Member

brian-mann commented Jun 14, 2018

@alovato88 if you switched to using cy.request to programmatically log in to receive your token, everything would just work. We have multiple recipes showcasing this.

@MaxwellGBrown we've been down this rabbit hole many times with many different user feedback and the answer is always the same - you can test your login page in isolation away from the main app once, and then use cy.request to programmatically use it afterwards. You get the benefit of "really testing it like a user" and then once that is done you get no further benefit.

Just visit the OTHER domain in the test and log in. You could even stub the network request if you wanted to prevent the 3rd party server from redirecting you. Once you do that, then use cy.request to programmatically receive the token and then START with the token in hand visiting your real app. Just set the token directly in cookies or localstorage and your app will "start" logged in.

There are many other issues in here in which I've commented providing different approaches and work arounds you all may find useful.

Our best practices cover this pretty in depth, and I've even given a talk about this subject and provide real world examples of how to approach this problem.

@ejoubaud You can do this in Cypress - simply visit the domains in different tests, not the same test. As long as you don't visit two different super domains in one test it will all just work. Visit one super domain, test your extension, and then in a separate test visit a different one and test your extension in there.

@alovato88
Copy link

@brian-mann Where can I find any of these showcased recipes?

@brian-mann
Copy link
Member

brian-mann commented Jun 23, 2018

https://on.cypress.io/recipes

@dchambers
Copy link

I think I have a good use case for this. We're migrating from a monolithic Ruby on Rails app, to micro-services on the back-ends and user-type differentiated front-ends. Our new front-ends are React SPAs, and one of them is way too big to replace at once, so for some app routes we just display the original page within an iframe whereas for others we're using React components to render those routes.

At present I can't write tests that exercise new and old at the same time. I'm presently working around this by putting every test in its own file, but this is far from ideal.

@e-e-e
Copy link

e-e-e commented Jul 16, 2018

We also require this functionality to test a third party integration

@tobocop
Copy link

tobocop commented Aug 1, 2018

I would love to be able to visit multiple domains. My use case is testing the integration between a front end site and a backend admin. There are certainly ways around not visiting multiple domains, however locally they are only running on different ports (3000, and 3001). There certainly are work arounds:

  1. Using cy.request. I could do this however I'd be making requests to the backend api to seed users and make changes. If the API shape changes now my integration suite changes as does my application code. It seems like a strange coupling that I'd rather not have
  2. Setting up a local apache to forward subdomains to the ports. Again. totally possible, however more complicated dev machine setup is something I'd rather avoid, especially since the different is only on port number and not on the hostname.

@duncan-bayne
Copy link

duncan-bayne commented Aug 17, 2018

This is an absolute blocker for my current client. They have built a solution that integrates several SaaS systems including Salesforce, and need to be able to test side-effects in integrated systems. For example, that registering in the Web front-end causes a lead to be created in Salesforce.

We too will have to abandon Cypress for Selenium, despite significant enthusiasm for Cypress, if this use case can't be addressed.

Update: maybe not ... we are able to subsequently test state on the SaaS system in a second context, and reset it in a third. Something of a hack though.

@jukefr
Copy link
Author

jukefr commented Aug 17, 2018

As this has not been addressed properly for almost a year, I would advise (only my opinion) people who think Cypress is too restrictive to simply use Puppeteer directly (with a framework like Jest if needed).

Same result but with tools that are actively maintained and improved upon.

@alexander-veshkin

This comment has been minimized.

@jennifer-shehane jennifer-shehane added type: feature New feature that does not currently exist stage: proposal 💡 No work has been done of this issue and removed stage: needs information Not enough info to reproduce the issue labels Nov 9, 2018
@marklagendijk
Copy link

marklagendijk commented Nov 15, 2018

This limitation is a blocker for us as well.

I thought I might be able to overcome this limitation by being a bit creative. I tried the following solutions without success:

Workaround attempt 1 - Use a custom proxy to remove security headers
For starters I set chromeWebSecurity to false.
This didn't help me because the external application I wanted to use sent back an x-frame-options header. I found out that Cypress does remove these for the Application Under Test (AUT), but not for external applications.

To solve this I created a proxy which would remove these headers, and passed this proxy to Cypress using environment variables:

const fs = require('fs');
const hoxy = require('hoxy');

const hostname = 'localhost';
const port = process.argv[2];

createProxy(hostname, port);
console.log(`Started proxy on ${hostname}:${port}`);

function createProxy(hostname, port) {
  const proxy = hoxy
    .createServer({
      certAuthority: {
        key: fs.readFileSync(`${__dirname}/ca/selfsigned-ca.key.pem`),
        cert: fs.readFileSync(`${__dirname}/ca/selfsigned-ca.crt.pem`)
      }
    })
    .listen(port, hostname);

  proxy.intercept({ phase: 'response' }, removeSecurityHeaders);
}

function removeSecurityHeaders(request, response) {
  console.log(request.fullUrl());
  delete response.headers['x-frame-options'];
}

Passing it to Cypress: HTTPS_PROXY=http://localhost:8080 HTTP_PROXY=http://localhost:8080 https_proxy=http://localhost:8080 http_proxy=http://localhost:8080 cypress open.

Requests where passing through my proxy, but it still didn't work. After a while I found out that only the requests for the AUT where passing though the proxy.
Later I also found out that Cypress uses a proxy itself, so combining this with a custom proxy probably wouldn't work well.

Workaround attempt 2 - Load Chrome extension to remove security headers
My second attempt was to load a Chrome extension which would remove those nasty headers.
I added chrome-ext-downloader to my package.json so it would download the extension.

{
  "scripts": {
    "download-extension": "ced gleekbfjekiniecknbkamfmkohkpodhe extensions/ignore-x-frame-headers"
  },
  "dependencies": {
    "chrome-ext-downloader": "^1.0.4",
  }
}

And loaded the extension via plugins/index.js

const path = require('path');

module.exports = (on, config) => {
  on('before:browser:launch', (browser = {}, args) => {
    console.log(config, browser, args);
    if (browser.name === 'chrome') {
      const ignoreXFrameHeadersExtension = path.join(__dirname, '../extensions/ignore-x-frame-headers');
      args.push(args.push(`--load-extension=${ignoreXFrameHeadersExtension}`));
    }
    return args;
  });
};

With this the external page did load. However, Cypress didn't work on that page. Apparently Cypress uses the proxy to inject itself into the page.

Conclusion
With the current version of Cypress it seems to be impossible to get it to work with multiple super domains. Creativity doesn't seem to help.
To solve this, it should be solved in Cypress itself.

Now let's discuss this.
I would say there are definitely e2e test use cases that require this.
Granted, in some cases one can use cy.request to achieve the same result as actually interacting with the extra domain.
When you are testing a SPA, you can either go with the cy.request solution, or just mock the whole backend.

Things are different when you want to test the integration between different applications. If testing such integrations is the main focus of your tests, you need support for multiple super domains. Often such integrations include more complicated flows such as: application1 => third party application1 => application2 => third party application 2 => application1.

Now one can argue that Cypress just isn't meant for use cases like this. Especially if there is a technical limitation which is nearly impossible to overcome.

What I am currently missing in this discussion is an explanation on what this technical limitation is. Why does Cypress currently support only one super domain? What would be needed to support multiple? Would implementing that make Cypress a lot more complex? Or would it be, just a lot of work?

Related:

@jennifer-shehane jennifer-shehane changed the title max 1 site... max 1 site... add ability to visit multiple superdomains in one test Nov 28, 2018
@jennifer-shehane jennifer-shehane changed the title max 1 site... add ability to visit multiple superdomains in one test max 1 site... support visiting multiple superdomains in one test Nov 28, 2018
@suchipi
Copy link

suchipi commented Dec 5, 2018

Here's a hacky workaround:

Cypress.Commands.add('forceVisit', url => {
  cy.get('body').then(body$ => {
    const appWindow = body$[0].ownerDocument.defaultView;
    const appIframe = appWindow.parent.document.querySelector('iframe');

    // We return a promise here because we don't want to
    // continue from this command until the new page is
    // loaded.
    return new Promise(resolve => {
      appIframe.onload = () => resolve();
      appWindow.location = url;
    });
  });
});

@oliver3
Copy link

oliver3 commented Dec 5, 2018

Hi @suchipi this looked like a promising workaround! But unfortunately the x-frame-options issue still remains for us...

Refused to display 'https://*****' in a frame because it set 'X-Frame-Options' to 'deny'.

@incoming-th
Copy link

Tested this with hope it will work, it is already an improvement sa it seems to load the page in the promise but then:

Refused to display 'https://****'' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

Will watch this post for updates.

@dirtyhenry
Copy link

dirtyhenry commented Jan 4, 2019

Let me present the use-case I need to visit 2 domains for, to bring in my 2 cents on this issue.

  • I have a back-office app (say localhost:8000), where people from our staff need to validate identity of users;
  • I have a user-facing app (say localhost:8001), where our users can digitally sign a contract once they were authentified.

This is the TL;DR version of our onboarding workflow. It is very collaborative between our users and our back-office, and it doesn't make sense to test one without the other.

Programming this via cy.request would cost much more in terms of maintenance than what we're doing now ie creating multiple specs called something-a, something-b, that are supposed to run one after another. Every step requiring to switch what app is being used needs a new subspec.

Maybe things running on localhost with different port numbers could be considered the same domain to make most developers from this thread happy? (ie we get out of the "3rd party" argument)

For the record, we tried to use subdomains to address this. It worked fine on developers' environment but it turned out to be very difficult to build in a CI pipeline, in terms of complexity and pipeline time.

@Joelasaur
Copy link

I have this in my cypress.json

{
  "baseUrl": "https://my-website.com",
  "chromeWebSecurity": false
}

but I'm still getting this error:

CypressError: Cypress detected a cross origin error happened on page load:

Blocked a frame with origin "https://my-website.com" from accessing a cross-origin frame.

Before the page load, you were bound to the origin policy:

https://my-other-website.com

A cross origin error happens when your application navigates to a new superdomain which does not match the origin policy above.

This typically happens in one of three ways:

  1. You clicked an that routed you outside of your application
  2. You submitted a form and your server redirected you outside of your application
  3. You used a javascript redirect to a page outside of your application

Cypress does not allow you to change superdomains within a single test.

You may need to restructure some of your test code to avoid this problem.

Alternatively you can also disable Chrome Web Security which will turn off this restriction by setting { chromeWebSecurity: false } in your 'cypress.json' file.

https://on.cypress.io/cross-origin-violation

Any ideas as to why explicitly disabling chromeWebSecurity doesn't work?

@dayvidwhy
Copy link

Is there a reason cypress suggests changing this config property but it doesn't work?

My current workaround is to have separate tests visit the different domains. It works because during dev I have a dummy server keeping up with requests made from the different origins.

describe("Do stuff on one site then visit another and check it worked", () => {
    it("Can open the first site and do some things", () => {
        cy.visit("localhost:8080");
        // do stuff that sends data to a dev server running on another port on localhost
    });

    it("Can see the results in the other place", () => {
        cy.visit("localhost:8888");
        // validate my things went good
    });
});

Test names and descriptions are vague on purpose.

It's not best practices since my tests have to be run sequentially and depend on the previous, but it's helped me test my workflow better.

@MuckT
Copy link

MuckT commented Feb 9, 2022

@jdborneman-terminus have you tried this workaround? It's pretty buried in this thread. You could do something similar to rehydrate your session storage if needed.

@jdborneman-terminus
Copy link

jdborneman-terminus commented Feb 9, 2022

this workaround

I'm inheriting some Cypress code that I'm trying to get up to snuff and implemented well and see bits and pieces (like save/restoreLocalStorage) already in there, but not in the exact same way. I'll definitely check that out once my meetings for the day are done! Thanks!

@edudelta
Copy link

edudelta commented Mar 1, 2022

This is an old issue that was blocker to me many times and I had two main situations to act:

  1. Confirm sandbox Paypal payment.
  2. Perform login in another domain.

The solution I used to work is open a new tab and perform javascript calls directly to this new tab and then close it, keeping cookies that came from there.
It needs chromeWebSecurity disabled and it works with Chrome and IE. Firefox throws and error, though (SecurityError: Permission denied to access property "document" on cross-origin object) that may be fixed with Firefox extension installed to allow CORS but Cypress is unable to install an extension...

Feel free to improve this solution.

support/index.js

Cypress.Commands.add('newTabAction', url => {
  cy.wrap(null).then({ timeout: 40000 }, async () => { // timeout enough to perform all steps in the worst (slowest) case
    const result = await new Cypress.Promise((resolve, reject) => {
      performNewTabAction(resolve, reject, url);
    });
    expect(result).to.not.eql('error');
  });
});

var performNewTabAction = function (resolve, reject, url) {
  const newWindow = window.open(url, '_blank');

  waitForEl(newWindow, '//input[@data-id="anExample"]', function(element) {
    element.click(); // just an example
    newWindow.close();
  }, function() {
    console.error("unable to find element");
    resolve('error');
  }, 30); // retries
}

var waitForEl = function (win, selectorDispatch, callback, errorCallback, maxTimes) { //implements wait for and call callback or errorCallback};

@kajaleprashant24
Copy link

any timeline on when this will be available. This is a turning to be a major blocker for e2e tests for us where user workflow navigated between different domains. E2e tests cannot be isolated in to seperate tests it restricts us from adding retry and using previous context previous steps

@rishoogofore
Copy link

I think this merged PR should provide us the feature of multidomain

@kaukul
Copy link

kaukul commented Mar 15, 2022

I think this merged PR should provide us the feature of multidomain

Not working for me. Not sure if its in prod yet.
I'm getting this error -
cy.switchToDomain is not a function

@d-koppenhagen
Copy link

It's not released yet. I wonder if it will come with the next minor or major version.

@AlexDaniel
Copy link

Either way we won't be able to use it because we can't upgrade cypress before #17759 is fixed. 😭

@thedotedge
Copy link

Looks like #20467 is merged, but not released yet https://github.com/cypress-io/cypress/releases?

@rsov
Copy link

rsov commented Apr 6, 2022

Is it going to be included in the next release? 🙏

@mjhenkes
Copy link
Member

mjhenkes commented Apr 7, 2022

Hey guys, cypress dev here. 👋
First of all thank you all for your patience on this issue, it's been a long time coming. We're diligently working away to add this feature on the feature-multidomain branch. You can follow along on our pull request #18075 as the feature gets pulled into the develop branch.

This feature will be released at first as an experimental feature so we can gather feedback from you all before it becomes generally available in a future major release.

As for timelines, take this with a grain of salt, but we're expecting to release the experimental version within the next couple of months.

@tl-madhulika-mitra
Copy link

Keenly waiting for this fix for redirection testing in Firefox as we cannot turn off web security on it.

@AdrienDong
Copy link

AdrienDong commented Apr 14, 2022

Hey guys, cypress dev here. 👋 First of all thank you all for your patience on this issue, it's been a long time coming. We're diligently working away to add this feature on the feature-multidomain branch. You can follow along on our pull request #18075 as the feature gets pulled into the develop branch.

This feature will be released at first as an experimental feature so we can gather feedback from you all before it becomes generally available in a future major release.

As for timelines, take this with a grain of salt, but we're expecting to release the experimental version within the next couple of months.

Hello @mjhenkes, thanks a lot for this great coming feature.
My usage context: email validation on an external domain, during a subscription. The flow of my test is: I subscribe a new account -> I receive a validation link by email -> I visit the link(cross-domain) sent by email -> redirection to original domain.
I am testing your feature branch feature-multidomain the cy.origin works as expected, indeed I can visit an external domain, however my test stuck after visiting the external domain, because there is a redirecting to the original domain (with pageLoadTimeout error).

 cy.origin(validateEmailLink, { args: { validateEmailLink } }, ({ validateEmailLink }) => {
    cy.visit(validateEmailLink); // visit cross domain and redirection back to original domain OK but test stuck here with page load timeout error (the page on the original domain is loaded)
    cy.get('[data-cy="emailValidated"]').should('be.visible'); 
 });

Is there a solution/workaround to go through this error?

@mjhenkes
Copy link
Member

mjhenkes commented Apr 20, 2022

@AdrienDong, thanks for bringing up this use case! It actually helped us find a bug that we're working on fixing in #21144.

Depending on how your redirect is setup, there are two ways to accomplish what you're attempting (once we finish that PR).

  1. Server side redirects. The server redirects the origin B request to origin A
// Assume baseUrl is origin A
it('follows the server side redirect', () => {
// This visits origin B but is redirected to origin A in the server so an origin B site 
// never loads in the  browser. No cy.origin needed! 
  cy.visit('originB/email/link');

  // This validation takes place on an origin A site.
  cy.get('[data-cy="emailValidated"]').should('be.visible');
})
  1. Client side redirect. Once the origin B page loads in the web browser, javascript changes location to origin A
// Assume baseUrl is origin A
it('follows the server side redirect', () => {
  cy.origin('originB', () => {
    // This visits an origin B page that uses javascript to redirect to origin A  
    cy.visit('originB/email/link');  
  })
  // This validation takes place on an origin A site.
  cy.get('[data-cy="emailValidated"]').should('be.visible');
})

Thanks for tying out the new feature!

@cypress-bot cypress-bot bot added stage: pending release and removed stage: proposal 💡 No work has been done of this issue labels Apr 25, 2022
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Apr 25, 2022

The code for this is done in cypress-io/cypress#18075, but has yet to be released.
We'll update this issue and reference the changelog when it's released.

@cypress-bot
Copy link
Contributor

cypress-bot bot commented Apr 25, 2022

Released in 9.6.0.

This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v9.6.0, please open a new issue.

@cypress-bot cypress-bot bot locked as resolved and limited conversation to collaborators Apr 25, 2022
@mjhenkes
Copy link
Member

Hey All,
We have released the cy.origin(api) command as an experimental feature in version 9.6.0.

The cy.origin command will allow you to run commands and visit in origins other than the one you started your test in. Give it a try and post any feedback in the discussion.

There is also have a blog post(with video!) that you can read through.

@jordanpowell88
Copy link
Collaborator

It feels good to finally close this issue! Great job to everyone who helped close this Issue

@AtofStryker
Copy link
Contributor

Here's a perfect use case for needing access to multiple "superdomains", for which using cy.request() simply won't work:

Our web site, https://shop.nordstrom.com has a search form in the header. Certain search terms result in a 302 redirect to our subsidiary companies' web sites. Searching for

image

We are liking Cypress (a lot!), but it does seem odd that there is seemingly no workaround for this problem.

Further... The fact that there's no useful error given seems odd. All I get instead of the expected redirect, I wind up at chrome-error://chromewebdata/ with no explanation whatsoever.

image

@nbcarey I am taking a look into nordstrom with cy.origin(). I have given a simple test a try, such as:

describe("nordstrom", () => {
  it("goes to nordstrom rack", () => {
    cy.visit("https://www.nordstrom.com/");
    cy.visit("https://www.nordstromrack.com/");
    cy.origin("https://www.nordstromrack.com", () => {
      // Seem to be running into cookie issues where page reloads? shopping cart is returning a 401 and reloading the page
      // which blows away state. Requires further investigation
      // automation tests repeated eventually get flagged for bot behavior.
      // Eventually the page does become stable
      cy.get("#keyword-search-input").clear().type("nordstrom rack shoes");
    });
  });
});

But seem to be running into possible request/cookie issues with the shopping cart. Have you given cy.origin() a try and experienced the same? This doesn't seem to happen with a first cy.visit to nordstromrack, which makes me think its a cookie issue.

@AtofStryker
Copy link
Contributor

AtofStryker commented Nov 21, 2022

You could also do an example that is less contrived. Same issues are present. I created #24756 to track this issue separately from the closed meta issue

describe("nordstrom", () => {
  it("goes to nordstrom rack", () => {
    cy.visit("https://www.nordstrom.com/")
      // give some time for items on the page to initialize in the SPA
      .wait(4000);
    cy.get("#keyword-search-input").click().type("nordstrom rack{enter}");
    cy.origin("https://www.nordstromrack.com", () => {
      // Seem to be running into cookie issues where page reloads? Requires further investigation
      // automation tests repeated eventually get flagged for bot behavior
      cy.get("#keyword-search-input").clear().type("nordstrom rack shoes");
    });
  });
});

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
topic: cross-origin ⤭ Topics related to cross-origin navigation in tests type: feature New feature that does not currently exist
Projects
None yet
Development

Successfully merging a pull request may close this issue.