Build/Test Tools: Measure additional load time metrics in performance tests.

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
This commit is contained in:
Pascal Birchler
2023-08-16 08:46:22 +00:00
parent 5fab140511
commit 59a4df1339
3 changed files with 92 additions and 17 deletions

View File

@@ -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 );
}
} );
} );

View File

@@ -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 );
}
} );
} );

View File

@@ -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<number>}
*/
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<number>}
*/
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,
};