Cypress: Custom Commands that return void are still chainable.

Created on 11 Aug 2019  路  6Comments  路  Source: cypress-io/cypress

When I have a custom command defined as:

myCommand = () => { 
  return void;
}

And I use it like so:

Cy.myCommand().pause(). 

I would expect to hit a run time error since my custom command myCommand doesn't return a Cypress.Chainable<T>,

But instead the pause() command executes after I chain if off of my void method, and I am able to continue chaining even further.

Most helpful comment

I respect the design decision but it kinda twists a language construct and makes it even worse for novice developers to fully grasp the concepts behind a thenable object (like promises).

JavaScript already has many quirks and adding _implicit returns_ can have its benefits, but it also can mislead developers (both novice and experienced) if they don't know the internals of the framework. All in all, my expectations when writing any code is that the _language restrictions and rules_ to be respected, and this is not the case.

All 6 comments

Update
I searched the docs to see if this behavior was called out anywhere as by design, but did not find any such examples.

A command will always return a Cypress.Chainable<T>. If I'm not mistaken your example returns Cypress.Chainable<void>

@Lakitna Does that mean there is an implicit cy.wrap happening on the return type then?

Kind of, yes.

A Cypress.Chainable<T> will always return a Cypress.Chainable<T>, the difference is in what T represents. T can be extracted using another Cypress.Chainable. This pattern is a lot like the one you'll find in promises.

An example:

// Create a new `Cypress.Chainable<T>` where T = undefined
// Return `Cypress.Chainable<undefined>`
cy

  // Unpack `Cypress.Chainable<undefined>` and grab the subject of type `undefined`
  // Pick up the value of type `string`. This is the value we'll yield.
  // Return `Cypress.Chainable<string>`
  .wrap('foo')

  // Unpack `Cypress.Chainable<string>` and grab the subject of type `string`
  // Pass the value of type `string` to the callback function and execute the callback
  // Pick up the returned value of the callback function. This is the value we'll yield
  // Return `Cypress.Chainable<number>`
  .then((val) => {
    return 123;
  });

Hope this makes it a bit clearer :)

I respect the design decision but it kinda twists a language construct and makes it even worse for novice developers to fully grasp the concepts behind a thenable object (like promises).

JavaScript already has many quirks and adding _implicit returns_ can have its benefits, but it also can mislead developers (both novice and experienced) if they don't know the internals of the framework. All in all, my expectations when writing any code is that the _language restrictions and rules_ to be respected, and this is not the case.

If you want to discuss the Promise decisions made in the API - there's at least some discussion on using await or making thenables more apparent here: https://github.com/cypress-io/cypress/issues/1417

Issues in our GitHub repo are reserved for potential bugs or feature requests. This issue will be closed since it appears to be neither a bug nor a feature request.

We recommend questions relating to how to use Cypress be asked in our community chat. Also try searching our existing GitHub issues, reading through our documentation, or searching Stack Overflow for relevant answers.

Was this page helpful?
0 / 5 - 0 ratings