Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@yagudaev
Last active September 16, 2021 08:46
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yagudaev/2ad1ef4a21a2d1cfe0e7d96afc7170bc to your computer and use it in GitHub Desktop.
Save yagudaev/2ad1ef4a21a2d1cfe0e7d96afc7170bc to your computer and use it in GitHub Desktop.
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
Copy link
Author

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
Copy link

blueo commented Jun 20, 2019

thanks for sharing this - it is super useful!

@paulpruteanu
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link
Author

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
Copy link

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
Copy link

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