Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Cypress Fetch Support Workaround - replaces fetch request with traditional XHR so cypress can track them
// cypress/support/hooks.js
// Cypress does not support listening to the fetch method
// Therefore, as a workaround we polyfill `fetch` with traditional XHR which
// are supported. See: https://github.com/cypress-io/cypress/issues/687
enableFetchWorkaround()
// private helpers
function enableFetchWorkaround() {
let polyfill
before(() => {
console.info('Load fetch XHR polyfill')
cy.readFile('./cypress/support/polyfills/unfetch.umd.js').then((content) => {
polyfill = content
})
})
Cypress.on('window:before:load', (win) => {
delete win.fetch
// since the application code does not ship with a polyfill
// load a polyfilled "fetch" from the test
win.eval(polyfill)
win.fetch = win.unfetch
})
}
// cypress/support/index.js
import './hooks'
// cypress/support/polyfills/unfetch.umd.js
// Version: 4.1.0
// from: https://unpkg.com/unfetch/dist/unfetch.umd.js
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):e.unfetch=n()}(this,function(){return function(e,n){return n=n||{},new Promise(function(t,o){var r=new XMLHttpRequest,s=[],u=[],i={},f=function(){return{ok:2==(r.status/100|0),statusText:r.statusText,status:r.status,url:r.responseURL,text:function(){return Promise.resolve(r.responseText)},json:function(){return Promise.resolve(JSON.parse(r.responseText))},blob:function(){return Promise.resolve(new Blob([r.response]))},clone:f,headers:{keys:function(){return s},entries:function(){return u},get:function(e){return i[e.toLowerCase()]},has:function(e){return e.toLowerCase()in i}}}};for(var a in r.open(n.method||"get",e,!0),r.onload=function(){r.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm,function(e,n,t){s.push(n=n.toLowerCase()),u.push([n,t]),i[n]=i[n]?i[n]+","+t:t}),t(f())},r.onerror=o,r.withCredentials="include"==n.credentials,n.headers)r.setRequestHeader(a,n.headers[a]);r.send(n.body||null)})}});
@yagudaev

This comment has been minimized.

Copy link
Owner Author

@yagudaev yagudaev commented Jun 15, 2019

This gist was inspired by https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/stubbing-spying__window-fetch/cypress/integration/polyfill-fetch-from-tests-spec.js.

It makes a couple of alterations to make the code scalable across your test suite:

  • Running the polyfill logic across your entire test suite
  • Using a local polyfill file and removing source maps from it to avoid chrome dev tools warnings in cypress
  • Abstracting away the workaround and documenting issue so it can be remove when cypress implements this feature
@blueo

This comment has been minimized.

Copy link

@blueo blueo commented Jun 20, 2019

thanks for sharing this - it is super useful!

@paulpruteanu

This comment has been minimized.

Copy link

@paulpruteanu paulpruteanu commented Jul 9, 2019

I've put this hook in place, yet I still can't identity "script" initiated requests.
Say for instance if the code has a <script src="https://domain.com/api/call"></script>, Cypress still won't be able to track and therefore allow me to stub the route's response. Any thoughts?

@Izhaki

This comment has been minimized.

Copy link

@Izhaki Izhaki commented Jan 21, 2020

// Cypress 3.3.1 and below do not support listening to the fetch method

This is confusing?

Using Cypress 3.8.2 and it doesn't look it supports listening to the fetch method?

@stigkj

This comment has been minimized.

Copy link

@stigkj stigkj commented Feb 28, 2020

@paulpruteanu Are you trying to mock out loading of a script? Or is the script that is loaded doing a fetch? The first one does not work as loading a script does not use fetch, but I would think the second one should work.

@Izhaki The issue mentioned (#687) is not implemented yet, so you still need to do this.

@Izhaki

This comment has been minimized.

Copy link

@Izhaki Izhaki commented Feb 29, 2020

@stigkj Aye, hence the confusion.

// Cypress 3.3.1 and below do not support listening to the fetch method

Is confusing as it gives the impression that with higher versions you don't need this workaround, where in fact they do.

Comment should change to something like

// For Cypress versions not supporting listening to the fetch method
@yagudaev

This comment has been minimized.

Copy link
Owner Author

@yagudaev yagudaev commented Mar 4, 2020

@Izhaki you are right, I should just remove it. I was hoping they would fix it already, but they have not even fixed it in 4.x.

@gwhitelaw

This comment has been minimized.

Copy link

@gwhitelaw gwhitelaw commented Mar 11, 2020

There is an unofficial plugin listed on cypress plugins that does this - sad they haven't fixed this yet: https://github.com/RcKeller/cypress-unfetch

@stefanoimperiale

This comment has been minimized.

Copy link

@stefanoimperiale stefanoimperiale commented Jun 29, 2020

This does not work for me. I use can-ndjson-stream to make the fetch requests.

I rewrite the polyfill file to work in my case. I hope it's all ok.

!function (e, n) {
  "object" == typeof exports && "undefined" != typeof module ? module.exports = n() : "function" == typeof define && define.amd ? define(n) : e.unfetch = n()
}(this, function () {
  return function (request) {
    return new Promise(function (success, rejected) {
        var r = new XMLHttpRequest;
        var keys = [];
        var values = [];
        var headers = {};
        var convertRequest = function () {
          var enc = new TextEncoder(); // always utf-8
          return {
            ok: 2 === (r.status / 100 | 0),
            statusText: r.statusText,
            status: r.status,
            url: r.responseURL,
            text: function () {
              return Promise.resolve(r.responseText)
            },
            text: function(){
                          return Promise.resolve(r.responseText)
            },
            json: function(){
                   return Promise.resolve(JSON.parse(r.responseText))
           },
            body: {
              getReader: function () {
                var stream = new ReadableStream({
                  start(controller) {
                    controller.enqueue(enc.encode(r.responseText));
                    controller.close();

                  }
                });
                return new ReadableStreamDefaultReader(stream);
              }
            },
            blob: function () {
              return Promise.resolve(new Blob([r.response]))
            },
            clone: convertRequest,
            headers: {
              keys: function () {
                return keys
              }, entries: function () {
                return values
              }, get: function (header) {
                return headers[header.toLowerCase()]
              }, has: function (header) {
                return header.toLowerCase() in headers
              }
            }
          }
        };

        r.open(request.method || "get", request.url, true);
        r.onload = function () {
          r.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, function (match, key, value) {
            key = key.toLowerCase();
            keys.push(key);
            values.push([key, value]);
            headers[key] = headers[key] ? headers[key] + "," + value : value;
          });
          success(convertRequest());
        }
        r.onerror = rejected;
        r.withCredentials = "include" === request.credentials;
        request.headers.forEach((value, key) => r.setRequestHeader(key, value));
        request.text().then(body => r.send(body));
      }
    )
  }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment