Many developers are migrating to window.fetch
from XHRs due to the simplified async interface it exposes. I happen to be one of those developers.
Cypress should support window.fetch
with similar abilities to those of XHRs: I should be able to query the request and response, wait on them, and mock with data and fixtures.
This feature will work seamlessly once Full network stubbing is implemented.
or do it yourself:
window.fetch
before visiting the page to force your application to polyfill it on top of XHR. If the application does not include a polyfill, you can load one from your tests.window.fetch
directly in JavaScriptSee these solutions in Stubbing window.fetch
example recipe
Per the 0.17.0
release at the very least window.fetch
will actually work properly now.
It's on our radar to adding stubbing to these as well. We're a lot closer to being able to do it now.
I would love to see this feature. I am very excited about stubbing. It will definitely solve a lot of problems when doing these tests. window.fetch
is very widely used by now 馃憤
My team would love this too! window.fetch
is very commonly used in React applications now, so I'm sure other teams would appreciate this as well.
Same here. We use fetch for it's simplicity in our react application. Until yet we use our own proxy that mocks the ajax requests, but your XHR approuch is much more readable.
Maybe you could offer a way to use fetch outside the service-worker's context first and in a second phase you could add fetch in service-workers?
There is now an example of using cy.stub
to control window.fetch
. It's not the same API as cy.route
but it will work today!
We have a bunch of examples of it here: https://github.com/cypress-io/cypress-example-recipes#controlling-behavior-with-spies-stubs-and-clocks
As not all browsers support window.fetch
yet, I guess most use some form of polyfill for this. Then one could do something like this:
cy
.visit('<some url>', {
onBeforeLoad: (win) => {
win.fetch = null
}
})
After doing this, the polyfill will just use XHR
instead of fetch
in the test, and one can use the normal Cypress's capturing/mocking tools. What do you think of this, @brian-mann?
Thank you for the alternative approach. Will try that.
What is the state of the real implementation? https://github.com/cypress-io/cypress/projects/20
@stigkj thx for the workaround/solution.
the solution in https://github.com/cypress-io/cypress-example-recipes#controlling-behavior-with-spies-stubs-and-clocks was working for several projects for me.
Now I need to test an app using redux-saga with yield call(POST, { url: '/api/login/users', options });
and there it is not working, your solution is working!
@stigkj thanks for the work around, like @mvandebunt this has worked for me in Chromium 56 :)
I find this a bit easier than the spy approach. I'm looking forward to Cypress getting better support for window.fetch :)
While people are waiting for this, As long as your app falls back to XHR (polyfill or other) you can use this in cypress/support/commands.js
to use @stigkj 's solution across all tests
Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
const opts = Object.assign({}, options, {
onBeforeLoad: (window, ...args) => {
window.fetch = null;
if (options.onBeforeLoad) {
return options.onBeforeLoad(window, ...args);
}
},
});
return originalFn(url, opts);
});
@Graham42 There is actually an easier way to do this and it will work not only for visits but all page transitions.
https://docs.cypress.io/api/events/catalog-of-events.html#Window-Before-Load
This event is called at the same time the onBeforeLoad
callback is invoked.
You also won't have to overwrite the visit
method
@Graham42 the overwrite of the visit command does not call onBeforeLoad
for me (cypress 1.1.1) what version do you use?
@vschoettke Do not use the visit overwrite - use what I suggested in my comment here: https://github.com/cypress-io/cypress/issues/95#issuecomment-343214638
There's nothing wrong per-se with overwriting visit but its unnecessary when you can use events which will apply to all page transitions.
@brian-mann worked like a charm! I just put this in support/index.js
:
// use `Cypress` instead of `cy` so this persists across all tests
Cypress.on("window:before:load", win => {
win.fetch = null;
});
@nmchaves thanks! Your comment came just right in when I was about to ask if there was a way to make this global instead of inside the tests! You rock man!
Thank you @nmchaves !! Make sure you are using the polyfill for this fallback behavior to work
Thank your solution @nmchaves, but with it, i cant get response body by function xhr.responseJSON
. Do you have same idea ?
Has anyone successfully getting the network stub to work with fetch requests made via a page within an iframe?
When using github/fetch you gotta add win.Blob = null;
if you don't wanna receive a blob.
github/fetch#481
Is there any way to (re)set the responseType
using cy.route?
I maintain a well-established library for mocking fetch
which I'd be happy to help integrate. https://github.com/wheresrhys/fetch-mock. fetch
is really well-established now and very many libraries use fetch
, not XHR
, so first class support for fetch
in a web testing platform seems like a no-brainer.
In the meantime, please at least make it explicit and up front in your docs that fetch
is not supported; transparency on this would've saved a lot of time for my team today.
We've already begun the network layer rewrite that will permanently fix this. Agree that docs could use updates - we do mention it in several places but it's probably not apparent enough.
@brian-mann That's good news. When do you expect this rewrite to be released. Days, weeks, months?
Glad I found this ticket to explain why the docs are so explicit about old-fashioned XHR.
Is the fix likely to be pushed soon? I see this is an old ticket....
This is still in progress. We do not have an official release date or version at the moment.
2 years later this is still with no solution at all ??
Simply can't use Cypress then
No workaround ?
@gflores the workarounds are all mentioned in the comments above. Forcing your fetch lib to use XHR or using cy.stub
are valid.
I check this thread once every couple weeks. Looking forward to a solution! 馃憤
We also could have used more documentation on this. Tried to get cy.route
working for a few hours today and could not figure out why it only worked for some routes until we realized it only worked with old superagent requests. Obviously +1 for this feature.
A solution I'm contemplating is, in test, injecting a polyfill.io script with fetch|always
in the query string. This will always inject the fetch polyfill, built around XHR.
Sorry to pile on but this just wasted hours of my time. Please state clearly in the docs you don't support fetch, especially on this page... https://docs.cypress.io/guides/guides/network-requests.html#
+1 this gets supported soon.
Re my solution above https://github.com/cypress-io/cypress/issues/95#issuecomment-390469094, could cypress-io offer an option to inject such a script, fetchHack: true
, - that way the docs would both highlight the problem and offer a one line solution.
IMHO such a complained about issue should not keep being pushed back to the next major release unless its arrival is imminent
I loved cypress, but stopped using it because of this and the lack of support for anything non-Chromium.
Hope this changes.
2 years later still the same :(
@brian-mann @jennifer-shehane @bahmutov Folks, could you at least implement some workaroud or tell when it is going to be ready?
@Pavel-Husakouski I am using cy.stub
as done in the examples and it works fine today.
@Pavel-Husakouski just like @ahx said this example stubs or polyfills window.fetch
in several ways to get around XHR limitation
I've found the problem. The wait method was skipping actual requests and I thought that workaround with polyfill was not working. Looks like the wait method should match the request. If the app made, say, five requests to the same route, the wait method should be called five times to match the order of calls.
We got strange behavior in our test. we used method for deleting fetch in event "window:before:load" which solved it in chrome but was failing in CI. Testing in headless electron localy was failing with kind of random behaviour (2 same test one after another and second was failing, sometimes it passed randomly, different behaviour with --headed and headless). We solved it by deleting SW and now its working. I found after it that it's documented here known-issues but it would be helpful to mention it in first comment in this issue to save other people some time.
Please add support for cy.route with windows.fetch!!
Can I ask why cypress doesn't just use something like Polly.Js. I think Polly could add alot of functionality to cypress and support many interfaces like xhr and fetch...
@jennifer-shehane
update (2018-nov-29): I switch to axios
due to lack of fetch
API support in Election/Cypres.
Encounter the same issue while using ky
. Here is how I fixed it, thanks to @rkistinger's comment:
yarn add whatwg-fetch
Then in my application index.js
:
import 'whatwg-fetch'; // required until cypress support fetch API
Then write the test as follow:
it('Searching reduce results', function() {
cy.server();
cy.route('**/vocabulaire.json', 'fixture:vocabulaire.json');
cy.visit('/', {
onBeforeLoad: win => {
win.fetch = null;
}
});
// whatever your test does
});
Once this work you can move the onBeforeLoad
in support/index.js
as suggested by @nmchaves's comment:
Cypress.on('window:before:load', win => {
win.fetch = null;
});
@edouard-lopez this works great in the interim and it's nice and clean. Thanks!
@edouard-lopez great solution! How are you testing multiple routes at the same time?
@vctormb You can put the onBeforeLoad
in _support/index.js_. You can see my project's code.
@edouard-lopez yea, I've tested here and it works great, but the problem is when you use two routes or more at the same time, it won't work. I had to use other approach to handle multiple routes with GraphQL. Take a look at this one: https://github.com/cypress-io/cypress-documentation/issues/122#issuecomment-409839089
Any update on this guys?
Hello, any update?
Unfortunately none of the solutions proposed worked.
The actual request goes through successfully but cypress keep catching a wrong request.
I have an app that use Service Worker heavily, so I cannot use axios
or any workaround in the long run.
So please support fetch
:pray:
Please provide us with an update regarding fetch.
We are switching to web workers, we could use the fetch support. Any progress?
Imaging paying nothing for an open source tool, and then having the audacity to complain about a missing feature on GitHub? :1st_place_medal: :100: :toilet:
@reno1979 @FaBeyyy please see the workarounds for fetch
in https://github.com/cypress-io/cypress/issues/95#issue-128632095
@reno1979 @FaBeyyy please see the workarounds for
fetch
in #95 (comment)
Sadly not usefull if one relies on fetch to not only make http requests but also fetch static data. Also manually stubbing is not a workaround for this issue it is something quite different from what would be ideal (see polly for example)
@FaBeyyy can I clarify: you mean you are paying for the dashboard and support subscription when you say "licensed versions"?
@FaBeyyy I made an assumption (correct me if it was a wrong one), that if you were a paying Cypress customer with a support subscription, that would have escalated this issue through your paid support subscription, not through a nagging comment on a pull request. Have you brought this up through your support subscription as a priority?
If your polyfill is not in your bundle and a simple window.fetch = null
does not work, you can use this method !
I wanted this to happen for all my tests so I moved to index.js
:
let polyfill;
before(() => {
const polyfillUrl = 'https://unpkg.com/[email protected]/dist/fetch.umd.js';
cy.request(polyfillUrl).then(response => {
polyfill = response.body;
});
});
Cypress.on('window:before:load', win => {
delete win.fetch;
win.eval(polyfill);
});
Note: in the example it uses unfetch
, but unfetch takes a url and options as arguments. I had to write something roughly like this to make it work:
win.fetch = args => {
if (typeof args === 'string') {
return win.unfetch(args);
}
if (typeof args === 'object' && args !== null) {
const { url, credentials, ...rest } = args;
return win.unfetch(url, { credentials: 'include', ...rest });
}
console.error("Invalid request");
};
But didn't like it much. So I use whatwg-fetch
instead! Hope this helps other people :)
The workaround doesn't work for me because all headers and fetch settings are ignored. Only standard browser headers are send Content-Type, Origin, Referer, User-Agent, no Cookie.
Starting with Chrome 73 hiddes all headers :smiley_cat:
https://www.chromium.org/Home/chromium-security/site-isolation#TOC-Known-Issues
In Chrome's DevTools, cookies and other request headers are not shown in the network panel for cross-site subresource requests. There are also issues with the DOM storage view, security panel, performance panel, and with scrolling over cross-site iframes in mobile device emulation mode, all of which are fixed in Chrome 68.
For anyone using the approach to set window.fetch
to the polyfill export from whatwg-fetch
, it does not work in ^3.0.0
.
Valid with ^2.0.0
/ Invalid with ^3.0.0
// cypress/support/index.js
import './commands'
import { fetch as fetchPolyfill } from 'whatwg-fetch'
Cypress.on('window:before:load', win => {
win.fetch = fetchPolyfill
})
I found that simply setting fetch to null
as described above worked perfectly for my use case.
Does anyone know of an effective workaround with ^3.0.0
, we have ^3.4.0
. The project to integrate this tool is partially crippled because of the lack of support of fetch.
@lassiter you can use the code above as a workaround, while not pretty, it works.
let polyfill; before(() => { const polyfillUrl = 'https://unpkg.com/[email protected]/dist/fetch.umd.js'; cy.request(polyfillUrl).then(response => { polyfill = response.body; }); }); Cypress.on('window:before:load', win => { delete win.fetch; win.eval(polyfill); });
I cannot find a good workaround for fetching the polyfill in the request, because importing normally uses the test window's this
, and if you execute the code in your window, then it will use the application's this
. Trying to polyfill from other code causes some very funky this
issues where you'll try and call fetch from the test runner window in the application window. The alternative is to add polyfill to your application code when you're in the cypress environment.
Trying to use whatwg-fetch
from local node modules
~
npm install whatwg-fetch --save-dev
~
~~~javascript
// cypress/support/fetch_to_xhr.js
function fetchToXhr() {
let polyfill
before(() => {
cy.readFile('node_modules/whatwg-fetch/dist/fetch.umd.js')
.then((contents) => polyfill = contents)
Cypress.on('window:before:load', (win) => {
delete win.fetch
win.eval(polyfill)
})
})
}
fetchToXhr()
~~~
Import in cypress/support/index.js
:
~
import './fetch_to_xhr'
~
https://github.com/cypress-io/cypress/issues/95#issuecomment-484811115
Does anyone know what I need to add to this workaround to not get a Blob back in my return?
let polyfill;
before(() => {
const polyfillUrl = 'https://unpkg.com/[email protected]/dist/fetch.umd.js';
cy.request(polyfillUrl).then(response => {
polyfill = response.body;
});
});
Cypress.on('window:before:load', win => {
delete win.fetch;
win.eval(polyfill);
});
@Harvnlenny We use:
// cypress only supports XHR Requests. https://docs.cypress.io/api/commands/route.html#Syntax
let polyfill;
before(() => {
const polyfillUrl = 'https://unpkg.com/[email protected]/dist/unfetch.umd.js';
cy.request(polyfillUrl).then(response => {
polyfill = response.body;
});
});
Cypress.on('window:before:load', function(win) {
delete win.fetch;
win.eval(polyfill);
win.fetch = args => {
if (typeof args === 'string') {
return win.unfetch(args);
}
if (typeof args === 'object' && args !== null) {
const { url, credentials, ...rest } = args;
return win.unfetch(url, { credentials: 'include', ...rest });
}
console.error('Invalid request');
};
});
@Alphy11 Thanks for the quick response. I can't seem to get unfetch to work for me.
Here's what I'm doing for this, in case this helps anyone... nothing really new, but I did pull it into a custom command to make it easy to delete when this issue is finally resolved:
Cypress.Commands.add("polyfillFetch", () => {
Cypress.log({});
cy.readFile("node_modules/unfetch/dist/unfetch.umd.js", { log: false })
.as("unfetch")
.then((unfetch) => {
Cypress.on("window:before:load", (win) => {
delete win.fetch;
win.eval(unfetch);
win.fetch = win.unfetch;
});
});
});
then in support/index.js
:
before(function() {
cy.polyfillFetch();
});
I didn't really get any success defining it in support/index.js
.
However, I was able to get away with using delete window.fetch
and overwriting the visit
command to set onBeforeLoad
in the options.
https://docs.cypress.io/api/cypress-api/custom-commands.html#Overwrite-Existing-Commands
@Harvnlenny
I have a similar problem.
Using whatwg-fetch works, but the response is always a blob.
Using unfetch breaks something because of CORS issues.
What I did for me as a workaround is this:
cy.wait('@apiCall').then(async xhr => {
const text = await new Response(xhr.response.body).text()
})
and then parse it to JSON and use it.
It's kinda hacky, but it works for me for now... maybe even until this whole fetch in cypress issue is fixed.
For my project i have overridden the wait command like this:
Cypress.Commands.overwrite('wait', (originalFn, ...rest) =>
originalFn(...rest).then(async xhr => {
if (xhr) {
const text = await new Response(xhr.response.body).text()
const res = JSON.parse(text)
return res
}
}),
)
This only works, because I know that I'm recieving a JSON String. As soon as I need to differentiate, I will need to change this implementation. But for now as a hacky temporary workaround it works just fine.
I can now do this:
cy.wait('@callApi')
.its('path.in.body.result)
.should('contain', 'test')
This is not needed, when you use axios for making the requests.
Not sure if this will help anyone but just in case. My graphQL calls will always result in something being rendered or not rendered (spinner during loading) so instead of fighting with the route, etc. I test for the existence (or not) of an element. Borrowed 99% of this from someone above (sorry lost the thread and want to get this out of my head). You can put it in command file or at the top of your test file -
Cypress.Commands.add('waitForResource', (name, options = {}) => {
cy.log(Waiting for resource ${name}
);
const log = false; // let's not log inner commands
const timeout = options.timeout || Cypress.config('defaultCommandTimeout');
cy.window({ log }).then(
// note that ".then" method has options first, callback second
// https://on.cypress.io/then
{ log, timeout },
(win) => new Cypress.Promise((resolve, reject) => {
let foundResource;
// control how long we should try finding the resource
// and if it is still not found. An explicit "reject"
// allows us to show nice informative message
setTimeout(() => {
if (options.waitForNotFound) {
if (foundResource.length === 0) {
// nothing needs to be done, successfully found the resource
return;
}
} else {
// eslint-disable-next-line no-lonely-if
if (foundResource.length > 0) {
// nothing needs to be done, successfully found the resource
return;
}
}
clearInterval(interval);
reject(new Error(`Timed out waiting for resource ${name}`));
}, timeout);
const interval = setInterval(() => {
// foundResource = win.performance
// .getEntriesByType('resource')
// .find((item) => item.name.endsWith(name));
foundResource = Cypress.$(options.selector);
if (options.waitForNotFound) {
if (foundResource.length > 0) {
cy.log('waiting for element to not exist');
// resource not found, will try again
return;
}
} else {
// eslint-disable-next-line no-lonely-if
if (foundResource.length === 0) {
cy.log('waiting for element to exist');
// resource not found, will try again
return;
}
}
clearInterval(interval);
// let's resolve with the found performance object
// to allow tests to inspect it
resolve(foundResource);
}, 100);
}),
);
});
Trying to use
whatwg-fetch
from local node modulesnpm install whatwg-fetch --save-dev
// cypress/support/fetch_to_xhr.js function fetchToXhr() { let polyfill before(() => { cy.readFile('node_modules/whatwg-fetch/dist/fetch.umd.js') .then((contents) => polyfill = contents) Cypress.on('window:before:load', (win) => { delete win.fetch win.eval(polyfill) }) }) } fetchToXhr()
Import in
cypress/support/index.js
:import './fetch_to_xhr'
If anyone is struggling to get visibility of firebase functions (due to them using fetch) calls in cypress logs. The use of this npm polyfill fix worked for me. Much appreciated. @mpan-wework
I have tried above solution to nullify window.fetch
and instead using a polyfill
. Though this works in chrome
but this still doesn't work in headless mode(cypress run
) which runs in electron 59
.
Am I missing anything? Is there any workaround for that?
_For my project_ i have overridden the wait command like this:
Cypress.Commands.overwrite('wait', (originalFn, ...rest) => originalFn(...rest).then(async xhr => { if (xhr) { const text = await new Response(xhr.response.body).text() const res = JSON.parse(text) return res } }), )
This only works, because I _know_ that I'm recieving a JSON String. As soon as I need to differentiate, I will need to change this implementation. But for now as a hacky temporary workaround it works just fine.
I can now do this:
cy.wait('@callApi') .its('path.in.body.result) .should('contain', 'test')
This is not needed, when you use axios for making the requests.
How about extending
the existing command rather than overwriting
the existing one as there can be other xhr calls which may return non JSON response or text
method is not available?
Cypress.Commands.add("waitJSON", (...args) =>
cy.wait(...args).then(async xhr => {
if (xhr) {
const text = await new Response(xhr.response.body).text();
const res = JSON.parse(text);
return res;
}
}),
);
This will allow me to use cy.wait
as it is for all other requirements where text
method won't be available and for JSON string response we can use cy.waitJSON
. Do you see any issue with this!
Has this been implemented yet? I'm using the polyfill stubbing (thank GOD for that) but it seems like it's been a while...
I didn't really get any success defining it in
support/index.js
.
However, I was able to get away with usingdelete window.fetch
and overwriting thevisit
command to setonBeforeLoad
in the options.https://docs.cypress.io/api/cypress-api/custom-commands.html#Overwrite-Existing-Commands
can you post the code snippet plz?
I didn't really get any success defining it in
support/index.js
.
However, I was able to get away with usingdelete window.fetch
and overwriting thevisit
command to setonBeforeLoad
in the options.
https://docs.cypress.io/api/cypress-api/custom-commands.html#Overwrite-Existing-Commandscan you post the code snippet plz?
This works for me if I put it in support/index.js
:
Cypress.on('window:before:load', win => {
win.fetch = null;
});
The methods above work pretty well.
Another possible workaround/solution may be to create a plugin that uses Chrome DevTools Protocol to intercept request/responses and replace it with your stub.
This uses experimental features so take that into consideration if you go with the following workaround. Also the Cypress team seems to be close to implementing a solution for this so I'm not sure if its worth coding this up but someone may find this useful. CDP can also be used for a variety of other things like hover states that @gabbersepp shows an example of in the link below.
I haven't tried this out in Cypress yet but I've done this using puppeteer successfully. This should work with Chromium/Chrome but it will not work with Electron because the CDP isn't fully implemented.
There are two ways to do this depending on the CDP version:
Newest/Recommend: This seems to be very recent as I've implemented this a month ago in puppeteer and it wasn't listed as deprecated.
CDP Fetch Domain docs - https://chromedevtools.github.io/devtools-protocol/tot/Fetch/
Network Domain/Deprecated:
https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setRequestInterception
An example not using Cypress: https://blog.shapesecurity.com/2018/09/17/intercepting-and-modifying-responses-with-chrome-via-the-devtools-protocol/
Setup CDI - An example of setting this up to implement hover functionality in Cypress https://github.com/cypress-io/cypress-example-recipes/pull/384
Enable request pausing Fetch.enable
, you can pass in the endpoints you want to listen to here
Listen for the requestPaused
event and call Fetch.fulfillRequest with your desired response.
While people are waiting for this, As long as your app falls back to XHR (polyfill or other) you can use this in
cypress/support/commands.js
to use @stigkj 's solution across all testsCypress.Commands.overwrite('visit', (originalFn, url, options) => { const opts = Object.assign({}, options, { onBeforeLoad: (window, ...args) => { window.fetch = null; if (options.onBeforeLoad) { return options.onBeforeLoad(window, ...args); } }, }); return originalFn(url, opts); });
This is the perfect solution however line #2
const opts = Object.assign({}, options, {
should be
const opts = Object.assign({}, options = {}, {
... this is relevant if you call visit() in your specs without passing any options.
this worked immediately for me and allowed me to keep requests to /
to my react app while making requests to an external API mocked out. I did some simple mocking to my API only with this technique:
const mock_data = {id: 1, name: "AAA"}, {id: 2, name: "BBB"}
context('opening sequence of the splash page', () => {
beforeEach(function () {
cy.server()
cy.route('http://___your_external_server___.example.com/your_api_endpoint', mock_data)
})
...
My app code is
import 'fetch' from 'isomorphic-fetch'
fetch('http://___your_external_server___.example.com/your_api_endpoint')
.then((response) => {
if (response.status >= 400) {
}
return response.json().then((result) => {
this._data = result
this._data.map((ele) => ele.key = ele.id) // this is because API (Rails) returns keys as 'id'
});
})
documented for stack overflow
https://stackoverflow.com/questions/59557480/when-attempting-to-stub-a-request-in-cypress-io-js-against-a-react-app-using-f/59557481#59557481
After implementing the above suggestions I still had an undefined fetch error. This worked for me...
https://gist.github.com/yagudaev/2ad1ef4a21a2d1cfe0e7d96afc7170bc
I had some problems with Yarn workspace not behaving nicely for whatwg-fetch installation... Even though it was in the package.json file, nothing was added to node_modules...
And after applying some of the "simple" solutions above cypress couldn't login to the application.
To fix it, I downloaded the latest fetch.umd.js and added it in cypress/support/, then changed a bit of @mpan-wework solutions cy.readFile()
path, then it started working perfectly.
This is what I'm doing while waiting for the actual implementation.
const getFetchPolyfill = async () => {
const polyfillUrl = "https://unpkg.com/unfetch/dist/unfetch.umd.js";
return await window.fetch(polyfillUrl).then(res => {
if (res.ok) {
return res.text();
}
});
};
Cypress.Commands.overwrite('visit', async (originalFn, url, options) => {
let polyfill = !window.fetchPoly && (await getFetchPolyfill());
const opts = Object.assign({}, options, {
onBeforeLoad: (window, ...args) => {
if (!window.fetchPoly) {
delete window.fetch;
window.eval(polyfill);
window.fetchPoly = true;
window.fetch = window.unfetch;
}
if (options && options.onBeforeLoad) {
return options.onBeforeLoad(window, ...args);
}
},
});
return originalFn(url, opts);
});
@ohelixa Thanks for the solution. After trying different solutions. It finally worked.
There is a plugin in the official cypress plugins list (https://docs.cypress.io/plugins/index.html) called cypress-unfetch, which basically replaces fetch
with unfetch
XHR based library (included in the plugin itself, so it doesn't download it at runtime) every time a new page loads. So no need for overrides, the rest of the code stays the same as usual.
Also, it has an optional addition which collects all on the inflight requests and throws an error if one of them doesn't end before the test does (which helps detecting some problems).
Be careful, cypress-unfetch
plugging doesn't differentiate requests by payloads.
So if you have multiple POST requests to /foo
- the responses will be the same.
const getFetchPolyfill = async () => { const polyfillUrl = "https://unpkg.com/unfetch/dist/unfetch.umd.js"; return await window.fetch(polyfillUrl).then(res => { if (res.ok) { return res.text(); } }); }; Cypress.Commands.overwrite('visit', async (originalFn, url, options) => { let polyfill = !window.fetchPoly && (await getFetchPolyfill()); const opts = Object.assign({}, options, { onBeforeLoad: (window, ...args) => { if (!window.fetchPoly) { delete window.fetch; window.eval(polyfill); window.fetchPoly = true; window.fetch = window.unfetch; } if (options && options.onBeforeLoad) { return options.onBeforeLoad(window, ...args); } }, }); return originalFn(url, opts); });
not is working for me :(
I didn't really get any success defining it in
support/index.js
.
However, I was able to get away with usingdelete window.fetch
and overwriting thevisit
command to setonBeforeLoad
in the options.
https://docs.cypress.io/api/cypress-api/custom-commands.html#Overwrite-Existing-Commandscan you post the code snippet plz?
This works for me if I put it in
support/index.js
:Cypress.on('window:before:load', win => { win.fetch = null; });
not is working for me :(
I follow the example in another project that works, but I try to add it to the company project that is not working
@brian-mann any updates on being able to mock fetch
? Your comment saying you guys were getting close to being able to do that is almost 4 years ago.
@brian-mann any updates on being able to mock
fetch
? Your comment saying you guys were getting close to being able to do that is almost 4 years ago.
@brian-mann I also want to know about the update
At the moment I can't use cy.request()
and cy.route()
due to the fetch api
@jennifer-shehane can you help me please?
For me the fetch
-fallback hack did not work instantly. I still had to unregister service workers as shown by @cintrzyk https://github.com/cypress-io/cypress/issues/1716#issuecomment-454161526
1) Unregister service works
Cypress.Commands.add('unregisterServiceWorkers', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations()
.then(registrations => registrations.forEach(reg => reg.unregister()));
}
});
2) Add fetch-fallback
// use `Cypress` instead of `cy` so this persists across all tests
Cypress.on("window:before:load", win => {
win.fetch = null;
});
3) Call unregister in beforeEach in the respective test file
beforeEach(() => {
cy.unregisterServiceWorkers();
});
is there any update on it yet? The workaround didn't work for me and I can't intercept fetch calls.
The workaround doesn't work for me either, and here are more details:
mapbox-gl
uses fetch
to make some of their calls. window.fetch = null
works for mocking mapbox calls.
_BUT_
apollo-client
uses fetch
to make calls but doesn't seem to have a fallback. window.fetch = null
breaks my GraphQL queries.
Luckily I was able to come up with a workaround, which was basically to prevent mapbox from fully rendering in test. 馃槩 Being able to properly intercept fetch
would be lovely.
@jfairley you can delete window.fetch and replace it with https://www.npmjs.com/package/unfetch for apollo-client
@tylergets I actually tried that and spent a lot of time setting it up a bunch of different ways (including this suggestion and permutations blending different solutions) hoping for a different outcome. Whether I was using fetch
(i.e. I did nothing) or unfetch
, mocking the map style load and the map data wouldn't work. Only when I removed fetch
completely was the map able to load fully, but of course, that broke GraphQL.
The error I was getting from the map was Style not done loading
, which showed whether I used fetch
or unfetch
and not when I did window.fetch = null
. My "solution" was to hook up a handler for 'style.load'
mentioned in a comment to prevent rendering layers, knowing that without my style load mock, the style never loads.
This guy nailed it https://dev.to/mjsarfatti/how-to-test-your-fetch-requests-with-cypress-bh5
Guys, please prioritize this task... it's open since 2016!
In 2020 the entire world use fetch
!
This guy nailed it https://dev.to/mjsarfatti/how-to-test-your-fetch-requests-with-cypress-bh5
That solution caused an infinite requests bug for me: https://github.com/cypress-io/cypress/issues/1068#issuecomment-629514166
Can we please have some news about this? This is 2020, fetch
has been the new standard for years.
There are a lot of tests that I simply cannot do because my local server is too fast and I cannot throttle fetch
responses.
I love Cypress but the lack of consideration for such a major issue is really annoying...
@brian-mann worked like a charm! I just put this in
support/index.js
:// use `Cypress` instead of `cy` so this persists across all tests Cypress.on("window:before:load", win => { win.fetch = null; });
Didn't worked for me
Would writing a plugin that, before running tests, replaces globalThis.fetch
with an XHR-based polyfill suffice?
Our team is currently working on implementing this feature. This is part of the larger work being done for #687.
You can see all current progress being made in this PR #4176. Some of the work may be done in other smaller PRs that would be linked within the main PR's comment, so check the task list there.
Hope this issue will be fixed soon. In the meantime, I'm using https://miragejs.com/docs/testing/application-tests/ for stubbing Fetch.
For anyone looking for stub Apollo/GraphQL queries, 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.
As @valentinogagliardi mentioned Mirage.js is a great tool. We are also using Mirage.js and there is also a guide for Cypress 馃帀 (you can find it here) maybe it makes sense to join the forces and create a nice integration of Mirage.js into Cypress. I think @samselikoff would be happy about helping hands.
This issue was opened more than 4 (!) years ago and it's still open. So maybe Mirage.js could be an option for some of use 馃檪
We love Cypress and would absolutely love to collaborate here if there's an interest!
We have experimental window.fetch
polyfill coming in 4.9.0 (opt-in feature) before full network stubbing is ready
We have experimental
window.fetch
polyfill coming in 4.9.0 (opt-in feature) before full network stubbing is ready
@bahmutov it's really nice, when it will be available in beta ?
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:
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.
In my case, the new experimentalFetchPolyfill
does not work, it changes completely my request, from the method to the url.
I use can-ndjson-stream library to make the fetch requests, so I rewrote the polyfill file to work in my case. I hope this can be helpful to anybody in the same mine situation: https://gist.github.com/yagudaev/2ad1ef4a21a2d1cfe0e7d96afc7170bc#gistcomment-3358272
Yeah, it doesn't work for me either. My login fetch gets a 415 (Unsupported Content-Type).
@stefanoimperiale @stevenvachon we are also using MirageJs which works great for every type of mocking network requests. It works fine with fetch
and it also has a lot of nice features on top 馃檪 so probably you could check out this project. Maybe it helps you to achieve what you need.
Yeah, it doesn't work for me either. My login fetch gets a 415 (Unsupported Content-Type).
Same here. We use a POST
request to make a login, passing in the username and password in the request's body as a JSON object. However, using the experimental polyfill the request headers are changed, and the Content-Type
is now text/plain;charset=UTF-8
, which our backend server rejects.
I think this happens because we utilize the Headers
class in our fetch
call, and don't pass a plain object (e.g. { 'Content-Type': 'application/json' }
). We do polyfill Headers
(using fetch-headers
), but seems like the polyfill used by experimentalFetchPolyfill
(unfetch
) doesn't support them even when they're polyfilled.
This workaround (in our app code) works, although slightly awkward:
fetch(url, {
...
- headers,
+ headers: Object.fromEntries(headers)
})
Is it valid to use fetch polyfill called whatwg-fetch
to capture fetch
?
@baeharam I did this for a project and it worked just fine.
I just ran into a Problem with very large response payloads.
If the response was bigger tham ~1mb the request failed.
Thats not a problem with the polyfill though, but rather with the way cypress mocks xhr requests.
@mefechoel Thanks for your feedback! I'll try whatwg-fetch
for my graphql query 馃槃
Is there a way to only replace window.fetch on stubbed routes?
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()
.
cy.route2()
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
@brian-mann worked like a charm! I just put this in
support/index.js
: