The Illustrated Guide to Using Promise.all in Playwright Tests

In this article, I will go over the way Playwright communicates between the browser automation runner and the browser, and explain how to correctly use Promise.all to avoid tests’ flakiness.

Playwright Communication

These two contexts run in parallel and are not synchronized in their actions. You cannot tell if something will happen in the node context before or after the browser has completed an action.

Browser and NodeJS are not synchronized in their actions

The two contexts are communicating over an asynchronous mechanism. NodeJS is sending requests to the browser and receives a response.

Playwright browser and node communication

You can imagine it like a Whatsapp conversation between two people, each one doing their own things during this chat:

(Hint: to see the real communication, run Playwright test with DEBUG=pw:api)

Waiting for an Activity

Attempt #1

await page.locator('button').click();
await page.waitForRequest('https://example.com/resource');

This code will go anywhere from not working to flaky. Let’s look at the timelines:

First — the NodeJS will send a request to the browser to click the button. Once the button is clicked and the NodeJS receives the response, it will send the browser an instruction to check if the request is sent. But since we do not know how long it takes the NodeJS to send this request, it can hit the browser at any time during the purple rectangle. Now if the instruction hits the browser before the request is sent, marked as A zone, we are lucky, and the browser will catch it. But we can also hit the browser during the section marked as B, and then it is too late. Our request has already left the station and we are going to miss it.

It is like telling your kid to open the door, and then sending them a message to make sure the dog does not run away. Well, by the time they read the second message, the dog is probably already chasing all of the neighborhood’s cats.

Attempt #2

await page.waitForRequest('https://example.com/resource');
await page.locator('button').click();

Well, this is not going to work at all. Our browser will wait for the request to fire, but as our button was not clicked, it will not fire and the instruction will timeout with an error.

Attempt #3

await Promise.all([
page.waitForRequest('https://example.com/resource'),
page.locator('button').click()
])

What happens here?

NodeJS instructs the browser to wait for the request, and immediately, before the request is resolved, it sends the browser the instruction to click on the button. We sent the waitForRequest before we sent the click, so the browser is already waiting for the request to fire.

Once the request is fired, the browser is already on the watch for it and intercepts it. The test is now stable.

Short-Lived Element

We want to send 3 instructions to the browser:

  • Click the button
  • Make sure that the loader appears
  • Make sure that the loader disappears.

The “problematic” zone here is B. We cannot be sure that we will hit it to check that the loader appears before the loader disappears. Therefore, we need to send a request to check if the loader appears in parallel and before the loader appears.

Our code will then send:

await Promise.all([
page.locator('#loader').waitFor(),
page.locator('button').click()
];

This will make sure that we wait for the loader to appear in zone A above. Then, we just need to wait for it to disappear. We can do it after we get the response that the loader appeared:

await page.locator('#loader').waitFor({state: 'detached'};

And this is what the all flow looks like:

Our loader gone may hit the browser before or after the loader has disappeared. If the request hits in zone C it will wait until the loader disappears. If it will hit in zone D, it will resolve immediately.

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tally Barak

If you can’t explain it simply, you don’t understand it well enough.” — Einstein