Next.js: Unable to use Cypress delete win.fetch trick with [email protected]

Created on 21 Nov 2019  Â·  7Comments  Â·  Source: vercel/next.js

Bug report

For mocking, or tracking, ajax requests in Cypress tests for applications that are using window.fetch, following trick is required: https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/stubbing-spying__window-fetch#deleting-fetch

Cypress.on('window:before:load', win => {
  delete win.fetch;
});

It seems that after upgrading to 9.1.4, which has some fetch polyfill changes, trick fails with this kind of error:

cypress_runner.js:177710 Uncaught TypeError: Cannot read property 'bind' of undefined

This error originated from your application code, not from Cypress.

When Cypress detects uncaught errors originating from your application it will automatically fail the current test.

This behavior is configurable, and you can choose to turn this off by listening to the 'uncaught:exception' event.

https://on.cypress.io/uncaught-exception-from-application
at Object../node_modules/next/dist/build/polyfills/fetch/index.js (http://localhost:3000/_next/static/runtime/main.js)

To Reproduce

Have following cypress command in your tests:

Cypress.on('window:before:load', win => {
  delete win.fetch;
});

And this kind of cypress test:

  beforeEach(() => {
    cy.visit('/do-test-login');
    cy.location('pathname').should('equal', '/');
    cy.server();
    cy.route('/api/**').as('apiRequest');
  });

  it('lists content-types', () => {
    ...
    cy.wait('@apiRequest');
    // verify that page content is visible after waiting for api-request
  });

Expected behavior

Next.js application starts and works also in cypress, polyfilling window.fetch.
Similar to how it works with next 9.1.2.

System information

  • Cypress 3.6.1 (Chrome, Chromium)
  • Version of Next.js: 9.1.4

Most helpful comment

You can't have win.fetch = require('unfetch'); because win comes from your app's iframe while the require doesn't. AFAIK, you can't bind one to another.

The simplest workaround I came up with is this:

Cypress.on('window:before:load', win => {
  fetch('https://unpkg.com/unfetch/dist/unfetch.umd.js')
    .then(stream => stream.text())
    .then(response => {
      win.eval(response)
      win.fetch = win.unfetch
    })
})

All 7 comments

Deleting window.fetch is expected to break. You're removing a feature from your browser.

We are not planning to detect this case. Instead, you should be replacing the window.fetch implementation with your own instance.

i.e.

Cypress.on('window:before:load', win => {
  win.fetch = require('unfetch');
});

I think deleting window.fetch is to make your code fallback on XHR which Cypress can intercept (cypress doesn't support fetch yet). Would win.fetch = require('unfetch'); still allow that?

@bbigras all fetch polyfills fall back to XHR -- so yes.

@Timer Thanks for your reply.

I know that the polyfills fall back to XHR but win.fetch = require('unfetch'); doesn't seem to force the XHR fallback just like delete win.fetch does.

If you want to test, this cypress test doesn't work (for a simple page using fetch on load).

describe("My First Test", function() {
  it("test", function() {
    cy.server();
    cy.route({
      method: "GET",
      url: "/test",
      response: []
    }).as("xhr_query");

    cy.visit("localhost:3000/", {
      onBeforeLoad: win => {
        win.fetch = require("unfetch");
      }
    });

    cy.wait("@xhr_query");
  });
});

I made a repo too. https://github.com/bbigras/test-next-fetch. The master branch uses next 9.1.4 and doesn't work. The 'ok' branch uses 9.1.2 and works. Run $(npm bin)/cypress open to test.

I realize that you might not have the time to care about this since it's cypress' own fault for not supporting fetch yet but I think cypress might be popular enough that it would be nice to have a workaround while they work on fetch support.

You might need to explicitly download and eval unfetch instead of importing it. The unfetch import is likely resulting in the native implementation.

✅ A quick and dirty solution for this is to remove the new aliases in the next.config.js:

config.resolve.alias = /*lodash.*/omit(config.resolve.alias, [
  'unfetch$',
  'isomorphic-unfetch$',
  'whatwg-fetch$',
]);

We only support evergreen browsers so this works well for us. There is still the possibility to wrap this into a process.env.NODE_ENV === 'test conditional.

You can't have win.fetch = require('unfetch'); because win comes from your app's iframe while the require doesn't. AFAIK, you can't bind one to another.

The simplest workaround I came up with is this:

Cypress.on('window:before:load', win => {
  fetch('https://unpkg.com/unfetch/dist/unfetch.umd.js')
    .then(stream => stream.text())
    .then(response => {
      win.eval(response)
      win.fetch = win.unfetch
    })
})
Was this page helpful?
0 / 5 - 0 ratings