Manually diagnose slow interactions in the lab

Learn how to take your field data into the lab to reproduce and identify the causes behind slow interactions through manual testing.

A challenging part of optimizing Interaction to Next Paint (INP) is figuring out what's causing poor INP. There's a large variety of potential causes: third-party scripts that schedule many tasks on the main thread, large DOM sizes, expensive event callbacks, and more.

Finding ways to fix poor INP can be difficult. To start, you have to know which interactions tend to be responsible for a page's INP. If you don't know which interactions on your website tend to be the slowest from a real user perspective, first read Find slow interactions in the field. Once you have field data to guide you, you can test those specific interactions manually in lab tools to work out why those interactions are slow.

What if you don't have field data?

Having field data is vital, as it saves you a lot of time trying to figure out which interactions need to be optimized. You might be in a position where you don't have field data, though. If that describes your situation, it's still possible to find interactions to improve, although it requires a bit more effort and a different approach.

Total Blocking Time (TBT) is a lab metric that assesses page responsiveness during load, and it correlates very well with INP. If your page has a high TBT, that's an important signal that your page may not be very responsive to user interactions during the initial page load.

To figure out your page's TBT, you can use either Lighthouse or PageSpeed Insights. If a page's TBT doesn't meet the "good" threshold, there's a good chance that the main thread is too busy during page load, and that can affect how quickly interactions occur during that crucial time in the page lifecycle.

To find slow interactions after the page has loaded, you might need to rely on other types of data, such as common user flows that you may already have identified in your website's analytics. If you work on an ecommerce website, for example, a common user flow would be the actions users take when they're adding items to an online shopping cart and subsequently going through the checkout process.

Whether or not you have field data, the next step is to manually test and reproduce slow interactions—because it's only when you're able to conclusively identify a slow interaction that you can fix it.

Reproduce slow interactions in the lab

There are a number of ways you can reproduce slow interactions in the lab through manual testing, but the following is a framework you can use to reproduce slow interactions in a lab setting while minimizing cognitive effort.

Don't reach for the performance profiler right away

If you're already familiar with Chrome's performance profiler, you know that it provides tons of useful diagnostic information when troubleshooting page performance issues. It's a useful tool with many upsides.

However, the downside is that Chrome's performance profiler doesn't provide a live view while interacting with the page. It takes significant time to use it, and there are more efficient ways to manually test interactions first. The idea is to spend the minimum amount of time and effort to reproduce slow interactions and—once a slow interaction has been conclusively identified—then you should use the performance profiler to dive deep into the cause(s) behind it.

Use the Web Vitals Chrome Extension

The Web Vitals Chrome Extension involves the lowest amount of effort in manually testing interaction latency. Once installed, the extension displays interaction data in the DevTools console, provided you do the following first:

  1. In Chrome, click the extensions icon to the right of the address bar.
  2. Locate the Web Vitals extension in the drop-down menu.
  3. Click the icon at the right to open the extension's settings.
  4. Click Options.
  5. Enable the Console logging checkbox in the resulting screen, and then click Save.

Once this has been done, open the console in Chrome DevTools, and begin testing suspect interactions on your website. As you interact with the page, useful diagnostic data appears in the console.

A screenshot of the console logging that the Web Vitals extension provides for interactions. The logging contains details such timings and other contextual information.
A console entry from the Web Vitals extension when console logging is turned on. Each qualifying interaction will log interaction data to the console.

Use a JavaScript snippet

As useful as it may be, the Web Vitals extension may not be a viable option for everyone. Browser extensions can be blocked in some environments due to device security policies. Extensions also can't be installed on mobile devices. The latter is problematic if you want to manually test on a physical Android-powered device with remote debugging.

An alternative method to using the Web Vital extension involves copying and pasting some JavaScript into the DevTools console. The following code gives you the same console output as the Web Vitals extension for every interaction:

let worstInp = 0;

const observer = new PerformanceObserver((list, obs, options) => {
  for (let entry of list.getEntries()) {
    if (!entry.interactionId) continue;

    entry.renderTime = entry.startTime + entry.duration;
    worstInp = Math.max(entry.duration, worstInp);

    console.log('[Interaction]', entry.duration, `type: ${entry.name} interactionCount: ${performance.interactionCount}, worstInp: ${worstInp}`, entry, options);
  }
});

observer.observe({
  type: 'event',
  durationThreshold: 0, // 16 minimum by spec
  buffered: true
});

Once you've determined that an interaction is reliably slow, you can then profile the interaction in the performance profiler to get detailed information on why it's slow.

What if you can't reproduce a slow interaction?

What if your field data suggests a particular interaction is slow, but you can't manually reproduce the problem the lab? There are a couple of reasons why this could be the case, and it's a common challenge in troubleshooting performance issues of any kind.

For once, your circumstances as you test interactions are dependent on the hardware and network connection you're using. After all, you may be using a fast device on a fast connection—but that doesn't mean your users are so lucky. You can do one of three things if this situation applies in your case:

  1. If you have a physical Android-powered device, use remote debugging to open a Chrome DevTools instance on your host machine and try to reproduce slow interactions there. Mobile devices are often not as fast as laptops or desktop machines, so slow interactions may be more readily observed in such conditions.
  2. If you don't have a physical device, enable the CPU throttling feature in Chrome DevTools.
  3. Try both steps 1 and 2 together, as you can also enable CPU throttling on the DevTools instance for a physical Android-powered device.

Another cause could be that you're waiting for a page to load before interacting with it, but your users are not. If you're on a fast network, simulate slower network conditions by enabling network throttling, then interact with the page as soon as it paints. You should do this because the main thread is often busiest during startup, and testing at that time may reveal what your users are experiencing.

Record a trace

To get more information on why an interaction is slow, the next step is to use the performance profiler in Chrome DevTools. To profile an interaction in Chrome's performance profiler, do the following:

  1. Have the page you need to test open.
  2. Open Chrome DevTools and go to the Performance panel.
  3. Click the Record button at the upper left of the panel to start tracing.
  4. Perform the interaction(s) you want to profile.
  5. Click the Record button again to stop tracing.

When the profiler populates, the first place to look should be the activity summary at the top of the profiler. The activity summary shows red bars at the top where long tasks occurred in the recording. This lets you quickly zoom in on problem areas.

A screenshot of the activity summary in the performance panel of Chrome DevTools. The activity displayed is mostly from JavaScript that causes a long task, which is highlighted in red above the flame chart.
The activity summary at the top of Chrome's performance profiler. Long tasks are highlighted in red above the activity flame chart. In this case, significant scripting work was responsible for most of the work in the long task.

You can quickly focus on problem areas by dragging and selecting a region in the activity summary. Once you've focused to where the interaction occurred, the Interactions track helps you line up the interaction and the activity that occurred in the main thread track beneath it:

A screenshot of an interaction as visualized in the performance panel of Chrome DevTools. An interactions track above the main thread track shows the duration of an interaction, which can be lined up with main thread activity.
An interaction profiled in the performance profiler in Chrome's DevTools. The Interactions track shows a series of events that correspond to a click interaction. The Interactions track entries span across the tasks responsible for driving the interaction.

From here, it's a matter of digging deeper into the problem causing the slow interaction. There are many things that can contribute to high interaction latency—some of which is covered further on in this guide.

Use Lighthouse timespans as an alternative to tracing

Chrome's performance profiler—while rich with diagnostic information—can be a bit intimidating to the uninitiated. One alternative to the performance profiler is Lighthouse's timespan mode. To use this mode, do the following:

  1. With DevTools open, go to the Lighthouse tab in DevTools.
  2. Under the section labeled Mode, select the Timespan option.
  3. Select either the Desktop or Mobile device type under the section labeled Device.
  4. Ensure at least the checkbox labeled Performance is selected under the Categories label.
  5. Click the Start timespan button.
  6. Test the interaction(s) on the page you want to get data for.
  7. Click the End timespan button and wait for the audit to appear
  8. Once the audit populates in the Lighthouse tab, you can filter the audits by INP by clicking the INP link next to the label which reads Show audits relevant to.

At this point, a drop-down list for audits that have failed or passed should appear. When you expand that drop-down, there should be a breakdown of time spent during the interaction.

A screenshot of a Lighthouse audit provided by its timespan mode. The audit is specific to INP, and shows details for an interaction, including a screenshot of the element that triggered it, and a table beneath detailing where time was spent processing the interaction.
An interaction profiled in Lighthouse's timespan mode. When interactions are made with the page, Lighthouse provides an audit detailing where the time during an interaction was spent, and breaks it down by input delay, processing delay, and presentation delay.

How to identify long input delays

One thing that could be contributing to high interaction latency is input delay. The input delay is the first phase of an interaction. This is the period of time from when the user action is first received by the operating system to the point at which the browser is able to start processing the first event triggered by that input. The input delay ends right as the event callbacks for the interaction begin to run.

Identifying input delays in Chrome's performance profiler can be done by locating the start of an interaction in the interactions track, and then finding the beginning of when the event callbacks for that interaction start to run.

Some amount of input delay should always be expected, as it takes some time for the operating system to pass the input event to the browser—but you do have some control over how long the input delay is. The key is to figure out if there is work running on the main thread that's preventing your callbacks from running.

A depiction of input delay in Chrome's performance panel. The start of the interaction comes significantly before the event callbacks because of increased input delay due to a timer firing from a third-party script.
Input delay caused by a task fired by a timer from a third-party script.

In the previous figure, a task from a third-party script is running as the user attempts to interact with the page, and therefore extends the input delay. The extended input delay affects the interaction's latency, and could therefore affect the page's INP.

How to identify expensive event callbacks

Event callbacks run immediately after the input delay. If an event callback runs for too long, it delays the browser from presenting the next frame, and can add significantly to an interaction's total latency. Long-running event callbacks can be the result of computationally expensive first-party or third-party JavaScript—and in some cases, both.

A depiction of event callback tasks in Chrome's performance panel. The event callbacks occur for the pointerdown and click events, which occur in a long task.
The event callbacks that run in response to a click interaction, as shown in the performance profiler in Chrome DevTools. Note the red triangle in the upper right corner of both the Event: pointerdown and Event: click entries, which identifies expensive event callbacks.

Finding expensive event callbacks can be done by observing the following in a trace for a specific interaction:

  1. Determine whether the task associated with the event callbacks is a long task. To reveal long tasks in a lab setting more reliably, you may need to enable CPU throttling in the performance panel, or connect a low to mid-tier Android-powered device and use remote debugging.
  2. If the task that runs the event callbacks is a long task, look for event handler entries—for example,entries with names such as Event: click—in the call stack that have a red triangle at the upper right corner of the entry. These are expensive event callbacks.

To address expensive event callbacks, try one of the following strategies:

  1. Do as little work as possible. Is everything that happens in an expensive event callback strictly necessary? If not, consider removing that code altogether if you can, or deferring its execution to a later point in time if you can't. You can also take advantage of framework features to help. For example, React's PureComponent class and memoization feature can skip unnecessary rendering work when props and state haven't changed for a component.
  2. Defer non-rendering work in the event callback to a later point in time. Long tasks can be broken up by yielding to the main thread. Whenever you yield to the main thread, you're ending execution of the current task and breaking up the remainder of the work into a separate task. This gives the renderer a chance to process updates to the user interface that were performed earlier in the event callback. If you happen to be using React, its transitions feature can do this for you.

By employing these strategies, you should be able to get your event callbacks in a place where they're responding more quickly to user input.

How to identify presentation delays

Long input delays and expensive event callbacks aren't the only possible culprits of poor INP. Sometimes the rendering updates that occur in response to even small amounts of event callback code can be expensive. The time it takes for the browser to render visual updates to the user interface to reflect the result of an interaction is known as presentation delay.

Rendering work as visualized in the performance panel of Chrome DevTools. The rendering work occurs after the event callback in order to paint the next frame.
Rendering tasks as shown in Chrome's performance profiler. The rendering work is shown in purple, with paint work in green.

Of all the possible causes of high interaction latency, rendering work can be the most difficult to troubleshoot and fix, but the result is worth the effort. Excessive rendering work could be caused by any of the following:

  • Large DOM sizes. The rendering work required to update a page's presentation often increases along with the size of the page's DOM. For more information, read How large DOM sizes affect interactivity—and what you can do about it.
  • Forced reflows. This happens when you apply style changes to elements in JavaScript, and then query the results of that work. The result is that the browser has to perform the layout work before doing anything else, so that the browser can return the updated styles. For more information and tips on avoiding forced reflows, read Avoid large, complex layouts and layout thrashing.
  • Excessive or unnecessary work in requestAnimationFrame callbacks. requestAnimationFrame() callbacks are run during the rendering phase of the event loop, and must complete before the next frame can be presented. If you're using requestAnimationFrame() to do work that doesn't involve changes to the user interface, understand that you could be delaying the next frame.
  • ResizeObserver callbacks. Such callbacks run prior to rendering, and may delay presentation of the next frame if the work in them is expensive. As with event callbacks, defer any logic not needed for the next frame.

Troubleshooting INP is an iterative process

Finding out what's causing high interaction latency that contributes to poor INP takes a lot of work—but if you can pin down the causes, you're halfway there. By following a methodical approach to troubleshooting poor INP, you can reliably pin down what's causing a problem, and arrive more quickly to the right fix. To review:

  • Rely on field data to find slow interactions.
  • Manually test problematic field interactions in the lab to see if they're reproducible.
  • Identify whether the cause is due to long input delay, expensive event callbacks, or expensive rendering work.
  • Repeat.

The last of these is the most important. Like most other work you do to improve page performance, troubleshooting and improving INP is a cyclical process. When you fix one slow interaction, move onto the next, and repeat until you start to see results. Stay vigilant!