cypress-io / cypress

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

Feature Request cy.all to work with multiple commands in parallel #8719

Open valoricDe opened 4 years ago

valoricDe commented 4 years ago

Summary

Sometimes you want to work with multiple cypress commands but want to wait for all comands to finish before proceeding with another task

Current behavior

currently you have to chain these commands and pass the fetched data from promiselike to promiselike.

cy.fixture('filea.json').then((fileAContents) => {
  cy.fixture('fileb.json').then((fileBContents => {
    ....
    cy.request... with fileAContents, fileBContents, fileCContents ...
  }
}

Desired behavior

like Bluebirds props method or my favorite

import { zipObject } from 'lodash'

export async function makePromiseFromObject(obj: { [key: string]: Promise<any> }) {
  return zipObject(Object.keys(obj), await Promise.all(Object.values(obj)))
}

we could aggregate all comand responses into one object with cy.all

cy.all({
  fileAContents: cy.fixture('filea.json'),
  fileBContents: cy.fixture('fileb.json'),
  ...
).then(props => cy.request( with ...props))

Relates to: https://github.com/cypress-io/cypress/issues/915

valoricDe commented 4 years ago

Hi Jessica sry it could be that I've choosen the wrong issue template. Could you reassign and delete labels as you see fit?

jayarjo commented 4 years ago

Absolutely required I'd say.

I stumbled on this repo. However it doesn't work when I try it with fixtures.

MCFreddie777 commented 3 years ago

Whats the status on this task? This would be great! Can't wait to see this in changelog!

Xapuu commented 3 years ago

Any updates here.

manuscriptmastr commented 2 years ago

Just wanted to leave this solution here for anyone interested (mixture of a few ideas). I have a use case where I needed to assert that subtotal + tip + tax = order total. The two challenges for testing here are: 1) I can't reasonably assert on exact values because the price of items are generally data driven. 2) I also can't dynamically look up these 4 items via something like cy.get(... all table rows) because other items like discounts/rewards/fees also match.

To assert that these values add up, I need all 4 Cypress results present. This normally forces me to nest 4 levels of cy.get(...).then(...) to have access to all 4 values, but it's fairly easy to create a custom Cypress command like this:

// cypress/support/commands.js

/**
 * @param cypressCommandFns An array of functions that return Cypress commands
 * @returns A Cypress chainable whose `.then()` passes an array of jQuery-wrapped DOM nodes
 */
Cypress.Commands.add('all', (cypressCommandsFns) =>
  cypressCommandsFns.reduce(
    (results, command) =>
      results.then((bucket) => command().then((res) => [...bucket, res])),
    cy.wrap([])
  )
)

// my-test.spec.js
cy.all([
  // Testing Library syntax
  () => cy.findByRole('row', { name: /sub-total/i }),
  () => cy.findByRole('row', { name: /estimated tax/i }),
  () => cy.findByRole('row', { name: /tip/i }),
  () => cy.findByRole('row', { name: /order total/i }),
]).then(([$subtotal, $tax, $tip, $total]) => {
  ... make sure values add up
})

Something to note: this strategy requires passing in functions that return Cypress commands.

Hopefully this helps anyone struggling with this kind of testing challenge!

bahmutov commented 2 years ago

@manuscriptmastr you probably can write the above test using the following solution https://cypresstips.substack.com/p/use-aliases-to-avoid-pyramid-of-callbacks

manuscriptmastr commented 2 years ago

@bahmutov Thanks for posting! Definitely see this working (and I love that it's the same use case 😃 ). TBH I don't love this.[property] even though I think that would be "standard" Cypress, but I'm glad there's an alternative solution to the problem!

xeger commented 1 year ago

Just to confirm, @bahmutov had the ideal answer. As long as we can predict the aliases we will bind, there is no need to wait for all promises; just let Cypress fulfill them serially, and then "remember" all predicted aliases inside a then(function() {}).