How to Wait for Font to Ensure Complete Page Load in Playwright Tests

How to Wait for Font to Ensure Complete Page Load in Playwright Tests
Photo by Alexander Andrews / Unsplash

The Problem

In my Playwright tests, I hit a snag from time to time where my tests jump the gun, launching before the page has had a chance to put on all its assets. I've given waiting for the domcontentloaded and load events a whirl during my page.goto(), but here's the catch: they don't hold up for fonts, especially those fancy ones coming from external sources. The result? Occasionally, my tests end up facing off with a page that's not quite ready for showtime, looking somewhat underprepared:

As you can see the icons for the navigation, search and profile are not loaded as they are coming from the font

You can picture how this scenario leads to unpredictable, 'flaky' test outcomes, especially the case for visual testing. These premature runs could trigger a cascade of unnecessary failures, all hinging on your set thresholds, and essentially chip away at the trustworthiness of your tests.

Sure, you might consider throwing in a manual wait to give the page a breather before continuing your test, but let's face it: that's a bit like using a stopwatch in a digital age. It not only slows down the pace of your tests when overused but also lacks the precision and efficiency of a more tailored, explicit wait approach.

TLDR Solution

So how do you tackle this issue elegantly? It's all about introducing a clever little step into your test routine:

await page.waitForFunction(() => document.fonts.ready);

This nifty line of code is like a pause button, ensuring your test only springs into action once the stage is fully set – meaning all fonts are loaded and ready. By implementing this, your tests gain the precision of a Swiss watch, avoiding unnecessary hiccups and ensuring that when your test runs, it's looking at the page exactly as your users would.

A Deeper Dive into the Solution

Alright, let's roll up our sleeves and really get into the nuts and bolts of our font-loading lifesaver: await page.waitForFunction(() => document.fonts.ready);

  1. The Heart of the Matter: waitForFunction()
    This gem in the Playwright toolbox is more than just a function; it keeps an eagle eye on a specified JavaScript function, waiting for it to return something that says, "Yes, I'm true!" (a 'truthy' value). But it's not just sitting there idly; it's actively checking, again and again (in other words, polling), right in the midst of the page's bustling activities.
  2. The Secret Sauce: () => document.fonts.ready
    Now, this part is where JavaScript shows its elegance. We're using an arrow function here – a sleek, modern way to write functions in JavaScript. And inside this arrow function lies the crux of our operation: document.fonts.ready. This is a promise that resolves when all the fonts on the page have loaded and are ready to use.

So, What's Actually Happening?

Imagine this line of code as a friendly nudge to Playwright, saying, 'Easy there, let's not rush until every font on this page is perfectly in place.' It's a way of ensuring that your tests interact with a completely rendered page, not one that's still in the process of loading its visual elements. By doing this, you avoid those unpredictable test results, steering you towards a smoother, more reliable testing journey.

Practical Implementation

Here's how I've seamlessly integrated this approach into my testing setup. I've added this functionality to my Page Object Model (POM) classes as a custom method, that handles both the page navigation and font loading:

export default class MainPage {
  page: Page;

  constructor(page: Page) {
    this.page = page;
  }

  ...

  // Actions
  public async goto() {
    await this.page.goto('https://testautomationmastery.com', { waitUntil: 'load' });
    await this.page.waitForFunction(() => document.fonts.ready);
  }

}

Page Object Model (POM)

  1. Navigation with page.goto(): Initially, the method invokes goto(), directing the browser to the target URL. The parameter { waitUntil: 'load' } signals it to wait until the page has fully loaded – but remember, 'loaded' doesn't necessarily mean 'all dressed up' in terms of fonts.
  2. The Font-Loading Finale with page.waitForFunction(): The key step follows with await this.page.evaluate(() => document.fonts.ready);. As we've covered before, this line is critical for ensuring that all the fonts are loaded and displayed correctly before proceeding.

Embedding this solution in the POM provides a structured and reliable way to ensure that each page navigation within our tests happens only after the page is fully prepared, including all fonts. This method enhances the consistency and reliability of our tests, ensuring that they are always interacting with a fully-rendered page. Resulting in:

All the beautiful icons loaded and ready to go

Best Practice and Tips

  • Incorporate Font Waiting in beforeAll or beforeEach: Using the font waiting code in beforeAll or beforeEach hooks is an effective practice. These hooks are executed before all test cases (beforeAll) or before each test case (beforeEach) in a suite. By placing the font waiting logic here, you ensure that every test starts only after the fonts have fully loaded, thereby maintaining consistency across your tests. This is an efficient way to streamline your testing process, eliminating the need to repeat the font waiting code in each test, thus keeping your tests clean and efficient.
  • Monitor Performance: While ensuring font readiness is crucial, it's also important to monitor the impact on test performance. Strive for a balance between complete page readiness and efficient test execution times.

Wrap up

And there we have it, folks – our deep dive into the world of ensuring our Playwright tests wait for those elusive fonts. Experiment, tweak, and see how it transforms your testing game. And hey, don't be a stranger – share your experiences, feedback, or any clever twists you add to this approach!