Commit 04ae865ba6e for woocommerce

commit 04ae865ba6eaba16bad4496d6cff1a14bbca7f16
Author: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com>
Date:   Thu Nov 12 14:54:31 2026 +0200

    Run Blocks e2e parallel test cleanup in fixture teardown (#70896)

    * Run Blocks e2e parallel test cleanup in fixture teardown

    After a test timeout, a finally block in the test body may not run.
    The test data then stays on the shared site for the retry and for
    other workers. A new cleanup fixture runs the cleanup in teardown,
    which runs after a timeout too.

    * Run every cleanup step when one throws at once

    A callback that throws before it returns a promise stopped the map,
    so the steps after it never ran. Start each step in a promise chain
    so Promise.allSettled catches that error too.

    * Add changelog entry

diff --git a/plugins/woocommerce/changelog/dev-e2e-blocks-parallel-cleanup-in-fixtures b/plugins/woocommerce/changelog/dev-e2e-blocks-parallel-cleanup-in-fixtures
new file mode 100644
index 00000000000..c7e4e515b27
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-e2e-blocks-parallel-cleanup-in-fixtures
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Run Blocks e2e parallel test cleanup in fixture teardown so it also runs after a timeout; tests only.
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts
index a08a4649bd1..39d57f1c2cb 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-form/add-to-cart-form.block_theme.spec.ts
@@ -145,6 +145,7 @@ test.describe( `${ blockData.name } Block`, () => {
 		admin,
 		editor,
 		requestUtils,
+		cleanup,
 		blockUtils,
 	} ) => {
 		// Add to Cart with Options in the Site Editor is only available as
@@ -155,35 +156,34 @@ test.describe( `${ blockData.name } Block`, () => {
 			title: `Add to Cart Form Test ${ test.info().repeatEachIndex }`,
 			content: 'placeholder',
 		} );
+		cleanup( () =>
+			requestUtils.revertTemplate( 'wp_template', template.id )
+		);

-		try {
-			await admin.visitSiteEditor( {
-				postId: template.id,
-				postType: 'wp_template',
-				canvas: 'edit',
-			} );
+		await admin.visitSiteEditor( {
+			postId: template.id,
+			postType: 'wp_template',
+			canvas: 'edit',
+		} );

-			await expect(
-				editor.getCustomHtmlBlockContentLocator( 'placeholder' )
-			).toBeVisible();
+		await expect(
+			editor.getCustomHtmlBlockContentLocator( 'placeholder' )
+		).toBeVisible();

-			await editor.insertBlock( { name: 'woocommerce/single-product' } );
+		await editor.insertBlock( { name: 'woocommerce/single-product' } );

-			await blockUtils.configureSingleProductBlock();
+		await blockUtils.configureSingleProductBlock();

-			await expect(
-				await editor.getBlockByName( blockData.slug )
-			).toBeVisible();
-
-			// When the block is registered as ancestor, the function doesn't throw an error, but the block is not added.
-			// So we check that only one instance of the block is present.
-			await editor.insertBlock( { name: blockData.slug } );
-			await expect(
-				await editor.getBlockByName( blockData.slug )
-			).toBeVisible();
-		} finally {
-			await requestUtils.revertTemplate( 'wp_template', template.id );
-		}
+		await expect(
+			await editor.getBlockByName( blockData.slug )
+		).toBeVisible();
+
+		// When the block is registered as ancestor, the function doesn't throw an error, but the block is not added.
+		// So we check that only one instance of the block is present.
+		await editor.insertBlock( { name: blockData.slug } );
+		await expect(
+			await editor.getBlockByName( blockData.slug )
+		).toBeVisible();
 	} );

 	test( 'can be added in the Post Editor - Single Product Template', async ( {
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 e8532f233b6..cc4f3171006 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
@@ -30,6 +30,7 @@ test.describe( `${ BLOCK_NAME } Block`, () => {
 		admin,
 		page,
 		requestUtils,
+		cleanup,
 	} ) => {
 		// WordPress uses `single-post-<slug>` for this post only. It has no
 		// header, so the page has no Mini-Cart block.
@@ -39,46 +40,42 @@ test.describe( `${ BLOCK_NAME } Block`, () => {
 			title: 'All Products without header',
 			content: '<!-- wp:post-content /-->',
 		} );
-		let postId: number | undefined;
+		cleanup( () =>
+			requestUtils.revertTemplate( 'wp_template', template.id )
+		);

-		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;
+		const post = await requestUtils.rest< { id: number } >( {
+			method: 'POST',
+			path: 'wp/v2/posts',
+			data: {
+				title: 'All Products without Mini-Cart',
+				slug: postSlug,
+				status: 'draft',
+			},
+		} );
+		cleanup( () =>
+			requestUtils.rest( {
+				method: 'DELETE',
+				path: `wp/v2/posts/${ post.id }`,
+				params: { force: true },
+			} )
+		);

-			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 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( '[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 ),
-			] );
-		}
+		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 );
 	} );

 	// Regression coverage for legacy filter markup:
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/breadcrumbs/breadcrumbs.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/breadcrumbs/breadcrumbs.block_theme.spec.ts
index a7286de9564..231a28b9e10 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/breadcrumbs/breadcrumbs.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/breadcrumbs/breadcrumbs.block_theme.spec.ts
@@ -12,6 +12,7 @@ test.describe( `${ blockData.slug } Block`, () => {
 	test( 'block can be inserted in the Site Editor', async ( {
 		admin,
 		requestUtils,
+		cleanup,
 		editor,
 	} ) => {
 		const template = await requestUtils.createTemplate( 'wp_template', {
@@ -19,29 +20,26 @@ test.describe( `${ blockData.slug } Block`, () => {
 			title: `Breadcrumbs Test ${ test.info().repeatEachIndex }`,
 			content: 'placeholder',
 		} );
+		cleanup( () =>
+			requestUtils.revertTemplate( 'wp_template', template.id )
+		);

-		try {
-			await admin.visitSiteEditor( {
-				postId: template.id,
-				postType: 'wp_template',
-				canvas: 'edit',
-			} );
+		await admin.visitSiteEditor( {
+			postId: template.id,
+			postType: 'wp_template',
+			canvas: 'edit',
+		} );

-			await expect(
-				editor.getCustomHtmlBlockContentLocator( 'placeholder' )
-			).toBeVisible();
+		await expect(
+			editor.getCustomHtmlBlockContentLocator( 'placeholder' )
+		).toBeVisible();

-			await editor.insertBlock( {
-				name: blockData.slug,
-			} );
+		await editor.insertBlock( {
+			name: blockData.slug,
+		} );

-			const block = await editor.getBlockByName( blockData.slug );
+		const block = await editor.getBlockByName( blockData.slug );

-			await expect( block ).toHaveText(
-				'Breadcrumbs / Navigation / Path'
-			);
-		} finally {
-			await requestUtils.revertTemplate( 'wp_template', template.id );
-		}
+		await expect( block ).toHaveText( 'Breadcrumbs / Navigation / Path' );
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/parent-item-key.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/parent-item-key.block_theme.spec.ts
index be4ee49d40e..b931b5b4191 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/parent-item-key.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/parent-item-key.block_theme.spec.ts
@@ -142,6 +142,7 @@ test.describe( 'ProductButton in-cart count', () => {
 	test( 'does not count a child cart line in the add to cart button', async ( {
 		page,
 		requestUtils,
+		cleanup,
 		addToCartWithOptionsPage,
 	} ) => {
 		await addToCartWithOptionsPage.createPostWithProductBlock(
@@ -155,75 +156,72 @@ test.describe( 'ProductButton in-cart count', () => {
 			)?.[ 1 ]
 		);
 		expect( postId ).toBeGreaterThan( 0 );
+		cleanup( async () => {
+			await requestUtils.rest( {
+				method: 'DELETE',
+				path: `/wp/v2/posts/${ postId }`,
+				params: { force: true },
+			} );
+		} );

 		// All workers share the admin cart, so the cart steps run as a guest.
 		await page.context().clearCookies();

-		try {
-			const variationId = await getSinglePostId(
-				'post list --post_type=product_variation --field=ID --name="Hoodie - Blue, Yes" --format=ids'
-			);
+		const variationId = await getSinglePostId(
+			'post list --post_type=product_variation --field=ID --name="Hoodie - Blue, Yes" --format=ids'
+		);

-			const capId = await getProductIdBySlug( 'cap' );
-			await page.goto( `/?add-to-cart=${ capId }` );
-			const parentCartItems = await readCartItems( page );
-			expect( parentCartItems ).toHaveLength( 1 );
-			const parentLine = parentCartItems.find(
-				( item ) => item.id === capId
+		const capId = await getProductIdBySlug( 'cap' );
+		await page.goto( `/?add-to-cart=${ capId }` );
+		const parentCartItems = await readCartItems( page );
+		expect( parentCartItems ).toHaveLength( 1 );
+		const parentLine = parentCartItems.find(
+			( item ) => item.id === capId
+		);
+		expect( parentLine ).toBeDefined();
+		const parentItemKey = ( parentLine as StoreApiCartItem ).key;
+
+		await seedDeclaredChildLine( page, variationId, parentItemKey );
+		const cartItems = await readCartItems( page );
+		expect( cartItems ).toHaveLength( 2 );
+		expect(
+			cartItems.find( ( item ) => item.id === variationId )
+				?.parent_item_key
+		).toBe( parentItemKey );
+		expect(
+			cartItems.find( ( item ) => item.key === parentItemKey )
+				?.parent_item_key
+		).toBeNull();
+
+		// Reads the server-rendered HTML without running scripts.
+		const readServerRenderedButtonText = async () => {
+			const response = await page.request.get( postUrl );
+			const button = ( await response.text() ).match(
+				/<button\b[^>]*class="[^"]*\bsingle_add_to_cart_button\b[^"]*"[^>]*>([\s\S]*?)<\/button>/
 			);
-			expect( parentLine ).toBeDefined();
-			const parentItemKey = ( parentLine as StoreApiCartItem ).key;
+			expect( button ).not.toBeNull();

-			await seedDeclaredChildLine( page, variationId, parentItemKey );
-			const cartItems = await readCartItems( page );
-			expect( cartItems ).toHaveLength( 2 );
-			expect(
-				cartItems.find( ( item ) => item.id === variationId )
-					?.parent_item_key
-			).toBe( parentItemKey );
-			expect(
-				cartItems.find( ( item ) => item.key === parentItemKey )
-					?.parent_item_key
-			).toBeNull();
+			return button?.[ 1 ]?.replace( /<[^>]*>/g, '' ).trim();
+		};

-			// Reads the server-rendered HTML without running scripts.
-			const readServerRenderedButtonText = async () => {
-				const response = await page.request.get( postUrl );
-				const button = ( await response.text() ).match(
-					/<button\b[^>]*class="[^"]*\bsingle_add_to_cart_button\b[^"]*"[^>]*>([\s\S]*?)<\/button>/
-				);
-				expect( button ).not.toBeNull();
+		const addToCartButton = page
+			.locator( '.wp-block-add-to-cart-with-options' )
+			.locator( '.single_add_to_cart_button' );

-				return button?.[ 1 ]?.replace( /<[^>]*>/g, '' ).trim();
-			};
+		expect( await readServerRenderedButtonText() ).toBe( 'Add to cart' );
+		await page.goto( postUrl );
+		// The server renders the button hidden and the client store reveals it, so
+		// waiting for it to be visible makes the next text check read the hydrated count.
+		await expect( addToCartButton ).toBeVisible();
+		await expect( addToCartButton ).toHaveText( 'Add to cart' );

-			const addToCartButton = page
-				.locator( '.wp-block-add-to-cart-with-options' )
-				.locator( '.single_add_to_cart_button' );
+		await addToCartButton.click();
+		await expect( addToCartButton ).toHaveText( '1 in cart' );

-			expect( await readServerRenderedButtonText() ).toBe(
-				'Add to cart'
-			);
-			await page.goto( postUrl );
-			// The server renders the button hidden and the client store reveals it, so
-			// waiting for it to be visible makes the next text check read the hydrated count.
-			await expect( addToCartButton ).toBeVisible();
-			await expect( addToCartButton ).toHaveText( 'Add to cart' );
-
-			await addToCartButton.click();
-			await expect( addToCartButton ).toHaveText( '1 in cart' );
-
-			expect( await readServerRenderedButtonText() ).toBe( '1 in cart' );
-			await page.reload();
-			await expect( addToCartButton ).toBeVisible();
-			await expect( addToCartButton ).toHaveText( '1 in cart' );
-		} finally {
-			await requestUtils.rest( {
-				method: 'DELETE',
-				path: `/wp/v2/posts/${ postId }`,
-				params: { force: true },
-			} );
-		}
+		expect( await readServerRenderedButtonText() ).toBe( '1 in cart' );
+		await page.reload();
+		await expect( addToCartButton ).toBeVisible();
+		await expect( addToCartButton ).toHaveText( '1 in cart' );
 	} );

 	test.describe( 'as a guest', () => {
@@ -299,6 +297,7 @@ test.describe( 'ProductButton in-cart count', () => {
 		test( 'counts a child when its declared parent product is drafted', async ( {
 			page,
 			requestUtils,
+			cleanup,
 		} ) => {
 			// Other specs read the sample catalog, so the parent is a product
 			// of its own. Hidden and dated in the past, it stays out of the
@@ -316,41 +315,38 @@ test.describe( 'ProductButton in-cart count', () => {
 				},
 			} );

-			try {
-				await seedBeanieAsChildOf( page, parentId );
-				await wpCLI( `post update ${ parentId } --post_status=draft` );
-
-				const items = await readCartItems( page );
-				expect(
-					items.find( ( item ) => item.id === parentId )
-				).toBeUndefined();
-				const childLine = items.find(
-					( item ) => item.id === PRODUCT_X.id
-				);
-				expect( childLine ).toBeDefined();
-				expect( childLine?.parent_item_key ).toBeNull();
-
-				expect(
-					await readServerRenderedProductButtonText(
-						page,
-						PRODUCT_X.id
-					)
-				).toBe( '1 in cart' );
-
-				await page.goto( '/shop/' );
-				await expectHydratedProductButton(
-					page,
-					PRODUCT_X.id,
-					'1 in cart',
-					'2 in cart'
-				);
-			} finally {
+			cleanup( async () => {
 				await requestUtils.rest( {
 					method: 'DELETE',
 					path: `wc/v3/products/${ parentId }`,
 					params: { force: true },
 				} );
-			}
+			} );
+
+			await seedBeanieAsChildOf( page, parentId );
+			await wpCLI( `post update ${ parentId } --post_status=draft` );
+
+			const items = await readCartItems( page );
+			expect(
+				items.find( ( item ) => item.id === parentId )
+			).toBeUndefined();
+			const childLine = items.find(
+				( item ) => item.id === PRODUCT_X.id
+			);
+			expect( childLine ).toBeDefined();
+			expect( childLine?.parent_item_key ).toBeNull();
+
+			expect(
+				await readServerRenderedProductButtonText( page, PRODUCT_X.id )
+			).toBe( '1 in cart' );
+
+			await page.goto( '/shop/' );
+			await expectHydratedProductButton(
+				page,
+				PRODUCT_X.id,
+				'1 in cart',
+				'2 in cart'
+			);
 		} );
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/catalog-sorting/catalog-sorting.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/catalog-sorting/catalog-sorting.block_theme.spec.ts
index c831e7008ab..95d88c1ed78 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/catalog-sorting/catalog-sorting.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/catalog-sorting/catalog-sorting.block_theme.spec.ts
@@ -13,6 +13,7 @@ test.describe( `${ blockData.slug } Block`, () => {
 	test( 'block can be inserted in the Site Editor', async ( {
 		admin,
 		requestUtils,
+		cleanup,
 		editor,
 	} ) => {
 		const template = await requestUtils.createTemplate( 'wp_template', {
@@ -20,26 +21,25 @@ test.describe( `${ blockData.slug } Block`, () => {
 			title: `Catalog Sorting Test ${ test.info().repeatEachIndex }`,
 			content: 'placeholder',
 		} );
+		cleanup( () =>
+			requestUtils.revertTemplate( 'wp_template', template.id )
+		);

-		try {
-			await admin.visitSiteEditor( {
-				postId: template.id,
-				postType: 'wp_template',
-				canvas: 'edit',
-			} );
+		await admin.visitSiteEditor( {
+			postId: template.id,
+			postType: 'wp_template',
+			canvas: 'edit',
+		} );

-			await expect(
-				editor.getCustomHtmlBlockContentLocator( 'placeholder' )
-			).toBeVisible();
+		await expect(
+			editor.getCustomHtmlBlockContentLocator( 'placeholder' )
+		).toBeVisible();

-			await editor.insertBlock( {
-				name: blockData.slug,
-			} );
+		await editor.insertBlock( {
+			name: blockData.slug,
+		} );

-			const block = await editor.getBlockByName( blockData.slug );
-			await expect( block ).toHaveText( 'Default sorting' );
-		} finally {
-			await requestUtils.revertTemplate( 'wp_template', template.id );
-		}
+		const block = await editor.getBlockByName( blockData.slug );
+		await expect( block ).toHaveText( 'Default sorting' );
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/featured-category/featured-category.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/featured-category/featured-category.block_theme.spec.ts
index 0abbb398edf..c627273006b 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/featured-category/featured-category.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/featured-category/featured-category.block_theme.spec.ts
@@ -33,68 +33,64 @@ test.describe( `${ blockData.slug } Block`, () => {
 		).toBeVisible();
 	} );

-	test( 'image can be edited', async ( { editor, admin, requestUtils } ) => {
-		let editedMediaId: number | undefined;
+	test( 'image can be edited', async ( {
+		editor,
+		admin,
+		requestUtils,
+		cleanup,
+	} ) => {
 		const media = await requestUtils.uploadMedia(
 			path.resolve( __dirname, '../../../test-data/images/image-01.png' )
 		);

-		try {
-			const cliOutput = await wpCLI(
-				'term list product_cat --slug=music --field=term_id'
-			);
-			const categoryId = Number(
-				cliOutput.stdout.match( /^[1-9]\d*$/m )?.[ 0 ]
-			);
-			if ( ! categoryId ) {
-				throw new Error(
-					`Failed to find the Music category: ${ cliOutput.stdout }`
-				);
-			}
+		cleanup( () => requestUtils.deleteMedia( media.id ) );

-			// The block's own image overrides the category image, so the test
-			// does not change any category that other specs read.
-			await admin.createNewPost();
-			await editor.insertBlock( {
-				name: blockData.slug,
-				attributes: {
-					categoryId,
-					mediaId: media.id,
-					mediaSrc: media.source_url,
-				},
-			} );
-			await editor.clickBlockToolbarButton( 'Edit category image' );
-			await editor.clickBlockToolbarButton( 'Rotate' );
-			const editImageResponse = editor.page.waitForResponse(
-				( response ) =>
-					response.request().method() === 'POST' &&
-					new URL( response.url() ).pathname ===
-						`/wp-json/wp/v2/media/${ media.id }/edit`
+		const cliOutput = await wpCLI(
+			'term list product_cat --slug=music --field=term_id'
+		);
+		const categoryId = Number(
+			cliOutput.stdout.match( /^[1-9]\d*$/m )?.[ 0 ]
+		);
+		if ( ! categoryId ) {
+			throw new Error(
+				`Failed to find the Music category: ${ cliOutput.stdout }`
 			);
-			await editor.page
-				.getByRole( 'button', { name: 'Apply', exact: true } )
-				.click();
-			const editResponse = await editImageResponse;
-			expect( editResponse.status() ).toBe( 201 );
-			const editedMedia = await editResponse.json();
-			if (
-				typeof editedMedia.id !== 'number' ||
-				! Number.isInteger( editedMedia.id )
-			) {
-				throw new Error( 'The image edit did not return a media ID.' );
-			}
-			editedMediaId = editedMedia.id;
-			await expect(
-				editor.canvas.locator( 'img[alt="Music"][src*="-edited"]' )
-			).toBeVisible();
-		} finally {
-			try {
-				if ( editedMediaId !== undefined ) {
-					await requestUtils.deleteMedia( editedMediaId );
-				}
-			} finally {
-				await requestUtils.deleteMedia( media.id );
-			}
 		}
+
+		// The block's own image overrides the category image, so the test
+		// does not change any category that other specs read.
+		await admin.createNewPost();
+		await editor.insertBlock( {
+			name: blockData.slug,
+			attributes: {
+				categoryId,
+				mediaId: media.id,
+				mediaSrc: media.source_url,
+			},
+		} );
+		await editor.clickBlockToolbarButton( 'Edit category image' );
+		await editor.clickBlockToolbarButton( 'Rotate' );
+		const editImageResponse = editor.page.waitForResponse(
+			( response ) =>
+				response.request().method() === 'POST' &&
+				new URL( response.url() ).pathname ===
+					`/wp-json/wp/v2/media/${ media.id }/edit`
+		);
+		await editor.page
+			.getByRole( 'button', { name: 'Apply', exact: true } )
+			.click();
+		const editResponse = await editImageResponse;
+		expect( editResponse.status() ).toBe( 201 );
+		const editedMedia = await editResponse.json();
+		if (
+			typeof editedMedia.id !== 'number' ||
+			! Number.isInteger( editedMedia.id )
+		) {
+			throw new Error( 'The image edit did not return a media ID.' );
+		}
+		cleanup( () => requestUtils.deleteMedia( editedMedia.id ) );
+		await expect(
+			editor.canvas.locator( 'img[alt="Music"][src*="-edited"]' )
+		).toBeVisible();
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/featured-product/featured-product.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/featured-product/featured-product.block_theme.spec.ts
index 1ac838495f2..b1c6ea78059 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/featured-product/featured-product.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/featured-product/featured-product.block_theme.spec.ts
@@ -33,68 +33,64 @@ test.describe( `${ blockData.slug } Block`, () => {
 		).toBeVisible();
 	} );

-	test( 'image can be edited', async ( { editor, admin, requestUtils } ) => {
-		let editedMediaId: number | undefined;
+	test( 'image can be edited', async ( {
+		editor,
+		admin,
+		requestUtils,
+		cleanup,
+	} ) => {
 		const media = await requestUtils.uploadMedia(
 			path.resolve( __dirname, '../../../test-data/images/image-01.png' )
 		);

-		try {
-			const cliOutput = await wpCLI(
-				'post list --post_type=product --title=Album --field=ID'
-			);
-			const productId = Number(
-				cliOutput.stdout.match( /^[1-9]\d*$/m )?.[ 0 ]
-			);
-			if ( ! productId ) {
-				throw new Error(
-					`Failed to find the Album product: ${ cliOutput.stdout }`
-				);
-			}
+		cleanup( () => requestUtils.deleteMedia( media.id ) );

-			// The block's own image overrides the product image, so the test
-			// does not change any product that other specs read.
-			await admin.createNewPost();
-			await editor.insertBlock( {
-				name: blockData.slug,
-				attributes: {
-					productId,
-					mediaId: media.id,
-					mediaSrc: media.source_url,
-				},
-			} );
-			await editor.clickBlockToolbarButton( 'Edit product image' );
-			await editor.clickBlockToolbarButton( 'Rotate' );
-			const editImageResponse = editor.page.waitForResponse(
-				( response ) =>
-					response.request().method() === 'POST' &&
-					new URL( response.url() ).pathname ===
-						`/wp-json/wp/v2/media/${ media.id }/edit`
+		const cliOutput = await wpCLI(
+			'post list --post_type=product --title=Album --field=ID'
+		);
+		const productId = Number(
+			cliOutput.stdout.match( /^[1-9]\d*$/m )?.[ 0 ]
+		);
+		if ( ! productId ) {
+			throw new Error(
+				`Failed to find the Album product: ${ cliOutput.stdout }`
 			);
-			await editor.page
-				.getByRole( 'button', { name: 'Apply', exact: true } )
-				.click();
-			const editResponse = await editImageResponse;
-			expect( editResponse.status() ).toBe( 201 );
-			const editedMedia = await editResponse.json();
-			if (
-				typeof editedMedia.id !== 'number' ||
-				! Number.isInteger( editedMedia.id )
-			) {
-				throw new Error( 'The image edit did not return a media ID.' );
-			}
-			editedMediaId = editedMedia.id;
-			await expect(
-				editor.canvas.locator( 'img[alt="Album"][src*="-edited"]' )
-			).toBeVisible();
-		} finally {
-			try {
-				if ( editedMediaId !== undefined ) {
-					await requestUtils.deleteMedia( editedMediaId );
-				}
-			} finally {
-				await requestUtils.deleteMedia( media.id );
-			}
 		}
+
+		// The block's own image overrides the product image, so the test
+		// does not change any product that other specs read.
+		await admin.createNewPost();
+		await editor.insertBlock( {
+			name: blockData.slug,
+			attributes: {
+				productId,
+				mediaId: media.id,
+				mediaSrc: media.source_url,
+			},
+		} );
+		await editor.clickBlockToolbarButton( 'Edit product image' );
+		await editor.clickBlockToolbarButton( 'Rotate' );
+		const editImageResponse = editor.page.waitForResponse(
+			( response ) =>
+				response.request().method() === 'POST' &&
+				new URL( response.url() ).pathname ===
+					`/wp-json/wp/v2/media/${ media.id }/edit`
+		);
+		await editor.page
+			.getByRole( 'button', { name: 'Apply', exact: true } )
+			.click();
+		const editResponse = await editImageResponse;
+		expect( editResponse.status() ).toBe( 201 );
+		const editedMedia = await editResponse.json();
+		if (
+			typeof editedMedia.id !== 'number' ||
+			! Number.isInteger( editedMedia.id )
+		) {
+			throw new Error( 'The image edit did not return a media ID.' );
+		}
+		cleanup( () => requestUtils.deleteMedia( editedMedia.id ) );
+		await expect(
+			editor.canvas.locator( 'img[alt="Album"][src*="-edited"]' )
+		).toBeVisible();
 	} );
 } );
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 7db273dc05a..43de9afefb1 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
@@ -416,6 +416,7 @@ test.describe( `${ blockData.name } Block (variation attributes)`, () => {
 		page,
 		frontendUtils,
 		miniCartUtils,
+		cleanup,
 	} ) => {
 		// The API returns the ampersand as "&amp;", so this also checks that
 		// the variation branch of the table decodes it.
@@ -435,35 +436,35 @@ test.describe( `${ blockData.name } Block (variation attributes)`, () => {
 			/Created product (\d+)/
 		)?.[ 1 ];
 		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' ] );
-		} finally {
+		cleanup( async () => {
 			await wpCLI(
 				`wc product delete ${ productId } --force=true --user=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();
+
+		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' ] );
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/patterns/add-new-pattern.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/patterns/add-new-pattern.block_theme.spec.ts
index d8a56869a8a..26f9b4efdc9 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/patterns/add-new-pattern.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/patterns/add-new-pattern.block_theme.spec.ts
@@ -13,19 +13,13 @@ test.describe( 'Patterns in block theme', () => {
 		admin,
 		editor,
 		requestUtils,
+		cleanup,
 	} ) => {
 		const patternName = `Woo Blocks Synced Pattern ${
 			test.info().repeatEachIndex
 		}`;

-		try {
-			await admin.createNewPattern( patternName );
-			const { productTitles, productPrices } =
-				await addTestingBlocks( editor );
-
-			await expect( productTitles ).toHaveText( expectedTitles );
-			await expect( productPrices ).toHaveText( expectedPrices );
-		} finally {
+		cleanup( async () => {
 			const patterns: { id: number; title: { raw: string } }[] =
 				await requestUtils.rest( {
 					path: '/wp/v2/blocks',
@@ -45,6 +39,13 @@ test.describe( 'Patterns in block theme', () => {
 					params: { force: true },
 				} );
 			}
-		}
+		} );
+
+		await admin.createNewPattern( patternName );
+		const { productTitles, productPrices } =
+			await addTestingBlocks( editor );
+
+		await expect( productTitles ).toHaveText( expectedTitles );
+		await expect( productPrices ).toHaveText( expectedPrices );
 	} );
 } );
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 37e6720ac81..e24faefc42f 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
@@ -275,6 +275,7 @@ test.describe( 'Product Collection', () => {
 		editor,
 		wpCoreVersion,
 		requestUtils,
+		cleanup,
 	} ) => {
 		const getLocationParams = ( request: Request ) =>
 			new URL( request.url() ).searchParams;
@@ -307,102 +308,7 @@ test.describe( 'Product Collection', () => {
 			);
 		};

-		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();
-
-			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 {
+		cleanup( async () => {
 			await Promise.allSettled(
 				[ 'single-product-cap', 'taxonomy-product_cat-hoodies' ].map(
 					( slug ) =>
@@ -412,7 +318,90 @@ 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,
+		} );
+
+		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();
 	} );

 	// Tests for regressions of https://github.com/woocommerce/woocommerce/pull/47994
@@ -452,6 +441,7 @@ test.describe( 'Product Collection', () => {
 			pageObject,
 			editor,
 			requestUtils,
+			cleanup,
 		} ) => {
 			const template = await requestUtils.createTemplate( 'wp_template', {
 				slug: `product-collection-on-sale-${ randomUUID() }`,
@@ -459,29 +449,29 @@ test.describe( 'Product Collection', () => {
 				content: 'placeholder',
 			} );

-			try {
-				await pageObject.goToEditorTemplate( template.id );
-				await expect(
-					editor.getCustomHtmlBlockContentLocator( 'placeholder' )
-				).toBeVisible();
-				await pageObject.insertProductCollection();
-				await pageObject.chooseCollectionInTemplate( 'onSale' );
+			cleanup( () =>
+				requestUtils.revertTemplate( 'wp_template', template.id )
+			);

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

-				await expect( productTemplate ).toHaveCount( 1 );
+			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( 1 );
-			} finally {
-				await requestUtils.revertTemplate( 'wp_template', template.id );
-			}
+			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 );
 		} );

 		test( 'On Sale Products collection should be visible after Refresh in a Post', async ( {
@@ -521,6 +511,7 @@ test.describe( 'Product Collection', () => {
 				test( 'Product Collection block matches with classic template block', async ( {
 					pageObject,
 					requestUtils,
+					cleanup,
 					admin,
 					editor,
 					page,
@@ -543,41 +534,39 @@ test.describe( 'Product Collection', () => {
 						}
 					);

-					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(
+					cleanup( () =>
+						requestUtils.revertTemplate(
 							'wp_template',
 							template.id
-						);
-					}
+						)
+					);
+
+					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
+					);
 				} );
 			} );
 		}
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-results-count/product-results-count.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-results-count/product-results-count.block_theme.spec.ts
index 1818fbcf55b..86239a06832 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-results-count/product-results-count.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-results-count/product-results-count.block_theme.spec.ts
@@ -13,6 +13,7 @@ test.describe( `${ blockData.slug } Block`, () => {
 	test( 'block can be inserted in the Site Editor', async ( {
 		admin,
 		requestUtils,
+		cleanup,
 		editor,
 	} ) => {
 		const template = await requestUtils.createTemplate( 'wp_template', {
@@ -22,27 +23,26 @@ test.describe( `${ blockData.slug } Block`, () => {
 			}`,
 			content: 'placeholder',
 		} );
+		cleanup( () =>
+			requestUtils.revertTemplate( 'wp_template', template.id )
+		);

-		try {
-			await admin.visitSiteEditor( {
-				postId: template.id,
-				postType: 'wp_template',
-				canvas: 'edit',
-			} );
+		await admin.visitSiteEditor( {
+			postId: template.id,
+			postType: 'wp_template',
+			canvas: 'edit',
+		} );

-			await expect(
-				editor.getCustomHtmlBlockContentLocator( 'placeholder' )
-			).toBeVisible();
-			await editor.insertBlock( {
-				name: blockData.slug,
-			} );
+		await expect(
+			editor.getCustomHtmlBlockContentLocator( 'placeholder' )
+		).toBeVisible();
+		await editor.insertBlock( {
+			name: blockData.slug,
+		} );

-			const block = await editor.getBlockByName( blockData.slug );
+		const block = await editor.getBlockByName( blockData.slug );

-			await expect( block ).toHaveText( 'Showing 1-X of X results' );
-		} finally {
-			await requestUtils.revertTemplate( 'wp_template', template.id );
-		}
+		await expect( block ).toHaveText( 'Showing 1-X of X results' );
 	} );

 	test( 'announces the new count to screen readers after sorting', async ( {
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/templates/single-product-template.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/templates/single-product-template.block_theme.spec.ts
index 3c9415c1b88..1e712eb7b12 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/templates/single-product-template.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/templates/single-product-template.block_theme.spec.ts
@@ -9,6 +9,7 @@ test.describe( 'Single Product template', () => {
 		editor,
 		page,
 		requestUtils,
+		cleanup,
 		wpCoreVersion,
 	} ) => {
 		const testData = {
@@ -20,60 +21,7 @@ test.describe( 'Single Product template', () => {
 		};
 		const userText = 'Hello World in the Belt template';

-		try {
-			// Create the specific product template.
-			await admin.visitSiteEditor( {
-				path: `/${ testData.templateType }`,
-			} );
-
-			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
-				.getByPlaceholder( 'Search products' )
-				.fill( testData.productName );
-			await page
-				.getByRole( 'option', { name: testData.productName } )
-				.click();
-			await page.getByLabel( 'Close', { exact: true } ).click();
-
-			await editor.canvas.locator( 'body' ).waitFor( { timeout: 20000 } );
-
-			// Edit the template.
-			await editor.insertBlock( {
-				name: 'core/paragraph',
-				attributes: { content: userText },
-			} );
-			await editor.saveSiteEditorEntities( {
-				isOnlyCurrentEntityDirty: true,
-			} );
-
-			// Verify edits are visible.
-			await page.goto( testData.permalink );
-			await expect( page.getByText( userText ).first() ).toBeVisible();
-
-			// Revert edition.
-			await admin.visitSiteEditor( {
-				postType: testData.templateType,
-			} );
-			await editor.revertTemplate( {
-				templateName: testData.templateName,
-			} );
-			await page.goto( testData.permalink );
-
-			// Verify the edits are no longer visible.
-			await expect( page.getByText( userText ) ).toHaveCount( 0 );
-		} finally {
+		cleanup( async () => {
 			const templates = (
 				await requestUtils.getTemplates( 'wp_template' )
 			).filter(
@@ -87,6 +35,57 @@ test.describe( 'Single Product template', () => {
 					params: { force: true },
 				} );
 			}
-		}
+		} );
+
+		// Create the specific product template.
+		await admin.visitSiteEditor( {
+			path: `/${ testData.templateType }`,
+		} );
+
+		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
+			.getByPlaceholder( 'Search products' )
+			.fill( testData.productName );
+		await page
+			.getByRole( 'option', { name: testData.productName } )
+			.click();
+		await page.getByLabel( 'Close', { exact: true } ).click();
+
+		await editor.canvas.locator( 'body' ).waitFor( { timeout: 20000 } );
+
+		// Edit the template.
+		await editor.insertBlock( {
+			name: 'core/paragraph',
+			attributes: { content: userText },
+		} );
+		await editor.saveSiteEditorEntities( {
+			isOnlyCurrentEntityDirty: true,
+		} );
+
+		// Verify edits are visible.
+		await page.goto( testData.permalink );
+		await expect( page.getByText( userText ).first() ).toBeVisible();
+
+		// Revert edition.
+		await admin.visitSiteEditor( {
+			postType: testData.templateType,
+		} );
+		await editor.revertTemplate( {
+			templateName: testData.templateName,
+		} );
+		await page.goto( testData.permalink );
+
+		// Verify the edits are no longer visible.
+		await expect( page.getByText( userText ) ).toHaveCount( 0 );
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/utils/blocks/test.ts b/plugins/woocommerce/tests/e2e/utils/blocks/test.ts
index 9c16b364215..79d9f3f947d 100644
--- a/plugins/woocommerce/tests/e2e/utils/blocks/test.ts
+++ b/plugins/woocommerce/tests/e2e/utils/blocks/test.ts
@@ -134,6 +134,7 @@ const test = base.extend<
 		localPickupUtils: LocalPickupUtils;
 		miniCartUtils: MiniCartUtils;
 		resetDatabaseAfterEachTest: boolean;
+		cleanup: ( callback: () => Promise< unknown > ) => void;
 		createProductTemplate: (
 			sourceProductSlug: string,
 			options?: { content?: string }
@@ -211,6 +212,26 @@ const test = base.extend<
 	miniCartUtils: async ( { page, frontendUtils }, use ) => {
 		await use( new MiniCartUtils( page, frontendUtils ) );
 	},
+	// Registers a callback that runs after the test, even after a timeout,
+	// when code after the timed-out step in the test body does not run.
+	// The page may already be closed then, so use `requestUtils` or `wpCLI`.
+	cleanup: async ( {}, use ) => {
+		const callbacks: ( () => Promise< unknown > )[] = [];
+
+		await use( ( callback ) => {
+			callbacks.push( callback );
+		} );
+
+		const results = await Promise.allSettled(
+			callbacks.map( ( callback ) => Promise.resolve().then( callback ) )
+		);
+		for ( const result of results ) {
+			if ( result.status === 'rejected' ) {
+				// eslint-disable-next-line no-console
+				console.warn( 'Test cleanup failed:', result.reason );
+			}
+		}
+	},
 	// Copies a sample product and creates a `single-product-<slug>` template
 	// for the copy, so a spec can save a Single Product template without
 	// changing the pages other workers visit. The copy is hidden from the