Code works as a regular function, but not as a Cypress command

215 Views Asked by At

I created a function that is supposed to click a dropdown to open it and then check if the options I passed are there. It goes like this:

function dropdown(placeholderText: string) {
  function contains(...values: string[]) {
    cy.contains(placeholderText).click();
    values.map((value) => cy.contains(value));
  }

  return { contains };
}

it('should show a dropdown with the correct options', () => {
  dropdown('Proteins').contains('Steak', 'Chicken Breast, 'Eggs', 'Fish')
})

So far, so good. It works fine. Then I tried to turn it into a Cypress command to make reuse easier:

declare namespace Cypress {
  interface Chainable<Subject> {
    dropdown: (placeholderText: string) => any;
  }
}

Cypress.Commands.add('dropdown', (placeholderText: string) => {
  function contains(...values: string[]) {
    cy.contains(placeholderText).click();
    values.map((value) => cy.contains(value));
  }

  return { contains };
});

It gives me a type error in the command implementation. although it's kinda cryptic...

Argument of type '(placeholderText: string) => { contains: (...values: string[]) => void; }' is not assignable to parameter of type '(...args: any[]) => CanReturnChainable'.
  Type '{ contains: (...values: string[]) => void; }' is not assignable to type 'CanReturnChainable'.
    Type '{ contains: (...values: string[]) => void; }' is missing the following properties from type 'Chainable<any>': dropdown, and, as, blur, and 81 more.

It seems like it was expecting me to return some Cypress chainable value but I returned something else. Don't know how to fix it. It still ran, though! But when it was time to run my custom command, I got this error:

cy.contains() failed because it requires the subject be a global window object.

The subject received was:

  > {contains: function(){}}

The previous command that ran was:

  > cy.dropdown()

All 3 subject validations failed on this subject.

What am I supposed to do here? It seems like one error (the typescript one) has something to do with the other (the cypress one), but I don't know how to fix them.

Image

a screenshot of the Cypress log. It shows this line of code highlighted, depicting where the error occurred: cy.dropdown('Escolha um tipo de bem').contains(

1

There are 1 best solutions below

0
On

The Function version is a higher-order function, returning a function that can be called with additional parameters.

The common example (not as fluent as your version) is

function dropdown2(placeholderText: string) {
  return function(...values: string[]) {
    cy.contains(placeholderText).click();
    values.map((value) => cy.contains(value));
  }
}

dropdown2('Proteins')('Steak','Eggs')
//or
const contains = dropdown2('Proteins')
contains('Steak','Eggs')

Cypress chaining is different, it always returns a Chainable<T>.

To use your dropdown command as it is at the moment, you need to unwrap the Chainable

Cypress.Commands.add('dropdown', (placeholderText: string) => {
  function contains(...values: string[]) {
    cy.contains(placeholderText).click();
    values.map((value) => cy.contains(value));
  }
  return { contains };
});

cy.dropdown('Proteins').then(({contains}) => contains('Steak','Eggs'))  // ✅

To make it a little easier to unwrap, return the contains function without an object wrapper.

Cypress.Commands.add('dropdown', (placeholderText: string) => {
  function contains(...values: string[]) {
    cy.contains(placeholderText).click();
    values.map((value) => cy.contains(value));
  }
  return contains
});

cy.dropdown('Proteins').then(contains => contains('Steak','Eggs'))  // ✅

To keep the fluent syntax, convert both outer and inner functions to custom commands.

Cypress.Commands.add('dropdown', (placeholderText: string) => {
  cy.contains(placeholderText).click();
});

Cypress.Commands.add('hasItems', {prevSubject: true}, (subject, ...values) => {
  cy.wrap(subject).within(() => {
    values.forEach((value) => cy.contains(value));
  })
});

cy.dropdown('Proteins').hasItems('Steak','Eggs')

Also add .within(), otherwise there is no connection between the dropdown and the inner items. Without it Steak and Eggs could appear anywhere on the page and the test would succeed.

Same applies to previous versions.