简体   繁体   中英

Handling multiple Promises in Cypress command

What I have

I have command that can request token and it resolves promise on response. Once the promise is being resolved - I put the token into the sessionStorage in SomeTest.js. It works. But I have to put exactly same cy.window()... in every test.

SomeTest.js:

before(()=>{
  cy.login().then(res=>
    cy.window().then((window) => {
       window.sessionStorage.setItem('token', JJSON.stringify(res))
    })
  );
});

command in commands.js

Cypress.Commands.add('login', function () { 
    fetcher.emit('tokenRequest',{email:'dummy@mail.com', password:'dummy'});
    return new Cypress.Promise((resolve, reject) => {
        fetcher.on('onTokenResponse',function(response) {
            resolve(response);
        });
    });
});

What I want

I would like to set sessionStorage in command login() too.

SomeTest.js:

before(()=>{
  cy.login();
});

command in commands.js

Cypress.Commands.add('login', function () { 
    fetcher.emit('tokenRequest',{email:'dummy@mail.com', password:'dummy'});
    return new Cypress.Promise((resolve, reject) => {
        fetcher.on('onTokenResponse',function(response) {
            cy.window().then((window) => {
                window.sessionStorage.setItem('token', JSON.stringify(response));
                resolve(response);
            })
        });
    });
});

But it throws error : Uncaught CypressError: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise. , The cy command you invoked inside the promise was:> cy.window() .


I understand the problem is that I am trying to call another promise which is not being returned. But how can I achieve something like this?

I need to

  1. emit tokenRequest
  2. wait for the tokenResponse
  3. get cy.window() and set some sessionStorage item

Edit:

I've found walkaround and I've wrapped login into another function/command which seems to be working but I am not sure how pretty is this solution.

Cypress.Commands.add('loginFull', function () {
    return cy.login().then((response)=>{
        cy.window().then((window) => {
            window.sessionStorage.setItem('token', JSON.stringify(response));
        });
    });
});
Cypress.Commands.add('login', function () { 
    function waitForSocket(callback) {
        fetcher.emit('tokenRequest',{email:'dummy@mail.com', password:'dummy'});
        fetcher.on('onTokenResponse',function(response) {
            callback(response);
        });
    }

    waitForSocket((res) => {
        cy.window().then((window) => {
           window.sessionStorage.setItem('token', JJSON.stringify(res))
        })
    })
});

The error points to this: Cypress commands are already promises and they will wait/resolve themselves . So I think there should not be a wrapping promise for the fetcher. Edit: I think you can wrap the socket tasks in a callback function.

One way that works is to move the cy.window() command to the outermost level of the custom command, so you are not trying to use it within the Promise.

Test

describe('web socket event handler', () => {

  let fetcher;
  before(() => {
    const io = require('socket.io-client'); 
    fetcher = io.connect('http://127.0.0.1:5000');
    cy.window().then(win => {
      win.sessionStorage.clear();  // ensure clean before test
    })
  })

  Cypress.Commands.add('login', function () { 
    cy.window().then(win => {
      fetcher.emit('tokenRequest',{email:'dummy@mail.com', password:'dummy'});
      return new Cypress.Promise((resolve, reject) => {
        fetcher.on('onTokenResponse',function(response) {
          win.sessionStorage.setItem('token', JSON.stringify(response));
          return resolve(response);
        });
      });
    });
  });

  it('waits for token', () => {
    cy.login().then(_ => {
      cy.window().then(win => {
        console.log(win.sessionStorage.getItem('token'))
        // logs "Response: dummy@mail.com"
      })
    })
  })
})

Server

io.on('connection', function(socket){
  socket.on('tokenRequest', function(msg){
    setTimeout(() => {
      io.emit('onTokenResponse', `Response: ${msg.email}`);
    }, 2000)
  });
});

Ideally would want to make the login command wait on the fetcher event (with timeout?).

I tried this approach Spying on the DOM API , but no luck so far.


Another way is to set a flag within the event listener, and add a command to the queue that waits on it.

Cypress.Commands.add('login', function () { 
  cy.window().then(win => {
    fetcher.emit('tokenRequest',{email:'dummy@mail.com', password:'dummy'});
    let flag = { done: false };
    fetcher.on('onTokenResponse',function(response) {
      win.sessionStorage.setItem('token', JSON.stringify(response));
      flag.done = true;
    })
    cy.wrap(flag).its('done').should('equal', true); // wait here until done
  });
});

Now you can call login synchronously

it('waits for token', () => {
  cy.login();
  cy.window().then(win => {
    console.log(win.sessionStorage.getItem('token'))
    // logs "Response: dummy@mail.com"
  })
})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM