Playwright tips and tricks #2

Playwright tips and tricks #2
Photo by Vaibhav Bharadwaj / Unsplash

I've written a post about tips and tricks and it got a lot of love. So, I've decided to do another one.

1. How to handle an element that appears after full page load in playwright

One of those rare cases where an element will appear in DOM only after some conditions or scripts are executed. Even if Playwright will wait for the page to fully load, you may encounter situations where your element will appear later than that. In this situation here are a few tricks I recommend:

  • First option, wait for network to be idle: page.goto('', {waitUntil: 'networkidle'})
  • Second option is to wait for the elements particular state. Use element.waitFor(state). Default value is "visible" but you can change to, for example, be present in the DOM, by doing first const element = page.locator('#locator') and then await element.waitFor("attached")

2. Custom expect message

Did you know that you can have your own custom expect message when an assertion fails? await expect(locatorOrValue, 'Failed to perform something').toBe(). This can be very useful when you use them in custom built methods or page methods and you want to have a more detailed message of what failed.

3. How can I assert receiving of an email with playwright ?

Imagine that you have a registration form, and upon submitting that form an email is being sent with the confirmation number/link. Since the email does not always appear instant in your inbox, you can perform a polling technique , using expect.poll from playwright. Read the comments below as I try to explain how it works.

await expect.poll(async () => {
  const allEmails = await page.request.get('')
  // do some logic here, for example search the emails for the one that includes the registration form data of username/email
  for (email of allEmails){
      if ("[email protected]"))
          // once email found write the logic to extract link/code from email contents
          return emailCode
  return false;
}, {
  // this part is the configuration of your polling and they are all optional
  // Custom error message, triggered after timeout runs out.
  message: 'Failed to find confirmation link in email',
  // Probe, wait 1s, probe, wait 2s, probe, wait 10s, probe, wait 10s, probe
  // ... Defaults to [100, 250, 500, 1000].
  intervals: [1_000, 2_000, 10_000],
  // last value in interval is repeated until timeout. And it will throw an error with the custom message
  timeout: 60_000

Remember that every time it fails to find the email the expect still runs like a loop. The only time it fails, is if it reaches 60 seconds and it didn't return a truthy value that was expecting at the last line of code. You can easily change toBeTruthy() to whatever jest expect like method you want, to be particular to your needs and what you return in your logic.

4. Is there a way to fail an assertion but not fail the test?

Have a look at soft assertions first, maybe that's what you want.

Or maybe you are expecting something that appears at random intervals of time. Playwright has an expect method that can repeat expect assertions (multiple yes) until they ALL pass, and its called expect.toPass(). Just group all assertions inside an expect.toPass(). How this method works is very similar to what I've explained above at expect.poll() but this time inside our expect method we can perform one or multiple expect() that may fail a couple of times before they succeed. Condition is they ALL have to pass. So the block of code repeats until the condition is met (or it timesout). Here is an example :

test("Element appears after 5 seconds", async ({ page }) => {
    await page.goto("");
    await expect(async () => {
        await expect(page.getByText("LOADING")).toBeVisible()
        await expect(page.getByText("COMPLETE.")).toBeVisible()
        intervals: [1_000, 5_000, 10_000],
        timeout: 60_000

Notice configuration now goes inside the toPass({})

5. How can I intercept a network call in playwright ?

We are not talking here about page.route()

There are two types of end to end testing, horizontal and vertical. Most common one is horizontal where you test for example purchasing a product. This is an user flow. And the less known one is vertical, where you test user action -> Front End (UI) -> Back End -> database or database -> Back End -> Front End -> what user sees.

Take for example an invoice that is generated if you click a button and the values are displayed at UI level in your account. You may have the front end perform a computation of data received from the backend to display the values. What if values are displayed incorrectly, who is to blame? front end devs will say its a backend problem and backend devs will say its a frontend problem. Sounds familiar? In order to catch this kind bug you do it like this:

  • for the front end you put an assertion of value that will be displayed in the UI
  • for the backend you listen to network calls and assert what the backend sends via the API before it reaches frontend

This will give you validation at two levels/layers . So, how do we intercept a call in playwright? With the use of waitForResponse . Let me give you a snippet of code for further clarification:

// the preceding double asterix is used to have dinamic wait on multiple environments. 
// Notice waitForResponse has no await. It just marks SINCE WHEN you want to listen for that call
const invoiceCall = page.waitForResponse("**/invoices/*");
await page.getByText("Generate Invoice").click();

// It will default wait for 30 seconds for the call
const response = await invoiceCall

// now make that response as json to be easily read
const responseAsJson = await response.json();
await expect(responseAsJson.invoice.value).toBe("355");
Why should I care about the SINCE WHEN part? This comes in handy when you want to intercept multiple calls from same endpoint. More on that soon.

6. How to debug like a pro in playwright ?

This topic actually deserves its own blog post, and I've written it here. But just wanted to point out one thing that probably almost everyone missed out. Playwrights own debugger called the inspector can give you a LOT more than you think. Let me show you what I mean. Lets say you have one line of code that you just can't figure it out why you can't achieve what you want. Here's what you can do, start your test with npx playwright test path/to/yourTest.spec.js --debug, then go to the inspector and open the action you want to find out more about, and behold all of the actions Playwright is performing under the hood:

Small notice: I am not a big fan of this inspector as at the moment v1.40 , its a bit buggy. But it can be useful sometimes.

7. How to get text and store in a variable in playwright for later reuse ?

Playwright will recommend to give an element to expect() and use its toHaveText() method to assert if an element has a certain text, but sometimes you just need to fetch that text from an element and later reuse it for other actions. This happens when values are not set and come dinamically. You can achieve this with const elementText = await page.locator(locator).innerText() . This will give you a value as a string inside elementText to later use.

Note that innerText() will give you only visible text. So if text is inside the DOM but is not visible then it will not return that value. To include even hidden text use textContent()

Hit the clap button if you found this useful. Or even buy me a coffee if you want to motivate me even more.

Feel free to checkout other nice tips:

Playwright tips and tricks #1
Common errors, data test id hacks, auto-waits, timeouts hack, asserting an array of strings, absence of an element and many more

External recommendations :

Tips for Writing Efficient Playwright Test Scripts
Maximize the efficiency of your Playwright test scripts with these practical tips and best practices. This comprehensive guide will help you navigate through the complexities of writing test scripts, making your tests more maintainable, reliable, and efficient. Let’s dive into these valuable insight…