React performance profiling with Playwright test integration
npm install react-performance-tracking




Automate React render performance checks in Playwright. Capture React Profiler metrics, apply CPU throttling, run warmups, enforce budgets (duration + rerenders, optional FPS), and ship JSON artifacts for debugging.
- Catch performance regressions early – Detect slow renders before they reach production
- Real-world conditions – Test with CPU throttling, network throttling, and device simulation
- CI/CD integration – Fail builds automatically when performance budgets are exceeded
- Comprehensive metrics – Track React renders, FPS, memory usage, and Core Web Vitals
- Zero boilerplate – Drop-in Playwright integration with minimal configuration
- 📦 Installation
- 🚀 Quick Start
- 📖 Documentation
- 💡 Examples
- 📝 Changelog
- 🐛 Troubleshooting
- 🤝 Contributing
- 📊 React Profiler wiring – Collect real render metrics via React's Profiler API
- 🎭 Playwright integration – test.performance() helper and performance fixture
- 🧩 Component-level profiling – Track per-component metrics with multiple profilers
- 🎞️ FPS tracking (Chromium/CDP) – Measure avg FPS via tracing; asserts and logs when enabled
- 🧠 Memory tracking (Chromium/CDP) – Track heap growth to detect memory leaks
- 📈 Web Vitals tracking – Capture LCP, INP, CLS via PerformanceObserver (all browsers)
- 🔦 Lighthouse audits (Chromium/CDP) – Run Lighthouse for performance, accessibility, SEO scores
- 🐢 CPU throttling (Chromium/CDP) – Simulate slower devices when supported
- 🌐 Network throttling (Chromium/CDP) – Simulate slow networks (3G/4G presets or custom)
- ⏱️ Custom metrics – Track custom performance marks and measures for fine-grained timing
- 🔥 Warmup runs – Default on CI to reduce cold-start noise
- 🔄 Multiple iterations – Run tests multiple times and aggregate results for statistical reliability
- 📊 Percentile metrics – P50/P95/P99 thresholds for tests with multiple iterations
- 🔥 Trace export (Chromium/CDP) – Export Chrome DevTools traces for flamegraph visualization
- ⚙️ Configurable thresholds – Separate local/CI budgets with optional buffers
- 📝 Detailed logging – Clear console output with thresholds, FPS, memory, component breakdown, and phase breakdown
- 📎 Artifacts – Attach performance JSON (metrics + config) to test reports
``bash`
npm install react-performance-tracking
If the npm package is not yet published, you can install directly from a GitHub release tarball:
`bash`Download and install a specific version
npm install https://github.com/mkaczkowski/react-performance-tracking/releases/download/v1.1.0/react-performance-tracking-1.1.0.tgz
Or build from source:
`bash`
git clone https://github.com/mkaczkowski/react-performance-tracking.git
cd react-performance-tracking
npm install && npm run build && npm pack
npm install ./react-performance-tracking-*.tgz
`json`
{
"react": "^18.0.0 || ^19.0.0",
"@playwright/test": "^1.40.0",
"lighthouse": ">=11.0.0"
}
Install only what you use:
- react for the provider/hooks@playwright/test
- for the Playwright integrationlighthouse
- for Lighthouse audits (install with npm install -D lighthouse)
> Note: You may see peer dependency warnings during install (e.g., npm warn peer react@...). These are expected and can be safely ignored - all peer dependencies are marked as optional, so you only need to install the ones you actually use.
`tsx
import { Profiler } from 'react';
import { PerformanceProvider, usePerformanceRequired } from 'react-performance-tracking/react';
function App() {
return (
);
}
// Each component wrapped with Profiler gets its own metrics
function Header() {
const { onProfilerRender } = usePerformanceRequired();
return (
);
}
function MainContent() {
const { onProfilerRender } = usePerformanceRequired();
return (
);
}
// Test output shows per-component tables when multiple components are profiled
`
`ts
// test/performance.setup.ts
import { test as base } from '@playwright/test';
import { createPerformanceTest } from 'react-performance-tracking/playwright';
export const test = createPerformanceTest(base);
export { expect } from '@playwright/test';
`
`ts
// test/my-page.perf.spec.ts
import { test } from './performance.setup';
test.describe('My Page Performance', () => {
// Simple test with basic thresholds
test.performance({
thresholds: {
base: {
profiler: {
'*': { duration: 500, rerenders: 20 },
},
},
},
})('page load performance', async ({ page, performance }) => {
await page.goto('/my-page');
await performance.init();
// Assertions run automatically
});
// Test user interactions
test.performance({
thresholds: { base: { profiler: { '*': { duration: 100, rerenders: 5 } } } },
})('button click interaction', async ({ page, performance }) => {
await page.goto('/my-page');
await performance.init();
await performance.reset(); // Isolate the interaction
await page.click('button[data-testid="submit"]');
await performance.waitUntilStable();
});
});
`
Advanced: Full configuration example
`ts`
test.performance({
warmup: true, // Run warmup iteration (default: true on CI)
throttleRate: 4, // Simulate 4x slower CPU
iterations: 3, // Run 3 times for statistical reliability
networkThrottling: 'fast-3g', // Simulate 3G network
thresholds: {
base: {
profiler: {
'*': { duration: 500, rerenders: 20 },
},
fps: 55, // Min 55 FPS (auto-enables FPS tracking)
memory: { heapGrowth: 10 1024 1024 }, // Max 10MB heap growth (auto-enables memory tracking)
webVitals: { lcp: 2500, inp: 200, cls: 0.1 }, // Auto-enables Web Vitals tracking
},
ci: {
profiler: { '*': { duration: 600 } }, // More lenient in CI
},
},
})('comprehensive performance test', async ({ page, performance }) => {
await page.goto('/my-page');
await performance.init();
});
`ts
test.describe('Advanced Usage', () => {
// Track custom timing metrics
test.performance({
thresholds: { base: { profiler: { '*': { duration: 500, rerenders: 20 } } } },
})('data loading with custom metrics', async ({ page, performance }) => {
await page.goto('/my-page');
await performance.init();
performance.mark('fetch-start');
await page.click('button[data-testid="load-data"]');
await page.waitForSelector('.data-loaded');
performance.mark('fetch-end');
performance.mark('render-start');
await performance.waitUntilStable();
performance.mark('render-end');
// Create measures for each operation
const fetchTime = performance.measure('data-fetch', 'fetch-start', 'fetch-end');
const renderTime = performance.measure('data-render', 'render-start', 'render-end');
console.log(Fetch: ${fetchTime}ms, Render: ${renderTime}ms);`
// Custom metrics are automatically included in test output and artifacts
});
});
When using createPerformanceTest(), only page and performance fixtures are passed to your test function. If you need custom fixtures (like page objects, mocks, etc.), see the Custom Fixtures Guide.
You can import from the root (react-performance-tracking) or subpaths (/react, /playwright).
`ts`
import {
PerformanceProvider,
usePerformance,
usePerformanceRequired,
usePerformanceStore,
} from 'react-performance-tracking/react';
Essential - what most users need:
`ts`
import {
createPerformanceTest,
PERFORMANCE_CONFIG,
NETWORK_PRESETS,
setLogLevel,
} from 'react-performance-tracking/playwright';
Advanced exports - for custom wrappers and low-level control
`ts
import {
// Building blocks for custom wrappers
createPerformanceInstance,
createConfiguredTestInfo,
addConfigurationAnnotation,
PerformanceTestRunner,
// Assertions
assertPerformanceThresholds,
assertDurationThreshold,
assertSampleCountThreshold,
assertFPSThreshold,
assertHeapGrowthThreshold,
// CDP Feature system
featureRegistry,
cpuThrottlingFeature,
networkThrottlingFeature,
fpsTrackingFeature,
memoryTrackingFeature,
createFeatureCoordination,
// Feature utilities
createCDPSession,
createFeatureHandle,
createResettableFeatureHandle,
// Network utilities
resolveNetworkConditions,
formatNetworkConditions,
isNetworkPreset,
// Profiler utilities
captureProfilerState,
logger,
} from 'react-performance-tracking/playwright';
`
`ts
test.performance({
warmup?: boolean; // default: true on CI, false locally
throttleRate?: number; // default: 1 (no throttling)
iterations?: number; // default: 1 (single run)
networkThrottling?: NetworkThrottlingConfig; // preset or custom (Chromium only)
exportTrace?: boolean | string; // export trace for flamegraph (Chromium only)
thresholds: {
base: {
profiler: {
'*': { // Default for all components
duration: number | { avg?: number; p50?: number; p95?: number; p99?: number };
rerenders: number;
};
// Additional component IDs can be specified
// 'header'?: { duration: 100, rerenders: 5 };
};
fps?: number | { avg?: number; p50?: number; p95?: number; p99?: number }; // Min FPS (auto-enables FPS tracking, Chromium only)
memory?: {
heapGrowth?: number; // max heap growth in bytes (auto-enables memory tracking, Chromium only)
};
webVitals?: { // Web Vitals thresholds (auto-enables tracking, all browsers)
lcp?: number; // max LCP in ms (Google recommends ≤2500)
inp?: number; // max INP in ms (Google recommends ≤200)
cls?: number; // max CLS score (Google recommends ≤0.1)
};
};
ci?: { // Overrides for CI environment
profiler?: {
[componentId: string]: Partial
};
fps?: number | { avg?: number; p50?: number; p95?: number; p99?: number };
memory?: { heapGrowth?: number };
webVitals?: { lcp?: number; inp?: number; cls?: number };
};
};
buffers?: {
duration?: number; // % buffer (default: 20) - also used for duration percentiles (p50, p95, p99)
rerenders?: number; // % buffer (default: 20)
fps?: number; // % buffer (default: 20) - also used for fps percentiles (p50, p95, p99)
heapGrowth?: number; // % buffer (default: 20)
webVitals?: { lcp?: number; inp?: number; cls?: number }; // % buffers (default: 20 each)
};
name?: string; // artifact name
})('test title', async ({ page, performance }) => { ... });
// NetworkThrottlingConfig can be:
// - Preset: 'slow-3g' | 'fast-3g' | 'slow-4g' | 'fast-4g' | 'offline'
// - Custom: { latency: number, downloadThroughput: number, uploadThroughput: number, offline?: boolean }
`
- init() – Wait for profiler initialization and stabilityreset()
- – Clear collected samples and custom metrics (isolate measurements)waitForInitialization(timeout?)
- – Wait for profiler to be readywaitUntilStable(options?)
- – Wait for React to settlemark(name)
- – Record a custom performance mark (timestamp)measure(name, startMark, endMark)
- – Create a measure between two marks (returns duration in ms)getCustomMetrics()
- – Get all recorded marks and measures
`ts`
const PERFORMANCE_CONFIG = {
profiler: {
stabilityPeriodMs: 1000,
checkIntervalMs: 100,
maxWaitMs: 5000,
initializationTimeoutMs: 10000,
},
buffers: {
duration: 20,
rerenders: 20,
avg: 20,
heapGrowth: 20,
webVitals: { lcp: 20, inp: 20, cls: 20 },
},
throttling: {
defaultRate: 1,
},
fps: {
defaultThreshold: 60,
},
memory: {
defaultThreshold: 0, // 0 = no threshold, just track
},
webVitals: {
enabled: false, // off by default to avoid overhead
},
iterations: {
defaultCount: 1,
},
get isCI() {
return Boolean(process.env.CI);
},
};
`ts
import { setLogLevel } from 'react-performance-tracking/playwright';
// Available levels: 'silent' | 'error' | 'warn' | 'info' | 'debug'
setLogLevel('silent'); // Disable all console output
setLogLevel('error'); // Only show errors
setLogLevel('info'); // Default - show info, warnings, errors
`
| Environment | Thresholds Used | Warmup Default |
| ----------- | ----------------------- | -------------- |
| CI | base merged with ci | true |base
| Local | only | false |
`
════════════════════════════════════════════════════════════════════════════════
[Performance] PERFORMANCE TEST: page-load-performance
════════════════════════════════════════════════════════════════════════════════
Environment: local | CPU: 4x | Iterations: 3
────────────────────────────────────────────────────────────────────────────────
ITERATIONS
┌─────┬─────────────┬─────────┬───────┐
│ # │ Duration │ Renders │ FPS │
├─────┼─────────────┼─────────┼───────┤
│ 1 ○ │ 35.20ms │ 26 │ 29.6 │
│ 2 │ 22.50ms │ 26 │ 47.4 │
│ 3 │ 21.60ms │ 26 │ 50.5 │
├─────┼─────────────┼─────────┼───────┤
│ AVG │ 22.05ms ±0.5│ 26 ±0.0│ 49.0 │
└─────┴─────────────┴─────────┴───────┘
○ = warmup (excluded from average)
RESULTS
┌──────────┬──────────┬───────────┬────────┐
│ Metric │ Actual │ Threshold │ Status │
├──────────┼──────────┼───────────┼────────┤
│ Duration │ 22.05ms │ < 600ms │ ✓ PASS │
│ Renders │ 26 │ ≤ 24 │ ✓ PASS │
│ FPS │ 49.0 │ ≥ 44.0 │ ✓ PASS │
│ LCP │ 1523ms │ ≤ 3000ms │ ✓ PASS │
│ INP │ 85ms │ ≤ 240ms │ ✓ PASS │
│ CLS │ 0.050 │ ≤ 0.12 │ ✓ PASS │
└──────────┴──────────┴───────────┴────────┘
════════════════════════════════════════════════════════════════════════════════
✓ ALL CHECKS PASSED
════════════════════════════════════════════════════════════════════════════════
`
- Increase initialization timeout: Adjust initializationTimeoutMs in your config
- Check PerformanceProvider: Ensure it wraps your app root component
- Verify profiler IDs: Make sure IDs match your test expectations
`ts`
// Extend timeout if needed
await performance.waitForInitialization(15000); // 15 seconds instead of default 10s
- Browser requirement: FPS tracking requires Chromium (Chrome/Edge)
- Enable in config: Add fps thresholds to automatically enable FPS tracking
- Check CDP availability: Non-Chromium browsers will silently skip FPS tracking
- Environment differences: Check if you're running in CI vs local (different thresholds apply)
- Review buffers: Default buffer is 20% - adjust in config if needed
- Component-specific thresholds: Use per-component thresholds for fine-grained control
`ts`
thresholds: {
base: {
profiler: {
'*': { duration: 500, rerenders: 20 },
},
},
ci: {
profiler: {
'*': { duration: 600 }, // More lenient in CI
},
},
},
buffers: {
duration: 10, // Reduce buffer to 10%
},
- Chromium only: These features require Chromium browser
- Check config: Verify memory.heapGrowth threshold or networkThrottling config is set
- Session conflicts: Ensure no other tools are using CDP on the same page
- 📖 Read the Documentation
- 💬 Open a GitHub Discussion
- 🐛 Report a bug via GitHub Issues
- CPU throttling, FPS tracking, memory tracking, and network throttling require Chromium/CDP; other browsers skip them quietly.
- React Profiler must be enabled in production builds if you test prod bundles.
- When using createPerformanceTest(), only page and performance` fixtures are exposed. Use the building blocks directly if you need custom fixtures (see Advanced Usage above).
- React 18+ or 19+
- Playwright 1.40+
- Node.js 18+
MIT