Commit bf948256984 for woocommerce

commit bf9482569840db35c063bd642fcd3aafb4a955a3
Author: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com>
Date:   Wed Jun 10 15:25:57 2026 +0200

    [tests] Move redundant coupon e2e checks down to php unit (#65592)

    * Reduce coupon creation E2E coverage

    * add changelog

    * Merge coupon E2E specs

    * Keep one coupon E2E smoke

    * Fix strict locator issue

    * Add coupon description assertion to E2E test

    * Use URLSearchParams for coupon post ID assertion

diff --git a/plugins/woocommerce/changelog/testops-139-woocommerce-core-move-redundant-coupon-e2e-down-to-php b/plugins/woocommerce/changelog/testops-139-woocommerce-core-move-redundant-coupon-e2e-down-to-php
new file mode 100644
index 00000000000..5df76d7534c
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-139-woocommerce-core-move-redundant-coupon-e2e-down-to-php
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+tests: move coupons test coverage from e2e to php unit
diff --git a/plugins/woocommerce/tests/e2e-pw/tests/coupons/coupons.spec.ts b/plugins/woocommerce/tests/e2e-pw/tests/coupons/coupons.spec.ts
new file mode 100644
index 00000000000..01f6bed9476
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e-pw/tests/coupons/coupons.spec.ts
@@ -0,0 +1,109 @@
+/**
+ * External dependencies
+ */
+import { WC_API_PATH } from '@woocommerce/e2e-utils-playwright';
+
+/**
+ * Internal dependencies
+ */
+import { expect, tags, test as baseTest } from '../../fixtures/fixtures';
+import { ADMIN_STATE_PATH } from '../../playwright.config';
+
+const test = baseTest.extend( {
+	storageState: ADMIN_STATE_PATH,
+	coupon: async ( { restApi }, use ) => {
+		const coupon = {
+			code: `restricted-wiring-${ Date.now() }`,
+			description: `Restricted coupon wiring ${ Date.now() }`,
+			amount: `${ Math.floor( Math.random() * 50 ) + 1 }`,
+		};
+		await use( coupon );
+		if ( coupon.id ) {
+			await restApi.delete( `${ WC_API_PATH }/coupons/${ coupon.id }`, {
+				force: true,
+			} );
+		}
+	},
+
+	product: async ( { restApi }, use ) => {
+		let product = {};
+		const productName = `Product ${ Date.now() }`;
+
+		await restApi
+			.post( `${ WC_API_PATH }/products`, {
+				name: productName,
+				regular_price: '100',
+			} )
+			.then( ( response ) => {
+				product = response.data;
+			} );
+
+		await use( product );
+
+		await restApi.delete( `${ WC_API_PATH }/products/${ product.id }`, {
+			force: true,
+		} );
+	},
+} );
+
+test.describe( 'Coupon management', { tag: tags.SERVICES }, () => {
+	test( 'can create a product-restricted coupon through the admin form', async ( {
+		page,
+		coupon,
+		product,
+	} ) => {
+		await test.step( 'fill the rendered coupon form', async () => {
+			await page.goto( 'wp-admin/post-new.php?post_type=shop_coupon' );
+			await page.getByLabel( 'Coupon code' ).fill( coupon.code );
+			await page
+				.getByPlaceholder( 'Description (optional)' )
+				.fill( coupon.description );
+			await page.getByLabel( 'Coupon amount' ).fill( coupon.amount );
+			await page
+				.getByRole( 'link', { name: 'Usage restriction' } )
+				.click();
+			await page
+				.locator( '#usage_restriction_coupon_data p' )
+				.filter( {
+					has: page.getByText( 'Products', { exact: true } ),
+				} )
+				.getByPlaceholder( 'Search for a product…' )
+				.pressSequentially( product.name );
+			await page.getByRole( 'option', { name: product.name } ).click();
+		} );
+
+		await test.step( 'publish the coupon', async () => {
+			await expect( page.getByText( 'Move to Trash' ) ).toBeVisible();
+			await page
+				.getByRole( 'button', { name: 'Publish', exact: true } )
+				.click();
+			await expect( page.getByText( 'Coupon updated.' ) ).toBeVisible();
+
+			const searchParams = new URL( page.url() ).searchParams;
+			coupon.id = searchParams.get( 'post' );
+			expect( coupon.id ).toBeDefined();
+			expect( coupon.id ).toMatch( /^\d+$/ );
+		} );
+
+		await test.step( 'verify persisted product restriction', async () => {
+			await page.goto(
+				`wp-admin/post.php?post=${ coupon.id }&action=edit`
+			);
+			await expect( page.getByLabel( 'Coupon code' ) ).toHaveValue(
+				coupon.code
+			);
+			await expect(
+				page.getByPlaceholder( 'Description (optional)' )
+			).toHaveValue( coupon.description );
+			await expect( page.getByLabel( 'Coupon amount' ) ).toHaveValue(
+				coupon.amount
+			);
+			await page
+				.getByRole( 'link', { name: 'Usage restriction' } )
+				.click();
+			await expect(
+				page.getByRole( 'listitem', { name: product.name } )
+			).toBeVisible();
+		} );
+	} );
+} );
diff --git a/plugins/woocommerce/tests/e2e-pw/tests/coupons/create-coupon.spec.ts b/plugins/woocommerce/tests/e2e-pw/tests/coupons/create-coupon.spec.ts
deleted file mode 100644
index 0976797e67b..00000000000
--- a/plugins/woocommerce/tests/e2e-pw/tests/coupons/create-coupon.spec.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * External dependencies
- */
-import { WC_API_PATH } from '@woocommerce/e2e-utils-playwright';
-
-/**
- * Internal dependencies
- */
-import { expect, tags, test as baseTest } from '../../fixtures/fixtures';
-import { ADMIN_STATE_PATH } from '../../playwright.config';
-
-const couponData = {
-	fixedCart: {
-		code: `fixedCart-${ Date.now() }`,
-		description: `Simple fixed cart discount ${ Date.now() }`,
-		amount: `${ Math.floor( Math.random() * 50 ) + 1 }`,
-	},
-	fixedProduct: {
-		code: `fixedProduct-${ Date.now() }`,
-		description: `Simple fixed product discount ${ Date.now() }`,
-		amount: `${ Math.floor( Math.random() * 50 ) + 1 }`,
-	},
-	percentage: {
-		code: `percentage-${ Date.now() }`,
-		description: `Simple percentage discount ${ Date.now() }`,
-		amount: `${ Math.floor( Math.random() * 50 ) + 1 }`,
-	},
-	expiryDate: {
-		code: `expiryDate-${ Date.now() }`,
-		description: `Simple expiry date discount ${ Date.now() }`,
-		amount: `${ Math.floor( Math.random() * 50 ) + 1 }`,
-		expiryDate: '2023-12-31',
-	},
-	freeShipping: {
-		code: `freeShipping-${ Date.now() }`,
-		description: `Simple free shipping discount ${ Date.now() }`,
-		amount: `${ Math.floor( Math.random() * 50 ) + 1 }`,
-		freeShipping: true,
-	},
-};
-
-const test = baseTest.extend( {
-	storageState: ADMIN_STATE_PATH,
-	coupon: async ( { restApi }, use ) => {
-		const coupon = {};
-		await use( coupon );
-		await restApi.delete( `${ WC_API_PATH }/coupons/${ coupon.id }`, {
-			force: true,
-		} );
-	},
-} );
-
-test.describe( 'Coupon management', { tag: tags.SERVICES }, () => {
-	for ( const couponType of Object.keys( couponData ) ) {
-		test( `can create new ${ couponType } coupon`, async ( {
-			page,
-			coupon,
-		} ) => {
-			await test.step( 'add new coupon', async () => {
-				await page.goto(
-					'wp-admin/post-new.php?post_type=shop_coupon'
-				);
-				await page
-					.getByLabel( 'Coupon code' )
-					.fill( couponData[ couponType ].code );
-				await page
-					.getByPlaceholder( 'Description (optional)' )
-					.fill( couponData[ couponType ].description );
-				await page
-					.getByPlaceholder( '0' )
-					.fill( couponData[ couponType ].amount );
-
-				// set expiry date if it was provided
-				if ( couponData[ couponType ].expiryDate ) {
-					await page
-						.getByPlaceholder( 'yyyy-mm-dd' )
-						.fill( couponData[ couponType ].expiryDate );
-				}
-
-				// be explicit about whether free shipping is allowed
-				if ( couponData[ couponType ].freeShipping ) {
-					await page.getByLabel( 'Allow free shipping' ).check();
-				} else {
-					await page.getByLabel( 'Allow free shipping' ).uncheck();
-				}
-			} );
-
-			// publish the coupon and retrieve the id
-			await test.step( 'publish the coupon', async () => {
-				await expect(
-					page.getByRole( 'link', { name: 'Move to Trash' } )
-				).toBeVisible();
-				await page
-					.getByRole( 'button', { name: 'Publish', exact: true } )
-					.click();
-				await expect(
-					page.getByText( 'Coupon updated.' )
-				).toBeVisible();
-				coupon.id = page.url().match( /(?<=post=)\d+/ )[ 0 ];
-				expect( coupon.id ).toBeDefined();
-			} );
-
-			// verify the creation of the coupon and details
-			await test.step( 'verify coupon creation', async () => {
-				await page.goto( 'wp-admin/edit.php?post_type=shop_coupon' );
-				await expect(
-					page.getByRole( 'cell', {
-						name: couponData[ couponType ].code,
-					} )
-				).toBeVisible();
-				await expect(
-					page.getByRole( 'cell', {
-						name: couponData[ couponType ].description,
-					} )
-				).toBeVisible();
-				await expect(
-					page.getByRole( 'cell', {
-						name: couponData[ couponType ].amount,
-						exact: true,
-					} )
-				).toBeVisible();
-			} );
-
-			// check expiry date if it was set
-			if ( couponData[ couponType ].expiryDate ) {
-				await test.step( 'verify coupon expiry date', async () => {
-					await page
-						.getByText( couponData[ couponType ].code )
-						.last()
-						.click();
-					await expect(
-						page.getByPlaceholder( 'yyyy-mm-dd' )
-					).toHaveValue( couponData[ couponType ].expiryDate );
-				} );
-			}
-
-			// if it was a free shipping coupon check that
-			if ( couponData[ couponType ].freeShipping ) {
-				await test.step( 'verify free shipping', async () => {
-					await page
-						.getByText( couponData[ couponType ].code )
-						.last()
-						.click();
-					await expect(
-						page.getByLabel( 'Allow free shipping' )
-					).toBeChecked();
-				} );
-			}
-		} );
-	}
-} );
diff --git a/plugins/woocommerce/tests/e2e-pw/tests/coupons/create-restricted-coupons.spec.ts b/plugins/woocommerce/tests/e2e-pw/tests/coupons/create-restricted-coupons.spec.ts
deleted file mode 100644
index ee3b894d2e0..00000000000
--- a/plugins/woocommerce/tests/e2e-pw/tests/coupons/create-restricted-coupons.spec.ts
+++ /dev/null
@@ -1,534 +0,0 @@
-/**
- * External dependencies
- */
-import { WC_API_PATH } from '@woocommerce/e2e-utils-playwright';
-
-/**
- * Internal dependencies
- */
-import { expect, tags, test as baseTest } from '../../fixtures/fixtures';
-import { ADMIN_STATE_PATH } from '../../playwright.config';
-
-const couponData = {
-	minimumSpend: {
-		code: `minSpend-${ new Date().getTime().toString() }`,
-		description: 'Minimum spend coupon',
-		amount: '10',
-		minSpend: '100',
-	},
-	maximumSpend: {
-		code: `maxSpend-${ new Date().getTime().toString() }`,
-		description: 'Maximum spend coupon',
-		amount: '20',
-		maxSpend: '100',
-	},
-	individualUse: {
-		code: `individualUse-${ new Date().getTime().toString() }`,
-		description: 'Individual use coupon',
-		amount: '30',
-		individualUse: true,
-	},
-	excludeSaleItems: {
-		code: `excludeSaleItems-${ new Date().getTime().toString() }`,
-		description: 'Exclude sale items coupon',
-		amount: '40',
-		excludeSaleItems: true,
-	},
-	productCategories: {
-		code: `productCategories-${ new Date().getTime().toString() }`,
-		description: 'Product categories coupon',
-		amount: '50',
-		productCategories: [ 'Uncategorized' ],
-	},
-	excludeProductCategories: {
-		code: `excludeProductCategories-${ new Date().getTime().toString() }`,
-		description: 'Exclude product categories coupon',
-		amount: '60',
-		excludeProductCategories: [ 'Uncategorized' ],
-	},
-	excludeProductBrands: {
-		code: `excludeProductBrands-${ new Date().getTime().toString() }`,
-		description: 'Exclude product brands coupon',
-		amount: '65',
-		excludeProductBrands: [
-			`WooCommerce Apparels ${ new Date().getTime().toString() }`,
-		],
-	},
-	products: {
-		code: `products-${ new Date().getTime().toString() }`,
-		description: 'Products coupon',
-		amount: '70',
-		products: [ 'Product' ],
-	},
-	excludeProducts: {
-		code: `excludeProducts-${ new Date().getTime().toString() }`,
-		description: 'Exclude products coupon',
-		amount: '80',
-		excludeProducts: [ 'Product' ],
-	},
-	allowedEmails: {
-		code: `allowedEmails-${ new Date().getTime().toString() }`,
-		description: 'Allowed emails coupon',
-		amount: '90',
-		allowedEmails: [ 'allowed@example.com' ],
-	},
-	usageLimitPerCoupon: {
-		code: `usageLimit-${ new Date().getTime().toString() }`,
-		description: 'Usage limit coupon',
-		amount: '100',
-		usageLimit: '1',
-	},
-	usageLimitPerUser: {
-		code: `usageLimitPerUser-${ new Date().getTime().toString() }`,
-		description: 'Usage limit per user coupon',
-		amount: '110',
-		usageLimitPerUser: '2',
-	},
-};
-
-const test = baseTest.extend( {
-	storageState: ADMIN_STATE_PATH,
-	coupon: async ( { restApi }, use ) => {
-		const coupon = {};
-		await use( coupon );
-		await restApi.delete( `${ WC_API_PATH }/coupons/${ coupon.id }`, {
-			force: true,
-		} );
-	},
-
-	product: async ( { restApi }, use ) => {
-		let product = {};
-		const productName = `Product ${ Date.now() }`;
-
-		await restApi
-			.post( `${ WC_API_PATH }/products`, {
-				name: productName,
-				regular_price: '100',
-			} )
-			.then( ( response ) => {
-				product = response.data;
-			} );
-
-		await use( product );
-
-		// Product cleanup
-		await restApi.delete( `${ WC_API_PATH }/products/${ product.id }`, {
-			force: true,
-		} );
-	},
-
-	brand: async ( { restApi }, use ) => {
-		let brand = {};
-
-		await restApi
-			.post( `${ WC_API_PATH }/products/brands`, {
-				name: couponData.excludeProductBrands.excludeProductBrands[ 0 ],
-			} )
-			.then( ( response ) => {
-				brand = response.data;
-			} );
-
-		await use( brand );
-
-		// Brand cleanup
-		await restApi.delete(
-			`${ WC_API_PATH }/products/brands/${ brand.id }`,
-			{ force: true }
-		);
-	},
-} );
-
-test.describe( 'Restricted coupon management', { tag: tags.SERVICES }, () => {
-	for ( const couponType of Object.keys( couponData ) ) {
-		test( `can create new ${ couponType } coupon`, async ( {
-			page,
-			coupon,
-			product,
-			brand,
-		} ) => {
-			// create basics for the coupon
-			await test.step( 'add new coupon', async () => {
-				await page.goto(
-					'wp-admin/post-new.php?post_type=shop_coupon'
-				);
-				await page
-					.getByLabel( 'Coupon code' )
-					.fill( couponData[ couponType ].code );
-				await page
-					.getByPlaceholder( 'Description (optional)' )
-					.fill( couponData[ couponType ].description );
-				await page
-					.getByPlaceholder( '0' )
-					.fill( couponData[ couponType ].amount );
-				await expect( page.getByText( 'Move to Trash' ) ).toBeVisible();
-			} );
-
-			// set up the restrictions for each coupon type
-			// set minimum spend
-			if ( couponType === 'minimumSpend' ) {
-				await test.step( 'set minimum spend coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'No minimum' )
-						.fill( couponData[ couponType ].minSpend );
-				} );
-			}
-			// set maximum spend
-			if ( couponType === 'maximumSpend' ) {
-				await test.step( 'set maximum spend coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'No maximum' )
-						.fill( couponData[ couponType ].maxSpend );
-				} );
-			}
-			// set individual use
-			if ( couponType === 'individualUse' ) {
-				await test.step( 'set individual use coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page.getByLabel( 'Individual use only' ).check();
-				} );
-			}
-			// set exclude sale items
-			if ( couponType === 'excludeSaleItems' ) {
-				await test.step( 'set exclude sale items coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page.getByLabel( 'Exclude sale items' ).check();
-				} );
-			}
-			// set product categories
-			if ( couponType === 'productCategories' ) {
-				await test.step( 'set product categories coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'Any category' )
-						.pressSequentially( 'Uncategorized' );
-					await page
-						.getByRole( 'option', { name: 'Uncategorized' } )
-						.click();
-				} );
-			}
-			// set exclude product categories
-			if ( couponType === 'excludeProductCategories' ) {
-				await test.step( 'set exclude product categories coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'No categories' )
-						.pressSequentially( 'Uncategorized' );
-					await page
-						.getByRole( 'option', { name: 'Uncategorized' } )
-						.click();
-				} );
-			}
-
-			// set exclude product brands
-			if ( couponType === 'excludeProductBrands' ) {
-				await test.step( 'set exclude product brands coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'No brands' )
-						.pressSequentially( brand.name );
-					await page
-						.getByRole( 'option', {
-							name: brand.name,
-						} )
-						.click();
-				} );
-			}
-			// set products
-			if ( couponType === 'products' ) {
-				await test.step( 'set products coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'Search for a product…' )
-						.first()
-						.pressSequentially( product.name );
-					await page
-						.getByRole( 'option', { name: product.name } )
-						.click();
-				} );
-			}
-			// set exclude products
-			if ( couponType === 'excludeProducts' ) {
-				await test.step( 'set exclude products coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'Search for a product…' )
-						.last()
-						.pressSequentially( product.name );
-					await page
-						.getByRole( 'option', { name: product.name } )
-						.click();
-				} );
-			}
-			// set allowed emails
-			if ( couponType === 'allowedEmails' ) {
-				await test.step( 'set allowed emails coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await page
-						.getByPlaceholder( 'No restrictions' )
-						.fill( couponData[ couponType ].allowedEmails[ 0 ] );
-				} );
-			}
-			// set usage limit
-			if ( couponType === 'usageLimitPerCoupon' ) {
-				await test.step( 'set usage limit coupon', async () => {
-					await page
-						.getByRole( 'link', { name: 'Usage limits' } )
-						.click();
-					await page
-						.getByLabel( 'Usage limit per coupon' )
-						.fill( couponData[ couponType ].usageLimit );
-				} );
-			}
-			// set usage limit per user
-			if ( couponType === 'usageLimitPerUser' ) {
-				await test.step( 'set usage limit per user coupon', async () => {
-					await page
-						.getByRole( 'link', { name: 'Usage limits' } )
-						.click();
-					await page
-						.getByLabel( 'Usage limit per user' )
-						.fill( couponData[ couponType ].usageLimitPerUser );
-				} );
-			}
-
-			// publish the coupon and retrieve the id
-			await test.step( 'publish the coupon', async () => {
-				await page
-					.getByRole( 'button', { name: 'Publish', exact: true } )
-					.click();
-				await expect(
-					page.getByText( 'Coupon updated.' )
-				).toBeVisible();
-				coupon.id = page.url().match( /(?<=post=)\d+/ )[ 0 ];
-				expect( coupon.id ).toBeDefined();
-			} );
-
-			// verify the creation of the coupon and basic details
-			await test.step( 'verify coupon creation', async () => {
-				await page.goto( 'wp-admin/edit.php?post_type=shop_coupon' );
-				await expect(
-					page.getByRole( 'cell', {
-						name: couponData[ couponType ].code,
-					} )
-				).toBeVisible();
-				await expect(
-					page.getByRole( 'cell', {
-						name: couponData[ couponType ].description,
-					} )
-				).toBeVisible();
-				await expect(
-					page.getByRole( 'cell', {
-						name: couponData[ couponType ].amount,
-						exact: true,
-					} )
-				).toBeVisible();
-
-				await page
-					.getByRole( 'link', {
-						name: couponData[ couponType ].code,
-					} )
-					.first()
-					.click();
-			} );
-
-			// verify the restrictions for each coupon type
-			// verify minimum spend
-			if ( couponType === 'minimumSpend' ) {
-				await test.step( 'verify minimum spend coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByPlaceholder( 'No minimum' )
-					).toHaveValue( couponData[ couponType ].minSpend );
-				} );
-			}
-
-			// verify maximum spend
-			if ( couponType === 'maximumSpend' ) {
-				await test.step( 'verify maximum spend coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByPlaceholder( 'No maximum' )
-					).toHaveValue( couponData[ couponType ].maxSpend );
-				} );
-			}
-
-			// verify individual use
-			if ( couponType === 'individualUse' ) {
-				await test.step( 'verify individual use coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByLabel( 'Individual use only' )
-					).toBeChecked();
-				} );
-			}
-
-			// verify exclude sale items
-			if ( couponType === 'excludeSaleItems' ) {
-				await test.step( 'verify exclude sale items coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByLabel( 'Exclude sale items' )
-					).toBeChecked();
-				} );
-			}
-
-			// verify product categories
-			if ( couponType === 'productCategories' ) {
-				await test.step( 'verify product categories coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByRole( 'listitem', {
-							name: 'Uncategorized',
-						} )
-					).toBeVisible();
-				} );
-			}
-
-			// verify exclude product categories
-			if ( couponType === 'excludeProductCategories' ) {
-				await test.step( 'verify exclude product categories coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByRole( 'listitem', {
-							name: 'Uncategorized',
-						} )
-					).toBeVisible();
-				} );
-			}
-
-			// verify products
-			if ( couponType === 'products' ) {
-				await test.step( 'verify products coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByRole( 'listitem', { name: product.name } )
-					).toBeVisible();
-				} );
-			}
-
-			// verify exclude products
-			if ( couponType === 'excludeProducts' ) {
-				await test.step( 'verify exclude products coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByRole( 'listitem', { name: product.name } )
-					).toBeVisible();
-				} );
-			}
-
-			// verify allowed emails
-			if ( couponType === 'allowedEmails' ) {
-				await test.step( 'verify allowed emails coupon', async () => {
-					await page
-						.getByRole( 'link', {
-							name: 'Usage restriction',
-						} )
-						.click();
-					await expect(
-						page.getByPlaceholder( 'No restrictions' )
-					).toHaveValue(
-						couponData[ couponType ].allowedEmails[ 0 ]
-					);
-				} );
-			}
-
-			// verify usage limit
-			if ( couponType === 'usageLimitPerCoupon' ) {
-				await test.step( 'verify usage limit coupon', async () => {
-					await page
-						.getByRole( 'link', { name: 'Usage limits' } )
-						.click();
-					await expect(
-						page.getByLabel( 'Usage limit per coupon' )
-					).toHaveValue( couponData[ couponType ].usageLimit );
-				} );
-			}
-
-			// verify usage limit per user
-			if ( couponType === 'usageLimitPerUser' ) {
-				await test.step( 'verify usage limit per user coupon', async () => {
-					await page
-						.getByRole( 'link', { name: 'Usage limits' } )
-						.click();
-					await expect(
-						page.getByLabel( 'Usage limit per user' )
-					).toHaveValue( couponData[ couponType ].usageLimitPerUser );
-				} );
-			}
-		} );
-	}
-} );
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
index 5ca5953daf5..60691f69c7a 100644
--- a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
@@ -113,4 +113,42 @@ class WC_Admin_Brands_Test extends WC_Unit_Test_Case {
 			$output
 		);
 	}
+
+	/**
+	 * @testdox save_coupon_brands() persists posted coupon brand restrictions.
+	 */
+	public function test_save_coupon_brands_persists_posted_coupon_brand_restrictions(): void {
+		WC_Brands::init_taxonomy();
+
+		$included_brand_id = $this->factory()->term->create(
+			array(
+				'taxonomy' => 'product_brand',
+				'name'     => 'Included brand',
+			)
+		);
+		$excluded_brand_id = $this->factory()->term->create(
+			array(
+				'taxonomy' => 'product_brand',
+				'name'     => 'Excluded brand',
+			)
+		);
+		$coupon            = WC_Helper_Coupon::create_coupon( 'brand-restrictions' );
+		$brands_admin      = new WC_Brands_Admin();
+
+		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Preserves test fixture state.
+		$previous_post = $_POST;
+		$_POST         = array(
+			'product_brands'         => array( (string) $included_brand_id ),
+			'exclude_product_brands' => array( (string) $excluded_brand_id ),
+		);
+
+		try {
+			$brands_admin->save_coupon_brands( $coupon->get_id() );
+		} finally {
+			$_POST = $previous_post;
+		}
+
+		$this->assertSame( array( $included_brand_id ), get_post_meta( $coupon->get_id(), 'product_brands', true ), 'Expected included brand restrictions to persist.' );
+		$this->assertSame( array( $excluded_brand_id ), get_post_meta( $coupon->get_id(), 'exclude_product_brands', true ), 'Expected excluded brand restrictions to persist.' );
+	}
 }
diff --git a/plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-coupon-data-test.php b/plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-coupon-data-test.php
new file mode 100644
index 00000000000..182e55af8c1
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-coupon-data-test.php
@@ -0,0 +1,198 @@
+<?php
+
+declare( strict_types = 1 );
+
+/**
+ * Tests for the coupon data meta box.
+ */
+class WC_Meta_Box_Coupon_Data_Test extends WC_Unit_Test_Case {
+	/**
+	 * Previously posted form data.
+	 *
+	 * @var array
+	 */
+	private $previous_post;
+
+	/**
+	 * Set up test fixtures.
+	 */
+	public function set_up(): void {
+		parent::set_up();
+
+		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Preserves test fixture state.
+		$this->previous_post = $_POST;
+
+		WC_Admin_Meta_Boxes::$meta_box_errors = array();
+	}
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tear_down(): void {
+		$_POST = $this->previous_post;
+
+		WC_Admin_Meta_Boxes::$meta_box_errors = array();
+
+		parent::tear_down();
+	}
+
+	/**
+	 * @testdox save() registers coupon validation errors from posted metabox data.
+	 */
+	public function test_save_registers_validation_errors_from_posted_data(): void {
+		$post_id = $this->create_coupon_post( 'metabox-invalid-amount' );
+		$post    = $this->get_coupon_post( $post_id );
+
+		$this->set_coupon_post_data(
+			array(
+				'coupon_amount' => '-1',
+			)
+		);
+
+		WC_Meta_Box_Coupon_Data::save( $post_id, $post );
+
+		$this->assertContains(
+			'Invalid discount amount.',
+			WC_Admin_Meta_Boxes::$meta_box_errors,
+			'Expected the metabox save handler to register model validation errors.'
+		);
+	}
+
+	/**
+	 * @testdox save() registers an error when a coupon code already exists.
+	 */
+	public function test_save_registers_duplicate_coupon_code_error(): void {
+		WC_Helper_Coupon::create_coupon( 'duplicate-metabox-code' );
+
+		$post_id = $this->create_coupon_post( 'duplicate-metabox-code' );
+		$post    = $this->get_coupon_post( $post_id );
+
+		$this->set_coupon_post_data();
+
+		WC_Meta_Box_Coupon_Data::save( $post_id, $post );
+
+		$this->assertContains(
+			'Coupon code already exists - customers will use the latest coupon with this code.',
+			WC_Admin_Meta_Boxes::$meta_box_errors,
+			'Expected the metabox save handler to register duplicate coupon code errors.'
+		);
+	}
+
+	/**
+	 * @testdox save() persists coupon fields from posted metabox data.
+	 */
+	public function test_save_persists_posted_coupon_fields(): void {
+		$post_id              = $this->create_coupon_post( 'metabox-persisted-fields' );
+		$post                 = $this->get_coupon_post( $post_id );
+		$product              = WC_Helper_Product::create_simple_product();
+		$excluded_product     = WC_Helper_Product::create_simple_product();
+		$product_category_id  = $this->factory()->term->create(
+			array(
+				'taxonomy' => 'product_cat',
+				'name'     => 'Included coupon category',
+			)
+		);
+		$excluded_category_id = $this->factory()->term->create(
+			array(
+				'taxonomy' => 'product_cat',
+				'name'     => 'Excluded coupon category',
+			)
+		);
+
+		$this->set_coupon_post_data(
+			array(
+				'discount_type'              => 'fixed_product',
+				'coupon_amount'              => '25',
+				'expiry_date'                => '2026-12-31',
+				'free_shipping'              => 'yes',
+				'individual_use'             => 'yes',
+				'exclude_sale_items'         => 'yes',
+				'minimum_amount'             => '50',
+				'maximum_amount'             => '500',
+				'usage_limit'                => '7',
+				'usage_limit_per_user'       => '2',
+				'limit_usage_to_x_items'     => '3',
+				'customer_email'             => 'customer@example.com, vip@example.com',
+				'product_ids'                => array( (string) $product->get_id() ),
+				'exclude_product_ids'        => array( (string) $excluded_product->get_id() ),
+				'product_categories'         => array( (string) $product_category_id ),
+				'exclude_product_categories' => array( (string) $excluded_category_id ),
+			)
+		);
+
+		WC_Meta_Box_Coupon_Data::save( $post_id, $post );
+
+		$coupon       = new WC_Coupon( $post_id );
+		$date_expires = $coupon->get_date_expires();
+
+		$this->assertSame( 'fixed_product', $coupon->get_discount_type(), 'Expected the posted discount type to persist.' );
+		$this->assertSame( '25', $coupon->get_amount(), 'Expected the posted amount to persist.' );
+		$this->assertInstanceOf( WC_DateTime::class, $date_expires, 'Expected the posted expiry date to persist.' );
+		$this->assertSame( '2026-12-31', $date_expires->format( 'Y-m-d' ), 'Expected the posted expiry date to persist.' );
+		$this->assertTrue( $coupon->get_free_shipping(), 'Expected the posted free shipping flag to persist.' );
+		$this->assertTrue( $coupon->get_individual_use(), 'Expected the posted individual use flag to persist.' );
+		$this->assertTrue( $coupon->get_exclude_sale_items(), 'Expected the posted exclude sale items flag to persist.' );
+		$this->assertSame( '50', $coupon->get_minimum_amount(), 'Expected the posted minimum amount to persist.' );
+		$this->assertSame( '500', $coupon->get_maximum_amount(), 'Expected the posted maximum amount to persist.' );
+		$this->assertSame( 7, $coupon->get_usage_limit(), 'Expected the posted usage limit to persist.' );
+		$this->assertSame( 2, $coupon->get_usage_limit_per_user(), 'Expected the posted per-user usage limit to persist.' );
+		$this->assertSame( 3, $coupon->get_limit_usage_to_x_items(), 'Expected the posted item usage limit to persist.' );
+		$this->assertSame( array( 'customer@example.com', 'vip@example.com' ), $coupon->get_email_restrictions(), 'Expected the posted email restrictions to persist.' );
+		$this->assertSame( array( $product->get_id() ), $coupon->get_product_ids(), 'Expected the posted product restrictions to persist.' );
+		$this->assertSame( array( $excluded_product->get_id() ), $coupon->get_excluded_product_ids(), 'Expected the posted excluded product restrictions to persist.' );
+		$this->assertSame( array( $product_category_id ), $coupon->get_product_categories(), 'Expected the posted category restrictions to persist.' );
+		$this->assertSame( array( $excluded_category_id ), $coupon->get_excluded_product_categories(), 'Expected the posted excluded category restrictions to persist.' );
+	}
+
+	/**
+	 * Create a coupon post for metabox save tests.
+	 *
+	 * @param string $coupon_code Coupon code.
+	 * @return int
+	 */
+	private function create_coupon_post( string $coupon_code ): int {
+		return $this->factory()->post->create(
+			array(
+				'post_title'  => $coupon_code,
+				'post_type'   => 'shop_coupon',
+				'post_status' => 'publish',
+			)
+		);
+	}
+
+	/**
+	 * Get a coupon post for metabox save tests.
+	 *
+	 * @param int $post_id Coupon post ID.
+	 * @return WP_Post
+	 */
+	private function get_coupon_post( int $post_id ): WP_Post {
+		$post = get_post( $post_id );
+
+		$this->assertInstanceOf( WP_Post::class, $post, 'Expected the coupon post fixture to exist.' );
+
+		return $post;
+	}
+
+	/**
+	 * Set posted coupon metabox data.
+	 *
+	 * @param array $overrides Posted data overrides.
+	 */
+	private function set_coupon_post_data( array $overrides = array() ): void {
+		$_POST = array_merge(
+			array(
+				'discount_type'          => 'fixed_cart',
+				'coupon_amount'          => '10',
+				'expiry_date'            => '',
+				'usage_limit'            => '',
+				'usage_limit_per_user'   => '',
+				'limit_usage_to_x_items' => '',
+				'minimum_amount'         => '',
+				'maximum_amount'         => '',
+				'customer_email'         => '',
+			),
+			$overrides
+		);
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Api/Mutations/Coupons/CreateCouponTest.php b/plugins/woocommerce/tests/php/src/Api/Mutations/Coupons/CreateCouponTest.php
index 1c53cc88c15..4a4c043e1b4 100644
--- a/plugins/woocommerce/tests/php/src/Api/Mutations/Coupons/CreateCouponTest.php
+++ b/plugins/woocommerce/tests/php/src/Api/Mutations/Coupons/CreateCouponTest.php
@@ -117,6 +117,37 @@ class CreateCouponTest extends WC_Unit_Test_Case {
 		$this->assertSame( 'percent', $wc_coupon->get_discount_type() );
 	}

+	/**
+	 * @testdox execute() persists fixed discount type enums when provided.
+	 *
+	 * @dataProvider fixed_discount_type_provider
+	 *
+	 * @param DiscountType $discount_type The discount type enum.
+	 * @param string       $expected      The expected stored discount type.
+	 */
+	public function test_execute_persists_fixed_discount_type_enums( DiscountType $discount_type, string $expected ): void {
+		$input                = new CreateCouponInput();
+		$input->code          = 'coupon-' . str_replace( '_', '-', $expected );
+		$input->discount_type = $discount_type;
+
+		$result = $this->sut->execute( $input );
+
+		$wc_coupon = new WC_Coupon( $result->id );
+		$this->assertSame( $expected, $wc_coupon->get_discount_type() );
+	}
+
+	/**
+	 * Data provider for fixed discount types.
+	 *
+	 * @return array<string, array{0: DiscountType, 1: string}>
+	 */
+	public function fixed_discount_type_provider(): array {
+		return array(
+			'fixed cart'    => array( DiscountType::FixedCart, 'fixed_cart' ),
+			'fixed product' => array( DiscountType::FixedProduct, 'fixed_product' ),
+		);
+	}
+
 	/**
 	 * @testdox execute() skips set_discount_type() when discount_type is null.
 	 */