Performance Metrics
Collect Core Web Vitals and resource timings from the pages your tests already visit.
The playwright-php/performance package samples browser-side paint, layout, interaction, navigation, and resource timing entries from the pages Playwright drives.
composer require --dev playwright-php/performance
Collect the vitals
use Playwright\Performance\Monitor\PerformanceMonitor;
use Playwright\Playwright;
$browser = Playwright::chromium();
$page = $browser->newPage();
$monitor = new PerformanceMonitor($page);
$monitor->navigate('https://example.com');
$vitals = $monitor->collectCoreWebVitals();
$resources = $monitor->collectResourceMetrics();
$browser->close();
What you get
echo $vitals->lcp; // Largest Contentful Paint (ms)
echo $vitals->fcp; // First Contentful Paint (ms)
echo $vitals->cls; // Cumulative Layout Shift
echo $vitals->inp; // Interaction to Next Paint (ms)
echo $vitals->fid; // First Input Delay
echo $vitals->ttfb; // Time to First Byte (ms)
TBT (Total Blocking Time) rounds out the set. collectResourceMetrics() returns per-resource timing entries for downstream analysis: budgets, regressions, slow-asset hunts.
Where it fits
Performance checks ride along with E2E flows because the pages are already loading in a real browser. The collector can wait up to five seconds for entries to settle, so keep these checks focused.
Recommended path
Start with a small number of budgets on pages that matter: homepage, search results, checkout, dashboard, or the first authenticated page. Pick one or two metrics that map to user impact. LCP and CLS are usually easier to stabilize than a full timing dashboard.
Keep the check close to the flow:
- navigate or perform the user action;
- wait for the page state that matters;
- collect vitals or resources;
- assert broad budgets;
- keep artifacts when the budget fails.
Operational notes
Performance tests are sensitive to CI noise. Use thresholds that catch regressions, not exact timings. If a shared runner makes a metric unstable, move that check to a scheduled job or a controlled runner before using it as a hard gate.
The collector reports zero when an interaction metric is unavailable and uses a navigation-timing fallback when a paint entry is missing. Treat those values as test signals, not field-grade Web Vitals data.
Combine performance metrics with traces when diagnosing a failure. A metric tells you something moved; a trace helps explain what loaded, blocked, or changed.
Adoption checklist
Before adding a performance gate, decide:
- which page or flow owns the budget;
- which metric maps to user impact;
- what threshold is stable in CI;
- who acts when the budget fails;
- whether the result is a hard gate, soft warning, or scheduled report.
Performance checks fail as a process when nobody owns the budget. A test that blocks release should have a clear owner and a clear remediation path.
What this integration is not
It is not a lab-grade benchmark and not a replacement for real-user monitoring. CI does not reproduce every device, geography, cache state, browser extension, or network condition.
Use it as a regression tripwire for controlled pages. Use production telemetry to understand real users.
Troubleshooting
- LCP varies too much: use a broader budget, pin fixture data, and check external resources.
- Resource count spikes: inspect third-party scripts, images, fonts, and lazy-loaded assets.
- INP or FID is zero: the test may not include a meaningful interaction.
- CI-only failures: compare CPU load, cache state, viewport, network access, and runner image.
FAQ
Should performance be a release gate?
Only after the signal is stable. Start as a report, then promote important budgets to gates.
Does this replace real-user monitoring?
No. It catches controlled regressions before merge. Real-user monitoring still owns field behavior.