Commit eee57322335 for woocommerce

commit eee573223351ed34b246fe6c4cc6f7d064b2bfc9
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date:   Thu Apr 9 14:37:27 2026 +0200

    Unify Add to Cart + Options e2e test utils (#64083)

    * Add to Cart + Options: unify e2e tests utils

    * Add changelog

    * Minor enhancements

    * Linting

    * Fix test

    * Typo

    * Improve block selection

    * Use 'options' instead of 'attributes' for frontend options to make naming aligned with other utils

    * Use disabled class when checking if button is enabled

diff --git a/plugins/woocommerce/changelog/update-add-to-cart-with-options-e2e-tests-unify-utils b/plugins/woocommerce/changelog/update-add-to-cart-with-options-e2e-tests-unify-utils
new file mode 100644
index 00000000000..5f00f94d4f1
--- /dev/null
+++ b/plugins/woocommerce/changelog/update-add-to-cart-with-options-e2e-tests-unify-utils
@@ -0,0 +1,5 @@
+Significance: patch
+Type: dev
+Comment: Unify Add to Cart + Options e2e test utils
+
+
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
index 80716f09bb6..fcf0bc52b33 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
@@ -1,7 +1,7 @@
 /**
  * External dependencies
  */
-import { test as base, expect, wpCLI, Editor } from '@woocommerce/e2e-utils';
+import { test as base, expect, wpCLI } from '@woocommerce/e2e-utils';

 /**
  * Internal dependencies
@@ -464,20 +464,20 @@ test.describe( 'Add to Cart + Options Block', () => {
 				exact: true,
 			} );

-			await expect( petitOption ).not.toBeDisabled();
-			await expect( grandOption ).not.toBeDisabled();
+			await expect( petitOption ).toBeEnabled();
+			await expect( grandOption ).toBeEnabled();

 			await petitOption.click();
-			await expect( addToCartButton ).not.toBeDisabled();
+			await expect( addToCartButton ).not.toHaveClass( /\bdisabled\b/ );
 			await addToCartButton.click();
 			await expect( page.getByText( '1 in cart' ) ).toBeVisible();
 		} );

 		await test.step( 'when in dropdown mode', async () => {
 			await pageObject.updateSingleProductTemplate();
-
-			await pageObject.switchToDropdownMode();
-
+			await pageObject.setVariationSelectorAttributes( {
+				optionStyle: 'dropdown',
+			} );
 			await editor.saveSiteEditorEntities();

 			await page.goto( '/product/custom-slug-variable/' );
@@ -499,11 +499,11 @@ test.describe( 'Add to Cart + Options Block', () => {
 				exact: true,
 			} );

-			await expect( petitOption ).not.toBeDisabled();
-			await expect( grandOption ).not.toBeDisabled();
+			await expect( petitOption ).toBeEnabled();
+			await expect( grandOption ).toBeEnabled();
 			await select.selectOption( { label: 'Petit' } );

-			await expect( addToCartButton ).not.toBeDisabled();
+			await expect( addToCartButton ).not.toHaveClass( /\bdisabled\b/ );
 			await addToCartButton.click();
 			await expect( page.getByText( '2 in cart' ) ).toBeVisible();
 		} );
@@ -805,9 +805,9 @@ test.describe( 'Add to Cart + Options Block', () => {
 		editor,
 	} ) => {
 		await pageObject.updateSingleProductTemplate();
-
-		await pageObject.switchToDropdownMode();
-
+		await pageObject.setVariationSelectorAttributes( {
+			optionStyle: 'dropdown',
+		} );
 		await editor.saveSiteEditorEntities();

 		await page.goto( '/product/hoodie/' );
@@ -1427,86 +1427,6 @@ test.describe( 'Add to Cart + Options Block', () => {
 			},
 		];

-		async function setAddToCartWithOptionsBlockAttributes(
-			pageObject: AddToCartWithOptionsPage,
-			editor: Editor,
-			{
-				optionStyle = 'Pills',
-				autoselect = false,
-				disabledAttributesAction = 'disable',
-			}: {
-				optionStyle?: 'Pills' | 'Dropdown';
-				autoselect?: boolean;
-				disabledAttributesAction?: 'disable' | 'hide';
-			} = {}
-		) {
-			const page = editor.page;
-			let isOnlyCurrentEntityDirty = true;
-
-			await pageObject.switchProductType( 'Variable product' );
-			await page.getByRole( 'tab', { name: 'Block' } ).click();
-			const addToCartWithOptionsBlock = editor.canvas.getByLabel(
-				'Block: Add to Cart + Options'
-			);
-			await addToCartWithOptionsBlock.click();
-			await addToCartWithOptionsBlock
-				.getByLabel( 'Block: Variation Selector: Attribute Options' )
-				.first()
-				.click();
-
-			const optionStyleInput = page.getByRole( 'radio', {
-				name: optionStyle,
-				exact: true,
-			} );
-			if ( ! ( await optionStyleInput.isChecked() ) ) {
-				isOnlyCurrentEntityDirty = false;
-				await optionStyleInput.click();
-			}
-
-			const autoselectInput = page.getByRole( 'checkbox', {
-				name: 'Auto-select when only one option is available',
-			} );
-			const invalidOptionsLabel =
-				disabledAttributesAction === 'disable'
-					? 'Grayed-out'
-					: 'Hidden';
-			const invalidOptionsRadio = page
-				.getByLabel( 'Invalid options' )
-				.getByRole( 'radio', { name: invalidOptionsLabel } );
-
-			const isAutoselectChecked = await autoselectInput.isChecked();
-			const isInvalidOptionSelected =
-				( await invalidOptionsRadio.getAttribute( 'aria-checked' ) ) ===
-				'true';
-
-			if (
-				isAutoselectChecked !== autoselect ||
-				! isInvalidOptionSelected
-			) {
-				isOnlyCurrentEntityDirty = false;
-			}
-
-			await autoselectInput.setChecked( autoselect );
-			if ( ! isInvalidOptionSelected ) {
-				await invalidOptionsRadio.click();
-			}
-			if (
-				await page
-					.getByRole( 'region', {
-						name: 'Editor top bar',
-					} )
-					.getByRole( 'button', {
-						name: 'Save',
-						exact: true,
-					} )
-					.isEnabled()
-			) {
-				await editor.saveSiteEditorEntities( {
-					isOnlyCurrentEntityDirty,
-				} );
-			}
-		}
-
 		test.beforeEach( async () => {
 			const cliOutput = await wpCLI(
 				`wc product create --user=1 --slug="${ productSlug }" --name="${ productName }" --type="variable" --attributes='${ JSON.stringify(
@@ -1536,9 +1456,9 @@ test.describe( 'Add to Cart + Options Block', () => {
 			}
 		} );

-		for ( const optionStyle of [ 'Pills', 'Dropdown' ] as (
-			| 'Pills'
-			| 'Dropdown'
+		for ( const optionStyle of [ 'pills', 'dropdown' ] as (
+			| 'pills'
+			| 'dropdown'
 		 )[] ) {
 			// eslint-disable-next-line playwright/expect-expect
 			test( `${ optionStyle }: Test the autoselect block attribute`, async ( {
@@ -1547,16 +1467,22 @@ test.describe( 'Add to Cart + Options Block', () => {
 				editor,
 			} ) => {
 				await pageObject.updateSingleProductTemplate();
-				await setAddToCartWithOptionsBlockAttributes(
-					pageObject,
-					editor,
-					{ optionStyle }
-				);
+
+				if ( optionStyle === 'pills' ) {
+					await editor.saveSiteEditorEntities( {
+						isOnlyCurrentEntityDirty: true,
+					} );
+				} else {
+					await pageObject.setVariationSelectorAttributes( {
+						optionStyle,
+					} );
+					await editor.saveSiteEditorEntities();
+				}

 				await test.step( `${ optionStyle }: Expect NOTHING to be auto-selected (on page load)`, async () => {
 					await page.goto( productPermalink );

-					await pageObject.expectSelectedAttributes(
+					await pageObject.expectVariationSelectorOptions(
 						productAttributes,
 						{ Type: '', Color: '', Size: '' },
 						optionStyle
@@ -1566,14 +1492,14 @@ test.describe( 'Add to Cart + Options Block', () => {
 				await test.step( `${ optionStyle }: Expect attributes to NOT auto-select when user selects something`, async () => {
 					await page.goto( productPermalink );

-					await pageObject.selectVariationSelectorOptionsBlockAttribute(
+					await pageObject.selectVariationSelectorOptions(
 						'Color',
 						'Blue',
 						optionStyle
 					);

 					// Expect nothing to be auto-selected
-					await pageObject.expectSelectedAttributes(
+					await pageObject.expectVariationSelectorOptions(
 						productAttributes,
 						{ Type: '', Color: 'Blue', Size: '' },
 						optionStyle
@@ -1582,18 +1508,18 @@ test.describe( 'Add to Cart + Options Block', () => {

 				await test.step( `${ optionStyle }: Set the autoselect setting to true`, async () => {
 					await pageObject.updateSingleProductTemplate();
-					await setAddToCartWithOptionsBlockAttributes(
-						pageObject,
-						editor,
-						{ optionStyle, autoselect: true }
-					);
+					await pageObject.setVariationSelectorAttributes( {
+						optionStyle,
+						autoselect: true,
+					} );
+					await editor.saveSiteEditorEntities();
 				} );

 				await test.step( `${ optionStyle }: Expect only the Type attribute to be auto-selected (on page load)`, async () => {
 					await page.goto( productPermalink );

 					// Expect the Type attribute to be auto-selected (on page load) to "T-shirt", the rest of the attributes should not be selected.
-					await pageObject.expectSelectedAttributes(
+					await pageObject.expectVariationSelectorOptions(
 						productAttributes,
 						{ Type: 'T-shirt', Color: '', Size: '' },
 						optionStyle
@@ -1604,13 +1530,13 @@ test.describe( 'Add to Cart + Options Block', () => {
 					await page.goto( productPermalink );

 					// By setting the Color to "Blue", we expect the Type attribute to be auto-selected to "T-shirt", and the Size to "XL".
-					await pageObject.selectVariationSelectorOptionsBlockAttribute(
+					await pageObject.selectVariationSelectorOptions(
 						'Color',
 						'Blue',
 						optionStyle
 					);

-					await pageObject.expectSelectedAttributes(
+					await pageObject.expectVariationSelectorOptions(
 						productAttributes,
 						{ Type: 'T-shirt', Color: 'Blue', Size: 'XL' },
 						optionStyle
@@ -1624,20 +1550,23 @@ test.describe( 'Add to Cart + Options Block', () => {
 			} ) => {
 				await test.step( `${ optionStyle }: Set the disabledAttributesAction block attribute to "disable"`, async () => {
 					await pageObject.updateSingleProductTemplate();
-					await setAddToCartWithOptionsBlockAttributes(
-						pageObject,
-						editor,
-						{
+
+					if ( optionStyle === 'pills' ) {
+						await editor.saveSiteEditorEntities( {
+							isOnlyCurrentEntityDirty: true,
+						} );
+					} else {
+						await pageObject.setVariationSelectorAttributes( {
 							optionStyle,
-							disabledAttributesAction: 'disable',
-						}
-					);
+						} );
+						await editor.saveSiteEditorEntities();
+					}
 				} );
 				await test.step( `${ optionStyle }: Expect invalid options to be disabled (by prop) and visible`, async () => {
 					await page.goto( productPermalink );

 					// By setting the Color to "Blue", the only possible Size remaining is "XL".
-					await pageObject.selectVariationSelectorOptionsBlockAttribute(
+					await pageObject.selectVariationSelectorOptions(
 						'Color',
 						'Blue',
 						optionStyle
@@ -1657,20 +1586,17 @@ test.describe( 'Add to Cart + Options Block', () => {

 				await test.step( `${ optionStyle }: Set the disabledAttributesAction block attribute to "hide"`, async () => {
 					await pageObject.updateSingleProductTemplate();
-					await setAddToCartWithOptionsBlockAttributes(
-						pageObject,
-						editor,
-						{
-							optionStyle,
-							disabledAttributesAction: 'hide',
-						}
-					);
+					await pageObject.setVariationSelectorAttributes( {
+						optionStyle,
+						disabledAttributesAction: 'hide',
+					} );
+					await editor.saveSiteEditorEntities();
 				} );
-				await test.step( `${ optionStyle }: Expect invalid options to be isabled (by prop) and hidden`, async () => {
+				await test.step( `${ optionStyle }: Expect invalid options to be disabled (by prop) and hidden`, async () => {
 					await page.goto( productPermalink );

 					// By setting the Color to "Blue", the only possible Size remaining is "XL".
-					await pageObject.selectVariationSelectorOptionsBlockAttribute(
+					await pageObject.selectVariationSelectorOptions(
 						'Color',
 						'Blue',
 						optionStyle
@@ -1701,27 +1627,24 @@ test.describe( 'Add to Cart + Options Block', () => {
 					await pageObject.updateSingleProductTemplate();

 					await test.step( `${ optionStyle }: Set the disabledAttributesAction block attribute to "${ disabledAttributesAction }"`, async () => {
-						await setAddToCartWithOptionsBlockAttributes(
-							pageObject,
-							editor,
-							{
-								autoselect: true,
-								optionStyle,
-								disabledAttributesAction,
-							}
-						);
+						await pageObject.setVariationSelectorAttributes( {
+							autoselect: true,
+							optionStyle,
+							disabledAttributesAction,
+						} );
+						await editor.saveSiteEditorEntities();
 					} );
 					await test.step( `disabledAttributesAction === ${ disabledAttributesAction }: Expect options to be properly auto-selected`, async () => {
 						await page.goto( productPermalink );

 						// By selecting the Color to "Blue", the only possible Size remaining is "XL".
-						await pageObject.selectVariationSelectorOptionsBlockAttribute(
+						await pageObject.selectVariationSelectorOptions(
 							'Color',
 							'Blue',
 							optionStyle
 						);
 						// Now, we deselect the Color.
-						await pageObject.selectVariationSelectorOptionsBlockAttribute(
+						await pageObject.selectVariationSelectorOptions(
 							'Color',
 							'',
 							optionStyle
@@ -1732,7 +1655,7 @@ test.describe( 'Add to Cart + Options Block', () => {
 						// Size: XL
 						// Because the Size is XL, the only Colors possible are Red and Blue.
 						// Now if we select Size: S, the Color should auto-select to Green.
-						await pageObject.selectVariationSelectorOptionsBlockAttribute(
+						await pageObject.selectVariationSelectorOptions(
 							'Size',
 							'S',
 							optionStyle
@@ -1742,7 +1665,7 @@ test.describe( 'Add to Cart + Options Block', () => {
 						// Color: Green
 						// Size: S

-						await pageObject.expectSelectedAttributes(
+						await pageObject.expectVariationSelectorOptions(
 							productAttributes,
 							{ Type: 'T-shirt', Color: 'Green', Size: 'S' },
 							optionStyle
@@ -1758,26 +1681,27 @@ test.describe( 'Add to Cart + Options Block', () => {
 			editor,
 		} ) => {
 			await pageObject.updateSingleProductTemplate();
-			await setAddToCartWithOptionsBlockAttributes( pageObject, editor, {
-				optionStyle: 'Pills',
+			await pageObject.setVariationSelectorAttributes( {
+				optionStyle: 'pills',
 				autoselect: true,
 			} );
+			await editor.saveSiteEditorEntities();

 			await test.step( 'Add the Blue/XL variation to cart', async () => {
 				await page.goto( productPermalink );

 				// Select Blue and XL to match the T-shirt, Blue, XL variation
-				await pageObject.selectVariationSelectorOptionsBlockAttribute(
+				await pageObject.selectVariationSelectorOptions(
 					'Color',
 					'Blue',
-					'Pills'
+					'pills'
 				);

 				// Type and Size should auto-select to T-shirt and XL
-				await pageObject.expectSelectedAttributes(
+				await pageObject.expectVariationSelectorOptions(
 					productAttributes,
 					{ Type: 'T-shirt', Color: 'Blue', Size: 'XL' },
-					'Pills'
+					'pills'
 				);

 				// Add to cart
@@ -1801,10 +1725,10 @@ test.describe( 'Add to Cart + Options Block', () => {

 				// Now select Blue - this should auto-select Size to XL
 				// (since Blue only has one valid size: XL)
-				await pageObject.selectVariationSelectorOptionsBlockAttribute(
+				await pageObject.selectVariationSelectorOptions(
 					'Color',
 					'Blue',
-					'Pills'
+					'pills'
 				);

 				// After auto-selection completes, the button should show "1 in cart"
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts
index 0a3b19b60ac..175c43752ce 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts
@@ -109,10 +109,19 @@ class AddToCartWithOptionsPage {
 		await this.updateAddToCartWithOptionsBlock();
 	}

-	async switchToDropdownMode() {
-		await this.switchProductType( 'Variable product' );
+	async setVariationSelectorAttributes( {
+		optionStyle,
+		autoselect,
+		disabledAttributesAction,
+	}: {
+		optionStyle?: 'pills' | 'dropdown';
+		autoselect?: boolean;
+		disabledAttributesAction?: 'disable' | 'hide';
+	} = {} ) {
+		const page = this.editor.page;

-		await this.page.getByRole( 'tab', { name: 'Block' } ).click();
+		await this.switchProductType( 'Variable product' );
+		await page.getByRole( 'tab', { name: 'Block' } ).click();

 		// Verify inner blocks have loaded.
 		await expect(
@@ -128,7 +137,33 @@ class AddToCartWithOptionsPage {
 		);
 		await this.editor.selectBlocks( attributeOptionsBlock.first() );

-		await this.page.getByRole( 'radio', { name: 'Dropdown' } ).click();
+		// Option style attribute.
+		if ( optionStyle ) {
+			const optionStyleInput = page.getByRole( 'radio', {
+				name: optionStyle,
+			} );
+			await optionStyleInput.click();
+		}
+
+		// Auto-select attribute.
+		if ( typeof autoselect === 'boolean' ) {
+			const autoselectInput = page.getByRole( 'checkbox', {
+				name: 'Auto-select when only one option is available',
+			} );
+			await autoselectInput.setChecked( autoselect );
+		}
+
+		// Invalid options attribute.
+		if ( disabledAttributesAction ) {
+			const invalidOptionsLabel =
+				disabledAttributesAction === 'disable'
+					? 'Grayed-out'
+					: 'Hidden';
+			const invalidOptionsRadio = page
+				.getByLabel( 'Invalid options' )
+				.getByRole( 'radio', { name: invalidOptionsLabel } );
+			await invalidOptionsRadio.click();
+		}
 	}

 	async createPostWithProductBlock( product: string, variation?: string ) {
@@ -157,31 +192,29 @@ class AddToCartWithOptionsPage {
 		await this.editor.publishAndVisitPost();
 	}

-	async selectVariationSelectorOptionsBlockAttribute(
+	async selectVariationSelectorOptions(
 		attributeName: string,
 		attributeValue: string,
-		optionStyle: 'Pills' | 'Dropdown'
+		optionStyle: 'pills' | 'dropdown'
 	) {
-		if ( optionStyle === 'Dropdown' ) {
+		if ( optionStyle === 'dropdown' ) {
 			await this.page
 				.getByLabel( attributeName )
 				.selectOption( attributeValue );
-			return;
-		}
-		if ( attributeValue !== '' ) {
+		} else if ( attributeValue !== '' ) {
 			await this.page
 				.getByLabel( attributeName )
 				.getByText( attributeValue )
 				.click();
-			return;
+		} else {
+			await this.page
+				.getByLabel( attributeName )
+				.locator( 'label:has(:checked)' )
+				.click();
 		}
-		await this.page
-			.getByLabel( attributeName )
-			.locator( 'label:has(:checked)' )
-			.click();
 	}

-	async expectSelectedAttributes(
+	async expectVariationSelectorOptions(
 		productAttributes: {
 			name: string;
 			options: string[];
@@ -189,7 +222,7 @@ class AddToCartWithOptionsPage {
 			visible: boolean;
 		}[],
 		expectedValues: Record< string, string | RegExp > = {},
-		optionStyle: 'Pills' | 'Dropdown'
+		optionStyle: 'pills' | 'dropdown'
 	) {
 		for ( let {
 			name: attributeName,
@@ -198,7 +231,7 @@ class AddToCartWithOptionsPage {
 			const attributeNameLocator = this.page.getByLabel( attributeName, {
 				exact: true,
 			} );
-			if ( optionStyle === 'Dropdown' ) {
+			if ( optionStyle === 'dropdown' ) {
 				let expectedValue: string | RegExp;
 				if (
 					attributeName in expectedValues &&
@@ -211,7 +244,7 @@ class AddToCartWithOptionsPage {
 				await expect( attributeNameLocator ).toHaveValue(
 					expectedValue
 				);
-				return;
+				continue;
 			}
 			if (
 				attributeName in expectedValues &&