Skip to content

Commit

Permalink
Performance Tests: Separate page setup from test (#53808)
Browse files Browse the repository at this point in the history
  • Loading branch information
WunderBart authored and mikachan committed Sep 27, 2023
1 parent 75a6e7c commit 4baed0b
Show file tree
Hide file tree
Showing 10 changed files with 902 additions and 621 deletions.
13 changes: 9 additions & 4 deletions packages/e2e-test-utils-playwright/src/lighthouse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
import type { Page } from '@playwright/test';
import * as lighthouse from 'lighthouse/core/index.cjs';

type LighthouseConstructorProps = {
page: Page;
port: number;
};

export class Lighthouse {
constructor(
public readonly page: Page,
public readonly port: number
) {
page: Page;
port: number;

constructor( { page, port }: LighthouseConstructorProps ) {
this.page = page;
this.port = port;
}
Expand Down
181 changes: 173 additions & 8 deletions packages/e2e-test-utils-playwright/src/metrics/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
/**
* External dependencies
*/
import type { Page } from '@playwright/test';
import type { Page, Browser } from '@playwright/test';

type EventType =
| 'click'
| 'focus'
| 'focusin'
| 'keydown'
| 'keypress'
| 'keyup'
| 'mouseout'
| 'mouseover';

interface TraceEvent {
cat: string;
name: string;
dur?: number;
args: {
data?: {
type: EventType;
};
};
}

interface Trace {
traceEvents: TraceEvent[];
}

type MetricsConstructorProps = {
page: Page;
};

export class Metrics {
constructor( public readonly page: Page ) {
browser: Browser;
page: Page;
trace: Trace;

constructor( { page }: MetricsConstructorProps ) {
this.page = page;
this.browser = page.context().browser()!;
this.trace = { traceEvents: [] };
}

/**
Expand Down Expand Up @@ -38,10 +73,10 @@ export class Metrics {
*
* @see https://web.dev/ttfb/#measure-ttfb-in-javascript
*
* @return {Promise<number>} TTFB value.
* @return TTFB value.
*/
async getTimeToFirstByte() {
return this.page.evaluate< number >( () => {
return await this.page.evaluate< number >( () => {
const { responseStart, startTime } = (
performance.getEntriesByType(
'navigation'
Expand All @@ -57,10 +92,10 @@ export class Metrics {
* @see https://w3c.github.io/largest-contentful-paint/
* @see https://web.dev/lcp/#measure-lcp-in-javascript
*
* @return {Promise<number>} LCP value.
* @return LCP value.
*/
async getLargestContentfulPaint() {
return this.page.evaluate< number >(
return await this.page.evaluate< number >(
() =>
new Promise( ( resolve ) => {
new PerformanceObserver( ( entryList ) => {
Expand All @@ -83,10 +118,10 @@ export class Metrics {
* @see https://github.com/WICG/layout-instability
* @see https://web.dev/cls/#measure-layout-shifts-in-javascript
*
* @return {Promise<number>} CLS value.
* @return CLS value.
*/
async getCumulativeLayoutShift() {
return this.page.evaluate< number >(
return await this.page.evaluate< number >(
() =>
new Promise( ( resolve ) => {
let CLS = 0;
Expand All @@ -108,4 +143,134 @@ export class Metrics {
} )
);
}

/**
* Returns the loading durations using the Navigation Timing API. All the
* durations exclude the server response time.
*
* @return Object with loading metrics durations.
*/
async getLoadingDurations() {
return await this.page.evaluate( () => {
const [
{
requestStart,
responseStart,
responseEnd,
domContentLoadedEventEnd,
loadEventEnd,
},
] = performance.getEntriesByType(
'navigation'
) as PerformanceNavigationTiming[];
const paintTimings = performance.getEntriesByType(
'paint'
) as PerformancePaintTiming[];

const firstPaintStartTime = paintTimings.find(
( { name } ) => name === 'first-paint'
)!.startTime;

const firstContentfulPaintStartTime = paintTimings.find(
( { name } ) => name === 'first-contentful-paint'
)!.startTime;

return {
// Server side metric.
serverResponse: responseStart - requestStart,
// For client side metrics, consider the end of the response (the
// browser receives the HTML) as the start time (0).
firstPaint: firstPaintStartTime - responseEnd,
domContentLoaded: domContentLoadedEventEnd - responseEnd,
loaded: loadEventEnd - responseEnd,
firstContentfulPaint:
firstContentfulPaintStartTime - responseEnd,
timeSinceResponseEnd: performance.now() - responseEnd,
};
} );
}

/**
* Starts Chromium tracing with predefined options for performance testing.
*
* @param options Options to pass to `browser.startTracing()`.
*/
async startTracing( options = {} ) {
return await this.browser.startTracing( this.page, {
screenshots: false,
categories: [ 'devtools.timeline' ],
...options,
} );
}

/**
* Stops Chromium tracing and saves the trace.
*/
async stopTracing() {
const traceBuffer = await this.browser.stopTracing();
const traceJSON = JSON.parse( traceBuffer.toString() );

this.trace = traceJSON;
}

/**
* @return Durations of all traced `keydown`, `keypress`, and `keyup`
* events.
*/
getTypingEventDurations() {
return [
this.getEventDurations( 'keydown' ),
this.getEventDurations( 'keypress' ),
this.getEventDurations( 'keyup' ),
];
}

/**
* @return Durations of all traced `focus` and `focusin` events.
*/
getSelectionEventDurations() {
return [
this.getEventDurations( 'focus' ),
this.getEventDurations( 'focusin' ),
];
}

/**
* @return Durations of all traced `click` events.
*/
getClickEventDurations() {
return [ this.getEventDurations( 'click' ) ];
}

/**
* @return Durations of all traced `mouseover` and `mouseout` events.
*/
getHoverEventDurations() {
return [
this.getEventDurations( 'mouseover' ),
this.getEventDurations( 'mouseout' ),
];
}

/**
* @param eventType Type of event to filter.
* @return Durations of all events of a given type.
*/
getEventDurations( eventType: EventType ) {
if ( this.trace.traceEvents.length === 0 ) {
throw new Error(
'No trace events found. Did you forget to call stopTracing()?'
);
}

return this.trace.traceEvents
.filter(
( item: TraceEvent ): boolean =>
item.cat === 'devtools.timeline' &&
item.name === 'EventDispatch' &&
item?.args?.data?.type === eventType &&
!! item.dur
)
.map( ( item ) => ( item.dur ? item.dur / 1000 : 0 ) );
}
}
4 changes: 2 additions & 2 deletions packages/e2e-test-utils-playwright/src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,10 @@ const test = base.extend<
{ scope: 'worker' },
],
lighthouse: async ( { page, lighthousePort }, use ) => {
await use( new Lighthouse( page, lighthousePort ) );
await use( new Lighthouse( { page, port: lighthousePort } ) );
},
metrics: async ( { page }, use ) => {
await use( new Metrics( page ) );
await use( new Metrics( { page } ) );
},
} );

Expand Down
1 change: 1 addition & 0 deletions test/performance/fixtures/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PerfUtils } from './perf-utils';
Loading

0 comments on commit 4baed0b

Please sign in to comment.