cypress-io / cypress

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

returning a promise in a custom command should throw #435

Closed brian-mann closed 7 years ago

brian-mann commented 7 years ago

You cannot return a 3rd party promise from a custom command because this breaks the chaining between cypress commands. This is because the .then methods are not the same.

It is an anti-pattern anyway because all cypress commands are promises.

For instance doing this:

Cypress.addParentCommand('login', function (email, password) {
  return new Cypress.Promise((resolve) => {
    cy.request({
      method: 'POST',
      url: '/login',
      body: {email, password}
    })
    .then(result => {
      resolve(result.body);
    })
  })
})

...can simply be rewritten as:

Cypress.addParentCommand('login', function (email, password) {
  cy.request({
    method: 'POST',
    url: '/login',
    body: {email, password}
  })
  .its("body")
})
Andrew1431 commented 7 years ago

Dual Command

export const createProductWithStock = (context, products) => {
  validate(products);

  return cy.goToInventory({ tab: 'purchases' })
    .get('button.absolute-header-position').click() // Create PO
    .get('div.purchase-order-modal input[name=vendorId]').type('west') // Type vendor name
    .get('a.result.active div.title').click() // Pick vendor from dropdown.
    .then(createProductLineItems(products)) // Add and receive line items.
    .get('div.payment-block a.close-modal').click()
    .then(walkthroughProductCreateModals(products))
    .then(getStockInformation(products));
};

Actual Test

... describe, beforeEach, etc
    it.only('should be able to place an order with all unit types', function() {

      cy.createProductWithStock([{
        type: 'unit',
        name: 'Chicken Butts',
        quantity: 500
      }]).then(result => {
        console.log('Result: ', result);
        expect(result.length).to.equal(1);
      });

    });

All of these actions are promise resolvers etc.

All of this code works up until the assertion. Result is undefined.

const getStockInformation = (products) => {
  return () => {
    // This will jump around the DOM looking for the values it needs for stocks.
    // How can I resolve a certain value? (Will just be a simple array of objects).
    // I'm not even getting the `true` value here.
    return Cypress.Promise.resolve(true);
  };
};

I'm not even getting the 'true' value. How can I achieve this? It's completely necessary. All the stock information is found via the UI so it should be possible, but it appears as though its not.

Andrew1431 commented 7 years ago

I'm very confused as to how all your chaining / sequences work! Would love to know more.

brian-mann commented 7 years ago

We are currently refactoring the custom command interface to make all of this easier. There is a non-documented cy.chain command which will make subjects available outside of the custom command.

Here is a comment where I show this happening. You likely need to add cy.chain to every custom command you write.

https://github.com/cypress-io/cypress/issues/198#issuecomment-281164094

You'll see in the next couple updates a complete overhaul of the custom command interface + proper docs and examples. You won't have to do the cy.chain either.

brian-mann commented 7 years ago

Also as an aside - you likely don't want to use your UI to build up state. It's totally fine to do these kinds of actions in a single spec file, but we disagree this being the best approach across multiple files. You're essentially trying to replicate the Page Object Pattern which works well for Selenium but is unnecessary (and slow) to do in Cypress.

It's much better to basically issue directly cy.request to your backend to generate the state, and then cy.visit once everything is in place. In other words, when you're testing the feature use the UI, but if its part of normal setup/seeding for use in other features you don't use the UI.

When you approach it like this there's much less need to try to "compose" various commands together and instead you can achieve the same result in 1 or 2 cy.request.

We'll have more opinions and examples of this coming pretty soon. Our docs are being updated pretty regularly now.

Andrew1431 commented 7 years ago

Those things would be lovely to do if this wasn't Meteor we were developing in ._.

Thank you for the timely response!

WHOAH! Just punched this into my code, and since all the sub-code is promise based, seemed to have worked right out of the box.

export const createProductWithStock = (context, products) => {
  validate(products);

  cy.chain()
    .goToInventory({ tab: 'purchases' })
    .get('button.absolute-header-position').click() // Create PO
    .get('div.purchase-order-modal input[name=vendorId]').type('west') // Type vendor name
    .get('a.result.active div.title').click() // Pick vendor from dropdown.
    .then(createProductLineItems(products)) // Add and receive line items.
    .get('div.payment-block a.close-modal').click()
    .then(walkthroughProductCreateModals(products))
    .then(getStockInformation(products));
};
Andrew1431 commented 7 years ago

Thank you very much! :+1: The whole team is absolutely loving working with Cypress, so thank you for all your guys hard work!

brian-mann commented 7 years ago

Going with this as the error message...

screen shot 2017-08-24 at 5 13 57 pm

brian-mann commented 7 years ago

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

brian-mann commented 7 years ago

Fixed in 0.20.0

mattblackdev commented 6 years ago

@Andrew1431 I read up above your team is using cypress with a Meteor app. How are you accessing Meteor inside your tests?

import { Meteor } from 'meteor/meteor' fails in the cypress environment because it's not actually a node module.

I've tried importing from all kinds of crazy paths in the .meteor/local directory. When I run meteor normally, in the browser console I can require('meteor/meteor') all day long. But in Cypress's browser require is undefined.

Thanks, Matt

Andrew1431 commented 6 years ago

Hey! Unfortunately I’m not with that company anymore and I have no recollection of how we did this :)

brian-mann commented 6 years ago

Browsers have no notion of require. Browsers must have a build process to parse the dependency tree and generate JS files. Cypress does this with browserify or you can swap this out with webpack.

You cannot import Meteor that way because it would create a whole new instance which has nothing to do with the Meteor instance your app is using.

In order to access the Meteor in your application is the same way you'd access it from the dev tools - you just set it on window and then access it with cy.window().then((win) => win.Meteor )

panneerselvamc commented 5 years ago

This Function returns nothing when i call this function kindly help me to overcome this

const search=()=>{
  cy.get('body').then(async ($body)=>{
    let flag =$body.text().includes("LOGIN")

    if (flag) {
      cy.log("in if")
      return(false)
    } else {
      cy.wait(4000)
      cy.log("else")
      return(true)
    }
  })
}
jennifer-shehane commented 5 years ago

@panneerselvamc Can you include the test code you've written where you call the function?