From 59a4df13397c2a466c51b4404319159d67cd7051 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 16 Aug 2023 08:46:22 +0000 Subject: [PATCH] Build/Test Tools: Measure additional load time metrics in performance tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new metrics are being collected and reported as part of this change: - Time To First Byte (TTFB) - the time between the request for a resource and when the first byte of a response begins to arrive - Largest Contentful Paint (LCP) — the render time of the largest image or text block visible within the viewport - The difference between the two (LCP minus TTFB) Props joemcgill, flixos90, oandregal, mukesh27, youknowriad, swissspidy. Fixes #58360. git-svn-id: https://develop.svn.wordpress.org/trunk@56399 602fd350-edb4-49c9-b593-d223f7449a82 --- .../specs/home-block-theme.test.js | 28 ++++++++--- .../specs/home-classic-theme.test.js | 32 +++++++++--- tests/performance/utils.js | 49 ++++++++++++++++++- 3 files changed, 92 insertions(+), 17 deletions(-) diff --git a/tests/performance/specs/home-block-theme.test.js b/tests/performance/specs/home-block-theme.test.js index 8811fee698..f9a93824a6 100644 --- a/tests/performance/specs/home-block-theme.test.js +++ b/tests/performance/specs/home-block-theme.test.js @@ -3,7 +3,11 @@ */ const { basename, join } = require( 'path' ); const { writeFileSync } = require( 'fs' ); -const { getResultsFilename } = require( './../utils' ); +const { + getResultsFilename, + getTimeToFirstByte, + getLargestContentfulPaint, +} = require( './../utils' ); /** * WordPress dependencies. @@ -15,6 +19,9 @@ describe( 'Server Timing - Twenty Twenty Three', () => { wpBeforeTemplate: [], wpTemplate: [], wpTotal: [], + timeToFirstByte: [], + largestContentfulPaint: [], + lcpMinusTtfb: [], }; beforeAll( async () => { @@ -22,7 +29,9 @@ describe( 'Server Timing - Twenty Twenty Three', () => { } ); afterAll( async () => { - const resultsFilename = getResultsFilename( basename( __filename, '.js' ) ); + const resultsFilename = getResultsFilename( + basename( __filename, '.js' ) + ); writeFileSync( join( __dirname, resultsFilename ), JSON.stringify( results, null, 2 ) @@ -40,14 +49,19 @@ describe( 'Server Timing - Twenty Twenty Three', () => { const [ navigationTiming ] = JSON.parse( navigationTimingJson ); results.wpBeforeTemplate.push( - navigationTiming.serverTiming[0].duration + navigationTiming.serverTiming[ 0 ].duration ); results.wpTemplate.push( - navigationTiming.serverTiming[1].duration - ); - results.wpTotal.push( - navigationTiming.serverTiming[2].duration + navigationTiming.serverTiming[ 1 ].duration ); + results.wpTotal.push( navigationTiming.serverTiming[ 2 ].duration ); + + const ttfb = await getTimeToFirstByte(); + const lcp = await getLargestContentfulPaint(); + + results.timeToFirstByte.push( ttfb ); + results.largestContentfulPaint.push( lcp ); + results.lcpMinusTtfb.push( lcp - ttfb ); } } ); } ); diff --git a/tests/performance/specs/home-classic-theme.test.js b/tests/performance/specs/home-classic-theme.test.js index 64a170207c..7ae9282ddc 100644 --- a/tests/performance/specs/home-classic-theme.test.js +++ b/tests/performance/specs/home-classic-theme.test.js @@ -4,7 +4,11 @@ const { basename, join } = require( 'path' ); const { writeFileSync } = require( 'fs' ); const { exec } = require( 'child_process' ); -const { getResultsFilename } = require( './../utils' ); +const { + getResultsFilename, + getTimeToFirstByte, + getLargestContentfulPaint, +} = require( './../utils' ); /** * WordPress dependencies. @@ -16,15 +20,22 @@ describe( 'Server Timing - Twenty Twenty One', () => { wpBeforeTemplate: [], wpTemplate: [], wpTotal: [], + timeToFirstByte: [], + largestContentfulPaint: [], + lcpMinusTtfb: [], }; beforeAll( async () => { await activateTheme( 'twentytwentyone' ); - await exec( 'npm run env:cli -- menu location assign all-pages primary' ); + await exec( + 'npm run env:cli -- menu location assign all-pages primary' + ); } ); afterAll( async () => { - const resultsFilename = getResultsFilename( basename( __filename, '.js' ) ); + const resultsFilename = getResultsFilename( + basename( __filename, '.js' ) + ); writeFileSync( join( __dirname, resultsFilename ), JSON.stringify( results, null, 2 ) @@ -42,14 +53,19 @@ describe( 'Server Timing - Twenty Twenty One', () => { const [ navigationTiming ] = JSON.parse( navigationTimingJson ); results.wpBeforeTemplate.push( - navigationTiming.serverTiming[0].duration + navigationTiming.serverTiming[ 0 ].duration ); results.wpTemplate.push( - navigationTiming.serverTiming[1].duration - ); - results.wpTotal.push( - navigationTiming.serverTiming[2].duration + navigationTiming.serverTiming[ 1 ].duration ); + results.wpTotal.push( navigationTiming.serverTiming[ 2 ].duration ); + + const ttfb = await getTimeToFirstByte(); + const lcp = await getLargestContentfulPaint(); + + results.timeToFirstByte.push( ttfb ); + results.largestContentfulPaint.push( lcp ); + results.lcpMinusTtfb.push( lcp - ttfb ); } } ); } ); diff --git a/tests/performance/utils.js b/tests/performance/utils.js index f85e708847..732ab66d44 100644 --- a/tests/performance/utils.js +++ b/tests/performance/utils.js @@ -21,13 +21,58 @@ function median( array ) { * @return {string} Result file name. */ function getResultsFilename( fileName ) { - const prefixArg = process.argv.find( ( arg ) => arg.startsWith( '--prefix' ) ); - const fileNamePrefix = prefixArg ? `${prefixArg.split( '=' )[1]}-` : ''; + const prefixArg = process.argv.find( ( arg ) => + arg.startsWith( '--prefix' ) + ); + const fileNamePrefix = prefixArg ? `${ prefixArg.split( '=' )[ 1 ] }-` : ''; const resultsFilename = fileNamePrefix + fileName + '.results.json'; return resultsFilename; } +/** + * Returns time to first byte (TTFB) using the Navigation Timing API. + * + * @see https://web.dev/ttfb/#measure-ttfb-in-javascript + * + * @return {Promise} + */ +async function getTimeToFirstByte() { + return page.evaluate( () => { + const { responseStart, startTime } = + performance.getEntriesByType( 'navigation' )[ 0 ]; + return responseStart - startTime; + } ); +} + +/** + * Returns the Largest Contentful Paint (LCP) value using the dedicated API. + * + * @see https://w3c.github.io/largest-contentful-paint/ + * @see https://web.dev/lcp/#measure-lcp-in-javascript + * + * @return {Promise} + */ +async function getLargestContentfulPaint() { + return page.evaluate( + () => + new Promise( ( resolve ) => { + new PerformanceObserver( ( entryList ) => { + const entries = entryList.getEntries(); + // The last entry is the largest contentful paint. + const largestPaintEntry = entries.at( -1 ); + + resolve( largestPaintEntry?.startTime || 0 ); + } ).observe( { + type: 'largest-contentful-paint', + buffered: true, + } ); + } ) + ); +} + module.exports = { median, getResultsFilename, + getTimeToFirstByte, + getLargestContentfulPaint, };