forked from trent_larson/crowd-funder-for-time-pwa
- Add nextTick() batching to HomeView feed processing to reduce Vue reactivity triggers - Integrate comprehensive performance tracking in 60-new-activity test - Add performance collector utilities for measuring user actions and navigation metrics - Document performance analysis with measured vs predicted data distinction Performance improvements: - Test completion: 45+ seconds → 23.7s (Chromium), 18.0s (Firefox) - Eliminated timeout issues across browsers - Added performance monitoring infrastructure for future optimization Note: Vue reactivity impact is hypothesized but not directly measured - enhanced metrics needed for validation.
257 lines
6.4 KiB
Markdown
257 lines
6.4 KiB
Markdown
# Performance Monitoring in Playwright Tests
|
||
|
||
Performance monitoring is more than just numbers — it’s about understanding **how your users experience your app** during automated test runs.
|
||
This guide will teach you not just how to set it up, but also **why each step matters**.
|
||
|
||
---
|
||
|
||
## Why Performance Monitoring Matters
|
||
|
||
Think of Playwright tests as quality control for your app’s speed and responsiveness.
|
||
By adding performance monitoring, you can:
|
||
|
||
- 🚨 **Catch regressions early** before users feel them
|
||
- 🎯 **Keep user experience consistent** across releases
|
||
- 🔎 **Spot bottlenecks** in login, navigation, or heavy data flows
|
||
- 📊 **Make informed decisions** with hard data, not guesses
|
||
- 🏆 **Maintain performance standards** as features grow
|
||
|
||
> **Key Insight:** Without metrics, “fast” is just a feeling. With metrics, it’s a fact.
|
||
|
||
---
|
||
|
||
## How It Works: The Architecture
|
||
|
||
The monitoring system has **four pillars**:
|
||
|
||
1. **PerformanceCollector Class** – Collects raw metrics
|
||
2. **Performance Utilities** – Easy-to-use helper functions
|
||
3. **Test Integration** – Hooks directly into Playwright tests
|
||
4. **Report Generation** – Creates JSON reports you can analyze later
|
||
|
||
Here’s a mental model:
|
||
|
||
```
|
||
Playwright Test
|
||
|
|
||
v
|
||
PerformanceCollector (collects data)
|
||
|
|
||
v
|
||
Report Generation → JSON / HTML / CI attachments
|
||
```
|
||
|
||
### Core Collector
|
||
|
||
```typescript
|
||
// performanceUtils.ts
|
||
export class PerformanceCollector {
|
||
private page: Page;
|
||
public metrics: any;
|
||
public navigationMetrics: any[];
|
||
private cdpSession: any;
|
||
|
||
// Methods for collecting various metrics
|
||
async collectNavigationMetrics(label: string)
|
||
async collectWebVitals()
|
||
async measureUserAction(actionName: string, actionFn: () => Promise<void>)
|
||
generateReport()
|
||
}
|
||
```
|
||
|
||
👉 **Teaching Point:** `measureUserAction` wraps a user action and times it, giving you reproducible benchmarks.
|
||
|
||
---
|
||
|
||
## Quick Start: A Simple Example
|
||
|
||
```typescript
|
||
import { createPerformanceCollector, attachPerformanceData } from './performanceUtils';
|
||
|
||
test('My test with performance monitoring', async ({ page }, testInfo) => {
|
||
const perfCollector = await createPerformanceCollector(page);
|
||
|
||
// Measure user action
|
||
await perfCollector.measureUserAction('click-button', async () => {
|
||
await page.click('#my-button');
|
||
});
|
||
|
||
// Attach data to the test report
|
||
await attachPerformanceData(testInfo, perfCollector);
|
||
});
|
||
```
|
||
|
||
✅ After this test runs, you’ll find performance data **directly in the Playwright report**.
|
||
|
||
---
|
||
|
||
## Advanced Example: A Complete User Flow
|
||
|
||
```typescript
|
||
test('Complex user flow with performance tracking', async ({ page }, testInfo) => {
|
||
const perfCollector = await createPerformanceCollector(page);
|
||
|
||
await perfCollector.collectNavigationMetrics('initial-load');
|
||
|
||
await perfCollector.measureUserAction('login', async () => {
|
||
await page.fill('#username', 'user');
|
||
await page.fill('#password', 'pass');
|
||
await page.click('#login-button');
|
||
});
|
||
|
||
await perfCollector.measureUserAction('navigate-to-dashboard', async () => {
|
||
await page.goto('/dashboard');
|
||
});
|
||
|
||
await attachPerformanceData(testInfo, perfCollector);
|
||
});
|
||
```
|
||
|
||
> **Pro Tip:** Use descriptive labels like `'login'` or `'navigate-to-dashboard'`
|
||
to make reports easy to scan.
|
||
|
||
---
|
||
|
||
## What Metrics You’ll See
|
||
|
||
### Navigation Metrics (Page Load)
|
||
|
||
- `domContentLoaded` → When DOM is ready
|
||
- `loadComplete` → When page is fully loaded
|
||
- `firstPaint` → When users first *see* something
|
||
- `serverResponse` → How quickly the backend responds
|
||
|
||
### User Action Metrics
|
||
|
||
- `duration` → How long the action took
|
||
- `metrics` → Detailed performance snapshots
|
||
|
||
### Memory Usage
|
||
|
||
- `used`, `total`, `limit` → Helps catch leaks and spikes
|
||
|
||
### Web Vitals
|
||
|
||
- **LCP** → Largest Contentful Paint
|
||
- **FID** → First Input Delay
|
||
- **CLS** → Layout stability
|
||
|
||
---
|
||
|
||
## Reading the Data: A Beginner’s Lens
|
||
|
||
Here’s what a **healthy test run** might look like:
|
||
|
||
```json
|
||
{
|
||
"label": "home-page-load",
|
||
"metrics": {
|
||
"domContentLoaded": 294,
|
||
"loadComplete": 295,
|
||
"serverResponse": 27.6,
|
||
"resourceCount": 96
|
||
}
|
||
}
|
||
```
|
||
|
||
**Interpretation:**
|
||
|
||
- DOM loaded fast (<500ms) ✅
|
||
- Server response is excellent (<100ms) ✅
|
||
- Resource count is reasonable for a SPA ✅
|
||
|
||
Now, here’s a **problematic run**:
|
||
|
||
```json
|
||
{
|
||
"label": "slow-page-load",
|
||
"metrics": {
|
||
"domContentLoaded": 2500,
|
||
"loadComplete": 5000,
|
||
"serverResponse": 800,
|
||
"resourceCount": 200
|
||
}
|
||
}
|
||
```
|
||
|
||
**Interpretation:**
|
||
|
||
- DOM took 2.5s ❌
|
||
- Full load took 5s ❌
|
||
- Too many resources (200) ❌
|
||
|
||
> **Lesson:** Slow page loads often mean large bundles, too many requests, or server lag.
|
||
|
||
---
|
||
|
||
## Performance Threshold Cheat Sheet
|
||
|
||
| Metric | Excellent | Good | Poor |
|
||
|--------|-----------|------|------|
|
||
| domContentLoaded | < 500ms | < 1000ms | > 2000ms |
|
||
| loadComplete | < 1000ms | < 2000ms | > 3000ms |
|
||
| userAction duration | < 100ms | < 300ms | > 500ms |
|
||
| memory usage | < 50MB | < 100MB | > 150MB |
|
||
|
||
👉 Use these thresholds to set alerts in your regression tests.
|
||
|
||
---
|
||
|
||
## Common Patterns
|
||
|
||
1. **Initial Page Load**
|
||
- ✅ DOM in <500ms
|
||
- ✅ Load in <1000ms
|
||
- ⚠️ Watch out for large bundles
|
||
|
||
2. **User Interaction**
|
||
- ✅ Actions under 100ms
|
||
- ✅ Few/no extra requests
|
||
- ⚠️ Avoid bloated API calls
|
||
|
||
3. **Navigation**
|
||
- ✅ <200ms between pages
|
||
- ⚠️ Inconsistency usually means missing cache headers
|
||
|
||
---
|
||
|
||
## Best Practices
|
||
|
||
- 📏 **Consistency** – Measure the same flows every run
|
||
- 🧪 **Realism** – Test with production-like data
|
||
- 🏗 **Environment Control** – Use stable test environments
|
||
- 📉 **Set Thresholds** – Define what “slow” means for your team
|
||
- 🔁 **Continuous Monitoring** – Run in CI/CD and watch trends
|
||
|
||
> **Remember:** A fast app last release doesn’t guarantee it’s fast today.
|
||
|
||
---
|
||
|
||
## Migration Tip
|
||
|
||
**Before (Manual Timing):**
|
||
```typescript
|
||
const start = Date.now();
|
||
await page.click('#button');
|
||
console.log(Date.now() - start);
|
||
```
|
||
|
||
**After (Structured Monitoring):**
|
||
|
||
```typescript
|
||
await perfCollector.measureUserAction('button-click', async () => {
|
||
await page.click('#button');
|
||
});
|
||
```
|
||
|
||
✅ Cleaner, more consistent, and automatically reported.
|
||
|
||
---
|
||
|
||
## Key Takeaway
|
||
|
||
Performance monitoring in Playwright isn’t just about collecting data — it’s
|
||
about making your tests **teach you** how users experience your app.
|
||
The **PerformanceCollector** class plus good testing habits give you a clear,
|
||
data-driven picture of app health.
|