Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uncaught CypressError #5

Closed
bautistaaa opened this issue Jul 3, 2019 · 29 comments
Closed

Uncaught CypressError #5

bautistaaa opened this issue Jul 3, 2019 · 29 comments
Labels
question Further information is requested

Comments

@bautistaaa
Copy link
Contributor

Hello! First off thanks for the tool!

I am encountering an error after the initial recording(see image below). I will try to see if I can prevent this somehow and make a PR but maybe you know something off the top of your head!

Here is my test code:

// / <reference types="Cypress" />
const autoRecord = require('cypress-autorecord');

context('Login', function() {
  autoRecord(__filename);
  beforeEach(() => {
    cy.visit('/login', {
      onBeforeLoad(win) {
        delete win.fetch;
      },
    });

    cy.dataQa('login_username_input').as('userNameInput');
    cy.dataQa('login_password_input').as('passwordInput');
    cy.dataQa('login_submit_button').as('loginButton');
  });

  // https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Sharing-Context
  it('user can login successfully', () => {
    cy.get('@userNameInput').type(Cypress.env('username'), { force: true });
    cy.get('@passwordInput').type(Cypress.env('password'), { force: true });

    cy.get('@loginButton').click();

    cy.location('pathname').should('equal', '/');
    cy.getCookie('jwt').should('exist');
  });
})

Versions

"cypress": "^3.3.1",
"cypress-autorecord": "^1.0.12",

Error

image

@Nanciee
Copy link
Owner

Nanciee commented Jul 5, 2019

@bautistaaa this is unfortunately an expected behaviour. You should only ever see this error when you have a test that calls the same url but expect different response bodies based on different request bodies. This shouldn't be effecting your mocks or tests in any way, though please let me know if it is! Although everything should be working, there's no way I could remove the error that Cypress is showing.

Just a little context on what is going on here! A big challenge I ran into while working on this plugin was that Cypress' built in stubbing command cy.route was not made to allow you to return different response bodies based on different request bodies. This wasn't a problem when you are doing simple mocks that required only url matching or when you are declaring the same urls with different responses directly inside your test. It becomes a problem when you need to mock all the routes upfront in the beforeEach, like what I'm doing with this plugin. To get around this limitation, I had to manually update the response body for a url after the previous call ended (recursively). To do that, I had to break the Cypress rule that is shown in the error message (i.e. calling another cy.route in the onResponse of a cy.route).

TLDR: I had to break some Cypress rules and do some hacky recursion in order for mocking to work for routes with the same URL but different response bodies (I would love any PRs for a better solution to this problem)

I do think this error is very confusing for someone using the plugin to encounter and it make sense for me to add a warning in the ReadMe! Thanks for submitting the bug! 😊 I will update the ReadMe before closing out this ticket if these errors are not breaking your tests!

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 5, 2019

Great explanation.. super confusing indeed. I'll continue to play around with it although it does skip any test after the point the error occurs. I'll go ahead and close and see if I can help in any way!

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 5, 2019

one last qq(sorry): My responses are always empty objects, is that intended?

{
  "user can login successfully": [
    {
      "url": "http://localhost:5100/web-api/v1/size-preferences",
      "method": "GET",
      "status": 200,
      "body": null,
      "response": {}
    }
  ]
}

looks like the response.response.body is of type Blob when I log it
image

any idea how to get the actual response?

@bautistaaa bautistaaa reopened this Jul 5, 2019
@Nanciee
Copy link
Owner

Nanciee commented Jul 11, 2019

Ahh interesting, let me look into this and get back to you!

@bautistaaa
Copy link
Contributor Author

i anticipate its cause im using fetch and I dont delete win.fetch until after your stuff runs in my own beforeEach? will keep ya posted

@Nanciee
Copy link
Owner

Nanciee commented Jul 13, 2019

I did my own test to see the behaviour for the CypressError you are encountering and it looks like it is behaving as expected. Although you do see the error, the tests afterwards are not skipped and it has no other effects on the tests or recordings. This is what it looked like for me:

Screen Shot 2019-07-12 at 9 44 58 PM

All 3 of my tests were producing the CypressError.

Could the test be failing/skipping for other reasons?

@Nanciee
Copy link
Owner

Nanciee commented Jul 13, 2019

As for you second question, it looks like Cypress has some issues with window.fetch and stubbing: https://docs.cypress.io/guides/references/known-issues.html#window-fetch-routing-and-stubbing

When I tried it in my test app, the fetch requests are not showing up in Cypress and not being recorded. Are you doing anything to get it to work? If you can help me reproduce the empty response error, I will be able to put in a fix for it!

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 13, 2019

@Nanciee yep if you look at my original post you'll see this after the record command

cy.visit('/login', {
  onBeforeLoad(win) {
     delete win.fetch;
  },
 });

This will cause the browser to default to XHR!
Would you mind sharing your test code? curious whats causing mine to fail!

I do have a custom command to log me in programmatically via a http so maybe thats it. I've been sidetracked a bit =*( but I plan to jump back into this and see if I can contribute in any way

@Nanciee
Copy link
Owner

Nanciee commented Jul 13, 2019

this is what my test looks like:

it('my first test', function() {
    cy.visit('localhost:4200/login');
    cy.get(':nth-child(2) > .form-control').type('asdfasdf');
    cy.get(':nth-child(3) > .form-control').type('asfasdf');
    cy.get('.btn').click();


    cy.get(':nth-child(2) > .form-control').clear().type('[email protected]');
    cy.get(':nth-child(3) > .form-control').clear().type('12345678');
    cy.get('.btn').click();
    cy.get('.tag-pill');
});

I don't have anything else in the beforeEach!

As for the blob bug, I'll open a separate bug to deal with all other responseType that isn't JSON. Really appreciate you posting the problem!

@Nanciee
Copy link
Owner

Nanciee commented Jul 21, 2019

Some changes were published recently that could have fixed the empty response issue you posted about earlier! Could you test this out on v1.0.13?

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 21, 2019

no dice! also still seeing the issue where it completely kill the test suite

Because this error occurred during a 'before each' hook we are skipping the remaining tests in the current suite: 'Register'

my beforeEach only has this

  beforeEach(() => {
    cy.server();
    /**
     * cypress currently cannot intercept fetch requests, see link for workaround
     * https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/stubbing-spying__window-fetch#readme
     */
    cy.visit('/register', {
      onBeforeLoad(win) {
        delete win.fetch;
      },
    });
  });

however, I am about to swap fetch with axios so that should resolve this I think, will let you know

@Nanciee
Copy link
Owner

Nanciee commented Jul 21, 2019

Hmm I'm also curious what your custom cy. dataQa is doing. If it's calling a cy.route in there, that would cause some problems with stubbing

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 21, 2019

update: moving everything away from fetch fixed the recording issue.

heres the full stack trace of the error I get running the test AFTER the initial record

cypress_runner.js:84963 Uncaught CypressError: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

The command that returned the promise was:

  > cy.visit()

The cy command you invoked inside the promise was:

  > cy.route()

Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.

Cypress will resolve your command with whatever the final Cypress command yields.

The reason this is an error instead of a warning is because Cypress internally queues commands serially whereas Promises execute as soon as they are invoked. Attempting to reconcile this would prevent Cypress from ever resolving.

https://on.cypress.io/returning-promise-and-commands-in-another-command
    at Object.cypressErr (http://localhost:5100/__cypress/runner/cypress_runner.js:84963:11)
    at Object.throwErr (http://localhost:5100/__cypress/runner/cypress_runner.js:84916:18)
    at Object.throwErrByPath (http://localhost:5100/__cypress/runner/cypress_runner.js:84947:17)
    at Object.cy.<computed> [as route] (http://localhost:5100/__cypress/runner/cypress_runner.js:80530:20)
    at Object.$Server.create.onAnyResponse (http://localhost:5100/__cypress/runner/cypress_runner.js:76987:35)
    at onLoadFn (http://localhost:5100/__cypress/runner/cypress_runner.js:84650:28)
    at XMLHttpRequest.<anonymous> (http://localhost:5100/__cypress/runner/cypress_runner.js:84625:25)

FULL TEST

const prefillUserDataAndSubmit = () => {
  cy.dataQa('register_name_input').type('Test User', { force: true });
  cy.dataQa('register_username_input').type('[email protected]', { force: true });
  cy.dataQa('register_password_input').type('password', { force: true });
  cy.dataQa('register_confirm_password_input').type('password', { force: true });
  cy.dataQa('btn_create_account').click();
};

context('Register', () => {
  autoRecord(__filename);
  beforeEach(() => {
    cy.visit('/register');
  });

  it('user can create account successfully', () => {

    prefillUserDataAndSubmit();
    cy.location('pathname').should('equal', '/');
  });
})
Cypress.Commands.add('dataQa', (attr) => {
  return cy.get(`[data-qa="${attr}"]`);
});

no references to route on my end I believe

@bautistaaa
Copy link
Contributor Author

actually looks like it hates my cy.visit in my own beforeEach according to the error

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 22, 2019

Looks like it comes down to when a single endpoint is hit multiple times.

size-preferences is just a GET request that gets triggered twice(legacy code not sure why its triggered twice)
graphql is two POST request with two different bodies which are graphql queries stringified.

image

This caused two size-preferences recording to be entered for a single test, if I remove one of the duplicates it will pass. (any duplicate request I believe)

Second Issue is with graphql requests. It recorded the two fine BUT when it goes to retrieve the recorded responses it appears to always pick whichever one shows up first in the list of recordings(I think still digging)

I'll see if I can dig into the recursion part that you added since you said that was created to avoid this error but I think one key thing is to not record a request if it has the same body and response?

Lmk if you can think of anything!

@Nanciee
Copy link
Owner

Nanciee commented Jul 22, 2019

This seems to be the same issue mentioned in #9! I published another change (v1.1.0) a few minutes ago which is meant to address this. Everything looks good when I tested it out but would love to hear if it fixes your issue!

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 22, 2019

no dice, still errors when two request exist with the same signature in the recording(deleting one will get past it seems at the moment)

It doesn't like my graphql requests since I am sending a stringified graphql query. It will always return whichever shows up first in the list it seems.

I'll see if I can resolve this and make a PR asap cause its a blocker for me atm!

e.g:

{
      "url": "http://localhost:5100/web-api/graphql",
      "method": "POST",
      "status": 200,
      "headers": {},
      "body": {
        "query": "{\n    viewer {\n      collections(limit: 16) {\n        slug\n        title\n      }\n    }\n  }"
      },
      "response": {
        "data": {
          "viewer": {
            "collections": []
          }
        }
      }
    }
{
      "url": "http://localhost:5100/web-api/graphql",
      "method": "POST",
      "status": 200,
      "headers": {},
      "body": {
        "query": "\n    {\n      viewer {\n        currentUser {\n          id\n          username\n          email\n          name\n          size\n          sizeBrand\n          sizeGender\n          sizeUnit\n          braintree_customer_id\n          addresses {\n            id\n            address1\n            address2\n            validation_failed\n            city\n            state\n            name\n            state_code\n            postal_code\n            country\n            country_code\n            phone\n            address_type\n          }\n          billing_infos {\n            id\n            card_brand\n            last_4_digits\n            affirm_checkout_token\n            processor_name\n            braintree_payment_method_token\n            payment_type\n            billing_address {\n              id\n              address1\n              address2\n              validation_failed\n              city\n              state\n              name\n              state_code\n              postal_code\n              country\n              country_code\n              phone\n              validation_failed\n            }\n          }\n        }\n      }\n    }\n  "
      },
      "response": {
        "data": {
          "viewer": {
            "currentUser": null
          }
        }
      }
    },

@bautistaaa
Copy link
Contributor Author

PR for duplicate recording #15

@Nanciee
Copy link
Owner

Nanciee commented Jul 23, 2019

Thanks!! Are you still having issues with the POST requests? I'm having a really hard time reproducing both of these issues you are having. It shouldn't matter what you are sending in the body since it will mock back the response in order that they were called.

Are your graphql requests happening at around the same time? I'm wondering if this is timing related. Since I change the response body in the onResponse hook, it might not have had enough time to switch to the second response body.

@Nanciee
Copy link
Owner

Nanciee commented Jul 23, 2019

I made a publish for v1.1.1@beta to get you to test out the changes! Let me know if it fixes your issue and I will make a full release.

@bautistaaa
Copy link
Contributor Author

bautistaaa commented Jul 23, 2019

Oh let me clarify, when I have duplicate GET requests with the same shape I will get the error screenshotted. My POST requests it also seems to hate

{
      "url": "http://localhost:5100/rpc/finch/assign",
      "method": "POST",
      "status": 200,
      "headers": {},
      "body": {
        "user": {
          "deviceId": "4819e011-f58c-4919-bc00-076e93d9a792",
          "sessionId": "6F05CE45-1A14-478A-A86C-29E0179C5318"
        },
        "experimentId": {
          "name": "styles_trending_web"
        }
      },
      "response": {
        "group": {
          "name": "control",
          "weight": 0.5
        }
      }
    },
    {
      "url": "http://localhost:5100/rpc/finch/assign",
      "method": "POST",
      "status": 200,
      "headers": {},
      "body": {
        "user": {
          "deviceId": "4819e011-f58c-4919-bc00-076e93d9a792",
          "sessionId": "6F05CE45-1A14-478A-A86C-29E0179C5318"
        },
        "experimentId": {
          "name": "recently_viewed_v1"
        }
      },
      "response": {
        "group": {
          "name": "control",
          "weight": 0.5
        }
      }
    }
  it('user can show/hide nav menu', () => {
    cy.visit('/');
    cy.dataQa('nav_overlay').should('not.be.visible');
    cy.dataQa('hamburger_menu').click();
    cy.dataQa('nav_overlay')
      .should('be.visible')
      .click()
      .should('not.be.visible');
  });

image

I'll keep you posted if I ever find a solution but just seems to be a cypress problem stubbing requests with same URL which is weird cause looks like it works for you here #9 but looks like the key difference for me is my status is the same for both

looks like I need to go the querystring route =*(
https://stackoverflow.com/questions/53654626/cypress-stub-response-for-same-route-with-three-different-responses

@bautistaaa
Copy link
Contributor Author

I made a publish for v1.1.1@beta to get you to test out the changes! Let me know if it fixes your issue and I will make a full release.

Yep it works! Have it linked locally as well so I am able to test that way too!

@nicolasiensen
Copy link

Same problem here, the library is unable to record the body or the response of the requests.

I tried the beta version (1.1.1) and it didn't help.

I'm using fetch to perform API calls, however, I'm deleting it like suggested by the Cypress team, so the requests have type xhr instead of fetch.

@hkarambizi
Copy link

Screenshot 2019-10-14 11 33 42

I also may be having the same issue. It fails as soon as it hits a point in which two multiple requests (that return the same payload) occur back to back.

However, this is also occurring in my beforeEach. I'd like to try the v1.1.1@beta to see if that helps.

How can I access this? @bautistaaa / @Nanciee

@bautistaaa
Copy link
Contributor Author

Screenshot 2019-10-14 11 33 42

I also may be having the same issue. It fails as soon as it hits a point in which two multiple requests (that return the same payload) occur back to back.

However, this is also occurring in my beforeEach. I'd like to try the v1.1.1@beta to see if that helps.

How can I access this? @bautistaaa / @Nanciee

If the url's are the exact same you will need to come up with a way to make those url's unique when stubbing.

@hkarambizi
Copy link

If the url's are the exact same you will need to come up with a way to make those url's unique when stubbing.

This specific request appears to be triggered by legacy code / duplication. This is fired when the login page is loaded. It isn't a request I am making in my test code. Is there a way to handle this or even skip duplicates?

@bautistaaa
Copy link
Contributor Author

We currently have a wrapper around our api calls that allows us to append a querystring in the context of cypress. you may want to look into doing something similar.

@hkarambizi
Copy link

Ok gotcha! Do you have an example of this implementation I can take a look at? Trying to wrap my head around it.

@bautistaaa
Copy link
Contributor Author

at its most basic form it will look something like this

const createCypressUrlOrDefault = (url, intent) => {
  if (!url) {
    throw Error('Url must be supplied!');
  }

  if (root.Cypress) {
    if (!intent) {
      return url;
    }

    return `${url}?${intent}`;
  }

  return url;
};

export default createCypressUrlOrDefault;

@Nanciee Nanciee closed this as completed Sep 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants