DEV Community

Styrow.dev
Styrow.dev

Posted on Originally published at styrow.dev

How do you measure and assert core web vitals or other performance metrics within a Playwright end-to-end test?

πŸ”₯ SDET Interview Scenario of the Day:
How do you measure and assert Core Web Vitals (FCP, LCP) inside a Playwright E2E test without flaky timing?

πŸ“Œ Problem Statement
β€’ Core Web Vitals reveal real‑user performance but are hidden inside the browser.
β€’ NaΓ―ve page.waitForTimeout() or relying on domcontentloaded misses paint events.
β€’ ❌ Relying on arbitrary delays leads to flaky tests and false regressions.

πŸ’‘ Solution & Code Walkthrough
βœ… Use a PerformanceObserver injected via page.evaluate() to capture FCP and LCP as they fire.
βœ… Collect metrics into a global variable, then read them after navigation.
βœ… Assert against thresholds with Playwright’s expect.

// performanceUtils.ts
import { Page, expect } from '@playwright/test';

export async function collectCoreVitals(page: Page): Promise<{ fcp: number; lcp: number }> {
  // Inject observer before navigation
  await page.addInitScript(() => {
    const vitals: { fcp?: number; lcp?: number } = {};
    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.name === 'first-contentful-paint') vitals.fcp = entry.startTime;
        if (entry.name === 'largest-contentful-paint') vitals.lcp = entry.startTime;
      }
    }).observe({ entryTypes: ['paint'] });
    // expose for test
    (window as any).__VITALS__ = vitals;
  });

  // Navigate to target page
  await page.goto('https://example.com');

  // Retrieve metrics
  const vitals = await page.evaluate(() => (window as any).__VITALS__);
  expect(vitals.fcp).toBeLessThan(2000); // 2s threshold example
  expect(vitals.lcp).toBeLessThan(2500);
  return { fcp: vitals.fcp!, lcp: vitals.lcp! };
}
Enter fullscreen mode Exit fullscreen mode

πŸ”‘ Key Takeaways
β€’ Inject observer before navigation to catch early paints.
β€’ Use addInitScript or page.evaluate() to run once per context.
β€’ βœ… Assert with realistic thresholds; adjust per‑project.
β€’ ❌ Avoid waitForTimeout – it hides real performance issues.

❓ Quick Summary Q&A
Q: Do I need to wait for networkidle?

A: No. The observer captures paint events independently of network state.

Q: Can I collect CLS or TTFB?

A: Yes – extend the observer with layout-shift and navigation entry types.

Q: Is this reliable across browsers?

A: Playwright uses the same Chromium/WebKit/Firefox APIs; the observer works in all supported browsers.

TAGS: playwright, typescript, web-vitals, performance-testing

────────────────────────────────────────
────────────────────────────────────────

πŸ“² 𝐅𝐑𝐄𝐄 πŒπŽππˆπ‹π„ 𝐀𝐏𝐏 β€” πŸ”πŸŽπŸŽ+ 𝐒𝐃𝐄𝐓 𝐐&𝐀𝐬
Practice real-world interview scenarios offline on the free QA Automation & SDET Prep app:

πŸ€– 𝐆𝐨𝐨𝐠π₯𝐞 𝐏π₯𝐚𝐲 (𝐀𝐧𝐝𝐫𝐨𝐒𝐝):
https://play.google.com/store/apps/details?id=com.app.seleniuminterviewquestions&referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3Dselenium_20261002

🍎 𝐀𝐩𝐩 π’π­π¨π«πž (π’πŽπ’):
https://apps.apple.com/app/id6786760948?pt=128640464&ct=devto_selenium_20261002&mt=8

────────────────────────────────────────
────────────────────────────────────────

Top comments (0)