Skip to content

Commit

Permalink
E2E Utils: add support for web-vitals.js (#55660)
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy authored and cbravobernal committed Nov 14, 2023
1 parent a73874f commit 252d58a
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 25 deletions.
18 changes: 16 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/e2e-test-utils-playwright/package.json
Expand Up @@ -37,7 +37,8 @@
"form-data": "^4.0.0",
"get-port": "^5.1.1",
"lighthouse": "^10.4.0",
"mime": "^3.0.0"
"mime": "^3.0.0",
"web-vitals": "^3.5.0"
},
"peerDependencies": {
"@playwright/test": ">=1"
Expand Down
107 changes: 107 additions & 0 deletions packages/e2e-test-utils-playwright/src/metrics/index.ts
Expand Up @@ -2,6 +2,11 @@
* External dependencies
*/
import type { Page, Browser } from '@playwright/test';
import { join } from 'path';
// resolution-mode support in TypeScript 5.3 will resolve this.
// See https://devblogs.microsoft.com/typescript/announcing-typescript-5-3-beta/
// @ts-expect-error
import type { Metric } from 'web-vitals';

type EventType =
| 'click'
Expand Down Expand Up @@ -32,11 +37,22 @@ type MetricsConstructorProps = {
page: Page;
};

interface WebVitalsMeasurements {
CLS?: number;
FCP?: number;
FID?: number;
INP?: number;
LCP?: number;
TTFB?: number;
}

export class Metrics {
browser: Browser;
page: Page;
trace: Trace;

webVitals: WebVitalsMeasurements = {};

constructor( { page }: MetricsConstructorProps ) {
this.page = page;
this.browser = page.context().browser()!;
Expand Down Expand Up @@ -273,4 +289,95 @@ export class Metrics {
)
.map( ( item ) => ( item.dur ? item.dur / 1000 : 0 ) );
}

/**
* Initializes the web-vitals library upon next page navigation.
*
* Defaults to automatically triggering the navigation,
* but it can also be done manually.
*
* @example
* ```js
* await metrics.initWebVitals();
* console.log( await metrics.getWebVitals() );
* ```
*
* @example
* ```js
* await metrics.initWebVitals( false );
* await page.goto( '/some-other-page' );
* console.log( await metrics.getWebVitals() );
* ```
*
* @param reload Whether to force navigation by reloading the current page.
*/
async initWebVitals( reload = true ) {
await this.page.addInitScript( {
path: join(
__dirname,
'../../../../node_modules/web-vitals/dist/web-vitals.umd.cjs'
),
} );

await this.page.exposeFunction(
'__reportVitals__',
( data: string ) => {
const measurement: Metric = JSON.parse( data );
this.webVitals[ measurement.name ] = measurement.value;
}
);

await this.page.addInitScript( () => {
const reportVitals = ( measurement: unknown ) =>
window.__reportVitals__( JSON.stringify( measurement ) );

window.addEventListener( 'DOMContentLoaded', () => {
// @ts-ignore
window.webVitals.onCLS( reportVitals );
// @ts-ignore
window.webVitals.onFCP( reportVitals );
// @ts-ignore
window.webVitals.onFID( reportVitals );
// @ts-ignore
window.webVitals.onINP( reportVitals );
// @ts-ignore
window.webVitals.onLCP( reportVitals );
// @ts-ignore
window.webVitals.onTTFB( reportVitals );
} );
} );

if ( reload ) {
// By reloading the page the script will be applied.
await this.page.reload();
}
}

/**
* Returns web vitals as collected by the web-vitals library.
*
* If the web-vitals library hasn't been loaded on the current page yet,
* it will be initialized with a page reload.
*
* Reloads the page to force web-vitals to report all collected metrics.
*
* @return {WebVitalsMeasurements} Web vitals measurements.
*/
async getWebVitals() {
// Reset values.
this.webVitals = {};

const hasScript = await this.page.evaluate(
() => typeof window.webVitals !== 'undefined'
);

if ( ! hasScript ) {
await this.initWebVitals();
}

// Trigger navigation so the web-vitals library reports values on unload.
await this.page.reload();

return this.webVitals;
}
}
27 changes: 5 additions & 22 deletions packages/e2e-test-utils-playwright/src/types.ts
@@ -1,29 +1,12 @@
/**
* External dependencies
*/
declare global {
interface Window {
// Silence the warning for `window.wp` in Playwright's evaluate functions.
wp: any;
}

// Experimental API that is subject to change.
// See https://developer.mozilla.org/en-US/docs/Web/API/LayoutShiftAttribution
interface LayoutShiftAttribution {
readonly node: Node;
readonly previousRect: DOMRectReadOnly;
readonly currentRect: DOMRectReadOnly;
readonly toJSON: () => string;
}

// Experimental API that is subject to change.
// See https://developer.mozilla.org/en-US/docs/Web/API/LayoutShift
interface LayoutShift extends PerformanceEntry {
readonly duration: number;
readonly entryType: 'layout-shift';
readonly name: 'layout-shift';
readonly startTime: DOMHighResTimeStamp;
readonly value: number;
readonly hadRecentInput: boolean;
readonly lastInputTime: DOMHighResTimeStamp;
readonly sources: LayoutShiftAttribution[];
// Helper function added by Metrics fixture for web-vitals.js.
__reportVitals__: ( data: string ) => void;
}
}

Expand Down

0 comments on commit 252d58a

Please sign in to comment.