Features

Measuring Performance with Playwright

Today, it is more important than ever to make sure your website loads fast. The speed of your website matters for a lot of reasons:

  • Faster websites receive more organic search engine traffic
  • Improved user experiences cause an increase in online sales
  • Less frustration when using your product

Making your webpages faster takes a lot of effort and requires measurements to figure out what to optimise and how to optimise. Modern browsers expose a lot of performance metrics that can help you with determining what to improve and which changes to make.

Playwright can help with identifying reasons that cause slowdowns, by querying the various performance related APIs that browsers provide. Chromium browsers for example provide a rich set of metrics, called Web Vitals metrics. These metrics include Time to First Byte (TTFB), Total Blocking Time (TBT) and First Contentful Paint (FCP), which are good user experience indicators and worth monitoring in your tests.

The Navigation Timing API allows you to retrieve all events and timings related to pageload times. The response contains various interesting fields, such as domComplete time, duration and connectEnd.

You can use these metrics in your tests, to detect performance regressions for each webpage you are testing.

Performance Budget

Your Playwright tests could use a performance budget, where you want to make sure that the performance of a webpage does not go below a specific standard that you set.

For example, see the following Playwright code where we detect the domComplete, if it goes below a threshold we want to fail the testcase, alerting us of a possible performance regression.

const pw = require('playwright-core');

(async () => {
  const browser = await pw.chromium.connect({
    wsEndpoint: 'wss://cloud.testingbot.com/playwright?key=api_key&secret=api_secret&browserName=chrome&browserVersion=latest',
  })
  const context = await browser.newContext()
  const page = await context.newPage()

  await page.goto('https://testingbot.com/');
  const navigationTimingJson = await page.evaluate(() =>
    JSON.stringify(performance.getEntriesByType('navigation'))
  )
  const navigationTiming = JSON.parse(navigationTimingJson)

  if (navigationTiming.domComplete < 2000) {
    console.error('ERROR: domComplete below 2 seconds')
  }

  await browser.close()
})()

Resource Timing API

The Resource Timing API provides a way to get timing information for specific resources on the page. For example, you can check how quickly an image loaded on your page.

const pw = require('playwright-core');

(async () => {
  const browser = await pw.chromium.connect({
    wsEndpoint: 'wss://cloud.testingbot.com/playwright?key=api_key&secret=api_secret&browserName=chrome&browserVersion=latest',
  })
  const context = await browser.newContext()
  const page = await context.newPage()

  await page.goto('https://testingbot.com/');
  const resourceTimingJson = await page.evaluate(() =>
    JSON.stringify(window.performance.getEntriesByType('resource'))
  )

  const resourceTiming = JSON.parse(resourceTimingJson)
  const logoResourceTiming = resourceTiming.find((element) =>
    element.name.includes('.webp')
  )

  console.log(logoResourceTiming)

  await browser.close()
})()

Paint Timing API

You can use the Paint Timing API to retrieve timings on First Paint (FP) and First Contentful Paint (FCP).

const pw = require('playwright-core');

(async () => {
  const browser = await pw.chromium.connect({
    wsEndpoint: 'wss://cloud.testingbot.com/playwright?key=api_key&secret=api_secret&browserName=chrome&browserVersion=latest',
  })
  const context = await browser.newContext()
  const page = await context.newPage()

  await page.goto('https://testingbot.com/');
  const paintTimingJson = await page.evaluate(() =>
    JSON.stringify(window.performance.getEntriesByType('paint'))
  )
  const paintTiming = JSON.parse(paintTimingJson)

  console.log(paintTiming)

  await browser.close()
})()

Layout Instability API

The Layout Instability API provides information regarding layout shifts. Layout shifts happen when the layout of your webpage changes during the loading of the page. You can use this API to evaluate the Core Web Vital Cumulative Layout Shift (CLS).

const pw = require('playwright-core');

(async () => {
  const browser = await pw.chromium.connect({
    wsEndpoint: 'wss://cloud.testingbot.com/playwright?key=api_key&secret=api_secret&browserName=chrome&browserVersion=latest',
  })
  const context = await browser.newContext()
  const page = await context.newPage()

  await page.goto('https://testingbot.com/');
  const cummulativeLayoutShift = await page.evaluate(() => {
    return new Promise((resolve) => {
      let CLS = 0

      new PerformanceObserver((l) => {
        const entries = l.getEntries()

        entries.forEach(entry => {
          if (!entry.hadRecentInput) {
            CLS += entry.value
          }
        })

        resolve(CLS)
      }).observe({
        type: 'layout-shift',
        buffered: true
      })
    })
  })

  console.log(parseFloat(cummulativeLayoutShift))

  await browser.close()
})()

Long Task API

The Long Task API returns a list of Javascript executions that take more than 50 milliseconds to complete (block the UI thread). You can use this API to calculate the Total Blocking Time (TBT).

To calculate the TBT value, we need to use a PerformanceObserver. This will observe longtasks entries and make a sum of the differences to the maximal JavaScript execution time of 50 milliseconds.

const pw = require('playwright-core');

(async () => {
  const browser = await pw.chromium.connect({
    wsEndpoint: 'wss://cloud.testingbot.com/playwright?key=api_key&secret=api_secret&browserName=chrome&browserVersion=latest',
  })
  const context = await browser.newContext()
  const page = await context.newPage()

  await page.goto('https://testingbot.com/');
  const totalBlockingTime = await page.evaluate(() => {
    return new Promise((resolve) => {
      let totalBlockingTime = 0
      new PerformanceObserver(function (list) {
        const perfEntries = list.getEntries()
        for (const perfEntry of perfEntries) {
          totalBlockingTime += perfEntry.duration - 50
        }
        resolve(totalBlockingTime)
      }).observe({ type: 'longtask', buffered: true })

      // Resolve promise if there haven't been any long tasks
      setTimeout(() => resolve(totalBlockingTime), 5000)
    })
  })

  console.log(parseFloat(totalBlockingTime))

  await browser.close()
})()

Chrome DevTools Performance

The Chrome DevTools Protocol provides many great performance tools that you can use. You can check the DevTools Protocol documentation for an extensive overview of all the possibilities.

One of the examples you can use with DevTools is throttling the network. To use this, you need to send the Network.emulateNetworkConditions command.

The syntax to connect to TestingBot is slightly different, as we now need to use connectOverCDP.

const pw = require('playwright-core');

(async () => {
  const browser = await pw.chromium.connectOverCDP({
    endpointURL: 'wss://cloud.testingbot.com/playwright?key=api_key&secret=api_secret&browserName=chrome&browserVersion=latest',
  })
  const context = await browser.newContext()
  const page = await context.newPage()

  await client.send('Network.enable')
  await client.send('Network.emulateNetworkConditions', {
    offline: false,
    downloadThroughput: (5 * 1024 * 1024) / 8,
    uploadThroughput: (4 * 1024 * 1024) / 8,
    latency: 30
  )

  await page.goto('https://testingbot.com/');
  await browser.close()
})()