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

Open
jukefr opened this Issue Nov 21, 2017 · 16 comments

Comments

@jukefr

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.

EDIT: sorry for the arrogance of this post ^^ was probably in a bad mood when I posted it... I edited it to be less aggressive

@brian-mann

This comment has been minimized.

Member

brian-mann commented Nov 21, 2017

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.

@ejoubaud

This comment has been minimized.

ejoubaud commented Apr 11, 2018

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

This comment has been minimized.

MaxwellGBrown commented May 8, 2018

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

This comment has been minimized.

alovato88 commented Jun 14, 2018

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

This comment has been minimized.

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

This comment has been minimized.

alovato88 commented Jun 22, 2018

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

@brian-mann

This comment has been minimized.

Member

brian-mann commented Jun 23, 2018

@dchambers

This comment has been minimized.

dchambers commented Jun 28, 2018

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

This comment has been minimized.

e-e-e commented Jul 16, 2018

We also require this functionality to test a third party integration

@tobocop

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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.

@Zerqd

This comment has been minimized.

Zerqd commented Oct 21, 2018

w8ing this too.

@marklagendijk

This comment has been minimized.

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 from max 1 site... to max 1 site... add ability to visit multiple superdomains in one test Nov 28, 2018

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

@suchipi

This comment has been minimized.

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

This comment has been minimized.

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'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment