5 min read
PlaywrightTest AutomationError HandlingLocator Handler

The Power of Playwright's Locator Handler

How to gracefully handle unpredictable popups and modals in Playwright using the powerful locator handler feature.

The classic scenario. Cookie consent popup randomly appears. Here, we handle it gracefully via a locator handler.

In Playwright, a particularly useful (but relatively unknown) feature shipped to little fanfare in version 1.42 early in 2025.

At first glance, it seems like an obscure concept. But for those who have experience in UI-based test automation, the brilliance of this function soon becomes apparent.

The Old Ways

In the old days of test automation there was a concept known as a Disaster Recovery. You'd specifically code a routine that kicked into life given a particular set of circumstances. Unpredictable events would be gracefully handled by such fallback scripts, or at least that was the idea.

For example, in QTP (QuickTest Professional) / Micro Focus UFT (Unified Functional Testing) you'd need to set up a 'Trigger Event' to define the unexpected scenario. This would usually be something like a rogue popup on a website that, unless clicked, would prevent the tool from interacting with other elements.

In 2012, I remember attending a course in London on this very subject. A weeklong dive into QTP (which was all driven by VBScript) where a whole day was spent on this very subject. I still own the course notes!

There were many limitations back then when it came to actually applying this. It massively slowed down your test execution for one thing. This was probably because it worked by spinning up dedicated threads to run and trigger as your test pack executed. More frequently than not, you'd simply handle it in code instead by introducing horrendous blocks of if statements, or the 'on error resume next' pattern of VB.

In the world of browser/website automation, the scenario you needed to cater for was, and continues to almost always be, related to the handling of unexpected elements aka popups/modals/prompts. The classic example is a cookie banner. Or perhaps even a particularly intrusive ad you can't block (or need to test!).

Surprisingly, I personally never found a neat way of handling these pesky elements during my days working with Selenium WebDriver in C#. I also never found a neat way to deal with them when working with Cypress in TypeScript as recently as 2023.

The reality was always a painful case of applying if statements and conditional logic around clicking an element if it is visible. This often involves introducing hard-coded waits and it is almost always inherently flaky.

The reality was always a painful case of applying if statements and conditional logic. This often involves introducing hard-coded waits and it is almost always inherently flaky.

It is easy to forget in 2026 that Playwright has been going now since 2020. That's close to 6 whole years as I type this! And for at least 4 of those the same issue around handling unpredictable elements existed. I recall hitting it in 2024 when working on a horrendously legacy website. Playwright was a huge leap forward but still lacked the magic touches.

Then in early 2025 along came locator handlers…

Implementing Locator Handlers

The concept is rather simple and eloquent in code. It's also ultimately doing precisely what QTP's Disaster Recoveries did back in the day:

  • You call the locator handler function, which is part of the page fixture, and pass in a locator as the first parameter. This is the locator you have identified as a troublesome, unpredictable fly in your framework's carefully crafted ointment.
  • You then pass in the desired action to take when/if this locator is encountered during test execution in the form of an anonymous function (lambda). For example: click OK on the popup, wait 1 second, console log it, move on.

That's it! The crucial part is registering this handler early on in your test. In my experience, you should set them up in your beforeEach hooks. This ensures you register them before the locator(s) are first likely to be encountered.

test.beforeEach(async ({ page, dcjPage }) => {
  await page.addLocatorHandler(
    page.getByRole('heading', { name: "Sorry, there's been a problem" }),
    async () => {
      assert.fail(
        `Sorry, there's been a problem pop up encountered on page - ${await page.url()}`,
      );
    },
  );
});

You don't need to handle the locator at all either. In the above example, I fail a test there and then when an uncaught application error is thrown and displayed in the UI. This ensures the test halts immediately and we receive feedback as quickly as possible instead of letting a test hang until a timeout is hit.

It is worth noting that the locatorHandler function is not asynchronous. Therefore, you do not use the await keyword when calling it. This is because of the nature of what it is doing. Once called, it runs synchronously alongside your test execution. It is also capable of working when you run in parallel using multiple workers. It essentially is applied to each worker.

However, the function/routine you pass to it as the action to take must be asynchronous in order to work nicely with the rest of your Playwright code.

What I would like to see next from the Playwright team is the ability to add network handlers. For example, fail the test if a particular network response is something other than a 200 status code.

Wrap Up

Utilising the locator handler where appropriate is a key way to ensure your framework is as robust as possible.

Utilising the locator handler where appropriate is a key way to ensure your framework is as robust as possible. If you have any issues with unpredictable elements causing flaky results and blocking your tests from resuming their flows then this is likely the answer.

Once applied, you can remove all that messy conditional logic if you've resorted to that up until now.

Generally, it is best to apply simple actions (like click OK in a popup) as your recoveries. Using it to perform more involved flows is perhaps best avoided. This really does depend on your specific use case though.

Join the discussion on LinkedIn

Share this post

Comments