Pull Request (in progress): https://github.com/cypress-io/cypress/pull/4176
window.fetch
XMLHttpRequest
object which limits us to only stubbing XHR's from the main application frame.cy.server
and instead just use cy.route
blacklistDomains
option for cypress.json
onRequest
and onResponse
currently provide the raw level xhr
object. This would no longer be possible. We'd have to serialize the request / response into basic objects. No method calling would be available.onError
callback could no longer existonerror
events for XHR's and will automatically fail the test if this is called. This would no longer be possible.https
cannot be dynamic. It would have to be set in cypress.json
or environment variables. The reason is that for each blacklisted domain that goes over https, we would have to route that traffic to a self signed certificate. Browser freak out if traffic to https gets assigned one certificate, and then within the same session it gets another cert applied. What this means is that before spawning the browser we have to isolate that traffic and know ahead of time not to route it to the actual server. This enables us to send a single cert to the browser.cy.route
but I'm not sure there's much of a use case here of only dynamically blacklisting non https traffic.XHR
and any other kind of http
request. We'd likely have to change the page events to say REQ
and REQ STUB
instead of XHR
and XHR STUB
.onResponse
callback functions. It's possible for the internal server to know when an HTTP request has been completed, but it's not possible to know whether the browser has actually finished processing it. This may or may not be a problem.cy.stub
, or perhaps a 3rd party extensionRelated to:
@brian-mann I would be very willing to help out with this issue. This is a big blocker for use when testing our app.
@ericadamski could you go into more detail about this? What exactly is a big blocker?
@brian-mann making network requests (using a fetch polyfill) and asserting on those requests is important to our integration testing. Currently we are just spying on the window.fetch
and doing some simple assertions on the params request. It would be ideal to provide the same functionality as XHR
s to completely omit the server and mock responses when needed, understanding that we could stub the fetch
as well, but this means stubbing each individual request with a tailored response.
You can do this right now using stubs. Albeit it's certainly not as nice as what we did for XHR stubbing, but it is possible and we have several examples of making this work. This is even in a new getting started video we're releasing later this week.
Yes we realize it can be done, albeit we would rather wait (or work toward) a more XHR style stubbing, it is just super nice.
Yeah it is. Okay just making sure we're on the same page.
The proposal here does a decent job outlining what needs to be done but it will be a significant amount of work.
Likely the next steps just need to be formalizing a plan for the API's themselves. There have been many requests for additional features and we should start there.
That sounds good.
Is it worth creating separate issues for some of the requested features for I know almost nothing about graphQL queries 馃槣
They mostly already are other issues. We need to factor in all of them and come up with a set of API's that unifies those different use cases and desired experiences.
Right OK, I see what you are saying.
@brian-mann side note, has it been thought of moving away from coffeescript and into es6?
Do you consider this feature in the next release? I reckon it would be highly valuable, for instance when
Yeah blacklisting specific requests is part of this feature roadmap
Nock https://github.com/node-nock/nock mocking syntax is really sweet, some examples
nock('http://www.example.com')
.get('/resource')
.reply(200, 'domain matched');
nock('http://myapp.iriscouch.com')
.post('/users', {
username: 'pgte',
email: 'pedro.teixeira@gmail.com'
})
.reply(201, {
ok: true,
id: '123ABC',
rev: '946B7D1C'
})
A lot more at https://github.com/node-nock/nock#specifying-hostname
Each part (like method, is an function .get, .post, etc
can take either exact match or a callback predicate function.
nock.get('http://myhost.com')
nock.get(url => { ... }) // return true to match
nock.query({foo: 'bar'})
nock.query(query => { ... }) // return true to match
Just wanted to mention that @bahmutov has started work on this. 馃帀
I do like nock's fluent API, but one problem is that it would end up adding a lottttt more Cypress methods. I also feel some of their method signatures are odd, or not what I expect, or I constantly have to look them up to remember - what is it that I use again? The surface area is really large. But definitely good stuff to pull from!
It might be an easier sell to mimic what request
does -> since we built cy.request
around the same API style....
Some generic pairing notes not intended to be canon
Client (Non Proxied Request) Browser
- GET https://www.github.com
- DNS resolution (local computer)
- DNS resolve (24.4512.23.23)
- Server gets
- SSL Handshaking
- Receives bytes
- GET http://localhost:98765/
Request Headers
---------------
GET / HTTP/1.1
Host: localhost:98765
--------------------------------
Client (Proxied Request) Browser
- CONNECT github.com:443
- CONNECT localhost:8080
GET http://localhost:8080/foo/bar
Host: localhost
GET https://www.github.com/ HTTP/1.1
Host: github.com
-------------------------------
Cypress creates a http server: localhost:98765
Cypress creates a file server server: localhost: 98764
Cypress creates an SNI proxy server: localhost: 98763
--proxy-server=http://localhost:98765
cy
.route('GET', 'http://localhost:8081', {
onRequest: req =>
onResponse: res =>
}).as('getFoo')
...
...
...
.wait('@getFoo.request')
injecting hosts
1. captureTrafficHosts: "*" by default we do it on '*'
1. captureTrafficHosts: ['foo', 'bar'] by default we do it on '*'
1. know ahead of time what hosts to inject (generate a cert / have access to traffic)
2. when any request come in, capture a HAR
3. expose an API to the user to enable them to control traffic
4. when we encounter user rules, the internal traffic rules instance has to know about them
5. when network request match traffic rules, we have to apply those rules to the network requests
- stubbing the responses
- waiting on the responses (pass through)
- modifying the requests
- delaying the responses
6. if there are callbacks (onRequest / onResponse) we have to decode / buffer the responses (potentially) send data to the driver (websockets) after serializing into objects
- wait for the user to potentially respond (if we support promises)
- socket.pause() the responses so the browser hasn't completed the http request
7. complete the http request
8. notify the driver of a completed request
Found the fetch-mock which has very similar api to cy.route()
so it makes stubbing/mocking of window.fetch
much easier and readable.
@FredyC do you use fetch-mock
in your cypress tests?
@ifeanyi I do, but my case is really simplified to a login scenario only. Rest of a communication goes through the GraphQL which is much more simple to mock.
I did it basically like this.
// it would be nice to have some global onBeforeLoad
onBeforeLoad: win => {
cy.stub(win, 'fetch', fetchMock.sandbox()).as('fetch')
},
// then in any test I can use API of fetch-mock like that
cy.get('@fetch').get('*', { token: 'AAA', userId: 'BBB' })
afterEach(() => {
cy.get('@fetch'}.restore()
})
@FredyC I am doing something similar, I created a fetchMock module so ensure that I re-use the same sandbox. I was able to mock requests in my command file but I am having trouble mocking requests in individual tests. I'm gonna keeping hacking at it till I find a reliable pattern.
// fetchMock.js
module.exports = require('fetch-mock').sandbox();
It's your call, but "re-use the same sandbox" does not make any sense :) With that approach, you can easily stumble into a situation when one test is influenced by other because you forgot to clean up the sandbox. So yea, I think that onBeforeLoad
is still the best spot.
Besides, I think it's the main reason you are struggling with it. You need to stub window.fetch
before a code of the tested site runs.
Besides the missing support for fetch
, it would be nice to support xhr/fetch stubbing requests made in web workers. Unless I'm missing something it doesn't seem straightforward to wrap the workers XmlHttpRequest
implementation?
If you use Apollo and would like to be able to stub calls to your GraphQL layer in Cypress, here is how you can do it until this problem gets resolved:
+import unfetch from 'unfetch'
createHttpLink({
uri: '/graphql',
credentials: 'same-origin',
+ fetch: unfetch
})
And in the support file:
// Cypress doesn鈥檛 recognize `window.fetch` calls as XHR requests, which makes
// it impossible to stub them. We delete `fetch` from the window object so the
// `unfetch` polyfill (which uses proper `XMLHttpRequest`) kicks in.
Cypress.on('window:before:load', win => {
delete win.fetch
})
@HugoGiraudel You don't even need to use unfetch
in your HttpLink. As long as you make win.fetch
unavailable in window:before:load
, Apollo will revert to using XHR. I think Apollo uses isomorphic-fetch
, which is built on top of Github's fetch
polyfill.
In other words, you don't need to do the first part of your answer. You just need the delete win.fetch
part.
Hi. Apologies if this has been answered or covered elsewhere, but after extensive searching, I'm struggling to nail down the answer I'm looking for, including within this issue.
In short, we would like to mock a websocket that we use with our Angular 6 UI, using Cypress (which is great, btw!).
I have seen in a couple of places that using cy.stub is the way to go. But are there any examples of doing this for a websocket? Specifically for an Angular 2+ UI? The Cypress examples cover stubbing AngularJS code, but not Angular 2+ (as far as I can tell).
Or is this issue coming up with another solution?
Any pointers to get me going in the right direction will be really appreciated.
I realize this is still in progress, but I wanted to mention the use case of simulating a network delay for specific requests; I have a bug that only manifests when a specific request resolves before another specific request. I would like to be able to simulate that in a test for a regression test; not to serve canned responses but to use real responses, just delay one of them so that it "comes back" after another.
Thanks for the work on this, I'm excited for this feature!
@bahmutov is starting back on this work this week.
full-network-stubbing-687
is in https://github.com/cypress-io/full-network-proxyHi,
Sorry for possibly duplicated question, but with this feature will it be possible to work (wait, check response body) with Websocket events? thank you
Do you have an ETA on this?
Hey guys,
is there any information, when this feature is going to be ready?
I have an issue when I use cy.server
and cy.request
as first operation in before()
or it()
blocks. There are sometimes test runs, where the request seems to be not to trigger. It runs into the requestTimeout but I can see the request in the developer tools. When I reload the spec, than it mostly runs without any problemens. But this is impossible for the CI server.
For me, it looks like the listeners listentens to late on my requests. Is this gona fixed with this issue too?
I see that this package has not been mentioned here. It has solved a lot of my team's problems: https://github.com/ijpiantanida/talkback
@Vages do I understand correctly, that talkback is replacement for my "original" / production server? Or is is possible to forward requests from cypress over talkback to my native apache?
Thanks for your reply
Talkback is a proxy which stores the last response for a given URL. Spin up one or more talkback-servers for the APIs you want to mock via cypress/plugins/
. When in the test environment, set your frontend to fetch/get data from these URLs instead of the regular URLs.
I'm using talkback also, working really good!
I mentioned this here https://github.com/cypress-io/cypress/issues/95#issuecomment-433679170 but i wonder why PollyJS wouldnt be a perfect fit, it can do what cypress wants and more and already offers adaptors and other means to hook in. I use it with cypress and its amazing. Would love to see it become part of cypress :). I think it was meant to be :P
Also my 2 cents, Think you should leave the stubbing in the client as this gives you the best flexibility and control, other wise syncing mock settings will be required and adds more complexity. Additionally stubbing in the client renders the fastest response, which is nice for testing purposes. Having stubs that return dynamic content based of your test suite might be tricky as you have to send the response to the server so it can respond to your app which is already in client ???
I feel your mocks should originate from your suite since whats where its executed from, and since this is in the client it feels clunky to push this config to the server and have it respond from there back to your client.
Just use Polly, has the backing and its already developed :P
Could we add a recipe for using https://github.com/ijpiantanida/talkback for HTTP stubbing?
@Vages Are you able to provide some detail as to how to add Talkback to cypress. I'm getting a bit stuck trying.
Start a Talkback server in cypress/plugins/index.js
. It will run automatically when Cypress starts. An example inspired by the Talkback Readme:
// cypress/plugins/index.js
const talkback = require("talkback");
const opts = {
host: "https://api.myapp.com/foo",
port: 5544,
path: "./my-tapes"
};
module.exports = () => {
const server = talkback(opts);
server.start(() => console.log("Talkback Started"));
}
How this works: When a request is sent to localhost:5544
, Talkback will look for a tape (previous request and response) matching the request, looking at the URL, headers, body, and all. If a matching tape is found, Talkback will respond with the stored response. If not, Talkback will forward the request to https://api.myapp.com/foo
, storing the request and response as a tape. If you send a request to localhost:5544/bar
, it will be forwarded to https://api.myapp.com/foo/bar
and so on.
Common pitfall: I have seen people going "Why isn't Talkback storing any tapes?" because they are sending their requests "production style" during testing, e.g. https://api.myapp.com/foo/baz
. Sadly, Talkback isn't magic (like Cypress): When you run your tests, you have to make your app request the localhost URL that the Talkback server is attached to. For example localhost:5544/baz
instead of https://api.myapp.com/foo/baz
. You've probably got a way of changing API base URLs from development to production already, so just make a test configuration that you use when running with Cypress. But it's likely _very_ project specific, so you have to do that homework yourself.
Thanks @Vages. That was a very complete explanation. Thanks for your effort. Clears things up for me.
I've been trying to get XMLHttpRequest mocking going using pollyjs and the basics have been working (after replacing the xhr adapter with one that works with cypress). I am seeing issues in some cases though, a maximum call stack issue keeps happening. Trying to track it down. Don't know if anyone else has had any luck with pollyjs.
@psachs21 i created this till i have a better solution https://github.com/timpur/cypress-polly-xhr-adapter (https://github.com/sinonjs/nise/issues/70#issuecomment-439674811)
Edit: Just add the git repo url to package.json and it should just work (install)
@timpur This is exactly what I was looking for. Figures someone had already solved it. Much appreciated (even as a temporary solution)
For anyone who is interested in auto stub based on existing cypress, I've created a simple example to demonstrate my solution https://github.com/PinkyJie/cypress-auto-stub-example
There has been some progress made on this issue - but it does involve many parts, so we do not have an ETA at the moment.
With the proxy approach, am I correct to say that it will not work for API with Kerberos authentication?
It would be great if the current browser side stubbing option is kept as an alternative.
Is sendBeacon in scope for this? I was testing to ensure Sentry (error tracking) fires errors appropriately, and saw that the calls to sendBeacon weren't intercepted by the route-stub I had created.
@frattaro yea, I was discussing this over on chat with someone the other day, and it seems that it's not possible to use cy.route
on them, which is understandable, considering.
WTBS, you can stub them and use XHR instead.
@dwelle The use case was to ensure a client's website using Sentry (or not) didn't interfere with our own SDK's implementation of Sentry. I tried removing sendBeacon
and polyfilling with XHR (like is suggested for fetch), but it wasn't successful for whatever reason.
Wasn't successful in what way?
The call wasn't even showing up in the network tab of chrome's dev tools. I was using this package: https://www.npmjs.com/package/navigator.sendbeacon
Otherwise mimicked the fetch workaround with the Cypress.on('window:before:load', win => { win.navigator.sendBeacon = null; });
Yea, that's because of how that lib checks for native support. You won't be able to go around it, unless it's fixed https://github.com/miguelmota/Navigator.sendBeacon/issues/17
For now, you can inline it:
cypress/support/index.js
Cypress.on( `window:before:load`, win => {
cy.window().then(win => {
// @copyright https://github.com/miguelmota/Navigator.sendBeacon
win.navigator.sendBeacon = function sendBeacon (url, data) {
const isString = val => typeof val === "string";
const isBlob = val => val instanceof Blob;
const event = this.event && this.event.type;
const sync = event === "unload" || event === "beforeunload";
const xhr = ("XMLHttpRequest" in this) ? new this.XMLHttpRequest() : new this.ActiveXObject("Microsoft.XMLHTTP");
xhr.open("POST", url, !sync);
xhr.withCredentials = true;
xhr.setRequestHeader("Accept", "*/*");
if (isString(data)) {
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
xhr.responseType = "text/plain";
} else if (isBlob(data) && data.type) {
xhr.setRequestHeader("Content-Type", data.type);
}
try {
xhr.send(data);
} catch (error) {
return false;
}
return true;
}.bind(win);
});
});
Got everything working - thanks so much! I had to make some changes:
1) remove the cy.window().then
wrapper, (lengthy error)
2) remove the withCredentials = true;
(error with CORS)
3) change xhr.responseType = "text/plain"
to xhr.responseType = "text"
to resolve a warning
Does anyone know if it's possible to test service workers prior to this change going in?
Anyone had any joy mocking responses?
Keeping an eye on this one, thanks for the hard work.
Is there a way of waiting for a specific JS script to run before this feature lands? In my case it's a 3rd party script that runs after the onload
event so I haven't been able to reliably wait for its execution without some cy.wait()
magic numbers.
Might be worth looking at Puppeteer鈥檚 implementation of:
Page.on('request')
Page.on('requestfailed')
Page.on('requestfinsihed')
Page.on('response')
see their docs for details.
Edit: to clarify, I'm saying that the Cypress team might want to take inspiration from Puppeteer's implementation.
@timpur This is exactly what I was looking for. Figures someone had already solved it. Much appreciated (even as a temporary solution)
Can you show how you were able to use this package and get it set up? No luck for me.
@alexlee-dev so I had to overwrite my visit because i wanted to preserve keep recording between navigations. That was the only thing that really hung up my use of the adapter.
import XHRAdapter from 'cypress-polly-xhr-adapter';
import { Polly } from '@pollyjs/core';
const startPolly = (mode, _win, _name?: string) => {
XHRAdapter.setWindow(_win || window);
const polly = new Polly(filename, {
adapters: [XHRAdapter],
mode,
...
});
return polly;
};
const stopPolly = polly => {
if (!polly) throw new Error('Polly not initialised');
cy.wrap(polly).invoke('stop');
cy.state('polly.instance', null)
};
Cypress.Commands.overwrite('visit', (orig, url, options) => {
const mode = cy.state('polly.mode');
const previousPolly = cy.state('polly.instance');
const doVisit = () =>
orig(url, {
...options,
onBeforeLoad: win => {
if (mode) {
const polly = startPolly(mode, win);
cy.state('polly.instance', polly);
}
if (options && options.onBeforeLoad) {
options.onBeforeLoad(window, options);
}
}
});
if (previousPolly) {
stopPolly(previousPolly);
}
return doVisit();
});
@alexlee-dev yeah i linked this https://github.com/timpur/cypress-polly-xhr-adapter though it was meant for temp, i was more thinking cypress could utilize Polly since it already has a community instead of reinventing, though i cant say it ticks all of cypress's requirements ... (i dont know enough to really comment)
But if you like to use polly my repo may help though dont expect much :P
@psachs21 also has you covered with an example (thanks mate)
Thanks @psachs21 and @timpur . I'll give it another go. I'm trying to do it within several layers of a multi-proxy-ed corporate application, so sometimes it's difficult to know what it really causing Cypress to not work correctly.
I am also interested in a solution so that we can be aware of loading scripts such as <script src="www....">
, similar to the Pupetteer option setRequestInterception
Hey guys,
Any updates on this? Or maybe you can suggest a temporary workaround for people running into this issue?
@DanielStoica85 I've been actively working on this feature, so hopefully it will be in the next release after 3.3.0. What are you trying to test, exactly? Maybe I can help you find a temporary workaround.
@flotwig any updates on this? Love a workaround we can use with Apollo client to see our graphql queries and make sure cypress waits on them before moving to the next test
I've created a Gist of a more scalable workaround until this is implemented: https://gist.github.com/yagudaev/2ad1ef4a21a2d1cfe0e7d96afc7170bc
It was inspired by the official cypress recipe with some tweaking.
Hopefully it helps somebody out there until the official feature is in place.
In a situation that I think is related to this issue. I have to wait for all initial bundles to be loaded for some interactions to be available. Once I perform the interaction another resource is loaded and this one is required for my assertions.
The test case would look something like this
cy.visit('example.com');
cy.waitForResourceToBeLoaded('bundle23.js')
cy.get('.btn').click();
cy.waitForResourceToBeLoaded('below-the-fold.css');
performAssertions();
Are there any updates on
we may not be able to provide callback functions anymore
from the op?
@paulfalgout We want to allow users to dynamically intercept network traffic in tests; however, this would also introduce some latency in network requests because instead of the traffic going...
Browser <-> Cypress <-> Internet
...it would go:
Browser <-> Cypress <-> Browser Websocket <-> Cypress <-> Internet
But even though this is slow, this is really the easiest way for users to write intercepting code. This is the reason why "we may not be able to provide callbacks" is in there, but it's not really representative of the direction things are going.
We might also expose network interception in such a way that it can be handled by your plugin file, which would be a little faster than sending it through the browser (IPC vs. websockets). This would allow users to shave off milliseconds if they want to intercept all traffic and don't need assertions:
Browser <-> Cypress <-> Plugin File IPC <-> Cypress <-> Internet
@flotwig It would be great if there was also support for navigator.sendBeacon
.
@thearegee Any type of HTTP traffic, including beacons, will be covered as part of this. :)
Any update on the timeline for this being released?
@flotwig Any update on the timeline of adding dynamic headers to any http requests? Are there any workarounds available?
I am a newbie with Cypress (and JavaScript), so I hope I am posting this in the correct thread. I am trying to identify ad urls when I visit a website that contain certain key/value pairs in the query string. I used to download the HAR file once the entire test completed, and parse through the JSON. However, if I understand this correctly, I can do this within cypress automatically via route(). I'm still trying to figure it out....
Anyway, from what I have been reading above, capturing anything but XHR requests is not yet available. When I use the Chrome Dev Tools Network Tab, the request is listed under Other, or Type text/html, so it's not even in a JS request.
Would I be able to query ALL requests, once this options is available? Any resources that may steer me in the right direction would be appreciated. Thanks!!!
@dipakspawar @mattvukas It's is in active development, see the PR here for progress updates: https://github.com/cypress-io/cypress/pull/4176
@simi823 Yeah, right now capturing anything except XHRs is not possible. This feature will allow querying/intercepting any type of network request. It should also eliminate some of the parts of the XHR implementation which cause strange behavior in the Network tab.
@flotwig Thanks for the updates.
Just to give you some context: I am trying here at Airbnb to setup automated UI tests. To able to route requests to specific server we need capability to add headers to all http requests.
I wrote my own chrome extension to do this and loaded it in cypress chrome (using plugins/index.js). It does load the plugin, in chrome webtools I can see request being sent with header but when I check server logs, the header doesn't seem to get propagated.
Is there any way to workaround this?
@flotwig Thanks for the updates.
yes, thank you @flotwig! I am working on a project that needs the ability to inject custom http headers into api/websocket requests originating from the application under test & this feature will enable me to use cypress for our e2e testing.
Could any moderators (@brian-mann, @jennifer-shehane or @flotwig) please restrict this discussion? I'm watching this issue because I want updates on it. However, every new comment is a request for help or 芦When will this be available?禄, ruining the watch feature.
Sorry to ruin your watch but with all due respect this is a public ticket not a private watchlist and Its always good practise check of available issues before posting new one. Most of existing issues with api not being captures are getting closed re-pointing to this issue. Maybe my post was not clear but it looks related to this open issue as i have other xhrs which are getting identified ..
I understand you guys might be looking for this ticket for long but usually new examples helps with more info on what is needed for ongoing feature. So please be patient we are also actively looking for solutions for our own project
I can see from your account that you are relatively new to Github, @ayandebbarman, so I understand that you don't have any bad intentions. But I think you'll find that you get better help more quickly if you post questions to Stack Overflow and the Cypress Gitter (https://on.cypress.io/chat) rather than as issues. If it turns out that your problem is actually related to some issue (or represents a new one), _then_ you turn to Github.
Also, see https://stackoverflow.com/help/how-to-ask; it's useful no matter where you post.
I think there are some business scenarios for supporting jsonp
requests
You can do this right now using stubs. Albeit it's certainly not as nice as what we did for XHR stubbing, but it is possible and we have several examples of making this work. This is even in a new getting started video we're releasing later this week.
this link is giving me a 404. Did things get moved?
This is also related to #2014 though it's not mentioned here. Will this add support for iframe request stubbing as well? That ticket led me here :)
For anyone looking into awaiting specific GQL requests we got this response from the support team: https://gist.github.com/CypressCecelia/29dfb1bcdc673d00702ab62ee99100e8
Edit: We've tested it and it works nicely. Yay. Thanks @amirrustam and @CypressCecelia
This is also related to #2014 though it's not mentioned here. Will this add support for iframe request stubbing as well? That ticket led me here :)
Yes, it will allow you to stub any type of HTTP request, including requests for subresources like iframes.
@Peterabsolon The solution you received from our support team is what we've recommended to some users. We're interested to know if you've had success with it or if you have any feedback.
It does have some caveats and requires so additional work if you are batching GQL queries. Keep us posted within the Gist's comment section. Thank you.
@amirrustam Works nicely! Thanks. Do you by any chance have a workaround for stubbing GQL requests too? I suppose we need to wait for this PR with that, don't we.
I wrote one, as detailed here: https://siawyoung.com/stub-graphql-cypress. The simple (non-batched) version is pasted here for convenience:
const responseStub = ({ payload, ok }) =>
Promise.resolve({
json() {
return Promise.resolve(payload)
},
text() {
return Promise.resolve(JSON.stringify(payload))
},
ok: ok === undefined ? true : ok,
})
Cypress.Commands.add('graphql', stubbedGQLResponses => {
cy.on('window:before:load', win => {
const originalFetch = win.fetch
const fetch = (path, options, ...rest) => {
if (options.body) {
try {
const body = JSON.parse(options.body)
if (body.operationName in stubbedGQLResponses) {
const stubbedResponse = stubbedGQLResponses[body.operationName]
return responseStub(stubbedResponse)
}
return originalFetch(path, options, ...rest)
} catch (e) {
return originalFetch(path, options, ...rest)
}
}
return originalFetch(path, options, ...rest)
}
cy.stub(win, 'fetch', fetch)
})
})
context('Accounts page', () => {
beforeEach(() => {
cy.graphql({
userAccount: {
payload: {
data: {
user: {
id: 1,
first_name: 'Bob',
last_name: 'Smith',
__typename: 'User',
},
},
},
},
})
cy.visit('/account')
})
it('should display user first and last name correctly', () => {
cy.get('.first-name').should('have.text', 'Bob')
cy.get('.last-name').should('have.text', 'Smith')
})
})
There's also a version that works with batched Apollo requests in the post itself. I hope this helps!
@Peterabsolon My original solution that was shared earlier used cy.spy
, but to stub/mock you can use cy.stub
. @siawyoung's solution does this, but his method might not be scalable depending on your needs.
You could use something like https://github.com/tgriesser/cypress-graphql-mock if you don't need to wait on the GQL requests using the way I linked because it completely stubs out fetch
internally.
Temporary experimental window.fetch
polyfill available in v4.9.0
The experimentalFetchPolyfill
configuration option has been released as part of 4.9.0. When this option is true
, Cypress will automatically replace window.fetch
with a polyfill that Cypress can spy on and stub.
You should be able to remove any of the following code regarding fetch
support:
Cypress.on('window:before:load', (win) => {
delete win.fetch
})
or
cy.visit('/', {
onBeforeLoad (win) {
delete win.fetch
},
})
And replace it with the below in your configuration file (cypress.json
)
{
"experimentalFetchPolyfill": true
}
You can see a full example of this in use here: https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/stubbing-spying__window-fetch
If you encounter any issues with the new experimentalFetchPolyfill
, please open a new issue with a reproducible example.
Is it possible to stub network calls in a way that that doesn't care about whether the app uses fetch or XHR? Seems like in most cases that is an implementation detail we don't want to enshrine in a test.
I was using xhook
for stubbing and unfetch
to polyfill fetches for Cypress. It doesn't appear that xhook
is compatible with experimentalFetchPolyfill
.
Using xhook
with experimentalFetchPolyfill
resulted in every network request failing. Once I removed xhook
and just used experimentalFetchPolyfill
, only the tests that relied on a stubbed response failed. So still waiting for #4176.
Looks good otherwise.
@alexkrolick We're working on bigger changes as part of #4176 to improve the experience behind network requests in general. The current workaround is meant to be helpful for users starting out so they don't have to dive through issues to figure out how to get fetch working.
@ngbrown could you please provide a small reproducible example showing how experimentalFetchPolyfill
fails?
I was using
xhook
for stubbing andunfetch
to polyfill fetches for Cypress. It doesn't appear thatxhook
is compatible withexperimentalFetchPolyfill
.Using
xhook
withexperimentalFetchPolyfill
resulted in every network request failing. Once I removedxhook
and just usedexperimentalFetchPolyfill
, only the tests that relied on a stubbed response failed. So still waiting for #4176.Looks good otherwise.
cc @bahmutov
xhook
replaces fetch
as well and changes the interface, you can support both if you properly restore native/polyfilled-fetch after enabling xhook
, something like this below:
return cy
.readFile("node_modules/xhook/dist/xhook.min.js", { log: false })
.as("xhook")
.then((xhook) => {
Cypress.on("window:before:load", (win) => {
// save original fetch (which is probably Cypress's polyfill)
const originalFetch = win.fetch
win.eval(xhook)
win.fetch = originalFetch // make sure we restore `fetch` to original
win.xhook.before((req) => {
let url = req.url
if (url.indexOf("someapi") > -1 && req.body) {
// do stuff
}
})
})
})
I was using
xhook
for stubbing andunfetch
to polyfill fetches for Cypress. It doesn't appear thatxhook
is compatible withexperimentalFetchPolyfill
.
Usingxhook
withexperimentalFetchPolyfill
resulted in every network request failing. Once I removedxhook
and just usedexperimentalFetchPolyfill
, only the tests that relied on a stubbed response failed. So still waiting for #4176.
Looks good otherwise.cc @bahmutov
xhook
replacesfetch
as well and changes the interface, you can support both if you properly restore native/polyfilled-fetch after enablingxhook
, something like this below:return cy .readFile("node_modules/xhook/dist/xhook.min.js", { log: false }) .as("xhook") .then((xhook) => { Cypress.on("window:before:load", (win) => { // save original fetch (which is probably Cypress's polyfill) const originalFetch = win.fetch win.eval(xhook) win.fetch = originalFetch // make sure we restore `fetch` to original win.xhook.before((req) => { let url = req.url if (url.indexOf("someapi") > -1 && req.body) { // do stuff } }) }) })
So this turns out not to be as straightforward as I thought. I finally have the two working in my project but it required me to patch xhook
if anyone is interested in I can share.
It would be very difficult to provide accurate onResponse callback functions. It's possible for the internal server to know when an HTTP request has been completed, but it's not possible to know whether the browser has actually finished processing it. This may or may not be a problem.
You can correlate it by hashing content length or other things like the URL & use https://developers.google.com/web/tools/chrome-devtools/network/resource-loading to have Cypress "see" in the network tab if the network request finished.
To play devil's advocate, it could also be argued people should not test in this way, because a normal user would not see if a network request has finished, and tests should closely match how a user behaves (maybe waiting for some element on the page to update in response to the network request finishing instead).
Released in 5.1.0
.
This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v5.1.0, please open a new issue.
The features requested in this issue are now possible as part of cy.route2()
.
This is currently experimental and requires being enabled by passing "experimentalNetworkStubbing": true
through your Cypress configuration. This will eventually be merged in as part of our standard API.
Please see the cy.route2()
docs for full details: https://on.cypress.io/route2
If you encounter any issues or unexpected behavior while using cy.route2()
we encourage you to open a new issue so that we can work out all the issues before public release. Thanks!
Most helpful comment
Any update on the timeline for this being released?