Closed jukefr closed 2 years ago
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.
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.
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.
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.
@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.
@brian-mann Where can I find any of these showcased recipes?
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.
We also require this functionality to test a third party integration
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:
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 haveThis 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.
w8ing this too.
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:
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;
});
});
});
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'.
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.
Let me present the use-case I need to visit 2 domains for, to bring in my 2 cents on this issue.
localhost:8000
), where people from our staff need to validate identity of users;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.
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:
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:
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?
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.
Is there any plans to fix this issue soon, as the way our login process works in our UI it's impossible to test e2e properly using Cypress without this fix. It seems a real shame as otherwise all our devs and testers really like it.
Hello!
I hope you are having a beautiful day!
As a paying customers, we hope that it will be fixed soon. We really need this.
I too am an advocate for this feature. In the mean time how do I use cy.request for dynamic urls? Our app creates workflows i.e. /workflow/3479 <-- This number is dynamic. Upon form submission, it adds /task/968 <-- This stays the same as long as the same workflow type is selected and created from. The end url we have is something like /workflow/3467/task/968. We have limited testing server dedication, so it takes 3 minutes for the form to be submitted, and it sucks being hit with a CORS error. I disabled web security and the app just crashes with no message for the console. It just says "chrome-error://chromewebdata/" in the runner. I think this is due to our internal SSO, and I will try to implement what was mentinoned in the sso recipe. The problem for me is I'm not as technical as I would like to be, and it will take me a long time just to implement this, if I can at all. That said, for me personally or perhaps people who aren't that technical as well, it would be cool to just move on to the next domain and continue with my test. Currently, I've spent more hours than I care to trying to get around this. Also I would like to see a real e2e test. I really like cypress more than selenium. We are currently using TestCafe, which at first glance seems to be more complicated than testing should be. I'm trying real hard to push our organization to go full on cypress because by far it is the coolest tool I've seen, but having something like this implemented would definitely give the extra push to adopt Cypress.
Edit: Also we have an additional refresh token that updates itself every 2 minutes, so I'm not sure if even setting the token in the redirect as shown in the recipe will work. 🙏
cypress 3.2.0 Windows 10 Chrome 73
I felt the same way until I actually spent a dozen hours trying to figure out our Auth0 architecture. Trust me: put in the time now to figure this out and you will profit in the future.
On Wed, Apr 10, 2019, 4:06 PM nchaudry notifications@github.com wrote:
I too am an advocate for this feature. In the mean time how do I use cy.request for dynamic urls? Our app creates workflows i.e. /workflow/3479 <-- This number is dynamic. Upon form submission, it adds /task/968 <-- This stays the same as long as the same workflow type is selected and created from. The end url we have is something like /workflow/3467/task/968. We have limited testing server dedication, so it takes 3minutes for the form to be submitted, and it sucks being hit with a CORS error. I disabled web security and the app just crashes with no message for the console. It just says "chrome-error://chromewebdata/" in the runner. I think this is due to our internal SSO, and I will try to implement what was mentinoed in the sso recipe https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/logging-in__single-sign-on. The problem for me is I'm not as technical as I would like to be, and it will take me a long time just to implement this, if I can at all. That said, for me personally or perhaps people who aren't that technical as well, it would be cool to just move on to the next domain and continue with my test. Currently, I've spent more hours than I care to trying to get around this. Also I would like to see a real e2e test.
cypress 3.2.0 Windows 10 Chrome 73
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/cypress-io/cypress/issues/944#issuecomment-481865482, or mute the thread https://github.com/notifications/unsubscribe-auth/AIeL6o2Hbtgw2xexUAYmKeHiAhiAszLmks5vflJigaJpZM4Ql_rR .
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; }); }); });
How do you get the xhr request appear. What I wish to do is the following
cy.wait("@aliasVisitWebPage").then(
xhr => {
const token = xhr.responseBody;
cy.log(token);
}
);
I also have a good use case for that:
We want to make sure that if a user visits a different page and the user spends more than x
seconds to come back to our web site, so when the user comes back, we should log out that user for security reasons.
cy.login().then(() => {
cy.visit('main-page');
cy.hash().should('eq', '#/main-page');
});
cy.log('Visit google website');
cy.visit('https://www.google.com');
cy.wait(15000);
cy.go('back');
cy.log('User should be logged out');
cy.hash().should('not.eq', '#/main-page');
I'm new to Cypress and evaluating it for possible use. I'm also new to Javascript. So, I need to login to a site which has internal redirect to SSO URL. I had a security but issue but now have "chromeWebSecurity": false i cypress.json. I can pas the security but during run after login see a blank page. So, my test fails badly. Please, suggest how to overcome that?
Thanks
Jeff
do we have any update about a solution for it? Currently, this issue is also blocking me to use cypress for some tests that I would like to do... because I have a form that I submit and takes me to a different domain, so I can't simply navigate to this different url...
and using chromeWebSecurity:false causes other issues in my applications, so it is not an option...
I also had issues with multiple domains that I could not work around (dependency on a 3rd party at this time). It's required (and desired) for our end to end tests. I spent some time getting this to work by examining the browser interactions via devtools then replicating with successive cy.request
calls. It's not pretty but sharing in case it helps someone else. Cypress is a great tool and I appreciate the active community around it
cy.get('a')
.contains('rabbit hole')
.get('a')
.invoke('attr', 'href')
.then((href) => {
cy.request(href)
.then ((resp) => {
// need to work with the last redirect
const postLoginUrl = resp.redirects.pop().split(': ')[1];
// scrape idsrv.xsrf for form post
const idsrv = resp.body.match(/idsrv\.xsrf\"\;\,\"\;value\"\;\:\"\;(.*?)\"\;/)[1];
const postLoginUrlOptions = {
method: 'POST',
url: postLoginUrl,
form: true,
body: {
"username": username,
"password": password,
"idsrv.xsrf": idsrv
}
}
cy.request(postLoginUrlOptions)
.then ((postResp) => {
const postShareUri = postResp.body.match(/consentUrl\"\;\:\"\;(.*?)\"\;\,\"\;/)[1];
const scopes = postShareUri.match(/scope\=(.*?)\%20(.*?)\%20/);
const postShareUrlHostMatch = postResp.redirects.pop().match(/\d+\: (.*?)\:\/\/(.*?)\//);
const postShareUrl = `${postShareUrlHostMatch[1]}://${postShareUrlHostMatch[2]}${postShareUri}`.replace(/\&/g,'&');
const postShareUrlOptions = {
method: 'POST',
url: postShareUrl,
form: true,
body: {
"idsrv.xsrf": idsrv,
"scopes": scopes[1],
"scopes": scopes[2],
"RememberConsent": "false",
"button": "yes"
}
}
// cheesy check that things are on track
cy.request(postShareUrlOptions)
.then ((postShareResp) => {
expect(postShareResp.redirects.length).to.equal(3);
});
const logoutUrl = `${postShareUrlHostMatch[1]}://${postShareUrlHostMatch[2]}/logout`;
const logoutOptions = {
method: 'POST',
url: logoutUrl,
form: true,
body: {
"idsrv.xsrf": idsrv,
}
}
cy.request(logoutOptions)
.then ((logoutResp) => {
expect(logoutResp.body).to.have.string("You are now logged out");
});
});
})
});
I think that Cypress simply doesn't know how to fix the issue or doesn't care about the above issue with multiple domain redirects
I really love this tool. unfortunately, this is a huge blocker when doing SSO authentication which requires the user to navigate to a different domain and come back. chromeWebSecurity doesn't help, so not sure how to solve this situation... though, this seems feasible with other tools for E2E automation
Get the token from your SSO provider using cy.request(), you should never have to leave your domain for this.
On Wed, Jun 19, 2019 at 3:16 PM Francesco Paolo Vitullo < notifications@github.com> wrote:
I really love this tool. unfortunately, this is a huge blocker when doing SSO authentication which requires the user to navigate to a different domain and come back. chromeWebSecurity doesn't help, so not sure how to solve this situation... though, this seems feasible with other tools for E2E automation
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/cypress-io/cypress/issues/944?email_source=notifications&email_token=ACDYX2XNMCKDQBZXETKQK4TP3KHZPA5CNFSM4EEX7LI2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYDE67Y#issuecomment-503730047, or mute the thread https://github.com/notifications/unsubscribe-auth/ACDYX2UETL3Z2ONJQATLW6DP3KHZPANCNFSM4EEX7LIQ .
My app uses OAuth for a lot of stuff and this is a huge blocker.
This is causing me a lot of grief right now. Programmatically authenticating can get quite complicated. Not everyone has ideal setups.
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
rack
redirects to https://www.nordstromrack.comtrunk club
redirects to https://www.trunkclub.comWe 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.
Hello Cypress Team- Will there be a fix anytime soon on this? . It is long pending from November 2017!!!
Hey! The docs say that disabling web security will allow navigating from a http site to a https site, but this results in chrome-error://chromewebdata/
in practice. It would be very useful for this functionality to actually be in place
I have a similar problem to everyone above, but maybe a different wrinkle.
We have an authentication app that's shared by several apps in our enterprise, and it's on different subdomains in the same superdomain (i.e., login at https://login.mycompany.com
, then redirects back to my app at https://app.mycompany.com
).
However, when I'm running my tests against locally hosted versions of those apps (i.e. login at http://localhost:5555
, app at http://localhost:7777
), I get the "You may only visit a single unique domain per test" error because of the different localhost ports. BTW, this only happens when I run in Electron (headless or in the runner). It's fine when I run Chrome, as i have chromeWebSecurity
turned off.
I know it's better to use request to login, and I do that for all of my other tests -- this case is my one smoke test that makes sure the login integration works correctly.
@jrnail23
this case is my one smoke test that makes sure the login integration works correctly.
Wouldn't whatever you're doing through cy.request in your other tests already cover this integration? If your login service isn't working, then it will fail those tests.
@Joelasaur that's kinda beside the point. The other tests don't cover the redirect from the login service. I can imagine this must also come up if you're testing a paypal payment flow, using one of their testing services.
Hello Cypress Team- Can we at least know if this issue will be fixed at all? This will help us in making a decision on wether to use Cypress or not.
Really would love to get an update about whether or not this is going to be fixed. This is basically the only roadblock to my team switching from protractor to cypress.
I am in the same situation as @Raiune stated above. We federate our identity (e.g. login) our to Azure Active Directory, upon successful authentication, it redirects back our app, with the necessary tokens. After this point, cypress will work very (incredibly) well, but the federated authentication, which requires multiple superdomain support is a show stopper for us.
In the same boat, we use Firebase for verification and we can't navigate to the Firebase link. Can't just use cy.request()
either, since the verification page needs to be loaded and some code on the page executed. All the workarounds I've found, like "chromeWebSecurity": false
don't work either.
Surprised this issue is outstanding for so long, such a small thing but such a big impact. We're wanting to move over from Puppeteer, but we can't while this is blocking us. Can't even write a simple signup test.
I agree with the crowd here. Provided workaround is not a real solution. End to end tests should not have any kind of hacking in between. I want my test to behave exactly as it would be human.
Human would not stub requests, copy tokens and change my API to force being logged in using third-party auth.
For everyone who find this issue blocking further progress in writing tests: jest-puppeteer seems to be a viable replacement for cypress with similar easy-to-use ratio.
+1000
With now many applications running with auth0 and many others, this actually becomes a very big blocker. Indeed I understand why it does not work and how the inner workings of cypress does not allow this, yet is still there and a blocker.
Perhaps a chrome plug-in? Or a hack in chromium?
To add to the chorus, we have invested time and resource in Cypress before discovering this blocker. Testing across super-domains isn't unusual in modern application testing - a concept Cypress claims to excel in.
Really looking forward to an update on this issue, for us it would be a great shame to have to switch everything back to Webdriver.
Also a blocker for my company. We're considering moving from TestCafe to Cypress, but this issue is impacting us.
@rodrigotolledo due to this issue, we are thinking of going the opposite - from Cypress to TestCafe. What are your reasons for not using TestCafe?
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.