Handle Errors and Fallbacks in Playwright

All robust automations share one thing in common: they can handle the tides of uncertainty the web throws at them day after day. When developing robust applications on top of Playwright scripts, managing the unexpected is key to winning the day.

This article explores some practical strategies for building resilience into Playwright scripts, ensuring they can handle errors, adapt to varied content, and provide clear control flow.

By the way, if you find this article helpful, check out my complete Playwright locator deep-dive for a detailed look at optimizing your locators for speed, scale, and stability.

Let's get started!

Try Multiple Locator Strategies

When scraping web pages, you may encounter variability in where and how data is presented. For instance, retrieving a star rating from a product page can be challenging because the value could be in a metadata field, within the text of an element, or even graphically represented with icons.

const maybeRatings = await Promise.all([
  page.locator('.rating-value').textContent(),
  page.locator('.star-rating img.star').count(),
  page.locator('meta[name="description"]')
    .getAttribute('content')
    .then((str) => (/\n\.\n+ stars/u).exec(str ?? '')?.[0]),
]);

const bestRating = maybeRatings.reduce((pick, rating) => {
  return pick ?? parseFloat(rating) || null;
}, null);

In this example, the script attempts multiple strategies to capture the rating, testing each result to ensure it looks like an actual rating number.

Quietly Skip Optional Content

Not all content is essential for the successful completion of a script. For example, let's say you wanted to scrape a book product page, but you can't always guarantee the page count is available. In this case, you'd only want the script to fail if critical values (like the book title) weren't found, but to pass otherwise.

const title = await page.locator('.book-title')
  .textContent();
const author = await page.locator('.author-name')
  .textContent();
const pageCount = await page.locator('.page-count')
  .textContent();

const details = {
  title, 
  author,
  pageCount: parseInt(pageCount) || null,
};

assert(!!details.title && !!details.author);

The function demonstrates the selective inclusion of optional data. If present, it enriches the results. But if absent, it doesn't constitute failure.

Clarify Your Control Flow

Control flow can become unclear if scripts are peppered with conditional statements and error handling. Playwright's locators can help us streamline the process.

In the following example, we only interact with the table of contents if it exists.

const $toc = page.locator('aside.contents');

if (await $toc.count() > 0) {
  const $lastLink = await $toc.locator('a').last();
  await $lastLink.click();

  const depth = await page.evaluate(() => window.scrollY);
  console.log(`Scrolled to depth: ${depth}`);
} else {
  console.log('No table of contents found.');
}

This example demonstrates a clear control flow where the presence of a table of contents triggers conditional behavior, but doesn't result in an error otherwise.

Retry Failed Gestures

In some scenarios, you might want a script to retry an action until a certain condition is met, like rolling a digital die until a specific number appears.

const $d20 = page.getByRole('button', {name: 'Roll d20'});
const $result = page.locator('#d20-result');

page.waitForFunction(async () => {
  await $d20.click();
  const roll = await $result.textContent().then((str) => parseInt(str));

  return roll === 20;
});

This code snippet rolls a virtual die, retrying the gesture until the desired outcome is achieved, showcasing a controlled and persistent interaction pattern.

As you can see, these strategies equip your web automation tasks with accuracy and fault tolerance, ensuring smooth interaction with a variety of web pages, content structures, and unexpected circumstances.

Next steps

Now that you've got some new tools in your belt, review your most recent automations and see if you can apply any of these strategies to improve their resilience.

I find the key to making my scripts production-ready is going line-by-line looking for potential failure modes and workarounds. It's a tedious process, but it pays off in the long run.

Remember, check out my complete Playwright locator deep-dive for a detailed look at optimizing your locators for speed, scale, and stability.

Happy automating!