Commit 2cdf2542915 for woocommerce

commit 2cdf2542915ddd3784a55d99b21914a9740d29bb
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Thu Jul 16 09:24:02 2026 +0200

    Add editor asset size performance metrics (#66606)

    * Add editor asset size performance metrics

    * Add changelog entry for editor asset metrics

    * add comment

    * improve quality

    * fix logic

diff --git a/plugins/woocommerce/changelog/performance-editor-asset-metrics b/plugins/woocommerce/changelog/performance-editor-asset-metrics
new file mode 100644
index 00000000000..8a9275f27e4
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-editor-asset-metrics
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Add editor asset count and network transfer size performance metrics.
diff --git a/plugins/woocommerce/tests/metrics/config/performance-reporter.ts b/plugins/woocommerce/tests/metrics/config/performance-reporter.ts
index d24f7f2f0b7..801c8f1d11c 100644
--- a/plugins/woocommerce/tests/metrics/config/performance-reporter.ts
+++ b/plugins/woocommerce/tests/metrics/config/performance-reporter.ts
@@ -12,6 +12,11 @@ import type {
 } from '@playwright/test/reporter';
 /* eslint-enable @typescript-eslint/no-unused-vars */

+/**
+ * Internal dependencies
+ */
+import { formatMetricValue } from '../utils.js';
+
 export type WPPerformanceResults = Record< string, number >;

 class PerformanceReporter implements Reporter {
@@ -66,7 +71,9 @@ class PerformanceReporter implements Reporter {
 			const printableResults: Record< string, { value: string } > = {};

 			for ( const [ key, value ] of Object.entries( results ) ) {
-				printableResults[ key ] = { value: `${ value } ms` };
+				printableResults[ key ] = {
+					value: formatMetricValue( key, value ),
+				};
 			}

 			// eslint-disable-next-line no-console
diff --git a/plugins/woocommerce/tests/metrics/specs/editor.spec.js b/plugins/woocommerce/tests/metrics/specs/editor.spec.js
index 01a0b4559c0..6a0ae19a4d4 100644
--- a/plugins/woocommerce/tests/metrics/specs/editor.spec.js
+++ b/plugins/woocommerce/tests/metrics/specs/editor.spec.js
@@ -9,7 +9,11 @@ import { test, Metrics } from '@wordpress/e2e-test-utils-playwright';
  * Internal dependencies
  */
 import { PerfUtils } from '../fixtures';
-import { getTotalBlockingTime, median } from '../utils';
+import {
+	getTotalBlockingTime,
+	median,
+	startWooEditorAssetMetrics,
+} from '../utils';

 // See https://github.com/WordPress/gutenberg/issues/51383#issuecomment-1613460429
 const BROWSER_IDLE_WAIT = 1000;
@@ -56,54 +60,77 @@ test.describe( 'Editor Performance', () => {
 				perfUtils,
 				metrics,
 			} ) => {
-				// Open the test draft.
-				await admin.editPost( draftId );
-				const canvas = await perfUtils.getCanvas();
+				const stopWooEditorAssetMetrics =
+					await startWooEditorAssetMetrics( page );

-				// Wait for the first block.
-				await canvas.locator( '.wp-block' ).first().waitFor();
+				try {
+					// Open the test draft.
+					await admin.editPost( draftId );
+					const canvas = await perfUtils.getCanvas();

-				// Get the durations.
-				const loadingDurations = await metrics.getLoadingDurations();
+					// Wait for the first block.
+					await canvas.locator( '.wp-block' ).first().waitFor();

-				// Measure CLS
-				const cumulativeLayoutShift =
-					await metrics.getCumulativeLayoutShift();
+					// Get the durations.
+					const loadingDurations =
+						await metrics.getLoadingDurations();

-				// Measure LCP
-				const largestContentfulPaint =
-					await metrics.getLargestContentfulPaint();
+					// Measure CLS
+					const cumulativeLayoutShift =
+						await metrics.getCumulativeLayoutShift();

-				// Measure TBT
-				const totalBlockingTime = await getTotalBlockingTime(
-					page,
-					BROWSER_IDLE_WAIT
-				);
+					// Measure LCP
+					const largestContentfulPaint =
+						await metrics.getLargestContentfulPaint();

-				// Save the results.
-				if ( i > throwaway ) {
-					results.totalBlockingTime = results.tbt || [];
-					results.totalBlockingTime.push( totalBlockingTime );
-					results.cumulativeLayoutShift =
-						results.cumulativeLayoutShift || [];
-					results.cumulativeLayoutShift.push( cumulativeLayoutShift );
-					results.largestContentfulPaint =
-						results.largestContentfulPaint || [];
-					results.largestContentfulPaint.push(
-						largestContentfulPaint
+					// Measure TBT
+					const totalBlockingTime = await getTotalBlockingTime(
+						page,
+						BROWSER_IDLE_WAIT
 					);
-					Object.entries( loadingDurations ).forEach(
-						( [ metric, duration ] ) => {
-							const metricKey =
-								metric === 'timeSinceResponseEnd'
-									? 'firstBlock'
-									: metric;
-							if ( ! results[ metricKey ] ) {
-								results[ metricKey ] = [];
+
+					// Measure WooCommerce editor assets.
+					const wooEditorAssetMetrics =
+						await stopWooEditorAssetMetrics();
+
+					// Save the results.
+					if ( i > throwaway ) {
+						results.totalBlockingTime =
+							results.totalBlockingTime || [];
+						results.totalBlockingTime.push( totalBlockingTime );
+						results.cumulativeLayoutShift =
+							results.cumulativeLayoutShift || [];
+						results.cumulativeLayoutShift.push(
+							cumulativeLayoutShift
+						);
+						results.largestContentfulPaint =
+							results.largestContentfulPaint || [];
+						results.largestContentfulPaint.push(
+							largestContentfulPaint
+						);
+						Object.entries( loadingDurations ).forEach(
+							( [ metric, duration ] ) => {
+								const metricKey =
+									metric === 'timeSinceResponseEnd'
+										? 'firstBlock'
+										: metric;
+								if ( ! results[ metricKey ] ) {
+									results[ metricKey ] = [];
+								}
+								results[ metricKey ].push( duration );
 							}
-							results[ metricKey ].push( duration );
-						}
-					);
+						);
+						Object.entries( wooEditorAssetMetrics ).forEach(
+							( [ metric, value ] ) => {
+								if ( ! results[ metric ] ) {
+									results[ metric ] = [];
+								}
+								results[ metric ].push( value );
+							}
+						);
+					}
+				} finally {
+					await stopWooEditorAssetMetrics();
 				}
 			} );
 		}
diff --git a/plugins/woocommerce/tests/metrics/utils.js b/plugins/woocommerce/tests/metrics/utils.js
index fc90c57e838..f5fca03e170 100644
--- a/plugins/woocommerce/tests/metrics/utils.js
+++ b/plugins/woocommerce/tests/metrics/utils.js
@@ -3,17 +3,14 @@
  */
 import { existsSync, readFileSync } from 'fs';

-export function median( array ) {
-	if ( ! array || ! array.length ) return undefined;
-
-	const numbers = [ ...array ].sort( ( a, b ) => a - b );
-	const middleIndex = Math.floor( numbers.length / 2 );
-
-	if ( numbers.length % 2 === 0 ) {
-		return ( numbers[ middleIndex - 1 ] + numbers[ middleIndex ] ) / 2;
-	}
-	return numbers[ middleIndex ];
-}
+/**
+ * Internal dependencies
+ */
+export {
+	median,
+	getMetricUnit,
+	formatMetricValue,
+} from '../../../../tools/compare-perf/metric-utils.js';

 export function readFile( filePath ) {
 	if ( ! existsSync( filePath ) ) {
@@ -23,6 +20,117 @@ export function readFile( filePath ) {
 	return readFileSync( filePath, 'utf8' ).trim();
 }

+const WOO_BLOCKS_ASSETS_PATH =
+	'/wp-content/plugins/woocommerce/assets/client/blocks/';
+const KB = 1024;
+
+function roundSize( value ) {
+	return Math.round( value * 100 ) / 100;
+}
+
+function getWooEditorAssetUrl( rawUrl, baseUrl ) {
+	const url = new URL( rawUrl, baseUrl );
+
+	if (
+		! url.pathname.includes( WOO_BLOCKS_ASSETS_PATH ) ||
+		! /\.(js|css)$/.test( url.pathname )
+	) {
+		return null;
+	}
+
+	return {
+		normalizedUrl: url.origin + url.pathname,
+		isScript: url.pathname.endsWith( '.js' ),
+		isStyle: url.pathname.endsWith( '.css' ),
+	};
+}
+
+function getEmptyWooEditorAssetMetrics() {
+	return {
+		wooEditorAssetCount: 0,
+		wooEditorNetworkTransferAssetSize: 0,
+		wooEditorNetworkTransferScriptSize: 0,
+		wooEditorNetworkTransferStyleSize: 0,
+	};
+}
+
+function formatWooEditorAssetMetrics( metrics ) {
+	return {
+		wooEditorAssetCount: metrics.wooEditorAssetCount,
+		wooEditorNetworkTransferAssetSize: roundSize(
+			metrics.wooEditorNetworkTransferAssetSize / KB
+		),
+		wooEditorNetworkTransferScriptSize: roundSize(
+			metrics.wooEditorNetworkTransferScriptSize / KB
+		),
+		wooEditorNetworkTransferStyleSize: roundSize(
+			metrics.wooEditorNetworkTransferStyleSize / KB
+		),
+	};
+}
+
+export async function startWooEditorAssetMetrics( page ) {
+	const client = await page.context().newCDPSession( page );
+	const { frameTree } = await client.send( 'Page.getFrameTree' );
+	const mainFrameId = frameTree.frame.id;
+	const requests = new Map();
+	const assetUrls = new Set();
+	const metrics = getEmptyWooEditorAssetMetrics();
+	let isCollecting = true;
+
+	const onRequestWillBeSent = ( event ) => {
+		// Exclude assets loaded within editor iframes.
+		if ( event.frameId !== mainFrameId ) {
+			return;
+		}
+
+		const assetUrl = getWooEditorAssetUrl( event.request.url, page.url() );
+
+		if ( assetUrl ) {
+			requests.set( event.requestId, assetUrl );
+		}
+	};
+
+	const onLoadingFinished = ( event ) => {
+		const assetUrl = requests.get( event.requestId );
+
+		if ( ! assetUrl || assetUrls.has( assetUrl.normalizedUrl ) ) {
+			return;
+		}
+
+		assetUrls.add( assetUrl.normalizedUrl );
+		metrics.wooEditorAssetCount += 1;
+
+		const size = event.encodedDataLength || 0;
+		metrics.wooEditorNetworkTransferAssetSize += size;
+
+		if ( assetUrl.isScript ) {
+			metrics.wooEditorNetworkTransferScriptSize += size;
+		}
+
+		if ( assetUrl.isStyle ) {
+			metrics.wooEditorNetworkTransferStyleSize += size;
+		}
+	};
+
+	client.on( 'Network.requestWillBeSent', onRequestWillBeSent );
+	client.on( 'Network.loadingFinished', onLoadingFinished );
+	await client.send( 'Network.enable' );
+
+	return async () => {
+		if ( ! isCollecting ) {
+			return formatWooEditorAssetMetrics( metrics );
+		}
+
+		isCollecting = false;
+		client.off( 'Network.requestWillBeSent', onRequestWillBeSent );
+		client.off( 'Network.loadingFinished', onLoadingFinished );
+		await client.detach();
+
+		return formatWooEditorAssetMetrics( metrics );
+	};
+}
+
 export async function getTotalBlockingTime( page, idleWait ) {
 	const totalBlockingTime = await page.evaluate( async ( waitTime ) => {
 		return new Promise( ( resolve ) => {
diff --git a/tools/compare-perf/metric-utils.js b/tools/compare-perf/metric-utils.js
new file mode 100644
index 00000000000..3fdeb374468
--- /dev/null
+++ b/tools/compare-perf/metric-utils.js
@@ -0,0 +1,40 @@
+/**
+ * Computes the median number from an array of numbers.
+ *
+ * @param {number[]} array
+ *
+ * @return {number|undefined} Median value or undefined if array empty.
+ */
+function median( array ) {
+	if ( ! array || ! array.length ) return undefined;
+
+	const numbers = [ ...array ].sort( ( a, b ) => a - b );
+	const middleIndex = Math.floor( numbers.length / 2 );
+
+	if ( numbers.length % 2 === 0 ) {
+		return ( numbers[ middleIndex - 1 ] + numbers[ middleIndex ] ) / 2;
+	}
+	return numbers[ middleIndex ];
+}
+
+function getMetricUnit( metric ) {
+	if ( metric.endsWith( 'Size' ) ) {
+		return 'KB';
+	}
+
+	if ( metric.endsWith( 'Count' ) ) {
+		return 'count';
+	}
+
+	return 'ms';
+}
+
+function formatMetricValue( metric, value ) {
+	return `${ value } ${ getMetricUnit( metric ) }`;
+}
+
+module.exports = {
+	median,
+	getMetricUnit,
+	formatMetricValue,
+};
diff --git a/tools/compare-perf/process-reports.ts b/tools/compare-perf/process-reports.ts
index ec2684161e9..89831c8206b 100644
--- a/tools/compare-perf/process-reports.ts
+++ b/tools/compare-perf/process-reports.ts
@@ -13,8 +13,8 @@ const {
 	readJSONFile,
 	logAtIndent,
 	sanitizeBranchName,
-	median
-} = require( './utils' ) ;
+} = require( './utils' );
+const { median, formatMetricValue } = require( './metric-utils' );

 const formats = {
 	success: bold.green,
@@ -104,7 +104,10 @@ async function processPerformanceReports(
 		) ) {
 			for ( const [ metric, value ] of Object.entries( metrics ) ) {
 				invertedResult[ metric ] = invertedResult[ metric ] || {};
-				invertedResult[ metric ][ branch ] = `${ value } ms`;
+				invertedResult[ metric ][ branch ] = formatMetricValue(
+					metric,
+					value
+				);
 			}
 		}

diff --git a/tools/compare-perf/utils.js b/tools/compare-perf/utils.js
index 3eafe7fdd89..86a2f122e01 100644
--- a/tools/compare-perf/utils.js
+++ b/tools/compare-perf/utils.js
@@ -124,25 +124,6 @@ function sanitizeBranchName( branch ) {
 	return branch.replace( /[^a-zA-Z0-9-]/g, '-' );
 }

-/**
- * Computes the median number from an array numbers.
- *
- * @param {number[]} array
- *
- * @return {number|undefined} Median value or undefined if array empty.
- */
-function median( array ) {
-	if ( ! array || ! array.length ) return undefined;
-
-	const numbers = [ ...array ].sort( ( a, b ) => a - b );
-	const middleIndex = Math.floor( numbers.length / 2 );
-
-	if ( numbers.length % 2 === 0 ) {
-		return ( numbers[ middleIndex - 1 ] + numbers[ middleIndex ] ) / 2;
-	}
-	return numbers[ middleIndex ];
-}
-
 module.exports = {
 	askForConfirmation,
 	readJSONFile,
@@ -150,5 +131,4 @@ module.exports = {
 	getFilesFromDir,
 	logAtIndent,
 	sanitizeBranchName,
-	median
 };