cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
46.66k stars 3.16k forks source link

Cypress crash: Error: Failed to parse or set cookie named #1321

Closed corneliutusnea closed 5 years ago

corneliutusnea commented 6 years ago

Is this a Feature or Bug?

Bug

Current behavior:

Sometimes when I go to my website using cy.visit(...) if the site returns a redirect and a clear cookie (e.g. logout) the Cypress crashes and logs:

`{ [Error: Failed to parse or set cookie named "aspAuth".] name: undefined }
undefined

Desired behavior:

How to reproduce:

Not sure, but these are the relevant headers my site returns when I navigate to it:

Cache-Control:no-cache, no-store, must-revalidate
Content-Length:0
Date:Fri, 16 Feb 2018 03:37:23 GMT
Expires:-1
Location:https://appcenter.intuit.com/connect/oauth2?...
p3p:CP="Company does not have a P3P policy"
Pragma:no-cache
Set-Cookie:Provision.ConnectionInfo=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax
Set-Cookie:XSRF-TOKEN=rzOLRz36OX_A5wQuhVPKkssxg7fCwZOa_iMi-Nx95-1RRU; path=/; secure; samesite=lax
Set-Cookie:XSRF-TOKEN=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax
Set-Cookie:aspAuth=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax
Strict-Transport-Security:max-age=31536000
X-Content-Type-Options:nosniff
X-Request-Id:0HLBKQPQAPOUV:00000003
X-Time:1
X-XSS-Protection:1

Test code:

cy.visit('https://localdev/start/app');
corneliutusnea commented 6 years ago

So, I can reproduce this quite easily. Just try to delete/expire a cookie that was never set from the server and it will crash the complete Cypress app.

Set-Cookie: Name=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax
bahmutov commented 6 years ago

Thank you for the reproducible example

Sent from my iPhone

On Feb 15, 2018, at 23:00, corneliutusnea notifications@github.com wrote:

So, I can reproduce this quite easily. Just try to delete/expire a cookie that was never set from the server and it will crash the complete Cypress app.

Set-Cookie: Name=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

lukemadera commented 6 years ago

I have this issue as well. @corneliutusnea did you find a workaround? It's making cypress unusable.

corneliutusnea commented 6 years ago

@lukemadera no, I didn't find a workaround. I gave up and moved to TestCafe that worked better for my needs.

lukemadera commented 6 years ago

Thanks for the prompt rely. @bahmutov I don't intend to give up that easily, but do need a fix - any progress on this?

brian-mann commented 6 years ago

We'll fix this in the next release.

lukemadera commented 6 years ago

Great, thanks Brian! Any ETA on when that will be?

brian-mann commented 6 years ago

Not sure yet - if the 3.0 is delayed any longer than this week we'll issue a patch release with these fixes.

lukemadera commented 6 years ago

Awesome thanks - so it seems either way it should be in by the end of the week!

lsplogi commented 6 years ago

Is the patch released? I'm running into the same problem

darmalovan commented 6 years ago

Having the same problem.

brian-mann commented 6 years ago

I looked into this issue and cannot reproduce. There is not enough information in the issue.

I tried recreating by setting cookies during a redirect which do not exist. This does not create the crash. To reproduce this we will need a full reproducible example either by specifying the exact cypress commands, server responses (including all headers), redirects, etc.

A STACK TRACE WOULD ALSO BE HELPFUL

Please try to minimize to the bare minimum. For instance, this does not cause a crash:

// server side code
app.get("/redirect", (req, res) => {
  res
  .set({
    "Set-Cookie": "aspAuth=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax"
  })
  .redirect("http://localhost:3501/fixtures/dom.html")
})

// cypress code
cy.visit("/redirect")

screen shot 2018-03-25 at 8 19 02 pm

brian-mann commented 6 years ago

I tried various combinations related to setting cookies and their values and none of them cause the crash.

brian-mann commented 6 years ago

@darmalovan please post the steps to reproduce... I cannot guarantee I will look at this issue again for awhile.

brian-mann commented 6 years ago

If nobody posts reproducible steps this issue will not be fixed. Really... anyone...?

lukemadera commented 6 years ago

For me it was trigger via logout and that call was not strictly necessary so I just removed it so found a work around for now. Additionally, now I'm getting a cookie too large / can't clear cookies issue that is failing the tests sooner so I can't even get anywhere, so Cypress has been down / blocked for me completely. See https://github.com/cypress-io/cypress/issues/1547 @brian-mann how do I manually set cookies / NOT have Cypress automatically set them for me?

itaykotler-fundbox commented 6 years ago

@lukemadera, @corneliutusnea, I had the same problem and figured a workaround: cy.window().then((win) => { win.location = "/logout"; });

lujianlujian commented 6 years ago

I am also having the same issue. Cypress 2.1.0 Chrome 66 OSX 10.12.6

Just run this test and it crash:

describe('crash issue https://github.com/cypress-io/cypress/issues/1321', function() {
    it.only('go into create step2', function() {
      cy.visit('https://www.surveymonkey.com') //working fine
      cy.visit('https://www.surveymonkey.com/create/?sm=JyO9zQ7UD4cy1qYivEKDxbW5vSI_2BIVzBnqW7paZOcEU_3D') //crashing when trying to access a URL requiring login
    })
})

Would really like this to be fixed. Attached a debug log here: https://gist.github.com/lujianlujian/aa2f4aa22c194c55c776d6cc1421bb5c

sillpa-polasani commented 6 years ago
describe('My First Test', function() {
  it('Visits the Kitchen Sink', function() {
    cy.visit('https://www.paypal.com')
  })
})
{ [Error: Failed to parse or set cookie named "X-PP-SILOVER".] name: undefined }

I get this error - any idea how to solve this ?

HeyDaniyar commented 6 years ago

Cypress 2.1.0 Chrome 66 OSX 10.12.6

I am also having the same issue with the cy.request()

{ [Error: Failed to parse or set cookie named "xso".] }

I have used Charles, an HTTP monitor to check all network requests sending from my environment. One interesting thing I found is when I use cy.request(https://xxx/api/order/campaigns/xxx) to send a request, I can see that I have received the right response in Charles dashboard, while the cypress console shows the error as below:

Error:     CypressError: cy.request() failed trying to load:

https://xxx/api/order/campaigns/xxx

We attempted to make an http request to this URL but the request failed without a response.

We received this error at the network level:

  > Failed to parse or set cookie named "xso".

-----------------------------------------------------------

The request we sent was:

Method: GET
URL: https://xxx/api/order/campaigns/xxx

-----------------------------------------------------------

Common situations why this would fail:
  - you don't have internet access
  - you forgot to run / boot your web server
  - your web server isn't accessible
  - you have weird network configuration settings on your computer

The stack trace for this error is:

undefined

Would really like this to be fixed. Thanks !

HeyDaniyar commented 6 years ago

Cypress 2.1.0 (also crashed in Cypress 3.0.1) Chrome 66 OSX 10.12.6

How to reproduce

As @sillpa-polasani and @lujianlujian describes, this crash can be easily reproduced by these cases:

describe('My First Test', function() {
  it('Visits the Kitchen Sink', function() {
    cy.visit('https://www.paypal.com')
  })
})

describe('crash issue https://github.com/cypress-io/cypress/issues/1321', function() {
    it.only('go into create step2', function() {
      cy.visit('https://www.surveymonkey.com') //working fine
      cy.visit('https://www.surveymonkey.com/create/?sm=JyO9zQ7UD4cy1qYivEKDxbW5vSI_2BIVzBnqW7paZOcEU_3D') //crashing when trying to access a URL requiring login
    })
})

Workaround:

After adding some hard codes in setCookie function incookies.js, everything seems just back to normal, including that two reproduce cases.

path: /Cypress/3.0.1/Cypress.app/Contents/Resources/app/packages/server/lib/automation/cookies.js

  (function() {
    var COOKIE_PROPERTIES, Promise, _, cookies, extension, normalizeCookieProps, normalizeCookies;

    _ = require("lodash");

    Promise = require("bluebird");

    moment = require('moment');

    extension = require("@packages/extension");

    COOKIE_PROPERTIES = "name value path domain secure httpOnly expiry".split(" ");

    normalizeCookies = function(cookies, includeHostOnly) {
      return _.map(cookies, function(c) {
        return normalizeCookieProps(c, includeHostOnly);
      });
    };

    normalizeCookieProps = function(props, includeHostOnly) {
      var cookie;
      if (!props) {
        return props;
      }
      cookie = _.chain(props, COOKIE_PROPERTIES).pick(COOKIE_PROPERTIES).omitBy(_.isUndefined).value();
      if (includeHostOnly) {
        cookie.hostOnly = props.hostOnly;
      }
      switch (false) {
        case props.expiry == null:
          delete cookie.expiry;
          cookie.expirationDate = props.expiry;
          break;
        case props.expirationDate == null:
          delete cookie.expirationDate;
          delete cookie.url;
          cookie.expiry = props.expirationDate;
      }
      return cookie;
    };

    cookies = function(cyNamespace, cookieNamespace) {
      var isNamespaced;
      isNamespaced = function(cookie) {
        var name;
        if (!(name = cookie != null ? cookie.name : void 0)) {
          return cookie;
        }
        return name.startsWith(cyNamespace) || name === cookieNamespace;
      };
      return {
        getCookies: function(data, automate) {
          var includeHostOnly;
          includeHostOnly = data.includeHostOnly;
          delete data.includeHostOnly;
          return automate(data).then(function(cookies) {
            return normalizeCookies(cookies, includeHostOnly);
          }).then(function(cookies) {
            return _.reject(cookies, isNamespaced);
          });
        },
        getCookie: function(data, automate) {
          return automate(data).then(function(cookie) {
            if (isNamespaced(cookie)) {
              throw new Error("Sorry, you cannot get a Cypress namespaced cookie.");
            } else {
              return cookie;
            }
          }).then(normalizeCookieProps);
        },
        setCookie: function(data, automate) {
          var cookie;
          if (isNamespaced(data)) {
            throw new Error("Sorry, you cannot set a Cypress namespaced cookie.");
          } else {
            cookie = normalizeCookieProps(data);
            cookie.url = extension.getCookieUrl(data);
            if (data.hostOnly) {
              cookie = _.omit(cookie, "domain");
            }
            /* hard code start */
            var currentTime = moment();
            var expires = data.expires;
            var expiry = data.expiry;
            if(( expiry && !expires) || (!expiry && expires)) {
              return;
            }

            if(expires &&  moment(expires) < currentTime ) {
              return;
            }
            /* hard code ends */
            return automate(cookie).then(normalizeCookieProps);
          }
        },
        clearCookie: function(data, automate) {
          if (isNamespaced(data)) {
            throw new Error("Sorry, you cannot clear a Cypress namespaced cookie.");
          } else {
            return automate(data).then(normalizeCookieProps);
          }
        },
        clearCookies: function(data, automate) {
          var clear;
          cookies = _.reject(normalizeCookies(data), isNamespaced);
          clear = function(cookie) {
            return automate("clear:cookie", {
              name: cookie.name
            }).then(normalizeCookieProps);
          };
          return Promise.map(cookies, clear);
        },
        changeCookie: function(data) {
          var c, msg;
          c = normalizeCookieProps(data.cookie);
          if (isNamespaced(c)) {
            return;
          }
          msg = data.removed ? "Cookie Removed: '" + c.name + "'" : "Cookie Set: '" + c.name + "'";
          return {
            cookie: c,
            message: msg,
            removed: data.removed
          };
        }
      };
    };

    cookies.normalizeCookies = normalizeCookies;

    cookies.normalizeCookieProps = normalizeCookieProps;

    module.exports = cookies;

  }).call(this);

I strongly suspect this unexpected error is related to cookie expires time. But I didn't figure out the root cause.

@brian-mann Can you guys help to check for this issue agian? Seems this is such a common usage scenario for Cypress request() and visit(), there must be lots of developers encounter the same confusion here.

Great appreciate!

brian-mann commented 6 years ago

I have spent several hours tonight reproducing this. I appreciate you providing a reproducible example - although many of our users mentioned having this problem we could not easily reproduce on our own.

Problem 1: I have yet to determine the exact root cause. Yes, cookies are failing to be set, but the actual reason why is much more complicated and is happening due to the way Cypress is handling visit resolution much earlier in the process.

Problem 2: I'm also wondering why those failed promises aren't being correctly handled / caught. It appears that all Promises are attached correctly and therefore the exception should be handled and sent back to the runner correctly. This error should not be crashing Cypress.

lukemadera commented 6 years ago

Thank you so much for putting all that time and effort in Brian! Sorry this is such a tricky issue.

tamtamchik commented 6 years ago

@brian-mann did you see issue #1264? It might be the clue.

I'm also getting Failed to parse or set cookie error when trying to preserve secured cookies during the tests.

brian-mann commented 6 years ago

We're aware of what the problem is. It's not a quick fix but it's an important one that's high up on our radar.

We are going to release 3.1 first and then circle back to this. It requires shifting around a bunch of things to fix.

debmalya commented 6 years ago

I just landed in cypress.io and met the above error.

If i run it through Electron 59 , did not get this issue. Only faced it with "Chrome 67".

xlenz commented 6 years ago

@debmalya nope, same issue in chrome 68

hiredgun commented 6 years ago

I've got the same problem :( Chrome 68

Azeirah commented 6 years ago

@HeyDaniyar I'd like to mention that I encountered the same problem, receiving "failed to parse or set ...". I used your two code snippets, but they failed due to the moment library missing.

I monkey-patched them to look like this:

setCookie: function(data, automate) {
  var cookie;
  if (isNamespaced(data)) {
    throw new Error("Sorry, you cannot set a Cypress namespaced cookie.");
  } else {
    cookie = normalizeCookieProps(data);
    cookie.url = extension.getCookieUrl(data);
    if (data.hostOnly) {
      cookie = _.omit(cookie, "domain");
    }

    /* hard code start */
    var currentTime = +new Date();
    var expires = data.expires;
    var expiry = data.expiry;
    if(( expiry && !expires) || (!expiry && expires)) {
      return;
    }

    if(expires && +new Date(expires) < currentTime ) {
      return;
    }
    /* hard code ends */
    return automate(cookie).then(normalizeCookieProps);
  }
},
setJarCookies: function(jar, automationFn) {
  var setCookie;
  setCookie = function(cookie) {
    var ex;
    cookie.name = cookie.key;
    if (cookie.name && cookie.name.startsWith("__cypress")) {
      return;
    }
    switch (false) {
      case cookie.maxAge == null:
        cookie.expiry = moment().unix() + cookie.maxAge;
        break;
      case !(ex = cookie.expires):
        cookie.expiry = moment(ex).unix();
    }
    return automationFn("set:cookie", cookie).then(function() {
      return Cookies.normalizeCookieProps(cookie);
     // when add catch funtion to the promise,
     // the request function would return result normally.
     // But  it will still show the error in  cypress node console
    }).catch(function(err) {
      console.log('**** Get Error in Set Coookie *****', err);
    });
  };
  return Promise["try"](function() {
    var store;
    store = jar.toJSON();
    return Promise.each(store.cookies, setCookie);
  });
},
kentcdodds commented 6 years ago

For those landing here after 3.1.0 was released, the location of the Cypress executable (Cypress.app) was changed and you can find the new location by running npx cypress verify and it'll print the path :)

For me it's here: /Users/kdodds/Library/Caches/Cypress/3.1.0/Cypress.app

HeyDaniyar commented 6 years ago

@Azeirah
You need to import themoment package first at the beginning of the function, just like this:

 // Contents/Resources/app/packages/server/lib/automation/cookie.js

(function() {
    var COOKIE_PROPERTIES, Promise, _, cookies, extension, normalizeCookieProps, normalizeCookies;

    _ = require("lodash");

    Promise = require("bluebird");

    moment = require('moment');

     ....

BTW, this solution is only tested in Cypress 3.0.1 , not sure it would still work in other versions.

adokania commented 6 years ago

Hi Brian, We at Kensho are running into this issue as well and would like to know if there is a quick workaround or an estimate on 3.1.1 will be released? Thanks

andezzat commented 6 years ago

This is also an issue for me when trying to set cookies with empty content and expiry date of 1970... I get Setting cookie failed.

PS: I can't use cy.clearCookies because the cookies are httpOnly.

cannibalcow commented 5 years ago

Does anyone have a workaround for this bug. It's a showstopper for my team right now. We are on the brink of abandoning cypress for this.

xlenz commented 5 years ago

@cannibalcow you'd better abandon if you can, regular webdriver is much much better. It takes time on the beginning to setup things, but then it works way better and faster (and supports headless chrome).

Cypress is good only for something really small. It is good on the first step, but then it is much more painful to work with. You'll have lot's of showstoppers

HeyDaniyar commented 5 years ago

@cannibalcow Have you ever tried my workaround? At least now it works for me.

cannibalcow commented 5 years ago

@cannibalcow Have you ever tried my workaround? At least now it works for me.

Yeah we are using your workaround at the moment, but it's not a good long term solution.

drod3763 commented 5 years ago

I'm able to reproduce this as well. It just so happens it fails to parse on a Cookie with a expiration date in the past.

vincentvb commented 5 years ago

I can also confirm that this issue is stopping my team from getting our cypress test suite off the ground. I know a fix is in the works, but is there an ETA by chance?

vinhlh commented 5 years ago

I have this issue when having consecutive visit commands,

cy.visit('/pageA')
  .doSomething()
  .visit('/anotherPage')

and it's also a bad practice when implenting e2e tests, then I just change it to clicking UI items.

brian-mann commented 5 years ago

We're prioritizing this issue in this sprint. It's long overdue to be fixed.

brian-mann commented 5 years ago

We have made a lot of progress on this issue, and may have a fix out in the next day or two. We had to rewrite the visit resolving layer of Cypress, which will help more things than just this bug as well.

paglajewel commented 5 years ago

Hi @brian-mann, we are doing a POC on using Cypress for our apps. We are also running into this issue. What is the eta on releasing this fix? You mentioned 3 days ago "may have a fix out in the next day or two". Its a blocker for us.

drewbrend commented 5 years ago

I also ran into this issue, but encountered it when sending a POST cy.request to authenticate with our SSO provider - okta. Looks like someone else hit the same blocker as well: https://github.com/cypress-io/cypress/issues/1489#issuecomment-418871535

paglajewel commented 5 years ago

Hey @drewbrend, you can try using password grant type in okta if you are doing via cy.request. https://developer.okta.com/blog/2018/06/29/what-is-the-oauth2-password-grant

jennifer-shehane commented 5 years ago

The code for this is done, but this has yet to be released. We'll update this issue and reference the changelog when it's released.

jm2242 commented 5 years ago

Interestingly, We are now getting this problem (or similar) with the upgrade to 3.1.1. Everything works in 3.1.0 for us. We are getting this error in a few scenarios.

Logging in

~/Quorum/quorum-site on  cypress-docs! ⌚ 11:33:02
$ npm run cypress                                                                                                                                                                              ‹ruby-2.4.1›

> quorum-site@2.8.3 cypress /Users/jonathanmares/Quorum/quorum-site
> cypress open

GET /__/ 200 28.124 ms - -
GET /__cypress/runner/cypress_runner.css 200 52.233 ms - -
GET /__cypress/runner/cypress_runner.js 200 383.054 ms - -
GET /__cypress/runner/fonts/fontawesome-webfont.woff2?v=4.6.3 200 1.634 ms - 71896
GET /__cypress/iframes/integration/main/login_spec.js 200 14.349 ms - 733
GET /__cypress/tests?p=cypress/integration/main/login_spec.js-216 200 682.720 ms - -
GET /__cypress/tests?p=cypress/support/index.js-177 200 840.621 ms - -
{ [Error: Failed to parse or set cookie named "current_version".] name: undefined }
undefined
(quorum)

Our configuration

This is our index.js file:

// cypress/support/index.js
// uncomment below to have Cypress log when Cookies change
Cypress.Cookies.debug(true)

// Make it possible to skip auth across tests
Cypress.Cookies.defaults({
    whitelist: ["qsesid", "csrftoken"],
})

(interestingly, with our without the defaults, we still get the crash. Our login configuration is the following:

Cypress.Commands.add('loginByCSRF', ({csrfToken, username, password}) => {
    cy.request({
        method: 'POST',
        url: '/login/',
        failOnStatusCode: false, // dont fail so we can make assertions
        form: true, // we are submitting a regular form body
        body: {
            username,
            password,
        },
        headers: {
            "X-CSRFTOKEN": csrfToken,
            // django spells this referer - very odd
            // this is required for Django to let this endpoint authenticate
            "referer": Cypress.config().baseUrl
        }
    })
})

/**
 * This function ("login") logs in to Quorum programmatically
 * @name login
 * @function
 * @param {String} username - a username to login with
 * @param {String} password - a password to log in with
 * @param {Boolean} useCurrentSessionIfPossible - if qsesid and csrftoken are defined, don't log in again
 * @param {Boolean} shouldVisitPath - Provide a way to skip visiting path if desired
 *
 * You might want to pass shouldVisitPath=false if you are refreshing the page in your beforeEach() block,
 *  in which case the initial visit is wasteful
 */
Cypress.Commands.add(
    "login",
    ({
        username = Cypress.config("TESTING_USER"),
        password = Cypress.config("TESTING_PASSWORD"),
        path = "/home/",
        shouldVisitPath = true,
        useCurrentSessionIfPossible = false,
    } = {}) => {
    if (useCurrentSessionIfPossible && cy.getCookie("qsesid").value && cy.getCookie("csrftoken").value) {
        console.log("Already logged in, not logging in again")
        return
    }
    // make a request to get a csrftoken
    cy.request("/login")
        .its("headers")
        .then((headers) => {
            const csrfToken = headers["set-cookie"][0].split(";")[0].replace("csrftoken=", "")
            cy.loginByCSRF({ csrfToken, username, password }).then(() => {
                shouldVisitPath && path && cy.visit(path)
            })
        })
})

here's the beginning of a test file that fails:

describe("Home page tests", function () {
    before(function() {

        // make sure we're logged in. Let beforeEach() handle visiting the home page
        cy.login({
            shouldVisitPath: false,
            useCurrentSessionIfPossible: true // if auth cookies are available, don't bother re-logging in
        })
    })
    beforeEach(function () {

        // set up server to listen for requests
        cy.server()

        // alias requests to newperson so we can wait until they resolve
        cy.route("GET", "/api/newperson/**").as("getNewPerson")

        // before each test, go to the home page
        cy.visit("/")
    })

Not logging in

This time it doesn't crash, it just gives an error in the GUI: image

jm2242 commented 5 years ago

@jennifer-shehane doesn't like I have permission to re-open the issue, should I make a separate issue for this?

jennifer-shehane commented 5 years ago

We're aware of a regression with cookies in 3.1.1 here: https://github.com/cypress-io/cypress/issues/2724 not sure if this is related.

jm2242 commented 5 years ago

We're still getting this error in all versions after 3.1.0, including latest 3.1.3 release