Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Playwright›Performance testing with Playwright

Advanced Playwright Techniques

Performance testing with Playwright

Performance testing in Playwright involves measuring metrics like load time, resource utilization, and interaction latency to ensure a seamless user experience. It matters because slow applications drive user churn and degrade conversion rates, making performance a critical functional requirement. You should reach for these techniques during continuous integration cycles or when investigating specific regressions in your front-end rendering pipeline.

Capturing Performance Metrics with Har Files

To understand how your application behaves over the network, you must inspect the raw data of every request and response. Playwright allows you to record HTTP archives, known as HAR files, which capture a complete record of network traffic during a test execution. This is essential because performance bottlenecks are frequently caused by oversized assets, inefficient API responses, or excessive round-trips to the server. By generating a HAR file, you gain a granular view of latency, headers, and payload sizes for every resource fetched by the browser. You should analyze these logs to identify 'heavy' requests that block the critical rendering path. Unlike simple timing functions, HAR analysis provides the evidentiary basis for performance optimizations by showing you exactly which assets contribute most to your total page weight, allowing you to prioritize your optimization efforts based on objective data rather than guesswork or generic performance assumptions.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({ recordHar: { path: 'performance.har' } });
  const page = await context.newPage();
  // Navigating forces the network recording to trigger
  await page.goto('https://example.com');
  await browser.close();
})();

Measuring Page Load Timing

The most fundamental way to track performance is to hook into the browser's navigation lifecycle events. Playwright grants access to high-resolution timestamps via the Performance API, which is accessible directly within the browser context. By injecting JavaScript into the page, you can extract navigation timing metrics, such as Time to First Byte (TTFB) or DOM Content Loaded (DCL). These metrics are vital because they tell you how fast the server responds and how quickly the browser parses the structure of your page. Understanding these timings helps you distinguish between server-side latency and client-side rendering delays. If your TTFB is high, the issue lies in your back-end architecture; if your DCL is slow, the issue is likely due to bloated HTML or poorly optimized third-party scripts. By wrapping these checks in your tests, you can assert that your application meets strict performance budgets, failing the build if thresholds are breached, thus preventing regressions in the delivery pipeline.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  // Access the browser-native performance timing API
  const timing = await page.evaluate(() => JSON.stringify(performance.timing));
  console.log('Page Load Metrics:', timing);
  await browser.close();
})();

Visualizing Request Latency and Tracing

Visualizing the timeline of your test execution is crucial when you need to pinpoint exactly when a performance degradation occurs. Playwright Tracing provides a full 'black box' recorder for your tests, capturing screenshots, console output, and network requests across the entire execution window. This is powerful because it bridges the gap between raw data and visual reality. When a test runs slowly, you can open the trace viewer to see if the delay was caused by a slow-loading image, a massive JavaScript bundle, or even a local network fluctuation. Tracing allows you to inspect the timeline of events, showing you exactly which network call was active while the UI remained unresponsive. By analyzing traces regularly, you can identify patterns like layout shifts or long-running tasks that interfere with user interactions, providing a clear path toward improving your application's responsiveness and overall interaction-to-next-paint scores during active development cycles.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  await context.tracing.start({ screenshots: true, snapshots: true });
  const page = await context.newPage();
  await page.goto('https://example.com');
  // Stop tracing to save the results to a zip file
  await context.tracing.stop({ path: 'trace.zip' });
  await browser.close();
})();

Throttling Network and CPU

Real-world users rarely experience your application on a high-speed development machine with an unrestricted CPU. To simulate realistic conditions, Playwright allows you to throttle the network and CPU of the browser context. Network throttling mimics slow connections like 3G or 4G, exposing issues with asset prioritization and inefficient caching strategies. CPU throttling simulates lower-end hardware, which is critical for identifying long-running scripts that lock the main thread and lead to a poor user experience. By constraining these resources, you ensure your application remains performant even under less-than-ideal conditions. This is essential for preventing 'performance drift,' where an application seems fast on a developer's machine but becomes unusable for a user on a mobile device in a remote area. By testing these extremes, you force your application to handle latency gracefully, encouraging developers to implement lazy loading, efficient caching, and code splitting earlier in the development lifecycle.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const session = await context.newCDPSession(await context.newPage());
  // Emulate a 3G network and 4x CPU slowdown
  await session.send('Network.emulateNetworkConditions', { offline: false, downloadThroughput: 1.5 * 1024 * 1024 / 8, uploadThroughput: 750 * 1024 / 8, latency: 150 });
  await session.send('Emulation.setCPUThrottlingRate', { rate: 4 });
})();

Monitoring Console and Network Logs

Monitoring console logs and network errors in real-time provides immediate insight into the health of your application's front-end execution. Sometimes, performance issues are not caused by slow network requests but by runtime JavaScript errors that block the browser's execution thread. By listening to 'console' and 'requestfailed' events, you can automatically capture these errors as they happen during your test run. This technique is indispensable for debugging intermittent performance issues that might be caused by silent failures in third-party scripts or API calls that return 4xx or 5xx status codes unexpectedly. By centralizing these logs, you gain visibility into the silent killers of web performance. If an API request fails, it might trigger a retry mechanism that consumes excessive CPU, leading to the impression of a slow interface. Capturing these events ensures your performance analysis is comprehensive, accounting for both network-level latency and application-level logic errors that hinder performance.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  page.on('console', msg => console.log(`Log: ${msg.text()}`));
  page.on('requestfailed', req => console.log(`Failed Request: ${req.url()}`));
  await page.goto('https://example.com');
  await browser.close();
})();

Key points

  • HAR files provide a comprehensive record of network activity for performance analysis.
  • Browser timing APIs can be accessed within tests to measure server and client performance.
  • Tracing is essential for visual correlation between user interaction and slow network events.
  • Network and CPU throttling are necessary to simulate real-world user hardware limitations.
  • Console and request monitoring helps identify silent failures that impact rendering performance.
  • Performance testing should be integrated into the CI/CD pipeline to prevent regressions.
  • Identifying bottleneck assets is a priority for optimizing the critical rendering path.
  • High-resolution timestamps enable granular measurement of load performance and responsiveness.

Common mistakes

  • Mistake: Testing performance using non-headful execution in a local environment. Why it's wrong: Headless mode can significantly impact rendering engine performance and resource allocation. Fix: Always perform performance benchmarks in the specific environment (CI/CD or local) using consistent configurations, ideally testing in headful mode to mimic real browser behavior.
  • Mistake: Measuring total page load time without isolating network latency. Why it's wrong: Total time is noisy and depends on external factors like network speed. Fix: Use Playwright's tracing and network event listeners to measure time-to-first-byte (TTFB) and request completion times precisely.
  • Mistake: Running performance assertions on an empty cache. Why it's wrong: Real users rarely experience a cold cache for repeat visits. Fix: Use `browserContext` to persist state or mock storage state to simulate realistic user scenarios.
  • Mistake: Neglecting to throttle the network during performance tests. Why it's wrong: Tests on high-speed internet hide performance bottlenecks in asset loading. Fix: Use the `CDPSession` to throttle network throughput and latency to simulate mobile or throttled environments.
  • Mistake: Including interaction latency in backend performance metrics. Why it's wrong: Playwright actions have overhead and browser engine lag that skew server response data. Fix: Separate browser-side interaction metrics from network-level performance metrics using Playwright's network interception.

Interview questions

What is the primary objective of performance testing using Playwright, and how does it differ from functional testing?

The primary objective of performance testing with Playwright is to measure how an application behaves under specific conditions, focusing on metrics like load time, response time, and resource utilization. Unlike functional testing, which verifies that features work as expected, performance testing ensures the application remains responsive and scalable. By using Playwright’s browser automation, we can simulate real user interactions to capture precise timing data via the `performance.timing` API, helping us identify bottlenecks before they affect the end-user experience.

How can you measure page load performance metrics within a Playwright test script?

To measure load performance, you can evaluate the browser's performance metrics directly within the context of the page. By using `page.evaluate(() => window.performance.timing)`, you gain access to high-resolution timestamps for events like navigation start, dom complete, and load event end. For example, calculating the time to interactive involves subtracting `navigationStart` from `domContentLoadedEventEnd`. This programmatic approach allows you to inject these metrics into your test reports to fail builds if performance thresholds are exceeded during automated execution cycles.

What is the purpose of network interception in Playwright performance testing, and how do you implement it?

Network interception in Playwright allows you to monitor and manipulate network requests, which is crucial for identifying slow-loading assets like heavy images or external scripts. You implement this using `page.on('request')` and `page.on('response')` listeners. By logging the `response.timing()` data for specific endpoints, you can pinpoint exactly which API call or static file is causing latency. This is essential for isolating backend delays from frontend rendering issues, providing developers with actionable data to optimize asset delivery.

Compare using Playwright for 'synthetic monitoring' versus using it for 'load testing'. What are the limitations and best use cases for each?

Synthetic monitoring with Playwright involves running automated scripts at intervals to check availability and performance of critical user paths, which is excellent for catching regressions in production environments. In contrast, load testing with Playwright, while possible, is limited because Playwright is resource-intensive for the client machine. While Playwright is perfect for verifying frontend rendering and interaction time under light load, it is not optimized for high-concurrency stress testing, where thousands of virtual users are required, as browser instantiation would quickly exhaust your testing infrastructure's memory and CPU.

How do you integrate Lighthouse performance auditing into your existing Playwright test suite?

Integrating Lighthouse into Playwright involves using the `lighthouse` node package alongside Playwright's Chrome DevTools Protocol (CDP) session. You launch a browser, connect via CDP using `browserContext.newCDPSession(page)`, and pass the session to the Lighthouse runner. This allows you to generate comprehensive performance scores for accessibility, SEO, and speed metrics during an existing Playwright test run. It effectively turns your functional tests into performance audits, ensuring that performance best practices are maintained as part of your Continuous Integration pipeline workflow.

Describe how to handle 'Performance Budgets' in Playwright. How do you ensure that a feature under development does not exceed these thresholds?

Handling performance budgets requires setting strict, measurable limits for metrics like 'First Contentful Paint' or 'Largest Contentful Paint' within your test logic. You can store these budgets in a configuration file and use `page.evaluate()` to extract the current metrics after a page load. If the measured time exceeds the defined threshold, you explicitly use `expect(metric).toBeLessThan(budget)` to fail the test. By including these checks in your CI/CD pipeline, you treat performance as a first-class citizen, forcing developers to optimize code whenever a PR introduces a performance regression that crosses your established boundaries.

All Playwright interview questions →

Check yourself

1. When measuring the performance of an action, why is it discouraged to rely solely on the duration of an `await page.click()` call?

  • A.Playwright waits for the actionability of the element before performing the click.
  • B.The browser engine handles input events asynchronously, making the promise resolve before the UI update completes.
  • C.The duration includes internal Playwright overhead, automatic retries, and waiting for stability.
  • D.Network events are not triggered by the click method.
Show answer

C. The duration includes internal Playwright overhead, automatic retries, and waiting for stability.
Option 2 is correct because 'await' on an action command includes the time taken for actionability checks and potential re-tries, not just the execution. The other options are either false or do not explain why the duration is inaccurate for performance benchmarks.

2. Which approach is most effective for capturing precise performance data for a network request during a test?

  • A.Using a timer before and after the action triggering the request.
  • B.Using page.on('requestfinished') to compare 'responseEnd' and 'requestStart' timings.
  • C.Checking the console logs for performance warnings.
  • D.Using page.waitForLoadState('networkidle').
Show answer

B. Using page.on('requestfinished') to compare 'responseEnd' and 'requestStart' timings.
Option 1 is correct as it utilizes granular data from the browser's internal timing events. Options 0 and 3 are unreliable due to overhead, and option 3 is a general state check rather than a data measurement.

3. Why does Playwright's 'networkidle' state often produce inconsistent results in performance testing?

  • A.Because it is hard-coded to wait exactly 5 seconds.
  • B.Because it depends on the absence of network requests for at least 500ms, which is too long for modern apps.
  • C.Because it fluctuates based on background telemetry and analytics scripts.
  • D.Because it only tracks WebSocket connections.
Show answer

C. Because it fluctuates based on background telemetry and analytics scripts.
Option 2 is correct because modern web apps frequently trigger analytics or monitoring requests. The other options are incorrect interpretations of how 'networkidle' functions.

4. How can you simulate a throttled environment to identify performance bottlenecks in asset loading?

  • A.By modifying the browser's internal clock using page.evaluate.
  • B.By using a CDPSession to set the network conditions emulation.
  • C.By increasing the timeout of every locator in the test.
  • D.By intentionally corrupting the local storage before the test starts.
Show answer

B. By using a CDPSession to set the network conditions emulation.
Option 1 is the standard way to programmatically limit bandwidth. The other options do not accurately simulate real-world throttled network conditions.

5. When generating a Trace Viewer file for performance analysis, what is the primary benefit over traditional console logging?

  • A.It generates a report that is automatically accepted by SEO crawlers.
  • B.It provides a synchronized view of network requests, console logs, and the DOM at every stage of execution.
  • C.It reduces the total execution time of the test script by 50%.
  • D.It automatically identifies and fixes broken CSS selectors.
Show answer

B. It provides a synchronized view of network requests, console logs, and the DOM at every stage of execution.
Option 1 is correct because the trace viewer provides a holistic view of the state. Other options suggest performance improvements or automation capabilities that are not part of trace analysis.

Take the full Playwright quiz →

← PreviousDebugging Playwright tests effectivelyNext →Writing maintainable and reusable test code

Playwright

37 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app