NicholasBoll / cypress-pipe

Create custom commands using plain-old functions
MIT License
110 stars 5 forks source link

retry entire cy command chain #12

Open tnrich opened 5 years ago

tnrich commented 5 years ago

Hey @NicholasBoll I am not sure if this is possible, but I'd love to be able to do something like

retryCmdChain(()=>{
  cy.get(".list-elements-item")
    .eq(5)
    .trigger("contextmenu")
})

Is there any way I can use cy.pipe to do this? I was trying to do it using this little function but it doesn't seem to work :

const retryCmdChain = (cmd) => {
  cy.wrap().pipe(function cmdToRety () {
    cmd()
  })
}

I understand that this isn't following the best practices for .pipe. Is there a way to use .pipe to retry the whole failing command chain?

Maybe this is a pipe dream ;) but an explanation of why this wouldn't be a possibility would be appreciated also. Thanks!

NicholasBoll commented 5 years ago

This is related to #7

I know I would love to do you first example. I even tried to make a plugin with that exact API. I was unsuccessful after a few hours, but I'm sure it is possible. Cypress actively worked against it throwing an error about running async code in a Chain. Basically it tried conditionally enqueue new commands on the Cypress command queue.

I ran into a similar limitation with pipe. I don't know if pipe should automatically retry functions with possible side effects automatically or not. I'm leaning towards a retryable Cypress plugin like your first example where opting into a retryable chain is intentional.

cypress-pipe uses verifyUpcomingAssertions from Cypress (found here) which handles the workload when a .should and/or .and followed a .pipe command. Cypress uses this internally for all retryable commands. Technically Cypress commands return promises that can be retried, so promises might be fine. Cypress commands might also be fine with creative enqueuing, but I haven't spent enough time making it work.

My team has embraced the way .pipe works where almost all our helper functions return results synchronously mostly using the jQuery API. Ex:

export const getAlarmByName = (name: string) => // this is the exported function name
  function getAlarmByName($container: JQuery): JQuery { // this is the named function that will be passed to pipe
    return $container
      .find(`.alarm-card .alarm-name:contains(${name})`) // test the alarm card's name
      .closest('.alarm-card') // we want to return the card, not the name element
  }

// usage in a test
getAlarmCardList() // returns a Cypress.Chainable<JQuery> of the alarm card list element
  .pipe(getAlarmByName('My Alarm')) // will retry until a non-empty jQuery node list is returned
  .should('contain', 'My Alarm') // the should causes the pipe command to retry until this condition is met

verifyUpcomingAssertions makes retrying with that use case very easy. Supporting functions with Promises or Cypress commands is probably possible, but I haven't been successful in implementing it.

jogelin commented 2 years ago

issue related #29

jogelin commented 2 years ago

This lib is also useful https://github.com/bahmutov/cypress-recurse