Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When using Promise.all to get multiple elements in Cypress, same last element is returned #915

Closed
bahmutov opened this issue Nov 14, 2017 · 36 comments
Labels
type: unexpected behavior User expected result, but got another

Comments

@bahmutov
Copy link
Contributor

  • Operating System: Mac
  • Cypress Version: 1.0.3
  • Browser Version: built-in

Is this a Feature or Bug?

Bug

Test code

Repo https://github.com/bahmutov/cypress-promise-all-test

Problem

We can get multiple promises resolved in parallel, this code works as expected

it('works with values', () => {
    Cypress.Promise.all([
      Promise.resolve('a'),
      Promise.resolve('b')
    ]).then(([a, b]) => {
      expect(a).to.equal('a')
      expect(b).to.equal('b')
    })
  })

We can even spread results using built-in Bluebird promise spread

  it('spreads resolved values', () => {
    Cypress.Promise.all([
      Promise.resolve('a'),
      Promise.resolve('b')
    ]).spread((a, b) => {
      expect(a).to.equal('a')
      expect(b).to.equal('b')
    })
  })

But if any of the promises are Cypress chains of commands, then all values passed into array are the same - the last value. For example this test grabs navigation links from https://example.cypress.io/

First link should be "Commands" and the second link should be "Utilities"

  it('grabs element values', () => {
    cy.visit('https://example.cypress.io/')

    const getNavCommands = () =>
      cy.get('ul.nav')
        .contains('Commands')

    const getNavUtilities = () =>
      cy.get('ul.nav')
        .contains('Utilities')

    // each works by itself
    getNavCommands()
      .should('be.visible')

    getNavUtilities()
      .should('be.visible')

    // lets get both elements
    Cypress.Promise.all([
      getNavCommands(),
      getNavUtilities()
    ]).then(([commands, utilities]) => {
      console.log('got commands', commands.text())
      console.log('got utilities', utilities.text())
      // debugger
      expect(utilities.text()).to.equal('Utilities')
      expect(commands.text()).to.equal('Commands')
    })
  })

I can see that it really grabbed each link correctly during in the reporter

screen shot 2017-11-14 at 3 00 29 pm

screen shot 2017-11-14 at 3 00 37 pm

But the arguments commands and utilities are both "Utilities" elements

screen shot 2017-11-14 at 3 01 56 pm

Also, the assertion is an unhandled promise itself - it fails but the test is green

screen shot 2017-11-14 at 3 00 17 pm

only console shows the unresolved promise message

screen shot 2017-11-14 at 3 01 05 pm

The tests are in https://github.com/bahmutov/cypress-promise-all-test

@bahmutov bahmutov added type: bug type: unexpected behavior User expected result, but got another labels Nov 14, 2017
@bahmutov
Copy link
Contributor Author

Ok, we must flatten the values we are getting using Cypress, so need a little utility function. This works

const all = (...fns) => {
    const results = []

    fns.reduce((prev, fn) => {
      fn().then(result => results.push(result))
      return results
    }, results)

    return cy.wrap(results)
  }

  it.only('wraps multiple cypress commands', () => {
    return all(
      getNavCommands,
      getNavUtilities
    ).spread((commands, utilities) => {
      console.log('got commands', commands.text())
      console.log('got utilities', utilities.text())
    })
  })

Need to describe this in a recipe for users

@brian-mann
Copy link
Member

We could wrap this into Cypress.all or something like cy.all.

It could take a chainer or a function as arguments and then massage them all together.

@jwalton
Copy link

jwalton commented Feb 23, 2018

I just ran into this issue - why is this closed? Why doesn't Promise.all() work here?

@jwalton
Copy link

jwalton commented Feb 23, 2018

(I actually didn't run into exactly this issue; I tried to use Promise.all() to run two cy.request(...)s in parallel, and instead of getting back two responses, I got back [undefined, undefined].)

@jwalton
Copy link

jwalton commented Feb 23, 2018

Oh, I've just answered my own question: https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Not-Promises

@qoc-dkg
Copy link

qoc-dkg commented Apr 5, 2018

Anything wrong with doing something like this:

const promisify = cmd => new Promise(resolve => cmd.then(resolve))
Promise.all([cy.get(), cy.get()].map(cmd => promisify(cmd))).then(...)

@brian-mann
Copy link
Member

brian-mann commented Apr 5, 2018

@qoc-dkg you cannot race commands at the same time, and you're not actually achieving anything here to be honest. If you want to aggregate an accumulation of command results you'd do something like this. Otherwise there's nothing special - commands will run in serial and you don't have to do anything to ensure they are awaited correctly.

const accum = (...cmds) => {
  const results = []

  cmds.forEach((cmd) => {
    cmd().then(results.push.bind(results))
  })

  return cy.wrap(results)
}

accum(cy.cmd1(), cy.cmd2(), cy.cmd3())
.then((results = []) => ...)

@tk-o
Copy link

tk-o commented Oct 8, 2018

Ok, we must flatten the values we are getting using Cypress, so need a little utility function. This works

const all = (...fns) => {
    const results = []

    fns.reduce((prev, fn) => {
      fn().then(result => results.push(result))
      return results
    }, results)

    return cy.wrap(results)
  }

  it.only('wraps multiple cypress commands', () => {
    return all(
      getNavCommands,
      getNavUtilities
    ).spread((commands, utilities) => {
      console.log('got commands', commands.text())
      console.log('got utilities', utilities.text())
    })
  })

Need to describe this in a recipe for users

Can we have this available in standard API as cy.all?

@dwelle
Copy link

dwelle commented Jan 29, 2019

Summing up several solutions:

  1. Note that @brian-mann's solution isn't correct due to how late-chaining of .then callbacks currently works in cypress, as can be tested below:

    describe("test", () => {
        it("test", async () => {
    
            const accum = (...cmds) => {
                const results = [];
    
                cmds.forEach((cmd) => {
                    cmd.then(val => { results.push(val); });
                });
    
                return cy.wrap(results);
            };
    
            cy.document().then( doc => {
                doc.write(`
                    <div class="test1">one</div>
                    <div class="test2">two</div>
                `);
            });
    
            accum(
                cy.get(".test1").invoke("text"),
                cy.get(".test2").invoke("text")
            ).spread((a, b) => console.log(a, b)); // two two
        });
    });
  2. @bahmutov's solution is correct, but disadvantage is you need to pass callbacks instead of commands directly. ✔️

  3. Another solution is this, where you can pass commands directly:

    ✔️

    (edit 19-03-23 → rewritten to fix issues)

    const chainStart = Symbol();
    cy.all = function ( ...commands ) {
        const _           = Cypress._;
        const chain       = cy.wrap(null, { log: false });
        const stopCommand = _.find( cy.queue.commands, {
            attributes: { chainerId: chain.chainerId }
        });
        const startCommand = _.find( cy.queue.commands, {
            attributes: { chainerId: commands[0].chainerId }
        });
        const p = chain.then(() => {
            return _( commands )
                .map( cmd => {
                    return cmd[chainStart]
                        ? cmd[chainStart].attributes
                        : _.find( cy.queue.commands, {
                            attributes: { chainerId: cmd.chainerId }
                        }).attributes;
                })
                .concat(stopCommand.attributes)
                .slice(1)
                .flatMap( cmd => {
                    return cmd.prev.get('subject');
                })
                .value();
        });
        p[chainStart] = startCommand;
        return p;
    }    
    
    describe("test", () => {
        it("test", async () => {
            cy.document().then( doc => {
                doc.write(`
                    <div class="test1">one</div>
                    <div class="test2">two</div>
                `);
            });
    
            cy.all(
                cy.get(".test1").invoke("text"),
                cy.get(".test2").invoke("text")
            ).spread((a, b) => console.log(a, b)); // one, two
        });
    });

    but note, you can't pass another cy.all() as an argument to cy.all() (but that's an edge case which you won't do anyway). Fixed. You can now nest as you like:

    describe("test", () => {
        it("test", () => {
            cy.window().then( win => {
                win.document.write(`
                    <div class="test1">one</div>
                    <div class="test2">two</div>
                    <div class="test3">three</div>
                    <div class="test4">four</div>
                    <div class="test5">five</div>
                    <div class="test6">six</div>
                `);
            });
    
            cy.all(
                cy.get(".test1").invoke("text"),
                cy.get(".test2").invoke("text"),
                cy.all(
                    cy.get(".test3").invoke("text"),
                    cy.all(
                        cy.get(".test4").invoke("text")
                    )
                )
            ).then(vals => {
    
                return cy.all(
                    cy.get(".test5").invoke("text"),
                    cy.get(".test6").invoke("text")
                ).then( vals2 => [ ...vals, ...vals2 ]);
            }).then( vals => console.log( vals )); // [ one, two, three, four, five, six ]
        });
    });
  4. Yet another solution, definitely not recommended, is to wrap the commands into bluebird promise which (unlike native promise) somehow correctly resolves with the value yielded by cy command:

    ✔️

    describe("test", () => {
        it("test", async () => {
            cy.document().then( doc => {
                doc.write(`
                    <div class="test1">one</div>
                    <div class="test2">two</div>
                `);
            });
    
            const Bluebird = Cypress.Promise;
            
            cy.wrap(Promise.all([
                Bluebird.resolve(cy.get(".test1").invoke("text")),
                Bluebird.resolve(cy.get(".test2").invoke("text"))
            ])).spread((a, b) => console.log(a, b)); // one two
        });
    });

    ⚠️ Also, cypress will complain about you mixing promises and cy commands.

    But beware: unlike the previous solutions, you can't nest those, for some reason:

    describe("test", () => {
        it("test", async () => {
            cy.document().then( doc => {
                doc.write(`
                    <div class="test1">one</div>
                    <div class="test2">two</div>
                `);
            });
    
            const Bluebird = Cypress.Promise;
            
            cy.wrap(Promise.all([
                Bluebird.resolve(cy.get(".test1").invoke("text")),
                Bluebird.resolve(cy.get(".test2").invoke("text"))
            ])).spread((a, b) => {
    
                console.log(a, b); // one two
    
                cy.wrap(Promise.all([
                    Cypress.Promise.resolve(cy.get(".test1").invoke("text")),
                    Cypress.Promise.resolve(cy.get(".test2").invoke("text"))
                ])).spread((a, b) => {
    
                    console.log(a, b); // undefined undefined
                });
            });
        });
    });

@dwelle
Copy link

dwelle commented Mar 23, 2019

Just a heads-up if anyone's using my cy.all helper. It turned out it was completely wrong :).

Here's an updated version:

const chainStart = Symbol();
cy.all = function ( ...commands ) {
    const _           = Cypress._;
    const chain       = cy.wrap(null, { log: false });
    const stopCommand = _.find( cy.queue.commands, {
        attributes: { chainerId: chain.chainerId }
    });
    const startCommand = _.find( cy.queue.commands, {
        attributes: { chainerId: commands[0].chainerId }
    });
    const p = chain.then(() => {
        return _( commands )
            .map( cmd => {
                return cmd[chainStart]
                    ? cmd[chainStart].attributes
                    : _.find( cy.queue.commands, {
                        attributes: { chainerId: cmd.chainerId }
                    }).attributes;
            })
            .concat(stopCommand.attributes)
            .slice(1)
            .flatMap( cmd => {
                return cmd.prev.get('subject');
            })
            .value();
    });
    p[chainStart] = startCommand;
    return p;
}  

@nataliaroshchyna
Copy link

@dwelle nice solution, but can we set it as Cypress commands e.g. Cypress.Commands.add('mergeResult', .... As I tried to do it, I got infinite loop or something like that. Tests hang out and nothing happens.

@dwelle
Copy link

dwelle commented Jun 5, 2019

@nataliaroshchyna yea, I'm not surprised. Making it into a command adds it to the command queue when called, which apparently messes it up. Why do you need it as a command? Declaring it on cy object should act the same. Or, if you think it's "dirty", you could make it into an util function that you'd import where you need.

@kickertw
Copy link

This is exactly what I'm looking for. I'm trying to use's the cy.all helper from @dwelle 's post, but am getting the following errors when inserting this helper above my describe.

  • Property 'all' does not exist on type 'cy'.
  • Rest parameter 'commands' implicitly has an 'any[]' type.
  • Property 'queue' does not exist on type 'cy'.

Any help would be greatly appreciated :)

@dwelle
Copy link

dwelle commented Dec 20, 2019

Here's a hacked TS version. I don't really have experience in TS, so if anyone wants to properly type it, have at it. I'll update it as I go along.

declare namespace Cypress {
  interface cy {
    all (...commands: Cypress.Chainable[]): Cypress.Chainable,
    queue: any
  }
  interface Chainable {
    chainerId: string,
    "___CY_ALL_CHAIN_START___": any
  }
}

// changed from Symbol to string because TS doesn't support symbol index types ATM.
const chainStart = "___CY_ALL_CHAIN_START___";
cy.all = function ( ...commands ) {
    const _           = Cypress._;
    const chain       = cy.wrap(null, { log: false });
    const stopCommand = _.find( cy.queue.commands, {
        attributes: { chainerId: chain.chainerId }
    })!;
    const startCommand = _.find( cy.queue.commands, {
        attributes: { chainerId: commands[0].chainerId }
    });
    const p = chain.then(() => {
        return _( commands )
            .map( cmd => {
                return cmd[chainStart]
                    ? cmd[chainStart].attributes
                    : _.find( cy.queue.commands, {
                        attributes: { chainerId: cmd.chainerId }
                    })!.attributes;
            })
            .concat(stopCommand.attributes)
            .slice(1)
            .flatMap( cmd => {
                return cmd.prev.get('subject');
            })
            .value();
    });
    p[chainStart] = startCommand;
    return p;
}  

@kickertw
Copy link

So I put this code above my describe in my spec file. It still doesn't like it. Sounds like this code should go somewhere else so it can be incorporated in my project properly?

@MCFreddie777
Copy link

Seems like @dwelle's solution takes the commands out of the cypress command queue , I was just wondering if there is any solution, where we could pass cy.request's into cy.all and they would happen parallel and not in sequence.

@dwelle
Copy link

dwelle commented Feb 18, 2020

@MCFreddie777 in that case I'd not use cy.all and cy.request at all, but do something like:

cy.wrap(Promise.all([
    window.fetch(/*...*/),
    window.fetch(/*...*/),
    window.fetch(/*...*/),
]));

or if you need the calls to be made at a given point of cypress test execution, you can do:

cy.wrap(null).then(() => {
    return Promise.all([
        window.fetch(/*...*/),
        window.fetch(/*...*/),
        window.fetch(/*...*/),
    ]);
});

(untested)

@crysislinux
Copy link

This is a typescript version I created from the above suggestions.

export type CypressFn<T> = () => Cypress.Chainable<T>;

// tslint:disable: max-line-length
export function CypressAll<T1, T2, T3, T4, T5, T6>(fns: [CypressFn<T1>, CypressFn<T2>, CypressFn<T3>, CypressFn<T4>, CypressFn<T5>, CypressFn<T6>]): Cypress.Chainable<[T1, T2, T3, T4, T5, T6]>;
export function CypressAll<T1, T2, T3, T4, T5>(fns: [CypressFn<T1>, CypressFn<T2>, CypressFn<T3>, CypressFn<T4>, CypressFn<T5>]): Cypress.Chainable<[T1, T2, T3, T4, T5]>;
export function CypressAll<T1, T2, T3, T4>(fns: [CypressFn<T1>, CypressFn<T2>, CypressFn<T3>, CypressFn<T4>]): Cypress.Chainable<[T1, T2, T3, T4]>;
export function CypressAll<T1, T2, T3>(fns: [CypressFn<T1>, CypressFn<T2>, CypressFn<T3>]): Cypress.Chainable<[T1, T2, T3]>;
export function CypressAll<T1, T2>(fns: [CypressFn<T1>, CypressFn<T2>]): Cypress.Chainable<[T1, T2]>;
export function CypressAll<T1>(fns: [CypressFn<T1>]): Cypress.Chainable<[T1]>;
export function CypressAll<T>(fns: CypressFn<T>[]): Cypress.Chainable<T[]> {
  const results: T[] = []

  fns.forEach(fn => {
    fn().then(result => results.push(result))
  });

  return cy.wrap(results)
}
// tslint:enable: max-line-length

The use it like this:

  const getBody = () => cy.wait(payloadAlias, { log: false }).then(xhr => xhr.requestBody);
  const getTable = () => cy.wrap(table.rawTable);

  CypressAll([getBody, getTable])
  .then(([payload, matches]) => {
    for (const [path, expected] of matches) {
      const value = getValue(payload as object, path);
      cy.wrap(value).should('eq', expected);
    }
  });

@ytingyeu
Copy link

ytingyeu commented Jul 23, 2020

Thanks @dwelle for providing a solution. But I get null for the last command. Somehow in my case the stopCommand doesn't have prev attributes. I have to modify two lines to get correct results. Haven't do further validation, please correct me if my fix would cause other issues

const p = chain.then(() => {
  return _(commands)
    .map(cmd => {
        return cmd[chainStart]
                ? cmd[chainStart].attributes
                : _.find(cy.queue.commands, {
                    attributes: { chainerId: cmd.chainerId }
                })!.attributes;
        })
        .concat(stopCommand.attributes)
        //.slice(1)                                    <-- here, modify slice() range
        .slice(0, commands.length)                      
        .flatMap(cmd => {
            // return cmd.prev.get('subject');         <-- here, get current subject instead of from prev
            return cmd['subject'];
        })
        .value();
    });

image

@albertmir
Copy link

Will there be an official solution to this?

@valoricDe
Copy link

@bahmutov shall I create another issue or can you reopen this one and set it to another type instead of bug. I think the community would still benefit for progress.

@MCFreddie777
Copy link

MCFreddie777 commented Jan 13, 2021

@dwelle Today I stumbled upon a problem in your function.

As the function really touches the cypress chain (stack), it struggles when the function inside it contains another cy commands.

Example below - using cy.log in the function which is called in cy.all.
It will end up in taking only result from the last command in the cy.all arguments.

image
image

Without cy.log (I think it would apply to all the cypress functions):

image
image

@dwelle
Copy link

dwelle commented Jan 13, 2021

@MCFreddie777 I don't recall whether this was meant to ever work or not, but it may also likely be because the implem depends on internals of a pretty old Cypress version. Would have to review & attempt to fix, but right now I don't have the time :(.

@albertmir
Copy link

@MCFreddie777 if it helps you, the @dwelle fix number 3 is working fine for me in Cypress version 4.12.1, but I'm just using it with cy.get() commands right now.

@cquevedod
Copy link

cquevedod commented Apr 7, 2021

Ok, we must flatten the values we are getting using Cypress, so need a little utility function. This works

const all = (...fns) => {
    const results = []

    fns.reduce((prev, fn) => {
      fn().then(result => results.push(result))
      return results
    }, results)

    return cy.wrap(results)
  }

  it.only('wraps multiple cypress commands', () => {
    return all(
      getNavCommands,
      getNavUtilities
    ).spread((commands, utilities) => {
      console.log('got commands', commands.text())
      console.log('got utilities', utilities.text())
    })
  })

Need to describe this in a recipe for users

Fighting with Promise.all() for almost five hours cause I tought that the issue( same value returned for all promises passed to Promise.all) was related with that method but it wasn't. Finally I figured out (thanks to Gleb) that it's better to handle multiple cy.request (non-related each other) with wrap() and spread() (built-in Cypress commands). Thanks a bunch for the solution @bahmutov

@jwalton
Copy link

jwalton commented Apr 7, 2021

I still don't understand why this is broken the way it is. I understand that when I call a command it isn't run immediately but at some point in the future, and that cypress commands are run in deterministic fashion, in-order. What I don't understand is why each Promise returned by each deterministic in-order command doesn't resolve to the result of the command that returned it, and instead resolves to something else. :/

@bahmutov
Copy link
Contributor Author

bahmutov commented Apr 7, 2021

Cypress commands are not promises but more similar to reactive streams thus promise.all does not work. May I ask to open new discussions with code samples of what you are trying to accomplish rather than commenting on this closed issue

@samtsai
Copy link
Contributor

samtsai commented Aug 19, 2021

FYI, anyone still using the above mentioned solution, here's an updated version for Cypress >=8.3.x due to updates to internal cy.queue

// @see https://github.com/cypress-io/cypress/issues/915#issuecomment-475862672
// Modified due to changes to `cy.queue` https://github.com/cypress-io/cypress/pull/17448
// Note: this does not run Promises in parallel like native `Promise.all` due to the nature
// of Cypress commands being promise-like/reactive streams. This only makes it convenient to use the same
// API but runs the commands sequentially.
declare namespace Cypress {
  interface cy {
    all(...commands: Cypress.Chainable[]): Cypress.Chainable
    queue: any
  }
  interface Chainable {
    chainerId: string
    ___CY_ALL_CHAIN_START___: any
  }
}

const chainStart = "___CY_ALL_CHAIN_START___"
cy.all = function (...commands) {
  const _ = Cypress._
  // eslint-disable-next-line
  const chain = cy.wrap(null, { log: false })
  const stopCommand = _.find(cy.queue.get(), {
    attributes: { chainerId: chain.chainerId },
  })
  const startCommand = _.find(cy.queue.get(), {
    attributes: { chainerId: commands[0].chainerId },
  })
  const p = chain.then(() => {
    return (
      _(commands)
        .map(cmd => {
          return cmd[chainStart]
            ? cmd[chainStart].attributes
            : _.find(cy.queue.get(), {
                attributes: { chainerId: cmd.chainerId },
              }).attributes
        })
        .concat(stopCommand.attributes)
        .slice(1)
        .flatMap(cmd => {
          return cmd.prev.get("subject")
        })
        .value()
    )
  })
  p[chainStart] = startCommand
  return p
}

@AmiChandra89
Copy link

@samtsai @dwelle Thanks for posting the solutions. I have been using this solution but with recent Cypress upgrade >=8.3.0, I am not able to use this method anymore. I am using js and not typescript. I tried to convert the solution mentioned above to js but it did not work.
By any chance, do you know how can we make it work in js for Cypress version 8.3.0 and above? Thanks

@samtsai
Copy link
Contributor

samtsai commented Sep 21, 2021

@samtsai @dwelle Thanks for posting the solutions. I have been using this solution but with recent Cypress upgrade >=8.3.0, I am not able to use this method anymore. I am using js and not typescript. I tried to convert the solution mentioned above to js but it did not work.
By any chance, do you know how can we make it work in js for Cypress version 8.3.0 and above? Thanks

@AmiChandra89 Can you post a code snippet of what you're trying and what errors if any are you seeing?

@AmiChandra89
Copy link

@samtsai I am using this code to combine the responses of multiple apis:

const chainStart = Symbol();
cy.all = function (commands) {
  const _ = Cypress._;
  const chain = cy.wrap(null, { log: false });
  const stopCommand = _.find(cy.queue.commands, {
    attributes: { chainerId: chain.chainerId },
  });
  const startCommand = _.find(cy.queue.commands, {
    attributes: { chainerId: commands[0].chainerId },
  });
  const p = chain.then(() => {
    return _(commands)
      .map(cmd => {
        return cmd[chainStart]
          ? cmd[chainStart].attributes
          : _.find(cy.queue.commands, {
              attributes: { chainerId: cmd.chainerId },
            }).attributes;
      })
      .concat(stopCommand.attributes)
      .slice(1)
      .map(cmd => {
        return cmd.prev.get('subject');
      })
      .value();
  });
  p[chainStart] = startCommand;
  return p;
};

Getting error over here:
Screenshot 2021-09-24 at 21 11 18

@samtsai
Copy link
Contributor

samtsai commented Sep 24, 2021

@AmiChandra89

try the code snippet I posted above:
#915 (comment)

You can start it at const chainStart = "___CY_ALL_CHAIN_START___" since you don't need the Typescript portion. The key is the updated API used to get access the queue.

@samtsai
Copy link
Contributor

samtsai commented Apr 18, 2022

For anyone still tracking this I created a gist with improved Types: https://gist.github.com/samtsai/687b117bf6de73a0aa2229b2b69aee1b

You can now use it like, where a, b, and c's types are "unchained/unwrapped" to assert on:

it("cy.all", () => {
      cy.wrap(1).as("a")
      cy.wrap(2).as("b")

      cy.all([cy.get<number>("@a"), cy.get<string>("@b"), cy.wrap(true)]).then(
        ([a, b, c]) => {
          cy.log(`Alias "a": ${a}`)
          expect(a).to.be.a("number")
          cy.log(`Alias "b": ${b}`)
          expect(b).to.be.a("number")
          cy.log(`Alias "c": ${c}`)
          expect(c).to.be.a("boolean")
        }
      )
    })

@n370
Copy link

n370 commented Oct 12, 2022

Stumbled on this problem today and came up with the following solution after reading a few comments in this issue. I'm still wondering why Promise.all and Crypress.Promise.all don't work as expected.

declare global {
  namespace Cypress {
    interface Chainable {
      all(
        thenableGetters: (() => Chainable<unknown>)[]
      ): Chainable<Promise<unknown>>
    }
  }
}

Cypress.Commands.add('all', thenableGetters =>
  cy.wrap(
    new Promise(resolve => {
      const result = []
      const firstLink = thenableGetters.shift()
      thenableGetters
        .reduce(
          (chain, nextLink) =>
            chain.then(value => {
              result.push(value)
              return nextLink()
            }),
          firstLink()
        )
        .then(value => {
          result.push(value)
          resolve(result)
        })
    })
  )
)

export {}

@eugensunic
Copy link

It's 2022 and we still don't have a cy.all in cypress? What's happening with this guys? Do I really need to code this myself each time cypress version changes?

@abrugaro
Copy link

abrugaro commented Mar 31, 2023

The same thing happened to me when trying to load multiple fixtures in parallel:

Cypress.Promise.all([cy.fixture("fixture1"), cy.fixture("fixture2")]).then(
  ([data1, data2]) => {
    console.log(data1);
    console.log(data2);
  }
);

In this case, both console logs print the values inside data2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: unexpected behavior User expected result, but got another
Projects
None yet
Development

No branches or pull requests