Skip to content

Instantly share code, notes, and snippets.

@cloudydaiyz
Last active October 15, 2025 15:06
Show Gist options
  • Select an option

  • Save cloudydaiyz/923ca44bdb0a1ff3434a9360967025a6 to your computer and use it in GitHub Desktop.

Select an option

Save cloudydaiyz/923ca44bdb0a1ff3434a9360967025a6 to your computer and use it in GitHub Desktop.
Using the web-vitals library in playwright browsers
import { test, expect } from "@playwright/test";
import type * as webVitals from 'web-vitals';
import path from 'path';
import fs from 'fs';
declare global {
interface Window {
webVitals: typeof webVitals;
validateMetric: (name: string, good: boolean) => void;
}
}
test.describe("HackerNews/newest", () => {
// Web vitals are collected differently for each browser.
// Some metrics may be collected inconsistently or not at all.
test('should have good web vitals', async ({ page }) => {
// Set up page listeners
page.on("console", async (msg) => {
if (msg.type() === "trace" || msg.type() === "error") {
console.log(msg.text());
}
});
const badMetrics: string[] = [];
function validateMetric(name: string, good: boolean) {
console.log(`${name} valid: ${good}`);
if(!good) {
badMetrics.push(name);
}
}
page.exposeFunction("validateMetric", validateMetric);
// Using classic script to get webVitals global namespace
const scriptPath = path.join(__dirname, 'node_modules/web-vitals/dist/web-vitals.attribution.iife.js');
// Read the web-vitals script content at runtime
const scriptBody = fs.readFileSync(scriptPath, 'utf8');
/*
* Inject the script into the page.
* Make sure to use the raw script content instead of creating a script with the src set to `webVitalsUrl`.
* Otherwise, the external script may get blocked on certain websites.
*/
await page.addInitScript(
async ([scriptBody]) => {
window.addEventListener("DOMContentLoaded", async () => {
const script = document.createElement("script");
script.text = scriptBody;
try {
document.head.appendChild(script);
} catch (e) {
console.error("Error when initializing injected CWV script");
console.error(e);
}
// From GoogleChrome/web-vitals:
// > Note that some of these metrics will not report until the user has interacted with the page,
// > switched tabs, or the page starts to unload.
// See: https://github.com/GoogleChrome/web-vitals#:~:text=Note%20that%20some%20of%20these%20metrics%20will%20not%20report%20until%20the%20user%20has%20interacted%20with%20the%20page%2C%20switched%20tabs%2C%20or%20the%20page%20starts%20to%20unload.
function checkMetric(m: webVitals.Metric) {
console.trace(JSON.stringify(m, null, 4));
if(m.rating !== "good") {
window.validateMetric(m.name, false);
return;
}
window.validateMetric(m.name, true);
}
window.webVitals.onCLS(checkMetric);
window.webVitals.onFCP(checkMetric);
window.webVitals.onFID(checkMetric); // NOTE: will be removed from library soon
window.webVitals.onINP(checkMetric);
window.webVitals.onLCP(checkMetric);
window.webVitals.onTTFB(checkMetric);
console.log("Initialized web vital monitors");
});
},
[scriptBody]
);
// Go to Hacker News, record FCP and TTFB
await page.goto("https://news.ycombinator.com/newest");
// Page click to record LCP and FID
await page.waitForLoadState('domcontentloaded');
console.log("Page click");
await page.locator("input[type=text]").click();
// Manually obtain navigation event metrics for page load time
// - NOTE: There is only one PerformanceNavigationTiming object in the performance timeline
// See: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming
const [navTiming] = await page.evaluate(() =>
window.performance.getEntriesByType("navigation").map((e) => e.toJSON())
);
console.debug("Navigation Entry:", navTiming);
console.log("Page load time:", navTiming.loadEventEnd);
validateMetric("Page load", navTiming.loadEventEnd < 2000);
// Page unload to record INP and CLS
console.log("Page unload");
await page.close();
expect(badMetrics).toHaveLength(0);
});
});
@cloudydaiyz
Copy link
Author

@dilirity Agreed. I've updated the gist to include these lines and removed the unnecessary script injection. Thank you for pointing this out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment