diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 433f9931334e61..869f36de4c87bc 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -32,6 +32,7 @@ const config = require( '../config' ); * * @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[]} lcpMinusTtfb Represents the difference between LCP and TTFB. * @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. @@ -50,37 +51,36 @@ const config = require( '../config' ); /** * @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=} 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. + * @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=} lcpMinusTtfb Represents the difference between LCP and TTFB. + * @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. */ /** @@ -109,19 +109,6 @@ function median( array ) { : ( numbers[ mid - 1 ] + numbers[ mid ] ) / 2; } -/** - * Computes the 75th percentile from an array of numbers. - * - * @param {number[]} array - * - * @return {number} 75th percentile of the given dataset. - */ -function percentile75( array ) { - const ascending = array.sort( ( a, b ) => a - b ); - const position = Math.floor( ( 75 / 100 ) * array.length ); - return ascending[ position ]; -} - /** * Rounds and format a time passed in milliseconds. * @@ -148,14 +135,9 @@ function curateResults( testSuite, results ) { testSuite === 'front-end-block-theme' ) { return { - timeToFirstByteMedian: median( results.timeToFirstByte ), - timeToFirstByteP75: percentile75( results.timeToFirstByte ), - largestContentfulPaintMedian: median( - results.largestContentfulPaint - ), - largestContentfulPaintP75: percentile75( - results.largestContentfulPaint - ), + timeToFirstByte: median( results.timeToFirstByte ), + largestContentfulPaint: median( results.largestContentfulPaint ), + lcpMinusTtfb: median( results.lcpMinusTtfb ), }; } diff --git a/packages/e2e-tests/specs/performance/front-end-block-theme.test.js b/packages/e2e-tests/specs/performance/front-end-block-theme.test.js index 89879e29605362..ebbd898e1c25b8 100644 --- a/packages/e2e-tests/specs/performance/front-end-block-theme.test.js +++ b/packages/e2e-tests/specs/performance/front-end-block-theme.test.js @@ -13,6 +13,7 @@ describe( 'Front End Performance', () => { const results = { timeToFirstByte: [], largestContentfulPaint: [], + lcpMinusTtfb: [], }; beforeAll( async () => { @@ -29,24 +30,7 @@ describe( 'Front End Performance', () => { ); } ); - it( 'Time To First Byte (TTFB)', async () => { - // 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.goto( createURL( '/' ) ); - const navigationTimingJson = await page.evaluate( () => - JSON.stringify( performance.getEntriesByType( 'navigation' ) ) - ); - const [ navigationTiming ] = JSON.parse( navigationTimingJson ); - results.timeToFirstByte.push( - navigationTiming.responseStart - navigationTiming.startTime - ); - } - } ); - - it( 'Largest Contentful Paint (LCP)', async () => { + it( 'Report TTFB, LCP, and LCP-TTFB', 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. @@ -74,9 +58,6 @@ describe( 'Front End Performance', () => { } ); } - // 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 ); @@ -85,10 +66,18 @@ describe( 'Front End Performance', () => { // https://pptr.dev/api/puppeteer.page.goto#remarks await page.goto( createURL( '/' ), { waitUntil: 'networkidle0' } ); - const lcp = await page.evaluate( - () => window.largestContentfulPaint - ); + const { lcp, ttfb } = await page.evaluate( () => { + const [ { responseStart, startTime } ] = + performance.getEntriesByType( 'navigation' ); + return { + lcp: window.largestContentfulPaint, + ttfb: responseStart - startTime, + }; + } ); + results.largestContentfulPaint.push( lcp ); + results.timeToFirstByte.push( ttfb ); + results.lcpMinusTtfb.push( lcp - ttfb ); } } ); } ); diff --git a/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js b/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js index f5ab7078ba5a7b..5ef281e8dd0e84 100644 --- a/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js +++ b/packages/e2e-tests/specs/performance/front-end-classic-theme.test.js @@ -13,6 +13,7 @@ describe( 'Front End Performance', () => { const results = { timeToFirstByte: [], largestContentfulPaint: [], + lcpMinusTtfb: [], }; beforeAll( async () => { @@ -28,24 +29,7 @@ describe( 'Front End Performance', () => { ); } ); - it( 'Time To First Byte (TTFB)', async () => { - // 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.goto( createURL( '/' ) ); - const navigationTimingJson = await page.evaluate( () => - JSON.stringify( performance.getEntriesByType( 'navigation' ) ) - ); - const [ navigationTiming ] = JSON.parse( navigationTimingJson ); - results.timeToFirstByte.push( - navigationTiming.responseStart - navigationTiming.startTime - ); - } - } ); - - it( 'Largest Contentful Paint (LCP)', async () => { + it( 'Report TTFB, LCP, and LCP-TTFB', 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. @@ -73,9 +57,6 @@ describe( 'Front End Performance', () => { } ); } - // 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 ); @@ -84,10 +65,18 @@ describe( 'Front End Performance', () => { // https://pptr.dev/api/puppeteer.page.goto#remarks await page.goto( createURL( '/' ), { waitUntil: 'networkidle0' } ); - const lcp = await page.evaluate( - () => window.largestContentfulPaint - ); + const { lcp, ttfb } = await page.evaluate( () => { + const [ { responseStart, startTime } ] = + performance.getEntriesByType( 'navigation' ); + return { + lcp: window.largestContentfulPaint, + ttfb: responseStart - startTime, + }; + } ); + results.largestContentfulPaint.push( lcp ); + results.timeToFirstByte.push( ttfb ); + results.lcpMinusTtfb.push( lcp - ttfb ); } } ); } );