Commit dac4645ca59 for woocommerce

commit dac4645ca59a5f607c060dd41a689aee0706991b
Author: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com>
Date:   Thu Nov 12 12:31:50 2026 +0200

    Run the safe tests of 5 Blocks e2e template specs in parallel (#70886)

    Tests that only read site-wide templates, or that delete what they
    create, move to blocks-parallel. Tests that save site-wide templates,
    the Cart and Checkout pages, or global options stay serial in new files.

diff --git a/plugins/woocommerce/changelog/dev-testops-309-blocks-e2e-split-template-specs b/plugins/woocommerce/changelog/dev-testops-309-blocks-e2e-split-template-specs
new file mode 100644
index 00000000000..513d8f25db4
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-testops-309-blocks-e2e-split-template-specs
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Split 5 Blocks e2e template specs and move their parallel-safe tests to the parallel pool; tests only.
diff --git a/plugins/woocommerce/tests/e2e/playwright.config.ts b/plugins/woocommerce/tests/e2e/playwright.config.ts
index 4ac7a3e02b1..8f44134614f 100644
--- a/plugins/woocommerce/tests/e2e/playwright.config.ts
+++ b/plugins/woocommerce/tests/e2e/playwright.config.ts
@@ -197,16 +197,15 @@ const blocksSerialSpecs = [
 	// `wc_blocks_use_blockified_product_grid_block_as_template` option. Every
 	// spec that visits those pages reads them.
 	'**/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts',
-	'**/tests/blocks/all-products/all-products.block_theme.spec.ts',
 	'**/tests/blocks/attributes-filter/attribute-filter.block_theme.spec.ts',
 	'**/tests/blocks/cart-store/cart-line-identity.block_theme.spec.ts',
 	'**/tests/blocks/checkout/checkout-block.merchant.block_theme.spec.ts',
-	'**/tests/blocks/classic-template/classic-template.block_theme.spec.ts',
+	'**/tests/blocks/classic-template/classic-template-blockified-option.block_theme.spec.ts',
 	'**/tests/blocks/mini-cart/mini-cart-block.shopper.block_theme.spec.ts',
-	'**/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts',
-	'**/tests/blocks/page-content-wrapper/page-content-wrapper.block_theme.spec.ts',
+	'**/tests/blocks/mini-cart/mini-cart-header-colors.block_theme.spec.ts',
+	'**/tests/blocks/page-content-wrapper/page-content-wrapper-page-edit.block_theme.spec.ts',
 	'**/tests/blocks/price-filter/price-filter.block_theme.spec.ts',
-	'**/tests/blocks/product-collection/product-collection.block_theme.spec.ts',
+	'**/tests/blocks/product-collection/product-collection-site-templates.block_theme.spec.ts',
 	'**/tests/blocks/product-filters/active-filter-frontend.block_theme.spec.ts',
 	'**/tests/blocks/product-filters/attribute-filter-frontend.block_theme.spec.ts',
 	'**/tests/blocks/product-filters/product-filters-frontend.block_theme.spec.ts',
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/all-products/all-products.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/all-products/all-products.block_theme.spec.ts
index b3fc42ca95a..e8532f233b6 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/all-products/all-products.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/all-products/all-products.block_theme.spec.ts
@@ -1,8 +1,8 @@
 /**
  * External dependencies
  */
+import { randomUUID } from 'crypto';
 import {
-	BLOCK_THEME_SLUG,
 	expect,
 	getProductAttributeIds,
 	PostCompiler,
@@ -29,31 +29,56 @@ test.describe( `${ BLOCK_NAME } Block`, () => {
 		editor,
 		admin,
 		page,
+		requestUtils,
 	} ) => {
-		const templatePath = 'header';
-		await admin.visitSiteEditor( {
-			postId: `${ BLOCK_THEME_SLUG }//${ templatePath }`,
-			postType: 'wp_template_part',
-			canvas: 'edit',
+		// WordPress uses `single-post-<slug>` for this post only. It has no
+		// header, so the page has no Mini-Cart block.
+		const postSlug = `all-products-no-header-${ randomUUID() }`;
+		const template = await requestUtils.createTemplate( 'wp_template', {
+			slug: `single-post-${ postSlug }`,
+			title: 'All Products without header',
+			content: '<!-- wp:post-content /-->',
 		} );
+		let postId: number | undefined;

-		await editor.setContent( '' );
-		await editor.saveSiteEditorEntities( {
-			isOnlyCurrentEntityDirty: true,
-		} );
+		try {
+			const post = await requestUtils.rest< { id: number } >( {
+				method: 'POST',
+				path: 'wp/v2/posts',
+				data: {
+					title: 'All Products without Mini-Cart',
+					slug: postSlug,
+					status: 'draft',
+				},
+			} );
+			postId = post.id;

-		await admin.createNewPost();
-		await editor.insertBlock( { name: BLOCK_NAME } );
-		await editor.publishAndVisitPost();
-		await page.waitForResponse(
-			( response ) =>
-				response.url().includes( 'wp-json/wc/store/v1/products' ) &&
-				response.status() === 200
-		);
+			await admin.editPost( post.id );
+			await editor.insertBlock( { name: BLOCK_NAME } );
+			await editor.publishAndVisitPost();
+			await page.waitForResponse(
+				( response ) =>
+					response.url().includes( 'wp-json/wc/store/v1/products' ) &&
+					response.status() === 200
+			);

-		await expect(
-			page.locator( '.wc-block-grid__product.wc-block-layout' )
-		).toHaveCount( 9 );
+			await expect(
+				page.locator( '[data-block-name="woocommerce/mini-cart"]' )
+			).toHaveCount( 0 );
+			await expect(
+				page.locator( '.wc-block-grid__product.wc-block-layout' )
+			).toHaveCount( 9 );
+		} finally {
+			await Promise.allSettled( [
+				postId &&
+					requestUtils.rest( {
+						method: 'DELETE',
+						path: `wp/v2/posts/${ postId }`,
+						params: { force: true },
+					} ),
+				requestUtils.revertTemplate( 'wp_template', template.id ),
+			] );
+		}
 	} );

 	// Regression coverage for legacy filter markup:
@@ -92,11 +117,21 @@ test.describe( `${ BLOCK_NAME } Block`, () => {
 		const outOfStockCheckbox = page.getByRole( 'checkbox', {
 			name: 'Out of Stock',
 		} );
+		await expect(
+			products.getByText( 'Album', { exact: true } )
+		).toBeVisible();
 		await expect( outOfStockCheckbox ).toBeVisible();
 		await outOfStockCheckbox.click();

 		await expect( page ).toHaveURL( /filter_stock_status=outofstock/ );
-		await expect( products ).toHaveCount( 1 );
+		// Other specs can add out-of-stock products, so check the sample one
+		// is listed and an in-stock one is gone instead of an exact count.
+		await expect(
+			products.getByText( 'T-Shirt with Logo', { exact: true } )
+		).toBeVisible();
+		await expect(
+			products.getByText( 'Album', { exact: true } )
+		).toHaveCount( 0 );
 		await expect(
 			page.getByRole( 'heading', { name: 'Active filters' } )
 		).toBeVisible();
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/classic-template/classic-template-blockified-option.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/classic-template/classic-template-blockified-option.block_theme.spec.ts
new file mode 100644
index 00000000000..fba19426da0
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/classic-template/classic-template-blockified-option.block_theme.spec.ts
@@ -0,0 +1,29 @@
+/**
+ * External dependencies
+ */
+import { test, expect, wpCLI, BLOCK_THEME_SLUG } from '@woocommerce/e2e-utils';
+
+const BLOCK_NAME = 'woocommerce/legacy-template';
+
+test.describe( `${ BLOCK_NAME } Block `, () => {
+	test( `is visible as default block when wc_blocks_use_blockified_product_grid_block_as_template is false`, async ( {
+		admin,
+		editor,
+		page,
+	} ) => {
+		await wpCLI(
+			'option update wc_blocks_use_blockified_product_grid_block_as_template false'
+		);
+		await admin.visitSiteEditor( {
+			postId: `${ BLOCK_THEME_SLUG }//archive-product`,
+			postType: 'wp_template',
+			canvas: 'edit',
+		} );
+
+		await expect( await editor.getBlockByName( BLOCK_NAME ) ).toBeVisible();
+
+		await page.goto( '/shop/' );
+
+		await expect( page.locator( 'div[data-template]' ) ).toBeVisible();
+	} );
+} );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/classic-template/classic-template.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/classic-template/classic-template.block_theme.spec.ts
index 778d38de3d8..5e9a5adcde4 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/classic-template/classic-template.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/classic-template/classic-template.block_theme.spec.ts
@@ -4,16 +4,11 @@
 import {
 	test,
 	expect,
-	wpCLI,
 	BlockData,
 	Editor,
 	BLOCK_THEME_SLUG,
 } from '@woocommerce/e2e-utils';

-/**
- * Internal dependencies
- */
-
 const blockData: Partial< BlockData > = {
 	name: 'woocommerce/legacy-template',
 };
@@ -70,27 +65,4 @@ test.describe( `${ blockData.name } Block `, () => {

 		expect( classicTemplateBlocks ).toHaveLength( 0 );
 	} );
-
-	test( `is visible as default block when wc_blocks_use_blockified_product_grid_block_as_template is false`, async ( {
-		admin,
-		editor,
-		page,
-	} ) => {
-		await wpCLI(
-			'option update wc_blocks_use_blockified_product_grid_block_as_template false'
-		);
-		await admin.visitSiteEditor( {
-			postId: `${ BLOCK_THEME_SLUG }//archive-product`,
-			postType: 'wp_template',
-			canvas: 'edit',
-		} );
-
-		await expect(
-			await editor.getBlockByName( blockData.name )
-		).toBeVisible();
-
-		await page.goto( '/shop/' );
-
-		await expect( page.locator( 'div[data-template]' ) ).toBeVisible();
-	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart-header-colors.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart-header-colors.block_theme.spec.ts
new file mode 100644
index 00000000000..a6508ef9367
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart-header-colors.block_theme.spec.ts
@@ -0,0 +1,129 @@
+/**
+ * External dependencies
+ */
+import { test, expect } from '@woocommerce/e2e-utils';
+
+/**
+ * Internal dependencies
+ */
+import { REGULAR_PRICED_PRODUCT_NAME } from '../checkout/constants';
+
+test.describe( 'Mini-Cart Block (admin)', () => {
+	// eslint-disable-next-line playwright/no-skipped-test
+	test.skip( 'should update badge colors when header background changes', async ( {
+		page,
+		admin,
+		editor,
+		frontendUtils,
+	} ) => {
+		// First, change the header background color in the site editor.
+		await admin.visitSiteEditor( {
+			postId: 'twentytwentyfour//header',
+			postType: 'wp_template_part',
+			canvas: 'edit',
+		} );
+
+		// Select the mini-cart block to get access to its parent (the header row).
+		const miniCartBlock = editor.canvas.locator(
+			'[data-type="woocommerce/mini-cart"]'
+		);
+		await miniCartBlock.click();
+
+		// Select the parent Row block that contains the mini-cart.
+		// Use the block toolbar to select parent.
+		await editor.clickBlockToolbarButton( 'Select parent block: Row' );
+
+		// Now open the Styles panel and set background color.
+		await editor.openDocumentSettingsSidebar();
+
+		// Click on the Styles tab.
+		const stylesTab = page.getByRole( 'tab', { name: 'Styles' } );
+		if ( await stylesTab.isVisible() ) {
+			await stylesTab.click();
+		}
+
+		// Find and click the background color control.
+		const bgColorButton = page
+			.getByRole( 'button', { name: 'Background' } )
+			.first();
+		await bgColorButton.click();
+
+		// Select "Contrast" preset color (black).
+		await page
+			.getByRole( 'option', { name: 'Contrast', exact: true } )
+			.click();
+
+		// Extract the background color hex value from the editor UI.
+		const parentBgColorHex = await page
+			.locator( '.components-color-palette__custom-color-value' )
+			.textContent();
+
+		// Close the background color popover by clicking outside.
+		await stylesTab.click();
+
+		// Find and click the text color control.
+		const textColorButton = page
+			.getByRole( 'button', { name: 'Text' } )
+			.first();
+		await textColorButton.click();
+
+		// Select "Base" preset color (white).
+		await page.getByRole( 'option', { name: 'Base', exact: true } ).click();
+
+		// Extract the text color hex value from the editor UI.
+		const parentTextColorHex = await page
+			.locator( '.components-color-palette__custom-color-value' )
+			.textContent();
+
+		// Save the changes.
+		await editor.saveSiteEditorEntities( {
+			isOnlyCurrentEntityDirty: true,
+		} );
+
+		// Add an item to cart (use a product that's on the first page).
+		await frontendUtils.goToShop();
+		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
+
+		// Verify color values were extracted from the editor.
+		expect( parentBgColorHex ).toBeTruthy();
+		expect( parentTextColorHex ).toBeTruthy();
+
+		// Helper to convert hex color to rgb format.
+		const hexToRgb = ( hex: string ) => {
+			const cleanHex = hex.replace( '#', '' );
+			const r = parseInt( cleanHex.slice( 0, 2 ), 16 );
+			const g = parseInt( cleanHex.slice( 2, 4 ), 16 );
+			const b = parseInt( cleanHex.slice( 4, 6 ), 16 );
+			return `rgb(${ r }, ${ g }, ${ b })`;
+		};
+
+		// Verify the badge has the correct colors:
+		// - Badge background = parent's text color (inverted)
+		// - Badge text = parent's background color (inverted)
+		const badge = page.locator( '.wc-block-mini-cart__badge' );
+		await expect( badge ).toHaveCSS(
+			'background-color',
+			hexToRgb( parentTextColorHex as string )
+		);
+		await expect( badge ).toHaveCSS(
+			'color',
+			hexToRgb( parentBgColorHex as string )
+		);
+
+		// Navigate to the next page using client-side navigation.
+		await page.getByRole( 'link', { name: 'Next Page' } ).click();
+
+		// Await for the navigation to happen.
+		await expect( page ).toHaveURL( /page\/2\/?$/ );
+
+		// Verify the badge colors persist after navigation.
+		await expect( badge ).toHaveCSS(
+			'background-color',
+			hexToRgb( parentTextColorHex as string )
+		);
+		await expect( badge ).toHaveCSS(
+			'color',
+			hexToRgb( parentBgColorHex as string )
+		);
+	} );
+} );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts
index b48b08352bf..7db273dc05a 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts
@@ -1,6 +1,7 @@
 /**
  * External dependencies
  */
+import { randomUUID } from 'crypto';
 import {
 	test,
 	expect,
@@ -403,126 +404,6 @@ test.describe( `${ blockData.name } Block`, () => {
 	} );
 } );

-test.describe( `${ blockData.name } Block (admin)`, () => {
-	// eslint-disable-next-line playwright/no-skipped-test
-	test.skip( 'should update badge colors when header background changes', async ( {
-		page,
-		admin,
-		editor,
-		frontendUtils,
-	} ) => {
-		// First, change the header background color in the site editor.
-		await admin.visitSiteEditor( {
-			postId: 'twentytwentyfour//header',
-			postType: 'wp_template_part',
-			canvas: 'edit',
-		} );
-
-		// Select the mini-cart block to get access to its parent (the header row).
-		const miniCartBlock = editor.canvas.locator(
-			'[data-type="woocommerce/mini-cart"]'
-		);
-		await miniCartBlock.click();
-
-		// Select the parent Row block that contains the mini-cart.
-		// Use the block toolbar to select parent.
-		await editor.clickBlockToolbarButton( 'Select parent block: Row' );
-
-		// Now open the Styles panel and set background color.
-		await editor.openDocumentSettingsSidebar();
-
-		// Click on the Styles tab.
-		const stylesTab = page.getByRole( 'tab', { name: 'Styles' } );
-		if ( await stylesTab.isVisible() ) {
-			await stylesTab.click();
-		}
-
-		// Find and click the background color control.
-		const bgColorButton = page
-			.getByRole( 'button', { name: 'Background' } )
-			.first();
-		await bgColorButton.click();
-
-		// Select "Contrast" preset color (black).
-		await page
-			.getByRole( 'option', { name: 'Contrast', exact: true } )
-			.click();
-
-		// Extract the background color hex value from the editor UI.
-		const parentBgColorHex = await page
-			.locator( '.components-color-palette__custom-color-value' )
-			.textContent();
-
-		// Close the background color popover by clicking outside.
-		await stylesTab.click();
-
-		// Find and click the text color control.
-		const textColorButton = page
-			.getByRole( 'button', { name: 'Text' } )
-			.first();
-		await textColorButton.click();
-
-		// Select "Base" preset color (white).
-		await page.getByRole( 'option', { name: 'Base', exact: true } ).click();
-
-		// Extract the text color hex value from the editor UI.
-		const parentTextColorHex = await page
-			.locator( '.components-color-palette__custom-color-value' )
-			.textContent();
-
-		// Save the changes.
-		await editor.saveSiteEditorEntities( {
-			isOnlyCurrentEntityDirty: true,
-		} );
-
-		// Add an item to cart (use a product that's on the first page).
-		await frontendUtils.goToShop();
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-
-		// Verify color values were extracted from the editor.
-		expect( parentBgColorHex ).toBeTruthy();
-		expect( parentTextColorHex ).toBeTruthy();
-
-		// Helper to convert hex color to rgb format.
-		const hexToRgb = ( hex: string ) => {
-			const cleanHex = hex.replace( '#', '' );
-			const r = parseInt( cleanHex.slice( 0, 2 ), 16 );
-			const g = parseInt( cleanHex.slice( 2, 4 ), 16 );
-			const b = parseInt( cleanHex.slice( 4, 6 ), 16 );
-			return `rgb(${ r }, ${ g }, ${ b })`;
-		};
-
-		// Verify the badge has the correct colors:
-		// - Badge background = parent's text color (inverted)
-		// - Badge text = parent's background color (inverted)
-		const badge = page.locator( '.wc-block-mini-cart__badge' );
-		await expect( badge ).toHaveCSS(
-			'background-color',
-			hexToRgb( parentTextColorHex as string )
-		);
-		await expect( badge ).toHaveCSS(
-			'color',
-			hexToRgb( parentBgColorHex as string )
-		);
-
-		// Navigate to the next page using client-side navigation.
-		await page.getByRole( 'link', { name: 'Next Page' } ).click();
-
-		// Await for the navigation to happen.
-		await expect( page ).toHaveURL( /page\/2\/?$/ );
-
-		// Verify the badge colors persist after navigation.
-		await expect( badge ).toHaveCSS(
-			'background-color',
-			hexToRgb( parentTextColorHex as string )
-		);
-		await expect( badge ).toHaveCSS(
-			'color',
-			hexToRgb( parentBgColorHex as string )
-		);
-	} );
-} );
-
 test.describe( `${ blockData.name } Block (variation attributes)`, () => {
 	test.use( {
 		storageState: {
@@ -539,7 +420,7 @@ test.describe( `${ blockData.name } Block (variation attributes)`, () => {
 		// The API returns the ampersand as "&amp;", so this also checks that
 		// the variation branch of the table decodes it.
 		const cliOutput = await wpCLI(
-			`wc product create --user=1 --name="Test Variable" --type="variable" --attributes='${ JSON.stringify(
+			`wc product create --user=1 --name="Test Variable ${ randomUUID() }" --type="variable" --catalog_visibility="hidden" --attributes='${ JSON.stringify(
 				[
 					{
 						name: 'Shade',
@@ -553,29 +434,36 @@ test.describe( `${ blockData.name } Block (variation attributes)`, () => {
 		const productId = cliOutput.stdout.match(
 			/Created product (\d+)/
 		)?.[ 1 ];
-		const variationOutput = await wpCLI(
-			`wc product_variation create --user=1 "${ productId }" --regular_price="10" --attributes='${ JSON.stringify(
-				[ { name: 'Shade', option: 'Red & Blue' } ]
-			) }'`
-		);
-		const variationId = variationOutput.stdout.match(
-			/Created product_variation (\d+)/
-		)?.[ 1 ];
-
-		// Add the variation through the classic add-to-cart URL, since the
-		// product page form is not what this test covers.
-		await page.goto(
-			`/?add-to-cart=${ productId }&variation_id=${ variationId }`
-		);
-		await frontendUtils.goToShop();
-		await miniCartUtils.openMiniCart();
+		expect( productId ).toBeDefined();
+		try {
+			const variationOutput = await wpCLI(
+				`wc product_variation create --user=1 "${ productId }" --regular_price="10" --attributes='${ JSON.stringify(
+					[ { name: 'Shade', option: 'Red & Blue' } ]
+				) }'`
+			);
+			const variationId = variationOutput.stdout.match(
+				/Created product_variation (\d+)/
+			)?.[ 1 ];
+
+			// Add the variation through the classic add-to-cart URL, since the
+			// product page form is not what this test covers.
+			await page.goto(
+				`/?add-to-cart=${ productId }&variation_id=${ variationId }`
+			);
+			await frontendUtils.goToShop();
+			await miniCartUtils.openMiniCart();

-		const dialog = page.getByRole( 'dialog' );
-		await expect(
-			dialog.locator( '.wc-block-components-product-details__name' )
-		).toContainText( [ 'Shade' ] );
-		await expect(
-			dialog.locator( '.wc-block-components-product-details__value' )
-		).toContainText( [ 'Red & Blue' ] );
+			const dialog = page.getByRole( 'dialog' );
+			await expect(
+				dialog.locator( '.wc-block-components-product-details__name' )
+			).toContainText( [ 'Shade' ] );
+			await expect(
+				dialog.locator( '.wc-block-components-product-details__value' )
+			).toContainText( [ 'Red & Blue' ] );
+		} finally {
+			await wpCLI(
+				`wc product delete ${ productId } --force=true --user=1`
+			);
+		}
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/page-content-wrapper/page-content-wrapper-page-edit.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/page-content-wrapper/page-content-wrapper-page-edit.block_theme.spec.ts
new file mode 100644
index 00000000000..d43cd28fa48
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/page-content-wrapper/page-content-wrapper-page-edit.block_theme.spec.ts
@@ -0,0 +1,73 @@
+/**
+ * External dependencies
+ */
+import { test, expect, FrontendUtils } from '@woocommerce/e2e-utils';
+
+// Instead of testing the block individually, we test the Cart and Checkout
+// templates, which make use of the block.
+const templates = [
+	{
+		title: 'Cart',
+		slug: 'cart',
+		blockClassName: '.wc-block-cart',
+		visitPage: async ( {
+			frontendUtils,
+		}: {
+			frontendUtils: FrontendUtils;
+		} ) => {
+			await frontendUtils.goToCart();
+		},
+	},
+	{
+		title: 'Checkout',
+		slug: 'checkout',
+		blockClassName: '.wc-block-checkout',
+		visitPage: async ( {
+			frontendUtils,
+		}: {
+			frontendUtils: FrontendUtils;
+		} ) => {
+			await frontendUtils.goToShop();
+			await frontendUtils.addToCart();
+			await frontendUtils.goToCheckout();
+		},
+	},
+];
+const userText = 'Hello World in the page';
+
+for ( const template of templates ) {
+	test.describe( 'Page Content Wrapper', () => {
+		test( `the content of the ${ template.title } page is correctly rendered in the ${ template.title } template`, async ( {
+			page,
+			admin,
+			editor,
+			frontendUtils,
+			requestUtils,
+		} ) => {
+			const pageData = await requestUtils.rest( {
+				path: 'wp/v2/pages?slug=' + template.slug,
+			} );
+			const pageId = pageData[ 0 ].id;
+
+			await admin.editPost( pageId );
+
+			await expect(
+				editor.canvas.locator( template.blockClassName )
+			).toBeVisible();
+
+			// editor.isertBlock() doesn't work here.
+			await editor.insertBlockUsingGlobalInserter( 'Paragraph' );
+			await editor.canvas
+				.getByRole( 'document', { name: 'Empty block' } )
+				.fill( userText );
+
+			await editor.saveSiteEditorEntities( {
+				isOnlyCurrentEntityDirty: true,
+			} );
+
+			// Verify edits are in the template when viewed from the frontend.
+			await template.visitPage( { frontendUtils } );
+			await expect( page.getByText( userText ).first() ).toBeVisible();
+		} );
+	} );
+}
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/page-content-wrapper/page-content-wrapper.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/page-content-wrapper/page-content-wrapper.block_theme.spec.ts
index c0b15d06662..e66ebcb1e55 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/page-content-wrapper/page-content-wrapper.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/page-content-wrapper/page-content-wrapper.block_theme.spec.ts
@@ -1,80 +1,23 @@
 /**
  * External dependencies
  */
-import {
-	test,
-	expect,
-	FrontendUtils,
-	BLOCK_THEME_SLUG,
-} from '@woocommerce/e2e-utils';
+import { test, expect, BLOCK_THEME_SLUG } from '@woocommerce/e2e-utils';

-// Instead of testing the block individually, we test the Cart and Checkout
-// templates, which make use of the block.
 const templates = [
 	{
 		title: 'Cart',
 		slug: 'cart',
 		blockClassName: '.wc-block-cart',
-		visitPage: async ( {
-			frontendUtils,
-		}: {
-			frontendUtils: FrontendUtils;
-		} ) => {
-			await frontendUtils.goToCart();
-		},
 	},
 	{
 		title: 'Checkout',
 		slug: 'checkout',
 		blockClassName: '.wc-block-checkout',
-		visitPage: async ( {
-			frontendUtils,
-		}: {
-			frontendUtils: FrontendUtils;
-		} ) => {
-			await frontendUtils.goToShop();
-			await frontendUtils.addToCart();
-			await frontendUtils.goToCheckout();
-		},
 	},
 ];
-const userText = 'Hello World in the page';

 for ( const template of templates ) {
 	test.describe( 'Page Content Wrapper', () => {
-		test( `the content of the ${ template.title } page is correctly rendered in the ${ template.title } template`, async ( {
-			page,
-			admin,
-			editor,
-			frontendUtils,
-			requestUtils,
-		} ) => {
-			const pageData = await requestUtils.rest( {
-				path: 'wp/v2/pages?slug=' + template.slug,
-			} );
-			const pageId = pageData[ 0 ].id;
-
-			await admin.editPost( pageId );
-
-			await expect(
-				editor.canvas.locator( template.blockClassName )
-			).toBeVisible();
-
-			// editor.isertBlock() doesn't work here.
-			await editor.insertBlockUsingGlobalInserter( 'Paragraph' );
-			await editor.canvas
-				.getByRole( 'document', { name: 'Empty block' } )
-				.fill( userText );
-
-			await editor.saveSiteEditorEntities( {
-				isOnlyCurrentEntityDirty: true,
-			} );
-
-			// Verify edits are in the template when viewed from the frontend.
-			await template.visitPage( { frontendUtils } );
-			await expect( page.getByText( userText ).first() ).toBeVisible();
-		} );
-
 		// Regression test for #48936: opening a template that uses this block
 		// must not flag it as dirty (the block previously wrote postId/postType
 		// to attributes on mount, enabling the Save button with no edits).
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-collection/product-collection-site-templates.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-collection/product-collection-site-templates.block_theme.spec.ts
new file mode 100644
index 00000000000..8e650a742f1
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-collection/product-collection-site-templates.block_theme.spec.ts
@@ -0,0 +1,152 @@
+/**
+ * External dependencies
+ */
+import { test as base, expect, wpCLI } from '@woocommerce/e2e-utils';
+
+/**
+ * Internal dependencies
+ */
+import ProductCollectionPage, { SELECTORS } from './product-collection.page';
+
+const test = base.extend< { pageObject: ProductCollectionPage } >( {
+	pageObject: async ( { page, admin, editor }, use ) => {
+		const pageObject = new ProductCollectionPage( {
+			page,
+			admin,
+			editor,
+		} );
+		await use( pageObject );
+	},
+} );
+
+test.describe( 'Product Collection', () => {
+	const templates = [
+		{
+			templateTitle: 'Product Category (all terms)',
+			slug: 'taxonomy-product_cat',
+			frontendPage: '/product-category/music/',
+			legacyBlockName: 'woocommerce/legacy-template',
+		},
+		{
+			templateTitle: 'Product Tag (all terms)',
+			slug: 'taxonomy-product_tag',
+			frontendPage: '/product-tag/recommended/',
+			legacyBlockName: 'woocommerce/legacy-template',
+		},
+		{
+			templateTitle: 'Product Catalog',
+			slug: 'archive-product',
+			frontendPage: '/shop/',
+			legacyBlockName: 'woocommerce/legacy-template',
+		},
+		{
+			templateTitle: 'Product Search Results',
+			slug: 'product-search-results',
+			frontendPage: '/?s=shirt&post_type=product',
+			legacyBlockName: 'woocommerce/legacy-template',
+		},
+	];
+
+	templates.forEach(
+		( { templateTitle, slug, frontendPage, legacyBlockName } ) => {
+			test.describe( `${ templateTitle } template`, () => {
+				test( 'Product Collection block matches with classic template block', async ( {
+					pageObject,
+					requestUtils,
+					admin,
+					editor,
+					page,
+				} ) => {
+					await pageObject.refreshLocators( 'frontend' );
+					await page.goto( frontendPage );
+
+					const productCollectionProductNames =
+						await pageObject.getProductNames();
+					expect(
+						productCollectionProductNames.length
+					).toBeGreaterThan( 0 );
+
+					const template = await requestUtils.createTemplate(
+						'wp_template',
+						{
+							slug,
+							title: 'classic template test',
+							content: 'placeholder',
+						}
+					);
+
+					await admin.visitSiteEditor( {
+						postId: template.id,
+						postType: 'wp_template',
+						canvas: 'edit',
+					} );
+					await expect(
+						editor.getCustomHtmlBlockContentLocator( 'placeholder' )
+					).toBeVisible();
+					await editor.insertBlock( { name: legacyBlockName } );
+					await editor.saveSiteEditorEntities( {
+						isOnlyCurrentEntityDirty: true,
+					} );
+
+					await page.goto( frontendPage );
+					const classicProducts = page.locator(
+						'.woocommerce-loop-product__title'
+					);
+					expect( await classicProducts.count() ).toBeGreaterThan(
+						0
+					);
+					const classicProductsNames =
+						await classicProducts.allTextContents();
+
+					expect( classicProductsNames ).toEqual(
+						productCollectionProductNames
+					);
+				} );
+			} );
+		}
+	);
+
+	test.describe( 'default query can be modified', () => {
+		test( 'default query can be modified', async ( {
+			page,
+			pageObject,
+			editor,
+		} ) => {
+			await wpCLI(
+				'option update woocommerce_default_catalog_orderby price'
+			);
+
+			await pageObject.goToEditorTemplate();
+
+			await pageObject.focusProductCollection();
+
+			// Verify the default order matches the option in the database.
+			const sidebarSettings = pageObject.locateSidebarSettings();
+			const orderBySelect = sidebarSettings.getByRole( 'combobox', {
+				name: 'Default sort by',
+			} );
+			const editorProductTitle = editor.canvas
+				.locator( SELECTORS.productTitle )
+				.first();
+
+			await expect( orderBySelect ).toHaveValue( 'price' );
+			await expect( editorProductTitle ).toHaveText( 'Single' );
+
+			await orderBySelect.selectOption( 'price-desc' );
+
+			await expect( editorProductTitle ).toHaveText( 'Sunglasses' );
+
+			await editor.saveSiteEditorEntities();
+			await pageObject.goToProductCatalogFrontend();
+
+			const frontendProductTitle = page
+				.locator( SELECTORS.productTitle )
+				.first();
+			await expect( frontendProductTitle ).toContainText( 'Sunglasses' );
+
+			await wpCLI(
+				'option update woocommerce_default_catalog_orderby menu_order'
+			);
+		} );
+	} );
+} );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-collection/product-collection.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-collection/product-collection.block_theme.spec.ts
index 49c6015a217..37e6720ac81 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-collection/product-collection.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-collection/product-collection.block_theme.spec.ts
@@ -1,8 +1,14 @@
 /**
  * External dependencies
  */
+import { randomUUID } from 'crypto';
 import { Request } from '@playwright/test';
-import { test as base, expect, wpCLI, BASE_URL } from '@woocommerce/e2e-utils';
+import {
+	test as base,
+	expect,
+	BASE_URL,
+	BLOCK_THEME_SLUG,
+} from '@woocommerce/e2e-utils';

 /**
  * Internal dependencies
@@ -268,6 +274,7 @@ test.describe( 'Product Collection', () => {
 		pageObject,
 		editor,
 		wpCoreVersion,
+		requestUtils,
 	} ) => {
 		const getLocationParams = ( request: Request ) =>
 			new URL( request.url() ).searchParams;
@@ -300,88 +307,112 @@ test.describe( 'Product Collection', () => {
 			);
 		};

-		await admin.visitSiteEditor( { path: '/wp_template' } );
-		await page
-			.getByRole( 'button', {
-				name:
-					wpCoreVersion >= 6.8 ? 'Add Template' : 'Add New Template',
-			} )
-			.click();
-		await page
-			.getByRole( 'button', { name: 'Single Item: Product' } )
-			.click();
-		await page
-			.getByRole( 'option', {
-				name: `Cap ${ BASE_URL }/product/cap/`,
-			} )
-			.click();
-		await page.getByRole( 'button', { name: 'Skip' } ).click();
-		await editor.insertBlockUsingGlobalInserter( pageObject.BLOCK_NAME );
-		await editor.closeGlobalBlockInserter();
-
-		const productRequestPromise = page.waitForRequest(
-			isSpecificProductRequest
-		);
-		await pageObject.chooseCollectionInTemplate( 'featured' );
-		const productParams = getLocationParams( await productRequestPromise );
-		expect(
-			productParams.get(
-				'productCollectionLocation[sourceData][productId]'
-			)
-		).toMatch( /^[1-9]\d*$/ );
-		expect(
-			productParams.get(
-				'productCollectionLocation[sourceData][taxonomy]'
-			)
-		).toBeNull();
-		expect(
-			productParams.get( 'productCollectionLocation[sourceData][termId]' )
-		).toBeNull();
-		await editor.saveSiteEditorEntities( {
-			isOnlyCurrentEntityDirty: true,
-		} );
+		try {
+			await admin.visitSiteEditor( { path: '/wp_template' } );
+			await page
+				.getByRole( 'button', {
+					name:
+						wpCoreVersion >= 6.8
+							? 'Add Template'
+							: 'Add New Template',
+				} )
+				.click();
+			await page
+				.getByRole( 'button', { name: 'Single Item: Product' } )
+				.click();
+			await page
+				.getByRole( 'option', {
+					name: `Cap ${ BASE_URL }/product/cap/`,
+				} )
+				.click();
+			await page.getByRole( 'button', { name: 'Skip' } ).click();
+			await editor.insertBlockUsingGlobalInserter(
+				pageObject.BLOCK_NAME
+			);
+			await editor.closeGlobalBlockInserter();

-		await admin.visitSiteEditor( { path: '/wp_template' } );
-		const categoriesLoaded = page.waitForResponse( ( response ) =>
-			response.url().includes( 'wp-json/wp/v2/product_cat' )
-		);
-		await page
-			.getByRole( 'button', {
-				name:
-					wpCoreVersion >= 6.8 ? 'Add Template' : 'Add New Template',
-			} )
-			.click();
-		await categoriesLoaded;
-		await page
-			.getByRole( 'button', { name: 'Products by Category' } )
-			.click();
-		await page
-			.getByRole( 'button', { name: 'For a specific item' } )
-			.click();
-		await page.getByRole( 'option', { name: 'Hoodies' } ).click();
-
-		const categoryRequestPromise = page.waitForRequest(
-			isSpecificCategoryRequest
-		);
-		await page.getByRole( 'option', { name: 'Fallback content' } ).click();
-		const categoryParams = getLocationParams(
-			await categoryRequestPromise
-		);
-		expect(
-			categoryParams.get(
-				'productCollectionLocation[sourceData][termId]'
-			)
-		).toMatch( /^[1-9]\d*$/ );
-		expect(
-			categoryParams.get(
-				'productCollectionLocation[sourceData][taxonomy]'
-			)
-		).toBe( 'product_cat' );
-		expect(
-			categoryParams.get(
-				'productCollectionLocation[sourceData][productId]'
-			)
-		).toBeNull();
+			const productRequestPromise = page.waitForRequest(
+				isSpecificProductRequest
+			);
+			await pageObject.chooseCollectionInTemplate( 'featured' );
+			const productParams = getLocationParams(
+				await productRequestPromise
+			);
+			expect(
+				productParams.get(
+					'productCollectionLocation[sourceData][productId]'
+				)
+			).toMatch( /^[1-9]\d*$/ );
+			expect(
+				productParams.get(
+					'productCollectionLocation[sourceData][taxonomy]'
+				)
+			).toBeNull();
+			expect(
+				productParams.get(
+					'productCollectionLocation[sourceData][termId]'
+				)
+			).toBeNull();
+			await editor.saveSiteEditorEntities( {
+				isOnlyCurrentEntityDirty: true,
+			} );
+
+			await admin.visitSiteEditor( { path: '/wp_template' } );
+			const categoriesLoaded = page.waitForResponse( ( response ) =>
+				response.url().includes( 'wp-json/wp/v2/product_cat' )
+			);
+			await page
+				.getByRole( 'button', {
+					name:
+						wpCoreVersion >= 6.8
+							? 'Add Template'
+							: 'Add New Template',
+				} )
+				.click();
+			await categoriesLoaded;
+			await page
+				.getByRole( 'button', { name: 'Products by Category' } )
+				.click();
+			await page
+				.getByRole( 'button', { name: 'For a specific item' } )
+				.click();
+			await page.getByRole( 'option', { name: 'Hoodies' } ).click();
+
+			const categoryRequestPromise = page.waitForRequest(
+				isSpecificCategoryRequest
+			);
+			await page
+				.getByRole( 'option', { name: 'Fallback content' } )
+				.click();
+			const categoryParams = getLocationParams(
+				await categoryRequestPromise
+			);
+			expect(
+				categoryParams.get(
+					'productCollectionLocation[sourceData][termId]'
+				)
+			).toMatch( /^[1-9]\d*$/ );
+			expect(
+				categoryParams.get(
+					'productCollectionLocation[sourceData][taxonomy]'
+				)
+			).toBe( 'product_cat' );
+			expect(
+				categoryParams.get(
+					'productCollectionLocation[sourceData][productId]'
+				)
+			).toBeNull();
+		} finally {
+			await Promise.allSettled(
+				[ 'single-product-cap', 'taxonomy-product_cat-hoodies' ].map(
+					( slug ) =>
+						requestUtils.revertTemplate(
+							'wp_template',
+							`${ BLOCK_THEME_SLUG }//${ slug }`
+						)
+				)
+			);
+		}
 	} );

 	// Tests for regressions of https://github.com/woocommerce/woocommerce/pull/47994
@@ -420,23 +451,37 @@ test.describe( 'Product Collection', () => {
 			page,
 			pageObject,
 			editor,
+			requestUtils,
 		} ) => {
-			await pageObject.goToEditorTemplate();
-			await pageObject.insertProductCollection();
-			await pageObject.chooseCollectionInTemplate( 'onSale' );
+			const template = await requestUtils.createTemplate( 'wp_template', {
+				slug: `product-collection-on-sale-${ randomUUID() }`,
+				title: 'Product Collection On Sale',
+				content: 'placeholder',
+			} );

-			const productTemplate = editor.canvas.getByLabel(
-				BLOCK_LABELS.productTemplate
-			);
+			try {
+				await pageObject.goToEditorTemplate( template.id );
+				await expect(
+					editor.getCustomHtmlBlockContentLocator( 'placeholder' )
+				).toBeVisible();
+				await pageObject.insertProductCollection();
+				await pageObject.chooseCollectionInTemplate( 'onSale' );

-			await expect( productTemplate ).toHaveCount( 2 );
+				const productTemplate = editor.canvas.getByLabel(
+					BLOCK_LABELS.productTemplate
+				);

-			// Refresh the template and verify "On Sale Products" collection is still visible
-			await editor.saveSiteEditorEntities( {
-				isOnlyCurrentEntityDirty: true,
-			} );
-			await page.reload();
-			await expect( productTemplate ).toHaveCount( 2 );
+				await expect( productTemplate ).toHaveCount( 1 );
+
+				// Refresh the template and verify "On Sale Products" collection is still visible
+				await editor.saveSiteEditorEntities( {
+					isOnlyCurrentEntityDirty: true,
+				} );
+				await page.reload();
+				await expect( productTemplate ).toHaveCount( 1 );
+			} finally {
+				await requestUtils.revertTemplate( 'wp_template', template.id );
+			}
 		} );

 		test( 'On Sale Products collection should be visible after Refresh in a Post', async ( {
@@ -454,31 +499,20 @@ test.describe( 'Product Collection', () => {
 		} );
 	} );

+	// Specific-term templates, so only these test pages change.
 	const templates = [
 		{
 			templateTitle: 'Product Category',
-			slug: 'taxonomy-product_cat',
+			slug: 'taxonomy-product_cat-music',
 			frontendPage: '/product-category/music/',
 			legacyBlockName: 'woocommerce/legacy-template',
 		},
 		{
 			templateTitle: 'Product Tag',
-			slug: 'taxonomy-product_tag',
+			slug: 'taxonomy-product_tag-recommended',
 			frontendPage: '/product-tag/recommended/',
 			legacyBlockName: 'woocommerce/legacy-template',
 		},
-		{
-			templateTitle: 'Product Catalog',
-			slug: 'archive-product',
-			frontendPage: '/shop/',
-			legacyBlockName: 'woocommerce/legacy-template',
-		},
-		{
-			templateTitle: 'Product Search Results',
-			slug: 'product-search-results',
-			frontendPage: '/?s=shirt&post_type=product',
-			legacyBlockName: 'woocommerce/legacy-template',
-		},
 	];

 	templates.forEach(
@@ -509,78 +543,43 @@ test.describe( 'Product Collection', () => {
 						}
 					);

-					await admin.visitSiteEditor( {
-						postId: template.id,
-						postType: 'wp_template',
-						canvas: 'edit',
-					} );
-					await expect(
-						editor.getCustomHtmlBlockContentLocator( 'placeholder' )
-					).toBeVisible();
-					await editor.insertBlock( { name: legacyBlockName } );
-					await editor.saveSiteEditorEntities( {
-						isOnlyCurrentEntityDirty: true,
-					} );
-
-					await page.goto( frontendPage );
-					const classicProducts = page.locator(
-						'.woocommerce-loop-product__title'
-					);
-					expect( await classicProducts.count() ).toBeGreaterThan(
-						0
-					);
-					const classicProductsNames =
-						await classicProducts.allTextContents();
-
-					expect( classicProductsNames ).toEqual(
-						productCollectionProductNames
-					);
+					try {
+						await admin.visitSiteEditor( {
+							postId: template.id,
+							postType: 'wp_template',
+							canvas: 'edit',
+						} );
+						await expect(
+							editor.getCustomHtmlBlockContentLocator(
+								'placeholder'
+							)
+						).toBeVisible();
+						await editor.insertBlock( { name: legacyBlockName } );
+						await editor.saveSiteEditorEntities( {
+							isOnlyCurrentEntityDirty: true,
+						} );
+
+						await page.goto( frontendPage );
+						const classicProducts = page.locator(
+							'.woocommerce-loop-product__title'
+						);
+						expect( await classicProducts.count() ).toBeGreaterThan(
+							0
+						);
+						const classicProductsNames =
+							await classicProducts.allTextContents();
+
+						expect( classicProductsNames ).toEqual(
+							productCollectionProductNames
+						);
+					} finally {
+						await requestUtils.revertTemplate(
+							'wp_template',
+							template.id
+						);
+					}
 				} );
 			} );
 		}
 	);
-
-	test.describe( 'default query can be modified', () => {
-		test( 'default query can be modified', async ( {
-			page,
-			pageObject,
-			editor,
-		} ) => {
-			await wpCLI(
-				'option update woocommerce_default_catalog_orderby price'
-			);
-
-			await pageObject.goToEditorTemplate();
-
-			await pageObject.focusProductCollection();
-
-			// Verify the default order matches the option in the database.
-			const sidebarSettings = pageObject.locateSidebarSettings();
-			const orderBySelect = sidebarSettings.getByRole( 'combobox', {
-				name: 'Default sort by',
-			} );
-			const editorProductTitle = editor.canvas
-				.locator( SELECTORS.productTitle )
-				.first();
-
-			await expect( orderBySelect ).toHaveValue( 'price' );
-			await expect( editorProductTitle ).toHaveText( 'Single' );
-
-			await orderBySelect.selectOption( 'price-desc' );
-
-			await expect( editorProductTitle ).toHaveText( 'Sunglasses' );
-
-			await editor.saveSiteEditorEntities();
-			await pageObject.goToProductCatalogFrontend();
-
-			const frontendProductTitle = page
-				.locator( SELECTORS.productTitle )
-				.first();
-			await expect( frontendProductTitle ).toContainText( 'Sunglasses' );
-
-			await wpCLI(
-				'option update woocommerce_default_catalog_orderby menu_order'
-			);
-		} );
-	} );
 } );