Skip to content

Commit

Permalink
Performance suite: track Largest Contentful Paint in the front-end (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
oandregal authored Feb 10, 2023
1 parent aae6bb9 commit c9f6767
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 43 deletions.
95 changes: 52 additions & 43 deletions bin/plugin/commands/performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,54 +30,57 @@ const config = require( '../config' );
/**
* @typedef WPRawPerformanceResults
*
* @property {number[]} timeToFirstByte Represents the time since the browser started the request until it received a response.
* @property {number[]} serverResponse Represents the time the server takes to respond.
* @property {number[]} firstPaint Represents the time when the user agent first rendered after navigation.
* @property {number[]} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes.
* @property {number[]} loaded Represents the time when the load event of the current document is completed.
* @property {number[]} firstContentfulPaint Represents the time when the browser first renders any text or media.
* @property {number[]} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM.
* @property {number[]} type Average type time.
* @property {number[]} typeContainer Average type time within a container.
* @property {number[]} focus Average block selection time.
* @property {number[]} inserterOpen Average time to open global inserter.
* @property {number[]} inserterSearch Average time to search the inserter.
* @property {number[]} inserterHover Average time to move mouse between two block item in the inserter.
* @property {number[]} listViewOpen Average time to open listView
* @property {number[]} timeToFirstByte Represents the time since the browser started the request until it received a response.
* @property {number[]} largestContentfulPaint Represents the time when the main content of the page has likely loaded.
* @property {number[]} serverResponse Represents the time the server takes to respond.
* @property {number[]} firstPaint Represents the time when the user agent first rendered after navigation.
* @property {number[]} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes.
* @property {number[]} loaded Represents the time when the load event of the current document is completed.
* @property {number[]} firstContentfulPaint Represents the time when the browser first renders any text or media.
* @property {number[]} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM.
* @property {number[]} type Average type time.
* @property {number[]} typeContainer Average type time within a container.
* @property {number[]} focus Average block selection time.
* @property {number[]} inserterOpen Average time to open global inserter.
* @property {number[]} inserterSearch Average time to search the inserter.
* @property {number[]} inserterHover Average time to move mouse between two block item in the inserter.
* @property {number[]} listViewOpen Average time to open listView
*/

/**
* @typedef WPPerformanceResults
*
* @property {number=} timeToFirstByteMedian Represents the time since the browser started the request until it received a response (median).
* @property {number=} timeToFirstByteP75 Represents the time since the browser started the request until it received a response (75th percentile).
* @property {number=} serverResponse Represents the time the server takes to respond.
* @property {number=} firstPaint Represents the time when the user agent first rendered after navigation.
* @property {number=} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes.
* @property {number=} loaded Represents the time when the load event of the current document is completed.
* @property {number=} firstContentfulPaint Represents the time when the browser first renders any text or media.
* @property {number=} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM.
* @property {number=} type Average type time.
* @property {number=} minType Minimum type time.
* @property {number=} maxType Maximum type time.
* @property {number=} typeContainer Average type time within a container.
* @property {number=} minTypeContainer Minimum type time within a container.
* @property {number=} maxTypeContainer Maximum type time within a container.
* @property {number=} focus Average block selection time.
* @property {number=} minFocus Min block selection time.
* @property {number=} maxFocus Max block selection time.
* @property {number=} inserterOpen Average time to open global inserter.
* @property {number=} minInserterOpen Min time to open global inserter.
* @property {number=} maxInserterOpen Max time to open global inserter.
* @property {number=} inserterSearch Average time to open global inserter.
* @property {number=} minInserterSearch Min time to open global inserter.
* @property {number=} maxInserterSearch Max time to open global inserter.
* @property {number=} inserterHover Average time to move mouse between two block item in the inserter.
* @property {number=} minInserterHover Min time to move mouse between two block item in the inserter.
* @property {number=} maxInserterHover Max time to move mouse between two block item in the inserter.
* @property {number=} listViewOpen Average time to open list view.
* @property {number=} minListViewOpen Min time to open list view.
* @property {number=} maxListViewOpen Max time to open list view.
* @property {number=} timeToFirstByteMedian Represents the time since the browser started the request until it received a response (median).
* @property {number=} timeToFirstByteP75 Represents the time since the browser started the request until it received a response (75th percentile).
* @property {number=} largestContentfulPaintMedian Represents the time when the main content of the page has likely loaded (median).
* @property {number=} largestContentfulPaintP75 Represents the time when the main content of the page has likely loaded (75th percentile).
* @property {number=} serverResponse Represents the time the server takes to respond.
* @property {number=} firstPaint Represents the time when the user agent first rendered after navigation.
* @property {number=} domContentLoaded Represents the time immediately after the document's DOMContentLoaded event completes.
* @property {number=} loaded Represents the time when the load event of the current document is completed.
* @property {number=} firstContentfulPaint Represents the time when the browser first renders any text or media.
* @property {number=} firstBlock Represents the time when Puppeteer first sees a block selector in the DOM.
* @property {number=} type Average type time.
* @property {number=} minType Minimum type time.
* @property {number=} maxType Maximum type time.
* @property {number=} typeContainer Average type time within a container.
* @property {number=} minTypeContainer Minimum type time within a container.
* @property {number=} maxTypeContainer Maximum type time within a container.
* @property {number=} focus Average block selection time.
* @property {number=} minFocus Min block selection time.
* @property {number=} maxFocus Max block selection time.
* @property {number=} inserterOpen Average time to open global inserter.
* @property {number=} minInserterOpen Min time to open global inserter.
* @property {number=} maxInserterOpen Max time to open global inserter.
* @property {number=} inserterSearch Average time to open global inserter.
* @property {number=} minInserterSearch Min time to open global inserter.
* @property {number=} maxInserterSearch Max time to open global inserter.
* @property {number=} inserterHover Average time to move mouse between two block item in the inserter.
* @property {number=} minInserterHover Min time to move mouse between two block item in the inserter.
* @property {number=} maxInserterHover Max time to move mouse between two block item in the inserter.
* @property {number=} listViewOpen Average time to open list view.
* @property {number=} minListViewOpen Min time to open list view.
* @property {number=} maxListViewOpen Max time to open list view.
*/

/**
Expand Down Expand Up @@ -147,6 +150,12 @@ function curateResults( testSuite, results ) {
return {
timeToFirstByteMedian: median( results.timeToFirstByte ),
timeToFirstByteP75: percentile75( results.timeToFirstByte ),
largestContentfulPaintMedian: median(
results.largestContentfulPaint
),
largestContentfulPaintP75: percentile75(
results.largestContentfulPaint
),
};
}

Expand Down
47 changes: 47 additions & 0 deletions packages/e2e-tests/specs/performance/front-end-block-theme.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { activateTheme, createURL, logout } from '@wordpress/e2e-test-utils';
describe( 'Front End Performance', () => {
const results = {
timeToFirstByte: [],
largestContentfulPaint: [],
};

beforeAll( async () => {
Expand Down Expand Up @@ -44,4 +45,50 @@ describe( 'Front End Performance', () => {
);
}
} );

it( 'Largest Contentful Paint (LCP)', async () => {
// Based on https://addyosmani.com/blog/puppeteer-recipes/#performance-observer-lcp
function calcLCP() {
// By using -1 we know when it didn't record any event.
window.largestContentfulPaint = -1;

const observer = new PerformanceObserver( ( entryList ) => {
const entries = entryList.getEntries();
const lastEntry = entries[ entries.length - 1 ];
// According to the spec, we can use startTime
// as it'll report renderTime || loadTime:
// https://www.w3.org/TR/largest-contentful-paint/#largestcontentfulpaint
window.largestContentfulPaint = lastEntry.startTime;
} );

observer.observe( {
type: 'largest-contentful-paint',
buffered: true,
} );

document.addEventListener( 'visibilitychange', () => {
if ( document.visibilityState === 'hidden' ) {
observer.takeRecords();
observer.disconnect();
}
} );
}

// We derive the 75th percentile of the TTFB based on these results.
// By running it 16 times, the percentile value would be (75/100)*16=12,
// meaning that we discard the worst 4 values.
let i = 16;
while ( i-- ) {
await page.evaluateOnNewDocument( calcLCP );
// By waiting for networkidle we make sure navigation won't be considered finished on load,
// hence, it'll paint the page and largest-contentful-paint events will be dispatched.
// https://pptr.dev/api/puppeteer.page.goto#remarks
await page.goto( createURL( '/' ), { waitUntil: 'networkidle0' } );

const lcp = await page.evaluate(
() => window.largestContentfulPaint
);
results.largestContentfulPaint.push( lcp );
}
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createURL, logout } from '@wordpress/e2e-test-utils';
describe( 'Front End Performance', () => {
const results = {
timeToFirstByte: [],
largestContentfulPaint: [],
};

beforeAll( async () => {
Expand Down Expand Up @@ -42,4 +43,50 @@ describe( 'Front End Performance', () => {
);
}
} );

it( 'Largest Contentful Paint (LCP)', async () => {
// Based on https://addyosmani.com/blog/puppeteer-recipes/#performance-observer-lcp
function calcLCP() {
// By using -1 we know when it didn't record any event.
window.largestContentfulPaint = -1;

const observer = new PerformanceObserver( ( entryList ) => {
const entries = entryList.getEntries();
const lastEntry = entries[ entries.length - 1 ];
// According to the spec, we can use startTime
// as it'll report renderTime || loadTime:
// https://www.w3.org/TR/largest-contentful-paint/#largestcontentfulpaint
window.largestContentfulPaint = lastEntry.startTime;
} );

observer.observe( {
type: 'largest-contentful-paint',
buffered: true,
} );

document.addEventListener( 'visibilitychange', () => {
if ( document.visibilityState === 'hidden' ) {
observer.takeRecords();
observer.disconnect();
}
} );
}

// We derive the 75th percentile of the TTFB based on these results.
// By running it 16 times, the percentile value would be (75/100)*16=12,
// meaning that we discard the worst 4 values.
let i = 16;
while ( i-- ) {
await page.evaluateOnNewDocument( calcLCP );
// By waiting for networkidle we make sure navigation won't be considered finished on load,
// hence, it'll paint the page and largest-contentful-paint events will be dispatched.
// https://pptr.dev/api/puppeteer.page.goto#remarks
await page.goto( createURL( '/' ), { waitUntil: 'networkidle0' } );

const lcp = await page.evaluate(
() => window.largestContentfulPaint
);
results.largestContentfulPaint.push( lcp );
}
} );
} );

1 comment on commit c9f6767

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in c9f6767.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4143299979
📝 Reported issues:

Please sign in to comment.