1

With the following snippet code the function should await all the promises mapped from the Object entries node. For instance the $eval function comes from the Playwright library and throws an error due to the fact that the target document in which the evaluate is performed will be closed by the time that the promises should be awaited.

The above error can be fixed by nesting another await Promise.all but I'm not getting why the external await Promise.all is not properly handling the inner loop given that I'm returning an array of Promise

const tableBodyRows = await page.locator(`tbody >> tr`).elementHandles()
      await Promise.all(
        tableData[pageIndex].map(async ({ node }, idx) => {
          // await Promise.all(
            return Object.entries(node).map(async ([key, val]) => {
              const cellText = await tableBodyRows[idx].$eval(
                `td.${key.toLowerCase()}`,
                (cell: HTMLTableCellElement) => cell.textContent,
              )
              return expect(cellText).toEqual(val)
            })
          // )
        }),
      )

3 Answers3

4

As the others have said - you are returning an array of arrays. If your target is es2019 or later - you can replace the map call, with flatMap

const tableBodyRows = await page.locator(`tbody >> tr`).elementHandles()
      await Promise.all(
        tableData[pageIndex].flatMap(({ node }, idx) =>
           Object.entries(node).map(async ([key, val]) => 
              expect(await tableBodyRows[idx].$eval(
                `td.${key.toLowerCase()}`,
                (cell: HTMLTableCellElement) => cell.textContent,
              )).toEqual(val)
           )
        ),
      )
Martin
  • 120
  • 7
2

The result of your map is an array of arrays like this:

await Promise.all([[promise1, prmise2], [promise3, promise4]])

You need to flatten input of Promise.all first like this:

  await Promise.all(
    tableData[pageIndex].reduce(async (acc, { node }, idx) => {
      return [
        ...acc,
        ...Object.entries(node).map(async ([key, val]) => {
          const cellText = await tableBodyRows[idx].$eval(
            `td.${key.toLowerCase()}`,
            (cell: HTMLTableCellElement) => cell.textContent
          );
          return expect(cellText).toEqual(val);
        }),
      ];
    }, [])
  );
mrafei
  • 345
  • 1
  • 10
  • Yes, this should work. Another option to flatten the array is to stick [`.flat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) at the end: `tableData[pageIndex].map(async ({ node }, idx) {...}).flat()` – RickN Aug 08 '22 at 11:50
  • @RickN using `flat()` doesn't work – Antonio Santoro Aug 08 '22 at 12:10
  • Ah, you're right, because it's a _function returning array elements_, rather than array themselves. – RickN Aug 08 '22 at 12:25
  • Because it's not flattening an array of arrays, but _trying_ to flatten an array of Promises. Since `tableData[pageIndex].map(async (...` doesn't actually do anything async itself, you could probably go with either flat/flatMap if you change it to `tableData[pageIndex].map((...` – RickN Aug 08 '22 at 12:31
  • So by getting rid of `async` for the outermost loop and using `flatMap` works, I'm not getting why – Antonio Santoro Aug 08 '22 at 12:43
  • Does `async` create another level of promises? – Antonio Santoro Aug 08 '22 at 12:44
0

I think you want to use Promise.allSettled() instead of inner Promise.all()

Apak
  • 137
  • 11