This guide shows how to measure basic browser performance metrics including Core Web Vitals. Perfect for monitoring page loading speed and user experience.
Use Cases
- Measure Core Web Vitals (LCP, FCP, CLS)
- Test page loading performance
- Monitor resource loading times
- Check performance across different pages
Simple Implementation
from locust import task, HttpUser
from locust_plugins.users.playwright import PlaywrightUser, pw
import time
class PerformanceUser(PlaywrightUser):
def on_start(self):
# Pages to test performance
self.test_pages = [
"/",
"/products",
"/about",
"/blog",
"/contact"
]
@task(3)
@pw
def measure_core_web_vitals(self, page):
"""Measure Core Web Vitals for a page"""
test_url = self.random_page()
# Start timing
start_time = time.time()
# Navigate to page
page.goto(test_url)
# Wait for page to fully load
page.wait_for_load_state("networkidle")
# Measure First Contentful Paint (FCP)
fcp = page.evaluate("""
() => {
const entries = performance.getEntriesByType('paint');
const fcpEntry = entries.find(entry => entry.name === 'first-contentful-paint');
return fcpEntry ? fcpEntry.startTime : null;
}
""")
# Measure Largest Contentful Paint (LCP)
lcp = page.evaluate("""
() => {
return new Promise((resolve) => {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
resolve(lastEntry.startTime);
});
observer.observe({entryTypes: ['largest-contentful-paint']});
// Fallback timeout
setTimeout(() => resolve(null), 3000);
});
}
""")
# Measure Cumulative Layout Shift (CLS)
cls = page.evaluate("""
() => {
let clsValue = 0;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
});
observer.observe({entryTypes: ['layout-shift']});
return new Promise((resolve) => {
setTimeout(() => resolve(clsValue), 2000);
});
}
""")
# Calculate total load time
load_time = (time.time() - start_time) * 1000
# Log results
print(f"Performance for {test_url}:")
print(f" Load Time: {load_time:.0f}ms")
if fcp:
print(f" FCP: {fcp:.0f}ms")
if lcp:
print(f" LCP: {lcp:.0f}ms")
if cls:
print(f" CLS: {cls:.3f}")
@task(2)
@pw
def measure_resource_loading(self, page):
"""Measure resource loading performance"""
test_url = self.random_page()
# Track network requests
requests = []
def handle_request(request):
requests.append({
'url': request.url,
'method': request.method,
'start_time': time.time()
})
def handle_response(response):
# Find matching request
for req in requests:
if req['url'] == response.url:
req['response_time'] = time.time() - req['start_time']
req['status'] = response.status
req['size'] = len(response.body()) if response.body() else 0
break
page.on('request', handle_request)
page.on('response', handle_response)
# Navigate and measure
page.goto(test_url)
page.wait_for_load_state("networkidle")
# Analyze resource performance
total_size = sum(req.get('size', 0) for req in requests)
slow_resources = [req for req in requests if req.get('response_time', 0) > 2.0]
print(f"Resource loading for {test_url}:")
print(f" Total requests: {len(requests)}")
print(f" Total size: {total_size / 1024:.1f}KB")
print(f" Slow resources (>2s): {len(slow_resources)}")
@task(2)
@pw
def test_page_speed(self, page):
"""Test basic page loading speed"""
test_url = self.random_page()
# Measure different loading stages
start_time = time.time()
# Navigate to page
page.goto(test_url)
# Measure time to DOM content loaded
dom_time = time.time()
# Wait for all resources
page.wait_for_load_state("networkidle")
# Measure total load time
total_time = time.time()
# Calculate metrics
dom_load_time = (dom_time - start_time) * 1000
total_load_time = (total_time - start_time) * 1000
# Get page size
page_size = page.evaluate("document.documentElement.outerHTML.length")
print(f"Page speed for {test_url}:")
print(f" DOM Load: {dom_load_time:.0f}ms")
print(f" Total Load: {total_load_time:.0f}ms")
print(f" Page Size: {page_size / 1024:.1f}KB")
# Performance thresholds
if total_load_time > 3000:
print(f" WARNING: Slow loading page ({total_load_time:.0f}ms)")
@task(1)
@pw
def test_mobile_performance(self, page):
"""Test performance on mobile viewport"""
test_url = self.random_page()
# Set mobile viewport
page.set_viewport_size(width=375, height=667)
# Simulate slower mobile connection
page.route("**/*", lambda route: (
time.sleep(0.1), # Add 100ms delay
route.continue_()
))
start_time = time.time()
# Navigate and measure
page.goto(test_url)
page.wait_for_load_state("networkidle")
load_time = (time.time() - start_time) * 1000
print(f"Mobile performance for {test_url}:")
print(f" Mobile Load Time: {load_time:.0f}ms")
# Mobile performance is typically slower
if load_time > 5000:
print(f" WARNING: Very slow on mobile ({load_time:.0f}ms)")
def random_page(self):
"""Get a random test page"""
import random
return random.choice(self.test_pages)
Setup Instructions
- Ensure Playwright is configured in LoadForge
- Test with realistic network conditions
- Focus on user-facing performance metrics
- Set performance budgets for your site
What This Tests
- Core Web Vitals: LCP, FCP, and CLS measurements
- Loading Speed: DOM and total page load times
- Resource Performance: Network request timing and sizes
- Mobile Performance: Performance on mobile devices
Performance Metrics
- FCP (First Contentful Paint): When first content appears (< 1.8s good)
- LCP (Largest Contentful Paint): When main content loads (< 2.5s good)
- CLS (Cumulative Layout Shift): Visual stability (< 0.1 good)
- Total Load Time: Complete page loading (< 3s good)
Performance Tips
- Optimize Images: Compress and use modern formats
- Minimize JavaScript: Reduce bundle sizes
- Use CDN: Serve static assets from edge locations
- Enable Caching: Set proper cache headers
Common Performance Issues
- Large Images: Unoptimized images slow loading
- Too Much JavaScript: Heavy JS bundles block rendering
- Slow Server: Backend response times affect performance
- Layout Shifts: Content moving during load hurts CLS