Commit 73c7ae1c925 for woocommerce

commit 73c7ae1c925bf1ca066bcced93526cb344c4da81
Author: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com>
Date:   Mon Oct 5 12:59:19 2026 +0300

    e2e tests: Run e2e specs that share options in core-parallel with test locks (#69326)

diff --git a/plugins/woocommerce/changelog/dev-e2e-playwright-test-locks b/plugins/woocommerce/changelog/dev-e2e-playwright-test-locks
new file mode 100644
index 00000000000..cf3296e4cb9
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-e2e-playwright-test-locks
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+Comment: Run e2e specs that share options in parallel with test locks; no production change.
+
diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json
index b2a518efafd..2f3c8422e96 100644
--- a/plugins/woocommerce/package.json
+++ b/plugins/woocommerce/package.json
@@ -79,8 +79,8 @@
 		"test:e2e:blocks": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test tests/e2e/utils/blocks/*.test.mjs && pnpm playwright test --config=tests/e2e/playwright.config.ts --project=blocks-chromium",
 		"test:e2e:blocks:unified-editor-assets": "pnpm wp-env:e2e run cli wp option update woocommerce_feature_block_editor_unified_assets_enabled yes && pnpm test:e2e:blocks",
 		"test:e2e:blocks:performance": "pnpm playwright test --config=tests/e2e/playwright.performance.config.ts --project=blocks-performance",
-		"test:e2e:email-update-propagation:pr": "pnpm test:e2e:default --project=core-serial --project=core-parallel --grep @pr tests/email-editor/update-propagation",
-		"test:e2e:email-update-propagation:nightly": "pnpm test:e2e:default --project=core-serial --project=core-parallel tests/email-editor/update-propagation",
+		"test:e2e:email-update-propagation:pr": "pnpm test:e2e:default --project=core-parallel --grep @pr tests/email-editor/update-propagation",
+		"test:e2e:email-update-propagation:nightly": "pnpm test:e2e:default --project=core-parallel tests/email-editor/update-propagation",
 		"test:e2e:with-env": "pnpm test:e2e:install && bash ./tests/e2e/run-tests-with-env.sh",
 		"test:e2e:pressable": "pnpm test:e2e:with-env default-pressable",
 		"test:e2e:wpcom": "pnpm test:e2e:with-env default-wpcom",
@@ -357,10 +357,7 @@
 					"testType": "e2e",
 					"usesSharedPluginBuild": true,
 					"command": "test:e2e:core-serial",
-					"shardingArguments": [
-						"--shard=1/2",
-						"--shard=2/2"
-					],
+					"shardingArguments": [],
 					"changes": [
 						"client/admin/config/*.json",
 						"composer.json",
@@ -399,10 +396,7 @@
 					"testType": "e2e",
 					"usesSharedPluginBuild": true,
 					"command": "test:e2e:core-serial",
-					"shardingArguments": [
-						"--shard=1/2",
-						"--shard=2/2"
-					],
+					"shardingArguments": [],
 					"changes": [],
 					"testEnv": {
 						"start": "env:e2e --debug",
@@ -513,10 +507,7 @@
 					"testType": "e2e",
 					"usesSharedPluginBuild": true,
 					"command": "test:e2e:core-serial",
-					"shardingArguments": [
-						"--shard=1/2",
-						"--shard=2/2"
-					],
+					"shardingArguments": [],
 					"changes": [
 						"tests/e2e/{*,!(tests)/**,tests/{*,!(blocks)/**}}"
 					],
@@ -579,10 +570,7 @@
 					"testType": "e2e",
 					"usesSharedPluginBuild": true,
 					"command": "test:e2e:core-serial",
-					"shardingArguments": [
-						"--shard=1/2",
-						"--shard=2/2"
-					],
+					"shardingArguments": [],
 					"changes": [
 						"tests/e2e/{*,!(tests)/**,tests/{*,!(blocks)/**}}"
 					],
@@ -648,10 +636,7 @@
 					"testType": "e2e",
 					"usesSharedPluginBuild": true,
 					"command": "test:e2e:core-serial",
-					"shardingArguments": [
-						"--shard=1/2",
-						"--shard=2/2"
-					],
+					"shardingArguments": [],
 					"changes": [
 						"tests/e2e/{*,!(tests)/**,tests/{*,!(blocks)/**}}"
 					],
diff --git a/plugins/woocommerce/tests/e2e/README.md b/plugins/woocommerce/tests/e2e/README.md
index 2ff2a2d95a3..e13bcf59d97 100644
--- a/plugins/woocommerce/tests/e2e/README.md
+++ b/plugins/woocommerce/tests/e2e/README.md
@@ -177,6 +177,27 @@ Still, here's a few tips to get you started:
 Playwright's Best Practices guide is a good
 read: [Playwright Best Practices](https://playwright.dev/docs/best-practices).

+### Parallel, locked, and serial specs
+
+Core specs run on one shared site, in two Playwright projects: `core-parallel` runs specs in several workers at the same time, and `core-serial` runs them one at a time. Put each spec in one of three groups:
+
+1. **Parallel.** The spec does not change site-wide state. It makes its own products, orders, customers, and pages, and does not change shared settings. This is the default: every spec runs in `core-parallel` unless something else is set.
+2. **Parallel with a lock.** The spec changes a site-wide option, but only specs that change the same option read it. Give all those specs the same [test lock](https://playwright.dev/docs/test-parallel#test-locks) from `locks` in `fixtures/fixtures.ts`. Specs that share a lock never run at the same time, in any worker or project. Add a new entry to `locks` if none fits.
+
+    ```ts
+    import { test, locks } from '../../fixtures/fixtures';
+
+    test.describe( 'Email settings', { lock: locks.EMAIL_FEATURE_FLAGS }, () => {
+    	// ...
+    } );
+    ```
+
+    The core projects do not use `fullyParallel`, so one worker runs a whole spec file. A lock on one test holds for the whole file, including `beforeAll` and `afterAll`.
+
+3. **Serial.** The spec changes a setting that many other specs read, such as tax, store address, or permalinks. A lock cannot help here, because the specs that read the setting do not take the lock. Add the spec to `serialRunSpecs` in `playwright.config.ts`, so it runs in `core-serial`.
+
+Prefer the first group. A spec that changes a setting only for itself can often use its own data instead, for example its own tax class or customer.
+
 ### Gotchas

 - **Never run two wp-env commands at once.** Await each `wpCLI` call (or any other helper that shells out to `wp-env`, such as `getInstalledWordPressVersion`) before starting the next, and keep them out of `Promise.all`. Every wp-env command rewrites `wp-env-cache.json` in the environment's work directory without locking, so two overlapping commands can drop its `runtime` key. From then on every wp-env command, `run` and `destroy` included, fails with "Environment not initialized. Run `wp-env start` first." until the environment starts again, so one overlap breaks every later spec in the CI job. Overlapping a single `wpCLI` call with browser work such as `page.goto` is fine, since that doesn't start wp-env.
diff --git a/plugins/woocommerce/tests/e2e/fixtures/fixtures.ts b/plugins/woocommerce/tests/e2e/fixtures/fixtures.ts
index 12659e7b21c..c7860f1f736 100644
--- a/plugins/woocommerce/tests/e2e/fixtures/fixtures.ts
+++ b/plugins/woocommerce/tests/e2e/fixtures/fixtures.ts
@@ -105,3 +105,21 @@ export const tags = {
 	WP_CORE: '@wp-core',
 	PAYPAL: '@paypal',
 } as const;
+
+/**
+ * Playwright test locks for specs that write the same global options.
+ * Specs that share a lock never run at the same time, in any worker or project.
+ */
+export const locks = {
+	// `woocommerce_analytics_scheduled_import`.
+	ANALYTICS_IMPORT_MODE: 'analytics-import-mode',
+	// `woocommerce_feature_block_email_editor_enabled` and
+	// `woocommerce_feature_email_improvements_enabled`. One file's afterAll
+	// turns a flag off while another file still needs it. Specs that assert
+	// sent emails or the Email settings page also take it, since the flags
+	// change both.
+	EMAIL_FEATURE_FLAGS: 'email-feature-flags',
+	// The Back in Stock feature flag and `woocommerce_customer_stock_notifications_*`.
+	// Concurrent writes of the same value make `e2e-options/update` return 400.
+	STOCK_NOTIFICATIONS: 'stock-notifications',
+} as const;
diff --git a/plugins/woocommerce/tests/e2e/playwright.config.ts b/plugins/woocommerce/tests/e2e/playwright.config.ts
index 9e3a243d3a4..0d6bb7881a3 100644
--- a/plugins/woocommerce/tests/e2e/playwright.config.ts
+++ b/plugins/woocommerce/tests/e2e/playwright.config.ts
@@ -119,26 +119,12 @@ const blocksSetupProject = {
  * Spec folders that must run serially in `core-serial` (they mutate global
  * state or share fixtures). Every other folder under `tests/` runs in
  * `core-parallel` by default, except the other-project folders in `nonCoreSpecs`.
+ *
+ * Specs that only race each other on the same options run in `core-parallel`
+ * with a shared `locks` entry (see `fixtures/fixtures.ts`) instead. A lock
+ * does not help when a spec changes a setting that unlocked specs read.
  */
 const serialRunSpecs = [
-	// Toggles the global `woocommerce_analytics_scheduled_import` option to
-	// exercise the Settings-page scheduled/immediate switch. Kept serial because,
-	// as a standalone file, running it in `core-parallel` would race
-	// `analytics.spec.ts` — which also toggles that option — across workers.
-	// (The other analytics specs are parallel: `analytics` owns its own
-	// import-mode toggles plus the Overview manual-trigger tests in one file, so
-	// those serialise within a single worker; `analytics-overview` only mutates
-	// the admin's own `dashboard_sections` meta. No other parallel spec depends on
-	// the order-import mode, and this serial job never runs concurrently with the
-	// parallel one.)
-	'**/tests/analytics/analytics-settings.spec.ts',
-	// Every spec sets the global `woocommerce_customer_stock_notifications_*`
-	// options in beforeAll (allow_signups / double_opt_in / require_account) and
-	// deletes them in afterAll. Run in parallel the files demand conflicting global
-	// config and race on those options: concurrent identical writes make
-	// `update_option` return false (`e2e-options/update` 400 "Update option FAILED"),
-	// and one file's afterAll strips the signup form mid-test for the others.
-	'**/tests/back-in-stock-notifications/**/*.spec.ts',
 	// Flips the global `woocommerce_default_customer_address` (geolocation) and
 	// `woocommerce_enable_ajax_add_to_cart` settings, which change add-to-cart
 	// behavior for every other worker. (`cart.spec.ts` runs in core-parallel — it
@@ -147,19 +133,6 @@ const serialRunSpecs = [
 	// Activates a custom-gateway test plugin globally, which would surface its extra
 	// payment button on every other worker's checkout.
 	'**/tests/checkout/checkout-shortcode-custom-place-order-button.spec.ts',
-	// Every spec toggles a global email feature flag via `setOption`:
-	// `editor-tracking-selectors`/`settings-email-listing` flip
-	// `woocommerce_feature_block_email_editor_enabled`, while `account-emails`/
-	// `order-emails`/`settings-email` flip `woocommerce_feature_email_improvements_enabled`.
-	// Run in parallel they race on those options — one file's afterAll disables the
-	// editor (or flips improvements) mid-test for the others. Proven not parallel-safe:
-	// an email-only `core-parallel` run failed across all three clusters.
-	'**/tests/email/**/*.spec.ts',
-	// Each spec toggles the global `woocommerce_feature_block_email_editor_enabled`
-	// flag in beforeAll/afterAll; running the files concurrently races on that option
-	// (`e2e-options/update` returns 400 "Update option FAILED") and the first file's
-	// afterAll disables the editor mid-test for the others. Proven not parallel-safe.
-	'**/tests/email-editor/**/*.spec.ts',
 	// Mutate the global onboarding profile/options, site-visibility options and
 	// the active theme.
 	'**/tests/onboarding/**/*.spec.ts',
diff --git a/plugins/woocommerce/tests/e2e/tests/analytics/analytics-settings.spec.ts b/plugins/woocommerce/tests/e2e/tests/analytics/analytics-settings.spec.ts
index a0b6cc00bfb..d4ec292b1cc 100644
--- a/plugins/woocommerce/tests/e2e/tests/analytics/analytics-settings.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/analytics/analytics-settings.spec.ts
@@ -7,7 +7,7 @@ import type { Page } from '@playwright/test';
 /**
  * Internal dependencies
  */
-import { tags } from '../../fixtures/fixtures';
+import { tags, locks } from '../../fixtures/fixtures';
 import { setOption, deleteOption } from '../../utils/options';
 import { ADMIN_STATE_PATH } from '../../playwright.config';

@@ -15,7 +15,10 @@ let page: Page;

 test.describe(
 	'Analytics Settings - Scheduled Import',
-	{ tag: [ tags.PAYMENTS, tags.SERVICES ] },
+	{
+		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
+	},
 	() => {
 		test.use( { storageState: ADMIN_STATE_PATH } );

diff --git a/plugins/woocommerce/tests/e2e/tests/analytics/analytics.spec.ts b/plugins/woocommerce/tests/e2e/tests/analytics/analytics.spec.ts
index 415569b7ffd..4c51a74703f 100644
--- a/plugins/woocommerce/tests/e2e/tests/analytics/analytics.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/analytics/analytics.spec.ts
@@ -10,7 +10,7 @@ import {
 /**
  * Internal dependencies
  */
-import { expect, tags, test as baseTest } from '../../fixtures/fixtures';
+import { expect, tags, test as baseTest, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import { setOption } from '../../utils/options';

@@ -370,6 +370,7 @@ test(
 	'renders the overview performance indicators',
 	{
 		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
 	},
 	async ( { page } ) => {
 		await page.goto(
@@ -405,6 +406,7 @@ test(
 	'downloads revenue report as CSV',
 	{
 		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
 	},
 	async ( { page } ) => {
 		await page.goto(
@@ -437,6 +439,7 @@ test(
 	'use date filter on products report',
 	{
 		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
 	},
 	async ( { page } ) => {
 		// Scope the report to this spec's variable product so cumulative store
@@ -469,6 +472,7 @@ test(
 	'set custom date range on revenue report',
 	{
 		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
 	},
 	async ( { page } ) => {
 		await page.goto(
@@ -513,6 +517,7 @@ test(
 	'scope orders report via advanced product filter',
 	{
 		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
 	},
 	async ( { page } ) => {
 		// Scope the orders report to this spec's data with the product advanced
@@ -542,6 +547,7 @@ test(
 	'use filter by single product on products report',
 	{
 		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
 	},
 	async ( { page } ) => {
 		// Land already scoped to this spec's variable product. There is no
@@ -585,6 +591,7 @@ test(
 	'analytics settings',
 	{
 		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
 	},
 	async ( { page } ) => {
 		await page.goto(
@@ -634,11 +641,13 @@ test(
 	}
 );

-// Overview manual-import trigger. Folded in here so its `scheduled_import`
-// toggle serialises with the data suite's within one core-parallel worker.
+// Overview manual-import trigger.
 test.describe(
 	'manual import trigger',
-	{ tag: [ tags.PAYMENTS, tags.SERVICES ] },
+	{
+		tag: [ tags.PAYMENTS, tags.SERVICES ],
+		lock: locks.ANALYTICS_IMPORT_MODE,
+	},
 	() => {
 		test.beforeEach( async ( { page } ) => {
 			await page.goto(
diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/README.md b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/README.md
index c7ccdfd34a6..4bd3f171def 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/README.md
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/README.md
@@ -87,13 +87,14 @@ respective feature tickets:
   suites, `'no'` for `feature-disabled.spec.ts`), and the enabled suites reset
   it to `'no'` in `afterAll`, through `setOption()` (never `deleteOption()` —
   that skips the `updated_option` hook the feature's teardown relies on). That
-  toggling is safe only because these specs run serially, single worker — see
-  below.
+  toggling is safe only because these specs take test locks — see below.
 - The tests assume the WP Mail Logging plugin is installed and active (it is,
   via the `.wp-env.e2e.json` plugins list).
 - `woocommerce-e2e-test-helper` zeroes
   `woocommerce_customer_stock_notifications_first_batch_delay`, so a restock
   dispatches its batch immediately instead of a minute later. Without it the
   back-in-stock specs time out with no email.
-- Run these under `core-serial` (`--project=core-serial`). They set global
-  options, so `playwright.config.ts` excludes them from `core-parallel`.
+- These specs run in `core-parallel`. They set global options, so each one
+  takes the `locks.STOCK_NOTIFICATIONS` lock (and the enabled suites also
+  `locks.EMAIL_FEATURE_FLAGS`) from `fixtures/fixtures.ts`. Specs that share a
+  lock never run at the same time.
diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/feature-disabled.spec.ts b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/feature-disabled.spec.ts
index d64e2befe36..ae95119e9db 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/feature-disabled.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/feature-disabled.spec.ts
@@ -1,7 +1,7 @@
 /**
  * Internal dependencies
  */
-import { expect, request, tags } from '../../fixtures/fixtures';
+import { expect, request, tags, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import {
 	BIS_FEATURE_OPTION,
@@ -12,7 +12,7 @@ import { setOption } from '../../utils/options';

 test.describe(
 	'Back in Stock Notifications — feature disabled',
-	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ], lock: locks.STOCK_NOTIFICATIONS },
 	() => {
 		test.beforeAll( async ( { baseURL } ) => {
 			await setOption( request, baseURL!, BIS_FEATURE_OPTION, 'no' );
diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/managing-notifications.spec.ts b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/managing-notifications.spec.ts
index 15387b69f88..5b930d42991 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/managing-notifications.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/managing-notifications.spec.ts
@@ -6,7 +6,7 @@ import type { Page } from '@playwright/test';
 /**
  * Internal dependencies
  */
-import { expect, request, tags } from '../../fixtures/fixtures';
+import { expect, request, tags, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import {
 	BIS_FEATURE_OPTION,
@@ -38,7 +38,10 @@ async function submitNotificationEditForm( page: Page ): Promise< void > {

 test.describe(
 	'Back in Stock Notifications — admin management',
-	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+	{
+		tag: [ tags.SKIP_ON_EXTERNAL_ENV ],
+		lock: [ locks.STOCK_NOTIFICATIONS, locks.EMAIL_FEATURE_FLAGS ],
+	},
 	() => {
 		test.use( { storageState: ADMIN_STATE_PATH } );

diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/my-account.spec.ts b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/my-account.spec.ts
index ee4cf2edd4f..ac0a570004e 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/my-account.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/my-account.spec.ts
@@ -6,7 +6,7 @@ import { WC_API_PATH } from '@woocommerce/e2e-utils-playwright';
 /**
  * Internal dependencies
  */
-import { expect, request, tags } from '../../fixtures/fixtures';
+import { expect, request, tags, locks } from '../../fixtures/fixtures';
 import {
 	BIS_FEATURE_OPTION,
 	createOutOfStockProduct,
@@ -60,7 +60,10 @@ async function createTestCustomer( restApi ): Promise< TestCustomer > {

 test.describe(
 	'Back in Stock Notifications — My Account',
-	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+	{
+		tag: [ tags.SKIP_ON_EXTERNAL_ENV ],
+		lock: [ locks.STOCK_NOTIFICATIONS, locks.EMAIL_FEATURE_FLAGS ],
+	},
 	() => {
 		test.beforeAll( async ( { baseURL } ) => {
 			await setOption( request, baseURL!, BIS_FEATURE_OPTION, 'yes' );
diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-confirmations.spec.ts b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-confirmations.spec.ts
index 14aedcdec4c..18c538f317c 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-confirmations.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-confirmations.spec.ts
@@ -1,7 +1,7 @@
 /**
  * Internal dependencies
  */
-import { expect, request, tags } from '../../fixtures/fixtures';
+import { expect, request, tags, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import { customer } from '../../test-data/data';
 import {
@@ -29,7 +29,10 @@ import { setOption } from '../../utils/options';

 test.describe(
 	'Back in Stock Notifications — receiving confirmations',
-	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+	{
+		tag: [ tags.SKIP_ON_EXTERNAL_ENV ],
+		lock: [ locks.STOCK_NOTIFICATIONS, locks.EMAIL_FEATURE_FLAGS ],
+	},
 	() => {
 		test.use( { storageState: ADMIN_STATE_PATH } );

diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-notifications.spec.ts b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-notifications.spec.ts
index 54ee41c009d..248a708e0c5 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-notifications.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/receiving-notifications.spec.ts
@@ -1,7 +1,7 @@
 /**
  * Internal dependencies
  */
-import { expect, request, tags } from '../../fixtures/fixtures';
+import { expect, request, tags, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import { customer } from '../../test-data/data';
 import {
@@ -29,7 +29,10 @@ import { setOption } from '../../utils/options';

 test.describe(
 	'Back in Stock Notifications — receiving back-in-stock emails',
-	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+	{
+		tag: [ tags.SKIP_ON_EXTERNAL_ENV ],
+		lock: [ locks.STOCK_NOTIFICATIONS, locks.EMAIL_FEATURE_FLAGS ],
+	},
 	() => {
 		test.use( { storageState: ADMIN_STATE_PATH } );

diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/signing-up.spec.ts b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/signing-up.spec.ts
index e8ae7b5751a..5173a190c7f 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/signing-up.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/signing-up.spec.ts
@@ -1,7 +1,7 @@
 /**
  * Internal dependencies
  */
-import { expect, request, tags } from '../../fixtures/fixtures';
+import { expect, request, tags, locks } from '../../fixtures/fixtures';
 import { CUSTOMER_STATE_PATH } from '../../playwright.config';
 import { customer } from '../../test-data/data';
 import {
@@ -29,7 +29,10 @@ const trimSlash = ( path: string ) => path.replace( /\/$/, '' );

 test.describe(
 	'Back in Stock Notifications — signing up',
-	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+	{
+		tag: [ tags.SKIP_ON_EXTERNAL_ENV ],
+		lock: [ locks.STOCK_NOTIFICATIONS, locks.EMAIL_FEATURE_FLAGS ],
+	},
 	() => {
 		test.beforeAll( async ( { baseURL } ) => {
 			await setOption( request, baseURL!, BIS_FEATURE_OPTION, 'yes' );
diff --git a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/variations.spec.ts b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/variations.spec.ts
index 302947efbf9..be03f0debc8 100644
--- a/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/variations.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/back-in-stock-notifications/variations.spec.ts
@@ -1,7 +1,7 @@
 /**
  * Internal dependencies
  */
-import { expect, request, tags } from '../../fixtures/fixtures';
+import { expect, request, tags, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import {
 	BIS_EMAIL_ELEMENTS,
@@ -29,7 +29,10 @@ import { setOption } from '../../utils/options';

 test.describe(
 	'Back in Stock Notifications — variable products and variations',
-	{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+	{
+		tag: [ tags.SKIP_ON_EXTERNAL_ENV ],
+		lock: [ locks.STOCK_NOTIFICATIONS, locks.EMAIL_FEATURE_FLAGS ],
+	},
 	() => {
 		test.use( { storageState: ADMIN_STATE_PATH } );

diff --git a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts
index b77e1b73227..0fbb2de7819 100644
--- a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts
@@ -6,7 +6,7 @@ import type { Page } from '@playwright/test';
 /**
  * Internal dependencies
  */
-import { expect, request, test } from '../../fixtures/fixtures';
+import { expect, request, test, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import {
 	deleteEmailPost,
@@ -16,286 +16,302 @@ import {
 import { accessTheEmailEditor } from '../../utils/email';
 import { setOption } from '../../utils/options';

-test.describe( 'WooCommerce Email Editor Core', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
+test.describe(
+	'WooCommerce Email Editor Core',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );

-	const emailPostIds = new Set< string >();
+		const emailPostIds = new Set< string >();

-	const captureEmailPostId = ( page: Page ) => {
-		const postId = new URL( page.url() ).searchParams.get( 'post' );
-		if ( postId && /^[1-9]\d*$/.test( postId ) ) {
-			emailPostIds.add( postId );
-		}
-		return postId;
-	};
+		const captureEmailPostId = ( page: Page ) => {
+			const postId = new URL( page.url() ).searchParams.get( 'post' );
+			if ( postId && /^[1-9]\d*$/.test( postId ) ) {
+				emailPostIds.add( postId );
+			}
+			return postId;
+		};
+
+		const accessAndTrackEmailPost = async ( page: Page ) => {
+			let postId: string | null = null;
+			try {
+				await accessTheEmailEditor( page, 'New order' );
+			} finally {
+				postId = captureEmailPostId( page );
+			}
+			// The editor has to have opened a post, and its id is what afterAll deletes.
+			expect( postId ).toMatch( /^[1-9]\d*$/ );
+		};

-	const accessAndTrackEmailPost = async ( page: Page ) => {
-		let postId: string | null = null;
-		try {
-			await accessTheEmailEditor( page, 'New order' );
-		} finally {
-			postId = captureEmailPostId( page );
-		}
-		// The editor has to have opened a post, and its id is what afterAll deletes.
-		expect( postId ).toMatch( /^[1-9]\d*$/ );
-	};
+		test.beforeAll( async ( { baseURL } ) => {
+			await enableEmailEditor( baseURL );
+		} );

-	test.beforeAll( async ( { baseURL } ) => {
-		await enableEmailEditor( baseURL );
-	} );
+		test.afterAll( async ( { baseURL } ) => {
+			const cleanupErrors: unknown[] = [];

-	test.afterAll( async ( { baseURL } ) => {
-		const cleanupErrors: unknown[] = [];
+			for ( const postId of emailPostIds ) {
+				try {
+					await deleteEmailPost( baseURL, postId );
+				} catch ( error ) {
+					cleanupErrors.push( error );
+				}
+			}

-		for ( const postId of emailPostIds ) {
 			try {
-				await deleteEmailPost( baseURL, postId );
+				await disableEmailEditor( baseURL );
+				const verification = await setOption(
+					request,
+					baseURL,
+					'woocommerce_feature_block_email_editor_enabled',
+					'no'
+				);
+				// The e2e test-helper plugin answers a no-op option write with this
+				// wording, so the match proves disableEmailEditor already wrote `no`.
+				// A failure here is cleanup failing, not the title that ran last.
+				expect( verification ).toContain( 'already set to: no' );
 			} catch ( error ) {
 				cleanupErrors.push( error );
 			}
-		}

-		try {
-			await disableEmailEditor( baseURL );
-			const verification = await setOption(
-				request,
-				baseURL,
-				'woocommerce_feature_block_email_editor_enabled',
-				'no'
-			);
-			// The e2e test-helper plugin answers a no-op option write with this
-			// wording, so the match proves disableEmailEditor already wrote `no`.
-			// A failure here is cleanup failing, not the title that ran last.
-			expect( verification ).toContain( 'already set to: no' );
-		} catch ( error ) {
-			cleanupErrors.push( error );
-		}
+			if ( cleanupErrors.length > 0 ) {
+				throw new AggregateError(
+					cleanupErrors,
+					`Email editor cleanup failed: ${ cleanupErrors
+						.map( ( error ) => String( error ) )
+						.join( '; ' ) }`
+				);
+			}
+		} );

-		if ( cleanupErrors.length > 0 ) {
-			throw new AggregateError(
-				cleanupErrors,
-				`Email editor cleanup failed: ${ cleanupErrors
-					.map( ( error ) => String( error ) )
-					.join( '; ' ) }`
-			);
-		}
-	} );
+		test( 'Can access the email editor', async ( { page } ) => {
+			// Try with the new order email.
+			await accessAndTrackEmailPost( page );
+			// TODO: WP 7.0 compat - WP 7.0 changed the editor sidebar tab role from
+			// tab to button. Simplify when WP 7.0 is the minimum supported version.
+			const emailTab = page
+				.getByRole( 'tab', { name: 'Email' } )
+				.or(
+					page.getByRole( 'button', { name: 'Email', exact: true } )
+				);
+			await emailTab.click();
+			await expect(
+				page.locator( '.editor-post-card-panel__title' )
+			).toContainText( 'New order' );
+			await expect(
+				page
+					.locator( 'iframe[name="editor-canvas"]' )
+					.contentFrame()
+					.getByLabel( 'Block: Heading' )
+			).toContainText( `New order: #[woocommerce/order-number]` );
+		} );

-	test( 'Can access the email editor', async ( { page } ) => {
-		// Try with the new order email.
-		await accessAndTrackEmailPost( page );
-		// TODO: WP 7.0 compat - WP 7.0 changed the editor sidebar tab role from
-		// tab to button. Simplify when WP 7.0 is the minimum supported version.
-		const emailTab = page
-			.getByRole( 'tab', { name: 'Email' } )
-			.or( page.getByRole( 'button', { name: 'Email', exact: true } ) );
-		await emailTab.click();
-		await expect(
-			page.locator( '.editor-post-card-panel__title' )
-		).toContainText( 'New order' );
-		await expect(
-			page
-				.locator( 'iframe[name="editor-canvas"]' )
-				.contentFrame()
-				.getByLabel( 'Block: Heading' )
-		).toContainText( `New order: #[woocommerce/order-number]` );
-	} );
+		test( 'Can preview in new tab', async ( { page } ) => {
+			await accessAndTrackEmailPost( page );
+			await page
+				.getByRole( 'button', { name: 'View', exact: true } )
+				.click();

-	test( 'Can preview in new tab', async ( { page } ) => {
-		await accessAndTrackEmailPost( page );
-		await page.getByRole( 'button', { name: 'View', exact: true } ).click();
+			// WP 7.1 adds a "Responsive styles" toggle to this menu; the email
+			// editor disables it because the email renderer cannot inline
+			// per-viewport styles. Also passes on older WP without the feature.
+			await expect(
+				page.getByRole( 'menuitemcheckbox', {
+					name: 'Responsive styles',
+				} )
+			).toBeHidden();

-		// WP 7.1 adds a "Responsive styles" toggle to this menu; the email
-		// editor disables it because the email renderer cannot inline
-		// per-viewport styles. Also passes on older WP without the feature.
-		await expect(
-			page.getByRole( 'menuitemcheckbox', {
-				name: 'Responsive styles',
-			} )
-		).toBeHidden();
+			const [ newPage ] = await Promise.all( [
+				page.waitForEvent( 'popup' ), // Waits for the new tab to open
+				page
+					.getByRole( 'menuitem', { name: 'Preview in new tab' } )
+					.click(),
+			] );
+			try {
+				await newPage.bringToFront();
+				await newPage.waitForLoadState( 'domcontentloaded' );
+				// eslint-disable-next-line playwright/no-wait-for-selector -- wait for the tab to be loaded.
+				await newPage.waitForSelector( '.wp-block-heading' );
+				await page.close(); // close the original tab.
+				await expect( newPage.locator( 'body' ) ).toContainText(
+					'New order: #12345'
+				);
+			} finally {
+				await newPage.close();
+			}
+		} );

-		const [ newPage ] = await Promise.all( [
-			page.waitForEvent( 'popup' ), // Waits for the new tab to open
-			page
-				.getByRole( 'menuitem', { name: 'Preview in new tab' } )
-				.click(),
-		] );
-		try {
-			await newPage.bringToFront();
-			await newPage.waitForLoadState( 'domcontentloaded' );
-			// eslint-disable-next-line playwright/no-wait-for-selector -- wait for the tab to be loaded.
-			await newPage.waitForSelector( '.wp-block-heading' );
-			await page.close(); // close the original tab.
-			await expect( newPage.locator( 'body' ) ).toContainText(
-				'New order: #12345'
-			);
-		} finally {
-			await newPage.close();
-		}
-	} );
+		test( 'Can send test email', async ( { page } ) => {
+			await accessAndTrackEmailPost( page );
+			await page
+				.getByRole( 'button', { name: 'View', exact: true } )
+				.click();
+			await page
+				.getByRole( 'menuitem', { name: 'Send a test email' } )
+				.click();
+			const sendButton = page.getByRole( 'button', {
+				name: 'Send test email',
+			} );
+			await expect( sendButton ).toBeEnabled();

-	test( 'Can send test email', async ( { page } ) => {
-		await accessAndTrackEmailPost( page );
-		await page.getByRole( 'button', { name: 'View', exact: true } ).click();
-		await page
-			.getByRole( 'menuitem', { name: 'Send a test email' } )
-			.click();
-		const sendButton = page.getByRole( 'button', {
-			name: 'Send test email',
+			// Assert the response, not the error notice: a route path that drifted on
+			// either side answers 404 and still renders the same notice. The test
+			// environment has no mailer, so the real send fails and the route answers 400.
+			const [ response ] = await Promise.all( [
+				page.waitForResponse(
+					( candidate ) =>
+						candidate.request().method() === 'POST' &&
+						decodeURIComponent( candidate.url() ).includes(
+							'/woocommerce-email-editor/v1/send_preview_email'
+						)
+				),
+				sendButton.click(),
+			] );
+			expect( response.status() ).toBe( 400 );
 		} );
-		await expect( sendButton ).toBeEnabled();

-		// Assert the response, not the error notice: a route path that drifted on
-		// either side answers 404 and still renders the same notice. The test
-		// environment has no mailer, so the real send fails and the route answers 400.
-		const [ response ] = await Promise.all( [
-			page.waitForResponse(
-				( candidate ) =>
-					candidate.request().method() === 'POST' &&
-					decodeURIComponent( candidate.url() ).includes(
-						'/woocommerce-email-editor/v1/send_preview_email'
-					)
-			),
-			sendButton.click(),
-		] );
-		expect( response.status() ).toBe( 400 );
-	} );
-
-	test( 'Can edit and save content', async ( { page } ) => {
-		await accessAndTrackEmailPost( page );
-		await expect(
-			page
-				.locator( 'iframe[name="editor-canvas"]' )
-				.contentFrame()
-				.getByText( 'You’ve received a new' )
-		).toBeVisible();
+		test( 'Can edit and save content', async ( { page } ) => {
+			await accessAndTrackEmailPost( page );
+			await expect(
+				page
+					.locator( 'iframe[name="editor-canvas"]' )
+					.contentFrame()
+					.getByText( 'You’ve received a new' )
+			).toBeVisible();

-		// Note: fill with a single line of text. On WP 7.1 a value containing a
-		// newline splits the paragraph but the edit never registers as a
-		// dirtying change, so the Save button stays disabled. A single-line
-		// edit commits normally and still exercises the edit → save → preview
-		// flow this test covers.
-		const editableParagraph = page
-			.locator( 'iframe[name="editor-canvas"]' )
-			.contentFrame()
-			.getByText( 'You’ve received a new' );
-		await editableParagraph.click();
-		await expect( editableParagraph ).toBeEditable();
-		await editableParagraph.fill( 'Hello world from Woo plugin' );
-		await expect(
-			page
-				.locator( 'iframe[name="editor-canvas"]' )
-				.contentFrame()
-				.getByText( 'Hello world from Woo plugin' )
-		).toBeVisible();
-		await expect(
-			page.getByRole( 'button', { name: 'Save', exact: true } )
-		).toBeVisible();
-		await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
-		// Assert the snackbar, not the a11y live region. @wordpress/a11y's speak()
-		// clears every .a11y-speak-region before writing, so announcements replace
-		// rather than accumulate: the region holds exactly one message, and any
-		// later announcement during the save wipes this one. The snackbar carries
-		// the same rewritten notice text and stays on screen.
-		await expect(
-			page
-				.locator( '.components-snackbar' )
-				.filter( { hasText: 'Email saved.' } )
-		).toBeVisible();
-		await expect(
-			page
+			// Note: fill with a single line of text. On WP 7.1 a value containing a
+			// newline splits the paragraph but the edit never registers as a
+			// dirtying change, so the Save button stays disabled. A single-line
+			// edit commits normally and still exercises the edit → save → preview
+			// flow this test covers.
+			const editableParagraph = page
 				.locator( 'iframe[name="editor-canvas"]' )
 				.contentFrame()
-				.getByText( 'Hello world from Woo' )
-		).toBeVisible();
-		await expect(
-			page.getByRole( 'button', { name: 'View', exact: true } )
-		).toBeVisible();
-		await page.getByRole( 'button', { name: 'View', exact: true } ).click();
-		const page1Promise = page.waitForEvent( 'popup' );
-		await page
-			.getByRole( 'menuitem', { name: 'Preview in new tab' } )
-			.click();
-		const page1 = await page1Promise;
-		try {
-			await page1.bringToFront();
-			await page1.waitForLoadState( 'domcontentloaded' );
-			// Wait for the generated preview to replace the loading screen.
+				.getByText( 'You’ve received a new' );
+			await editableParagraph.click();
+			await expect( editableParagraph ).toBeEditable();
+			await editableParagraph.fill( 'Hello world from Woo plugin' );
+			await expect(
+				page
+					.locator( 'iframe[name="editor-canvas"]' )
+					.contentFrame()
+					.getByText( 'Hello world from Woo plugin' )
+			).toBeVisible();
+			await expect(
+				page.getByRole( 'button', { name: 'Save', exact: true } )
+			).toBeVisible();
+			await page
+				.getByRole( 'button', { name: 'Save', exact: true } )
+				.click();
+			// Assert the snackbar, not the a11y live region. @wordpress/a11y's speak()
+			// clears every .a11y-speak-region before writing, so announcements replace
+			// rather than accumulate: the region holds exactly one message, and any
+			// later announcement during the save wipes this one. The snackbar carries
+			// the same rewritten notice text and stays on screen.
+			await expect(
+				page
+					.locator( '.components-snackbar' )
+					.filter( { hasText: 'Email saved.' } )
+			).toBeVisible();
+			await expect(
+				page
+					.locator( 'iframe[name="editor-canvas"]' )
+					.contentFrame()
+					.getByText( 'Hello world from Woo' )
+			).toBeVisible();
 			await expect(
-				page1.locator( '.wp-block-heading' ).first()
+				page.getByRole( 'button', { name: 'View', exact: true } )
 			).toBeVisible();
-			await expect( page1.locator( 'body' ) ).toContainText(
-				'Hello world from Woo plugin'
-			);
-		} finally {
-			await page1.close();
-		}
-	} );
+			await page
+				.getByRole( 'button', { name: 'View', exact: true } )
+				.click();
+			const page1Promise = page.waitForEvent( 'popup' );
+			await page
+				.getByRole( 'menuitem', { name: 'Preview in new tab' } )
+				.click();
+			const page1 = await page1Promise;
+			try {
+				await page1.bringToFront();
+				await page1.waitForLoadState( 'domcontentloaded' );
+				// Wait for the generated preview to replace the loading screen.
+				await expect(
+					page1.locator( '.wp-block-heading' ).first()
+				).toBeVisible();
+				await expect( page1.locator( 'body' ) ).toContainText(
+					'Hello world from Woo plugin'
+				);
+			} finally {
+				await page1.close();
+			}
+		} );

-	test( 'Can use personalization tags in the Button block', async ( {
-		page,
-		baseURL,
-	} ) => {
-		// Redundant with beforeAll, which owns enablement for this suite and runs
-		// again in a restarted worker. Kept so that merging this title forward
-		// stays a merge: it is harmless, and removing it would be a behavior
-		// change in a title this batch does not otherwise touch.
-		await enableEmailEditor( baseURL );
-		await accessAndTrackEmailPost( page );
-		const canvas = page
-			.locator( 'iframe[name="editor-canvas"]' )
-			.contentFrame();
+		test( 'Can use personalization tags in the Button block', async ( {
+			page,
+			baseURL,
+		} ) => {
+			// Redundant with beforeAll, which owns enablement for this suite and runs
+			// again in a restarted worker. Kept so that merging this title forward
+			// stays a merge: it is harmless, and removing it would be a behavior
+			// change in a title this batch does not otherwise touch.
+			await enableEmailEditor( baseURL );
+			await accessAndTrackEmailPost( page );
+			const canvas = page
+				.locator( 'iframe[name="editor-canvas"]' )
+				.contentFrame();

-		// Set the insertion point inside the email content. The heading is not
-		// touched by the other tests in this suite, unlike the first paragraph.
-		await canvas.getByLabel( 'Block: Heading' ).click();
+			// Set the insertion point inside the email content. The heading is not
+			// touched by the other tests in this suite, unlike the first paragraph.
+			await canvas.getByLabel( 'Block: Heading' ).click();

-		// Insert a Button block.
-		await page.getByRole( 'button', { name: 'Block Inserter' } ).click();
-		await page
-			.getByRole( 'searchbox', { name: 'Search' } )
-			.fill( 'Buttons' );
-		await page
-			.getByRole( 'option', { name: 'Buttons', exact: true } )
-			.click();
+			// Insert a Button block.
+			await page
+				.getByRole( 'button', { name: 'Block Inserter' } )
+				.click();
+			await page
+				.getByRole( 'searchbox', { name: 'Search' } )
+				.fill( 'Buttons' );
+			await page
+				.getByRole( 'option', { name: 'Buttons', exact: true } )
+				.click();

-		// Focus the button text. The Personalization Tags toolbar button must be
-		// available there — the button text field uses
-		// `withoutInteractiveFormatting`, which hides the button when the
-		// personalization tags format is registered as interactive.
-		await canvas
-			.getByRole( 'document', { name: 'Block: Button', exact: true } )
-			.click();
-		const personalizationTagsButton = page
-			.getByRole( 'toolbar', { name: 'Block tools' } )
-			.getByRole( 'button', { name: 'Personalization Tags' } );
-		await expect( personalizationTagsButton ).toBeVisible();
+			// Focus the button text. The Personalization Tags toolbar button must be
+			// available there — the button text field uses
+			// `withoutInteractiveFormatting`, which hides the button when the
+			// personalization tags format is registered as interactive.
+			await canvas
+				.getByRole( 'document', { name: 'Block: Button', exact: true } )
+				.click();
+			const personalizationTagsButton = page
+				.getByRole( 'toolbar', { name: 'Block tools' } )
+				.getByRole( 'button', { name: 'Personalization Tags' } );
+			await expect( personalizationTagsButton ).toBeVisible();

-		// Set a URL personalization tag as the button link.
-		await personalizationTagsButton.click();
-		await expect(
-			page.getByRole( 'heading', { name: 'Personalization Tags' } )
-		).toBeVisible();
-		await page
-			.locator(
-				'.woocommerce-personalization-tags-modal-category-group-item'
-			)
-			.filter( { hasText: 'Payment URL' } )
-			.getByRole( 'button', { name: 'Set as URL' } )
-			.click();
-		await expect(
-			page.getByRole( 'heading', { name: 'Personalization Tags' } )
-		).toBeHidden();
+			// Set a URL personalization tag as the button link.
+			await personalizationTagsButton.click();
+			await expect(
+				page.getByRole( 'heading', { name: 'Personalization Tags' } )
+			).toBeVisible();
+			await page
+				.locator(
+					'.woocommerce-personalization-tags-modal-category-group-item'
+				)
+				.filter( { hasText: 'Payment URL' } )
+				.getByRole( 'button', { name: 'Set as URL' } )
+				.click();
+			await expect(
+				page.getByRole( 'heading', { name: 'Personalization Tags' } )
+			).toBeHidden();

-		await expect
-			.poll( () =>
-				page.evaluate(
-					() =>
-						window.wp.data
-							.select( 'core/block-editor' )
-							.getSelectedBlock()?.attributes?.url
+			await expect
+				.poll( () =>
+					page.evaluate(
+						() =>
+							window.wp.data
+								.select( 'core/block-editor' )
+								.getSelectedBlock()?.attributes?.url
+					)
 				)
-			)
-			.toBe( '[woocommerce/order-payment-url]' );
-	} );
-} );
+				.toBe( '[woocommerce/order-payment-url]' );
+		} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-reset-template.spec.ts b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-reset-template.spec.ts
index e219168dae0..ab7328017db 100644
--- a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-reset-template.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-reset-template.spec.ts
@@ -13,6 +13,7 @@ import {
 	resetWCTransactionalEmail,
 } from './helpers/enable-email-editor-feature';
 import { accessTheEmailEditor } from '../../utils/email';
+import { locks } from '../../fixtures/fixtures';

 /**
  * Helper function to switch from email editor to template editing mode.
@@ -53,109 +54,115 @@ async function switchToTemplateEditingMode( page: Page ) {
 	).toBeVisible();
 }

-test.describe( 'WooCommerce Email Editor Reset Template', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
-
-	test.beforeAll( async ( { baseURL } ) => {
-		await enableEmailEditor( baseURL );
-	} );
-
-	test.afterAll( async ( { baseURL } ) => {
-		await resetWCTransactionalEmail( baseURL, 'customer_note' );
-		await disableEmailEditor( baseURL );
-	} );
-
-	test( 'Can reset a customized email template to default', async ( {
-		page,
-	} ) => {
-		// Access the email editor
-		await accessTheEmailEditor( page, 'Customer note' );
-
-		// Wait for the editor to load
-		await expect(
-			page.locator( '#woocommerce-email-editor' )
-		).toBeVisible();
-
-		// Switch to template editing mode
-		await switchToTemplateEditingMode( page );
-
-		// Make a customization - edit an existing paragraph block
-		const uniqueText = `CUSTOM TEXT ${ Date.now() }`;
-		const editorFrame = page
-			.frameLocator( 'iframe[name="editor-canvas"]' )
-			.first();
-
-		// Update the footer text and add a custom text
-		await page
-			.locator( 'iframe[name="editor-canvas"]' )
-			.contentFrame()
-			.getByText( 'All Rights Reserved' )
-			.fill( `All Rights Reserved. Custom text: ${ uniqueText }` );
-
-		// Save the customization
-		await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
-
-		// Wait for the save to complete
-		// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for content to be saved
-		await page.waitForTimeout( 1000 );
-
-		// Verify the customization is present
-		await expect( editorFrame.getByText( uniqueText ) ).toBeVisible();
-
-		// Switch to Template tab to access the Actions button
-		await page.getByRole( 'tab', { name: 'Template' } ).click();
-
-		// Wait for Actions button to be enabled
-		await expect(
-			page.getByRole( 'button', { name: 'Actions' } )
-		).toBeEnabled();
-
-		// Open the actions dropdown (three dots menu)
-		await page.getByRole( 'button', { name: 'Actions' } ).click();
-
-		// Verify the Reset action is present
-		await expect(
-			page.getByRole( 'menuitem', { name: 'Reset' } )
-		).toBeVisible();
-
-		// Click the Reset action
-		await page.getByRole( 'menuitem', { name: 'Reset' } ).click();
-
-		// Verify the reset confirmation modal appears
-		await expect(
-			page.getByText( /Are you sure you want to reset.*to default\?/ )
-		).toBeVisible();
-
-		// Verify Cancel button exists
-		await expect(
-			page.getByRole( 'button', { name: 'Cancel' } )
-		).toBeVisible();
-
-		// Click the Reset button in the modal
-		await page
-			.getByRole( 'button', { name: 'Reset', exact: true } )
-			.click();
-
-		// Wait for the reset operation to complete
-		// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for reset to complete
-		await page.waitForTimeout( 2000 );
-
-		// Verify the custom content is gone (template is reset)
-		const editorFrameAfterReset = page
-			.frameLocator( 'iframe[name="editor-canvas"]' )
-			.first();
-		await expect(
-			editorFrameAfterReset.getByText( uniqueText )
-		).toBeHidden();
-
-		// Verify Save button is disabled (no changes to save)
-		await expect(
-			page.getByRole( 'button', { name: 'Save', exact: true } )
-		).toBeDisabled();
-
-		// Verify Actions button is disabled (template is no longer custom)
-		await expect(
-			page.getByRole( 'button', { name: 'Actions' } )
-		).toBeDisabled();
-	} );
-} );
+test.describe(
+	'WooCommerce Email Editor Reset Template',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );
+
+		test.beforeAll( async ( { baseURL } ) => {
+			await enableEmailEditor( baseURL );
+		} );
+
+		test.afterAll( async ( { baseURL } ) => {
+			await resetWCTransactionalEmail( baseURL, 'customer_note' );
+			await disableEmailEditor( baseURL );
+		} );
+
+		test( 'Can reset a customized email template to default', async ( {
+			page,
+		} ) => {
+			// Access the email editor
+			await accessTheEmailEditor( page, 'Customer note' );
+
+			// Wait for the editor to load
+			await expect(
+				page.locator( '#woocommerce-email-editor' )
+			).toBeVisible();
+
+			// Switch to template editing mode
+			await switchToTemplateEditingMode( page );
+
+			// Make a customization - edit an existing paragraph block
+			const uniqueText = `CUSTOM TEXT ${ Date.now() }`;
+			const editorFrame = page
+				.frameLocator( 'iframe[name="editor-canvas"]' )
+				.first();
+
+			// Update the footer text and add a custom text
+			await page
+				.locator( 'iframe[name="editor-canvas"]' )
+				.contentFrame()
+				.getByText( 'All Rights Reserved' )
+				.fill( `All Rights Reserved. Custom text: ${ uniqueText }` );
+
+			// Save the customization
+			await page
+				.getByRole( 'button', { name: 'Save', exact: true } )
+				.click();
+
+			// Wait for the save to complete
+			// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for content to be saved
+			await page.waitForTimeout( 1000 );
+
+			// Verify the customization is present
+			await expect( editorFrame.getByText( uniqueText ) ).toBeVisible();
+
+			// Switch to Template tab to access the Actions button
+			await page.getByRole( 'tab', { name: 'Template' } ).click();
+
+			// Wait for Actions button to be enabled
+			await expect(
+				page.getByRole( 'button', { name: 'Actions' } )
+			).toBeEnabled();
+
+			// Open the actions dropdown (three dots menu)
+			await page.getByRole( 'button', { name: 'Actions' } ).click();
+
+			// Verify the Reset action is present
+			await expect(
+				page.getByRole( 'menuitem', { name: 'Reset' } )
+			).toBeVisible();
+
+			// Click the Reset action
+			await page.getByRole( 'menuitem', { name: 'Reset' } ).click();
+
+			// Verify the reset confirmation modal appears
+			await expect(
+				page.getByText( /Are you sure you want to reset.*to default\?/ )
+			).toBeVisible();
+
+			// Verify Cancel button exists
+			await expect(
+				page.getByRole( 'button', { name: 'Cancel' } )
+			).toBeVisible();
+
+			// Click the Reset button in the modal
+			await page
+				.getByRole( 'button', { name: 'Reset', exact: true } )
+				.click();
+
+			// Wait for the reset operation to complete
+			// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for reset to complete
+			await page.waitForTimeout( 2000 );
+
+			// Verify the custom content is gone (template is reset)
+			const editorFrameAfterReset = page
+				.frameLocator( 'iframe[name="editor-canvas"]' )
+				.first();
+			await expect(
+				editorFrameAfterReset.getByText( uniqueText )
+			).toBeHidden();
+
+			// Verify Save button is disabled (no changes to save)
+			await expect(
+				page.getByRole( 'button', { name: 'Save', exact: true } )
+			).toBeDisabled();
+
+			// Verify Actions button is disabled (template is no longer custom)
+			await expect(
+				page.getByRole( 'button', { name: 'Actions' } )
+			).toBeDisabled();
+		} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-settings-sidebar.spec.ts b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-settings-sidebar.spec.ts
index 8232197a41b..9504362a835 100644
--- a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-settings-sidebar.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-settings-sidebar.spec.ts
@@ -16,228 +16,259 @@ import {
 	accessTheEmailEditor,
 	ensureEmailEditorSettingsPanelIsOpened,
 } from '../../utils/email';
+import { locks } from '../../fixtures/fixtures';

-test.describe( 'WooCommerce Email Editor Settings Sidebar Integration', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
-
-	test.beforeAll( async ( { baseURL } ) => {
-		await enableEmailEditor( baseURL );
-	} );
-
-	test.afterAll( async ( { baseURL } ) => {
-		await resetWCTransactionalEmail( baseURL, 'customer_note' );
-		await resetWCTransactionalEmail( baseURL, 'new_order' );
-		await disableEmailEditor( baseURL );
-	} );
-
-	test( 'Can update email status', async ( { page } ) => {
-		await accessTheEmailEditor( page, 'Customer note' );
-
-		await page
-			.getByLabel( 'Email' )
-			.getByRole( 'button', { name: 'Settings' } )
-			.click();
-		await ensureEmailEditorSettingsPanelIsOpened( page );
-		await expect(
-			page.locator( '.editor-post-status__toggle' )
-		).toContainText( 'Active' );
-		await page.locator( '.editor-post-status__toggle' ).click();
-		await page.getByRole( 'radio', { name: 'Inactive' } ).click();
-		await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
-		await expect(
-			page.locator( '.editor-post-status__toggle' )
-		).toContainText( 'Inactive' );
-		// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for content to be saved and updated.
-		await page.waitForTimeout( 1000 );
-		await page.reload();
-		await expect(
-			page.locator( '.editor-post-status__toggle' )
-		).toContainText( 'Inactive' );
-		// reset the email status.
-		await page.locator( '.editor-post-status__toggle' ).click();
-		await page
-			.getByRole( 'radio', { name: 'Active', exact: true } )
-			.click();
-		await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
-		await expect(
-			page.locator( '.editor-post-status__toggle' )
-		).toContainText( 'Active' );
-	} );
-
-	test( 'Can update email subject and preview text', async ( { page } ) => {
-		await accessTheEmailEditor( page, 'Customer note' );
-
-		await page
-			.getByLabel( 'Email' )
-			.getByRole( 'button', { name: 'Settings' } )
-			.click();
-		await ensureEmailEditorSettingsPanelIsOpened( page );
-
-		const randomNum = new Date().getTime().toString();
-		const subject = `hello subject ${ randomNum } `;
-		const preheader = `hello preheader ${ randomNum } `;
-
-		// fill the subject.
-		await expect(
-			page
-				.locator( '.woocommerce-settings-panel-subject-text span' )
-				.filter( { hasText: 'Subject' } )
+test.describe(
+	'WooCommerce Email Editor Settings Sidebar Integration',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );
+
+		test.beforeAll( async ( { baseURL } ) => {
+			await enableEmailEditor( baseURL );
+		} );
+
+		test.afterAll( async ( { baseURL } ) => {
+			await resetWCTransactionalEmail( baseURL, 'customer_note' );
+			await resetWCTransactionalEmail( baseURL, 'new_order' );
+			await disableEmailEditor( baseURL );
+		} );
+
+		test( 'Can update email status', async ( { page } ) => {
+			await accessTheEmailEditor( page, 'Customer note' );
+
+			await page
+				.getByLabel( 'Email' )
+				.getByRole( 'button', { name: 'Settings' } )
+				.click();
+			await ensureEmailEditorSettingsPanelIsOpened( page );
+			await expect(
+				page.locator( '.editor-post-status__toggle' )
+			).toContainText( 'Active' );
+			await page.locator( '.editor-post-status__toggle' ).click();
+			await page.getByRole( 'radio', { name: 'Inactive' } ).click();
+			await page
+				.getByRole( 'button', { name: 'Save', exact: true } )
+				.click();
+			await expect(
+				page.locator( '.editor-post-status__toggle' )
+			).toContainText( 'Inactive' );
+			// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for content to be saved and updated.
+			await page.waitForTimeout( 1000 );
+			await page.reload();
+			await expect(
+				page.locator( '.editor-post-status__toggle' )
+			).toContainText( 'Inactive' );
+			// reset the email status.
+			await page.locator( '.editor-post-status__toggle' ).click();
+			await page
+				.getByRole( 'radio', { name: 'Active', exact: true } )
+				.click();
+			await page
+				.getByRole( 'button', { name: 'Save', exact: true } )
+				.click();
+			await expect(
+				page.locator( '.editor-post-status__toggle' )
+			).toContainText( 'Active' );
+		} );
+
+		test( 'Can update email subject and preview text', async ( {
+			page,
+		} ) => {
+			await accessTheEmailEditor( page, 'Customer note' );
+
+			await page
+				.getByLabel( 'Email' )
+				.getByRole( 'button', { name: 'Settings' } )
+				.click();
+			await ensureEmailEditorSettingsPanelIsOpened( page );
+
+			const randomNum = new Date().getTime().toString();
+			const subject = `hello subject ${ randomNum } `;
+			const preheader = `hello preheader ${ randomNum } `;
+
+			// fill the subject.
+			await expect(
+				page
+					.locator( '.woocommerce-settings-panel-subject-text span' )
+					.filter( { hasText: 'Subject' } )
+					.first()
+			).toBeVisible();
+			await page
+				.locator( '[data-automation-id="email_subject"]' )
+				.fill( subject );
+			await page
+				.locator( '[data-automation-id="email_subject"]' )
+				.click(); // put the cursor at the end of the subject.
+			await page
+				.locator(
+					'.woocommerce-settings-panel-subject-text button[title="Personalization Tags"]'
+				)
 				.first()
-		).toBeVisible();
-		await page
-			.locator( '[data-automation-id="email_subject"]' )
-			.fill( subject );
-		await page.locator( '[data-automation-id="email_subject"]' ).click(); // put the cursor at the end of the subject.
-		await page
-			.locator(
-				'.woocommerce-settings-panel-subject-text button[title="Personalization Tags"]'
-			)
-			.first()
-			.click(); // open personalization tags modal.
-		await expect(
-			page.getByRole( 'heading', { name: 'Personalization Tags' } )
-		).toBeVisible();
-		await expect( page.getByLabel( 'Scrollable section' ) ).toContainText(
-			'Customer Email'
-		);
-		await page
-			.locator( 'div' )
-			.filter( {
-				hasText:
-					/^Customer Email\[woocommerce\/customer-email\]Insert$/,
-			} )
-			.getByRole( 'button' )
-			.click();
-
-		// fill the preheader.
-		await page
-			.locator( '[data-automation-id="email_preheader"]' )
-			.fill( preheader );
-		await page.locator( '[data-automation-id="email_preheader"]' ).click(); // put the cursor at the end of the preheader.
-		await page
-			.locator(
-				'.woocommerce-settings-panel-preheader-text button[title="Personalization Tags"]'
-			)
-			.first()
-			.click(); // open personalization tags modal.
-		await expect(
-			page.getByRole( 'heading', { name: 'Personalization Tags' } )
-		).toBeVisible();
-		await expect( page.getByText( 'Customer First Name' ) ).toBeVisible();
-		await page.getByText( 'Customer First Name[' ).click();
-		await page
-			.locator( 'div' )
-			.filter( {
-				hasText:
-					/^Customer First Name\[woocommerce\/customer-first-name\]Insert$/,
-			} )
-			.getByRole( 'button' )
-			.click();
-		await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
-		await expect(
-			page.locator( '[data-automation-id="email_subject"]' )
-		).toContainText( `${ subject } [woocommerce/customer-email]` );
-		await expect(
-			page.locator( '[data-automation-id="email_preheader"]' )
-		).toContainText( `${ preheader } [woocommerce/customer-first-name]` );
-	} );
-
-	test( 'Can update email recipients', async ( { page } ) => {
-		await accessTheEmailEditor( page, 'New order' );
-		await page
-			.getByLabel( 'Email' )
-			.getByRole( 'button', { name: 'Settings' } )
-			.click();
-		await ensureEmailEditorSettingsPanelIsOpened( page );
-		await expect(
-			page.locator( '[for="woocommerce-email-editor-recipients"]' )
-		).toBeVisible();
-		await expect( page.getByTestId( 'email_recipient' ) ).toBeVisible(); // form is filled with the default value.
-
-		const randomNum = new Date().getTime().toString();
-		const ccEmail = `cc-mail-${ randomNum }@example.com`;
-		const bccEmail = `bcc-mail-${ randomNum }@example.com`;
-
-		// cc.
-		await expect( page.getByText( 'Add CC' ) ).toBeVisible();
-		await page.getByRole( 'checkbox', { name: 'Add CC' } ).check();
-		await expect(
-			page.getByText(
-				'Add recipients who will receive a copy of the email.'
-			)
-		).toBeVisible();
-		await expect( page.getByTestId( 'email_cc' ) ).toBeVisible();
-		await page.getByTestId( 'email_cc' ).click();
-		await page.getByTestId( 'email_cc' ).fill( ccEmail );
-		// bcc.
-		await expect( page.getByText( 'Add BCC' ) ).toBeVisible();
-		await page.getByRole( 'checkbox', { name: 'Add BCC' } ).check();
-		await expect(
-			page.getByText(
-				'Add recipients who will receive a hidden copy of the email.'
-			)
-		).toBeVisible();
-		await page.getByTestId( 'email_bcc' ).click();
-		await page.getByTestId( 'email_bcc' ).fill( bccEmail );
-		await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
-
-		// assert the values.
-		await expect( page.getByTestId( 'email_cc' ) ).toHaveValue( ccEmail );
-		await expect( page.getByTestId( 'email_bcc' ) ).toHaveValue( bccEmail );
-	} );
-
-	test( 'Does not show the core Content block list in the Email tab', async ( {
-		page,
-	} ) => {
-		await accessTheEmailEditor( page, 'Customer note' );
-
-		const emailTab = page.getByLabel( 'Email' );
-		await expect(
-			emailTab.getByRole( 'button', { name: 'Settings' } )
-		).toBeVisible();
-		await expect(
-			emailTab.getByRole( 'button', { name: 'Content', exact: true } )
-		).toHaveCount( 0 );
-
-		// Asserting the item exists keeps this test from passing when core never
-		// rendered the panel. If core stops rendering it, remove the CSS rule.
-		const quickNavItem = emailTab
-			.locator( '.components-button .block-editor-block-icon' )
-			.first();
-		await expect( quickNavItem ).toBeAttached();
-		await expect( quickNavItem ).toBeHidden();
-	} );
-
-	test( 'Shows the Content list in the Block tab of a content-only block', async ( {
-		page,
-	} ) => {
-		await accessTheEmailEditor( page, 'Customer note' );
-		await expect( page.getByLabel( 'Email' ) ).toBeVisible();
-
-		await page.evaluate( () => {
-			const { createBlock } = window.wp.blocks;
-			const { select, dispatch } = window.wp.data;
-			const group = createBlock(
-				'core/group',
-				{ templateLock: 'contentOnly' },
-				[ createBlock( 'core/paragraph', { content: 'Nested text' } ) ]
+				.click(); // open personalization tags modal.
+			await expect(
+				page.getByRole( 'heading', { name: 'Personalization Tags' } )
+			).toBeVisible();
+			await expect(
+				page.getByLabel( 'Scrollable section' )
+			).toContainText( 'Customer Email' );
+			await page
+				.locator( 'div' )
+				.filter( {
+					hasText:
+						/^Customer Email\[woocommerce\/customer-email\]Insert$/,
+				} )
+				.getByRole( 'button' )
+				.click();
+
+			// fill the preheader.
+			await page
+				.locator( '[data-automation-id="email_preheader"]' )
+				.fill( preheader );
+			await page
+				.locator( '[data-automation-id="email_preheader"]' )
+				.click(); // put the cursor at the end of the preheader.
+			await page
+				.locator(
+					'.woocommerce-settings-panel-preheader-text button[title="Personalization Tags"]'
+				)
+				.first()
+				.click(); // open personalization tags modal.
+			await expect(
+				page.getByRole( 'heading', { name: 'Personalization Tags' } )
+			).toBeVisible();
+			await expect(
+				page.getByText( 'Customer First Name' )
+			).toBeVisible();
+			await page.getByText( 'Customer First Name[' ).click();
+			await page
+				.locator( 'div' )
+				.filter( {
+					hasText:
+						/^Customer First Name\[woocommerce\/customer-first-name\]Insert$/,
+				} )
+				.getByRole( 'button' )
+				.click();
+			await page
+				.getByRole( 'button', { name: 'Save', exact: true } )
+				.click();
+			await expect(
+				page.locator( '[data-automation-id="email_subject"]' )
+			).toContainText( `${ subject } [woocommerce/customer-email]` );
+			await expect(
+				page.locator( '[data-automation-id="email_preheader"]' )
+			).toContainText(
+				`${ preheader } [woocommerce/customer-first-name]`
 			);
-			const [ postContent ] =
-				select( 'core/block-editor' ).getBlocksByName(
-					'core/post-content'
-				);
-			dispatch( 'core/block-editor' ).insertBlocks(
-				group,
-				0,
-				postContent
+		} );
+
+		test( 'Can update email recipients', async ( { page } ) => {
+			await accessTheEmailEditor( page, 'New order' );
+			await page
+				.getByLabel( 'Email' )
+				.getByRole( 'button', { name: 'Settings' } )
+				.click();
+			await ensureEmailEditorSettingsPanelIsOpened( page );
+			await expect(
+				page.locator( '[for="woocommerce-email-editor-recipients"]' )
+			).toBeVisible();
+			await expect( page.getByTestId( 'email_recipient' ) ).toBeVisible(); // form is filled with the default value.
+
+			const randomNum = new Date().getTime().toString();
+			const ccEmail = `cc-mail-${ randomNum }@example.com`;
+			const bccEmail = `bcc-mail-${ randomNum }@example.com`;
+
+			// cc.
+			await expect( page.getByText( 'Add CC' ) ).toBeVisible();
+			await page.getByRole( 'checkbox', { name: 'Add CC' } ).check();
+			await expect(
+				page.getByText(
+					'Add recipients who will receive a copy of the email.'
+				)
+			).toBeVisible();
+			await expect( page.getByTestId( 'email_cc' ) ).toBeVisible();
+			await page.getByTestId( 'email_cc' ).click();
+			await page.getByTestId( 'email_cc' ).fill( ccEmail );
+			// bcc.
+			await expect( page.getByText( 'Add BCC' ) ).toBeVisible();
+			await page.getByRole( 'checkbox', { name: 'Add BCC' } ).check();
+			await expect(
+				page.getByText(
+					'Add recipients who will receive a hidden copy of the email.'
+				)
+			).toBeVisible();
+			await page.getByTestId( 'email_bcc' ).click();
+			await page.getByTestId( 'email_bcc' ).fill( bccEmail );
+			await page
+				.getByRole( 'button', { name: 'Save', exact: true } )
+				.click();
+
+			// assert the values.
+			await expect( page.getByTestId( 'email_cc' ) ).toHaveValue(
+				ccEmail
 			);
-			dispatch( 'core/block-editor' ).selectBlock( group.clientId );
+			await expect( page.getByTestId( 'email_bcc' ) ).toHaveValue(
+				bccEmail
+			);
+		} );
+
+		test( 'Does not show the core Content block list in the Email tab', async ( {
+			page,
+		} ) => {
+			await accessTheEmailEditor( page, 'Customer note' );
+
+			const emailTab = page.getByLabel( 'Email' );
+			await expect(
+				emailTab.getByRole( 'button', { name: 'Settings' } )
+			).toBeVisible();
+			await expect(
+				emailTab.getByRole( 'button', { name: 'Content', exact: true } )
+			).toHaveCount( 0 );
+
+			// Asserting the item exists keeps this test from passing when core never
+			// rendered the panel. If core stops rendering it, remove the CSS rule.
+			const quickNavItem = emailTab
+				.locator( '.components-button .block-editor-block-icon' )
+				.first();
+			await expect( quickNavItem ).toBeAttached();
+			await expect( quickNavItem ).toBeHidden();
 		} );

-		await page.getByRole( 'tab', { name: 'Block' } ).click();
-		const sidebar = page.locator( '.editor-sidebar__panel' );
-		await expect(
-			sidebar.getByRole( 'button', { name: 'Paragraph' } )
-		).toBeVisible();
-	} );
-} );
+		test( 'Shows the Content list in the Block tab of a content-only block', async ( {
+			page,
+		} ) => {
+			await accessTheEmailEditor( page, 'Customer note' );
+			await expect( page.getByLabel( 'Email' ) ).toBeVisible();
+
+			await page.evaluate( () => {
+				const { createBlock } = window.wp.blocks;
+				const { select, dispatch } = window.wp.data;
+				const group = createBlock(
+					'core/group',
+					{ templateLock: 'contentOnly' },
+					[
+						createBlock( 'core/paragraph', {
+							content: 'Nested text',
+						} ),
+					]
+				);
+				const [ postContent ] =
+					select( 'core/block-editor' ).getBlocksByName(
+						'core/post-content'
+					);
+				dispatch( 'core/block-editor' ).insertBlocks(
+					group,
+					0,
+					postContent
+				);
+				dispatch( 'core/block-editor' ).selectBlock( group.clientId );
+			} );
+
+			await page.getByRole( 'tab', { name: 'Block' } ).click();
+			const sidebar = page.locator( '.editor-sidebar__panel' );
+			await expect(
+				sidebar.getByRole( 'button', { name: 'Paragraph' } )
+			).toBeVisible();
+		} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email-editor/update-propagation/core-flows.spec.ts b/plugins/woocommerce/tests/e2e/tests/email-editor/update-propagation/core-flows.spec.ts
index fdf0d14bb39..7c5e4f607db 100644
--- a/plugins/woocommerce/tests/e2e/tests/email-editor/update-propagation/core-flows.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email-editor/update-propagation/core-flows.spec.ts
@@ -24,135 +24,142 @@ import {
 } from './helpers/simulate-plugin-update';
 import { assertNoLeakedFixtureState } from './helpers/leaked-state-checks';
 import { STATUS } from './helpers/classifications';
+import { locks } from '../../../fixtures/fixtures';

-test.describe( 'Update propagation — core flows', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
-	let seededPostId: number | null = null;
+test.describe(
+	'Update propagation — core flows',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );
+		let seededPostId: number | null = null;

-	test.beforeAll( async ( { baseURL } ) => {
-		await enableEmailEditor( baseURL! );
-	} );
-
-	test.afterEach( async ( { baseURL } ) => {
-		const cleanupErrors: unknown[] = [];
+		test.beforeAll( async ( { baseURL } ) => {
+			await enableEmailEditor( baseURL! );
+		} );

-		try {
-			await assertNoLeakedFixtureState();
-		} catch ( error ) {
-			cleanupErrors.push( error );
-		}
+		test.afterEach( async ( { baseURL } ) => {
+			const cleanupErrors: unknown[] = [];

-		if ( seededPostId !== null ) {
 			try {
-				await deleteEmailPost( baseURL!, String( seededPostId ) );
+				await assertNoLeakedFixtureState();
 			} catch ( error ) {
 				cleanupErrors.push( error );
-			} finally {
-				seededPostId = null;
 			}
-		}

-		if ( cleanupErrors.length > 0 ) {
-			throw new AggregateError(
-				cleanupErrors,
-				'Update propagation cleanup failed.'
-			);
-		}
-	} );
-
-	test.afterAll( async ( { baseURL } ) => {
-		await disableEmailEditor( baseURL! );
-	} );
-
-	/**
-	 * Verifies the installed list-to-editor update flow: the list and editor
-	 * surface a customized core update, the review drawer applies one explicit
-	 * core choice, and the merged content is persisted.
-	 */
-	test( '@pr Review drawer: pick per-conflict yours vs core and apply', async ( {
-		page,
-	} ) => {
-		const oldHtml =
-			'<!-- wp:paragraph --><p>OLD BLOCK A</p><!-- /wp:paragraph -->' +
-			'<!-- wp:paragraph --><p>OLD BLOCK B</p><!-- /wp:paragraph -->' +
-			'<!-- wp:paragraph --><p>OLD BLOCK C</p><!-- /wp:paragraph -->';
-
-		const customized = oldHtml.replace(
-			'OLD BLOCK A',
-			'MERCHANT EDITED A'
-		);
-
-		const newCanonical =
-			'<!-- wp:paragraph --><p>NEW CORE A</p><!-- /wp:paragraph -->' +
-			'<!-- wp:paragraph --><p>NEW CORE B</p><!-- /wp:paragraph -->' +
-			'<!-- wp:paragraph --><p>NEW CORE C</p><!-- /wp:paragraph -->';
-
-		// Seed the merchant edit against the old canonical content.
-		await simulateCoreBump( 'new_order', oldHtml );
-		const postId = await seedWooEmailPost( {
-			emailId: 'new_order',
-			postContent: customized,
-			storedSourceHash: 'AUTO_CURRENT',
-			status: STATUS.IN_SYNC,
-			version: '10.0.0',
+			if ( seededPostId !== null ) {
+				try {
+					await deleteEmailPost( baseURL!, String( seededPostId ) );
+				} catch ( error ) {
+					cleanupErrors.push( error );
+				} finally {
+					seededPostId = null;
+				}
+			}
+
+			if ( cleanupErrors.length > 0 ) {
+				throw new AggregateError(
+					cleanupErrors,
+					'Update propagation cleanup failed.'
+				);
+			}
 		} );
-		seededPostId = postId;
-
-		// Move the canonical template and classify the post as requiring review.
-		await setTemplateHtmlOverride( 'new_order', newCanonical );
-		await triggerDetectionSweep();
-
-		// Prove the installed list surfaces the update on the exact email row.
-		await page.goto( '/wp-admin/admin.php?page=wc-settings&tab=email' );
-		const newOrderRow = page
-			.locator( 'tr' )
-			.filter( { hasText: /New order/i } )
-			.first();
-		await expect(
-			newOrderRow.getByRole( 'button', { name: /review update/i } )
-		).toBeVisible( { timeout: 15000 } );
-
-		// Enter through the real list/editor helper and open the review drawer
-		// from the editor banner.
-		await accessTheEmailEditor( page, 'New order' );
-		await expect( page.locator( '#woocommerce-email-editor' ) ).toBeVisible(
-			{
+
+		test.afterAll( async ( { baseURL } ) => {
+			await disableEmailEditor( baseURL! );
+		} );
+
+		/**
+		 * Verifies the installed list-to-editor update flow: the list and editor
+		 * surface a customized core update, the review drawer applies one explicit
+		 * core choice, and the merged content is persisted.
+		 */
+		test( '@pr Review drawer: pick per-conflict yours vs core and apply', async ( {
+			page,
+		} ) => {
+			const oldHtml =
+				'<!-- wp:paragraph --><p>OLD BLOCK A</p><!-- /wp:paragraph -->' +
+				'<!-- wp:paragraph --><p>OLD BLOCK B</p><!-- /wp:paragraph -->' +
+				'<!-- wp:paragraph --><p>OLD BLOCK C</p><!-- /wp:paragraph -->';
+
+			const customized = oldHtml.replace(
+				'OLD BLOCK A',
+				'MERCHANT EDITED A'
+			);
+
+			const newCanonical =
+				'<!-- wp:paragraph --><p>NEW CORE A</p><!-- /wp:paragraph -->' +
+				'<!-- wp:paragraph --><p>NEW CORE B</p><!-- /wp:paragraph -->' +
+				'<!-- wp:paragraph --><p>NEW CORE C</p><!-- /wp:paragraph -->';
+
+			// Seed the merchant edit against the old canonical content.
+			await simulateCoreBump( 'new_order', oldHtml );
+			const postId = await seedWooEmailPost( {
+				emailId: 'new_order',
+				postContent: customized,
+				storedSourceHash: 'AUTO_CURRENT',
+				status: STATUS.IN_SYNC,
+				version: '10.0.0',
+			} );
+			seededPostId = postId;
+
+			// Move the canonical template and classify the post as requiring review.
+			await setTemplateHtmlOverride( 'new_order', newCanonical );
+			await triggerDetectionSweep();
+
+			// Prove the installed list surfaces the update on the exact email row.
+			await page.goto( '/wp-admin/admin.php?page=wc-settings&tab=email' );
+			const newOrderRow = page
+				.locator( 'tr' )
+				.filter( { hasText: /New order/i } )
+				.first();
+			await expect(
+				newOrderRow.getByRole( 'button', { name: /review update/i } )
+			).toBeVisible( { timeout: 15000 } );
+
+			// Enter through the real list/editor helper and open the review drawer
+			// from the editor banner.
+			await accessTheEmailEditor( page, 'New order' );
+			await expect(
+				page.locator( '#woocommerce-email-editor' )
+			).toBeVisible( {
 				timeout: 20000,
-			}
-		);
-		await expect(
-			page.getByText( /template update available/i ).first()
-		).toBeVisible( { timeout: 15000 } );
-		await page.getByRole( 'button', { name: /^review changes$/i } ).click();
-
-		const drawer = page.getByRole( 'dialog', {
-			name: /review template update/i,
+			} );
+			await expect(
+				page.getByText( /template update available/i ).first()
+			).toBeVisible( { timeout: 15000 } );
+			await page
+				.getByRole( 'button', { name: /^review changes$/i } )
+				.click();
+
+			const drawer = page.getByRole( 'dialog', {
+				name: /review template update/i,
+			} );
+			await expect( drawer ).toBeVisible( { timeout: 15000 } );
+			await expect(
+				drawer.getByRole( 'heading', { name: /needs your attention/i } )
+			).toBeVisible( { timeout: 15000 } );
+
+			const firstRadioGroup = drawer
+				.getByRole( 'radiogroup', {
+					name: /choose which version to apply/i,
+				} )
+				.first();
+			await expect(
+				firstRadioGroup.getByRole( 'radio', { name: /keep yours/i } )
+			).toHaveAttribute( 'aria-checked', 'true' );
+			await firstRadioGroup
+				.getByRole( 'radio', { name: /use core/i } )
+				.click();
+			await expect(
+				firstRadioGroup.getByRole( 'radio', { name: /use core/i } )
+			).toHaveAttribute( 'aria-checked', 'true' );
+
+			await drawer.getByRole( 'button', { name: /^apply/i } ).click();
+			await expect( drawer ).toBeHidden( { timeout: 15000 } );
+
+			const content = await getWooEmailPostContent( postId );
+			expect( content ).toContain( 'NEW CORE A' );
+			expect( content ).not.toContain( 'MERCHANT EDITED A' );
 		} );
-		await expect( drawer ).toBeVisible( { timeout: 15000 } );
-		await expect(
-			drawer.getByRole( 'heading', { name: /needs your attention/i } )
-		).toBeVisible( { timeout: 15000 } );
-
-		const firstRadioGroup = drawer
-			.getByRole( 'radiogroup', {
-				name: /choose which version to apply/i,
-			} )
-			.first();
-		await expect(
-			firstRadioGroup.getByRole( 'radio', { name: /keep yours/i } )
-		).toHaveAttribute( 'aria-checked', 'true' );
-		await firstRadioGroup
-			.getByRole( 'radio', { name: /use core/i } )
-			.click();
-		await expect(
-			firstRadioGroup.getByRole( 'radio', { name: /use core/i } )
-		).toHaveAttribute( 'aria-checked', 'true' );
-
-		await drawer.getByRole( 'button', { name: /^apply/i } ).click();
-		await expect( drawer ).toBeHidden( { timeout: 15000 } );
-
-		const content = await getWooEmailPostContent( postId );
-		expect( content ).toContain( 'NEW CORE A' );
-		expect( content ).not.toContain( 'MERCHANT EDITED A' );
-	} );
-} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email/account-emails.spec.ts b/plugins/woocommerce/tests/e2e/tests/email/account-emails.spec.ts
index df509508a66..a9bdc4b555b 100644
--- a/plugins/woocommerce/tests/e2e/tests/email/account-emails.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email/account-emails.spec.ts
@@ -9,7 +9,7 @@ import { WC_API_PATH } from '@woocommerce/e2e-utils-playwright';
  * Internal dependencies
  */
 import { getFakeCustomer } from '../../utils/data';
-import { expect, test as baseTest } from '../../fixtures/fixtures';
+import { expect, test as baseTest, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import { expectEmail, expectEmailContent } from '../../utils/email';
 import { setFeatureEmailImprovementsFlag } from './helpers/set-email-improvements-feature-flag';
@@ -39,103 +39,105 @@ test.skip(
 	'Test not working on a multisite setup, see https://github.com/woocommerce/woocommerce/issues/55082'
 );

-test( 'New customer should receive an email with login details', async ( {
-	page,
-	user,
-} ) => {
-	let emailRow;
-	await test.step( 'check the email exists', async () => {
-		emailRow = await expectEmail(
-			page,
-			user.email,
-			/Your .* account has been created/
-		);
-	} );
-
-	await test.step( 'check the email content', async () => {
-		await emailRow.getByRole( 'button', { name: 'View log' } ).click();
-
-		await expectEmailContent(
-			page,
-			user.email,
-			/Your .* account has been created/,
-			/Welcome to .*/
-		);
-	} );
-} );
+test(
+	'New customer should receive an email with login details',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	async ( { page, user } ) => {
+		let emailRow;
+		await test.step( 'check the email exists', async () => {
+			emailRow = await expectEmail(
+				page,
+				user.email,
+				/Your .* account has been created/
+			);
+		} );
+
+		await test.step( 'check the email content', async () => {
+			await emailRow.getByRole( 'button', { name: 'View log' } ).click();

-test( 'Customer should receive an email when initiating a password reset', async ( {
-	page,
-	user,
-	browser,
-} ) => {
-	await test.step( 'initiate password reset from my account', async () => {
-		const loggedOutContext = await browser.newContext( {
-			storageState: { cookies: [], origins: [] },
+			await expectEmailContent(
+				page,
+				user.email,
+				/Your .* account has been created/,
+				/Welcome to .*/
+			);
 		} );
-		const loggedOutPage = await loggedOutContext.newPage();
-		await loggedOutPage.goto( 'my-account/lost-password/' );
-		await loggedOutPage
-			.getByLabel( 'Username or email' )
-			.fill( user.email );
-		await loggedOutPage
-			.getByRole( 'button', { name: 'Reset password' } )
-			.click();
-
-		await expect(
-			loggedOutPage
-				.getByRole( 'alert' )
-				.getByText( 'Password reset email has been sent.' )
-		).toBeVisible();
-	} );
-
-	let emailRow;
-	await test.step( 'check the email exists', async () => {
-		emailRow = await expectEmail(
-			page,
-			user.email,
-			/Password Reset Request for .*/
-		);
-	} );
-
-	await test.step( 'check the email content', async () => {
-		await emailRow.getByRole( 'button', { name: 'View log' } ).click();
-
-		await expectEmailContent(
-			page,
-			user.email,
-			/Password Reset Request for .*/,
-			/Password Reset Request/
-		);
-	} );
-} );
+	}
+);

-test( 'Customer should receive an email when password reset initiated from admin', async ( {
-	page,
-	user,
-} ) => {
-	await test.step( 'admin sends password reset link', async () => {
-		await page.goto( 'wp-admin/users.php' );
-		await page.getByText( user.email ).hover();
-		await page
-			.locator( `#user-${ user.id }` )
-			.getByRole( 'link', { name: 'Send password reset' } )
-			.click();
-	} );
-
-	let emailRow;
-	await test.step( 'check the email exists', async () => {
-		emailRow = await expectEmail( page, user.email, /Password Reset/ );
-	} );
-
-	await test.step( 'check the email content', async () => {
-		await emailRow.getByRole( 'button', { name: 'View log' } ).click();
-
-		await expectEmailContent(
-			page,
-			user.email,
-			/Password Reset/,
-			/Someone has requested a password reset for the following account/
-		);
-	} );
-} );
+test(
+	'Customer should receive an email when initiating a password reset',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	async ( { page, user, browser } ) => {
+		await test.step( 'initiate password reset from my account', async () => {
+			const loggedOutContext = await browser.newContext( {
+				storageState: { cookies: [], origins: [] },
+			} );
+			const loggedOutPage = await loggedOutContext.newPage();
+			await loggedOutPage.goto( 'my-account/lost-password/' );
+			await loggedOutPage
+				.getByLabel( 'Username or email' )
+				.fill( user.email );
+			await loggedOutPage
+				.getByRole( 'button', { name: 'Reset password' } )
+				.click();
+
+			await expect(
+				loggedOutPage
+					.getByRole( 'alert' )
+					.getByText( 'Password reset email has been sent.' )
+			).toBeVisible();
+		} );
+
+		let emailRow;
+		await test.step( 'check the email exists', async () => {
+			emailRow = await expectEmail(
+				page,
+				user.email,
+				/Password Reset Request for .*/
+			);
+		} );
+
+		await test.step( 'check the email content', async () => {
+			await emailRow.getByRole( 'button', { name: 'View log' } ).click();
+
+			await expectEmailContent(
+				page,
+				user.email,
+				/Password Reset Request for .*/,
+				/Password Reset Request/
+			);
+		} );
+	}
+);
+
+test(
+	'Customer should receive an email when password reset initiated from admin',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	async ( { page, user } ) => {
+		await test.step( 'admin sends password reset link', async () => {
+			await page.goto( 'wp-admin/users.php' );
+			await page.getByText( user.email ).hover();
+			await page
+				.locator( `#user-${ user.id }` )
+				.getByRole( 'link', { name: 'Send password reset' } )
+				.click();
+		} );
+
+		let emailRow;
+		await test.step( 'check the email exists', async () => {
+			emailRow = await expectEmail( page, user.email, /Password Reset/ );
+		} );
+
+		await test.step( 'check the email content', async () => {
+			await emailRow.getByRole( 'button', { name: 'View log' } ).click();
+
+			await expectEmailContent(
+				page,
+				user.email,
+				/Password Reset/,
+				/Someone has requested a password reset for the following account/
+			);
+		} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email/editor-tracking-selectors.spec.ts b/plugins/woocommerce/tests/e2e/tests/email/editor-tracking-selectors.spec.ts
index c862e2498cb..1e8c8e0a682 100644
--- a/plugins/woocommerce/tests/e2e/tests/email/editor-tracking-selectors.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email/editor-tracking-selectors.spec.ts
@@ -9,6 +9,7 @@ import { test, expect, request } from '@playwright/test';
 import { setOption } from '../../utils/options';
 import { accessTheEmailEditor } from '../../utils/email';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
+import { locks } from '../../fixtures/fixtures';

 const setFeatureFlag = async ( baseURL: string | undefined, value: string ) => {
 	if ( ! baseURL ) {
@@ -27,133 +28,141 @@ const setFeatureFlag = async ( baseURL: string | undefined, value: string ) => {
  *
  * The test checks that the selectors that are used to track telemetry events in the email editor are present.
  */
-test.describe( 'WooCommerce Email Editor Tracking Selectors', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
+test.describe(
+	'WooCommerce Email Editor Tracking Selectors',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );

-	test.afterAll( async ( { baseURL } ) => {
-		await setFeatureFlag( baseURL, 'no' );
-	} );
+		test.afterAll( async ( { baseURL } ) => {
+			await setFeatureFlag( baseURL, 'no' );
+		} );

-	test( 'Check selectors for tracking events', async ( {
-		page,
-		baseURL,
-	} ) => {
-		await setFeatureFlag( baseURL, 'yes' );
+		test( 'Check selectors for tracking events', async ( {
+			page,
+			baseURL,
+		} ) => {
+			await setFeatureFlag( baseURL, 'yes' );

-		// Open an email through the listing — with lazy post creation the
-		// Edit action creates the post on demand before opening the editor.
-		await accessTheEmailEditor( page, 'New order' );
+			// Open an email through the listing — with lazy post creation the
+			// Edit action creates the post on demand before opening the editor.
+			await accessTheEmailEditor( page, 'New order' );

-		// Check that the Editor is present
-		const editorLocator = page.locator( '#woocommerce-email-editor' );
-		await expect( editorLocator ).toBeVisible();
+			// Check that the Editor is present
+			const editorLocator = page.locator( '#woocommerce-email-editor' );
+			await expect( editorLocator ).toBeVisible();

-		// Check listview sidebar toggle button selector
-		await expect(
-			editorLocator.locator(
-				'.editor-document-tools__document-overview-toggle'
-			)
-		).toBeVisible();
-		// Check inserter sidebar toggle button selector
-		await expect(
-			editorLocator.locator( '.editor-document-tools__inserter-toggle' )
-		).toBeVisible();
-		// Check Email tab in the sidebar
-		await expect(
-			editorLocator.locator( '[data-tab-id="edit-post/block"]' )
-		).toBeVisible();
-		// Check Block tab in the sidebar
-		await expect(
-			editorLocator.locator( '[data-tab-id="edit-post/document"]' )
-		).toBeVisible();
-		// Check preview dropdown toggle
-		await expect(
-			editorLocator.locator( '.editor-preview-dropdown__toggle' )
-		).toBeVisible();
+			// Check listview sidebar toggle button selector
+			await expect(
+				editorLocator.locator(
+					'.editor-document-tools__document-overview-toggle'
+				)
+			).toBeVisible();
+			// Check inserter sidebar toggle button selector
+			await expect(
+				editorLocator.locator(
+					'.editor-document-tools__inserter-toggle'
+				)
+			).toBeVisible();
+			// Check Email tab in the sidebar
+			await expect(
+				editorLocator.locator( '[data-tab-id="edit-post/block"]' )
+			).toBeVisible();
+			// Check Block tab in the sidebar
+			await expect(
+				editorLocator.locator( '[data-tab-id="edit-post/document"]' )
+			).toBeVisible();
+			// Check preview dropdown toggle
+			await expect(
+				editorLocator.locator( '.editor-preview-dropdown__toggle' )
+			).toBeVisible();

-		// Check inserter sidebar close icon
-		// Click inserter sidebar toggle button
-		await editorLocator
-			.locator( '.editor-document-tools__inserter-toggle' )
-			.click();
-		// Check inserter sidebar close icon is now visible after opening
-		await expect(
-			editorLocator.locator(
-				'.block-editor-inserter__menu .block-editor-tabbed-sidebar__close-button'
-			)
-		).toBeVisible();
+			// Check inserter sidebar close icon
+			// Click inserter sidebar toggle button
+			await editorLocator
+				.locator( '.editor-document-tools__inserter-toggle' )
+				.click();
+			// Check inserter sidebar close icon is now visible after opening
+			await expect(
+				editorLocator.locator(
+					'.block-editor-inserter__menu .block-editor-tabbed-sidebar__close-button'
+				)
+			).toBeVisible();

-		// Check save button selector
-		await expect(
-			editorLocator.locator( '.editor-post-publish-button' )
-		).toBeVisible();
+			// Check save button selector
+			await expect(
+				editorLocator.locator( '.editor-post-publish-button' )
+			).toBeVisible();

-		// Check more menu toggle selector
-		await expect(
-			editorLocator.locator(
-				'.components-dropdown-menu__toggle[aria-label="Options"]'
-			)
-		).toBeVisible();
+			// Check more menu toggle selector
+			await expect(
+				editorLocator.locator(
+					'.components-dropdown-menu__toggle[aria-label="Options"]'
+				)
+			).toBeVisible();

-		// Check open preview in new tab button
-		// Click preview dropdown toggle
-		await editorLocator
-			.locator( '.editor-preview-dropdown__toggle' )
-			.click();
-		// Check open in new tab selector.
-		// Mirrors the selector the email editor telemetry and preview save
-		// guard rely on (packages/js/email-editor). WP 7.1 dropped the
-		// `.editor-preview-dropdown__button-external` class; the entry is now a
-		// menu item anchor with target="wp-preview-<postId>". Assert the same
-		// selector so this canary stays faithful to what the product code
-		// matches. Drop the old class once WP 7.1 is the minimum supported
-		// version.
-		await expect(
-			page.locator(
-				'.editor-preview-dropdown__button-external, a[role="menuitem"][target^="wp-preview-"]'
-			)
-		).toBeVisible();
-		// Close preview dropdown
-		await editorLocator
-			.locator( '.editor-preview-dropdown__toggle' )
-			.click();
+			// Check open preview in new tab button
+			// Click preview dropdown toggle
+			await editorLocator
+				.locator( '.editor-preview-dropdown__toggle' )
+				.click();
+			// Check open in new tab selector.
+			// Mirrors the selector the email editor telemetry and preview save
+			// guard rely on (packages/js/email-editor). WP 7.1 dropped the
+			// `.editor-preview-dropdown__button-external` class; the entry is now a
+			// menu item anchor with target="wp-preview-<postId>". Assert the same
+			// selector so this canary stays faithful to what the product code
+			// matches. Drop the old class once WP 7.1 is the minimum supported
+			// version.
+			await expect(
+				page.locator(
+					'.editor-preview-dropdown__button-external, a[role="menuitem"][target^="wp-preview-"]'
+				)
+			).toBeVisible();
+			// Close preview dropdown
+			await editorLocator
+				.locator( '.editor-preview-dropdown__toggle' )
+				.click();

-		// Check command bar button
-		await editorLocator.locator( '.editor-document-bar' ).click();
-		// Fill command bar input with 'a' to get some results
-		await page
-			.locator( '.commands-command-menu__header input' )
-			.fill( 'a' );
-		// Check command selected selector
-		await expect(
-			page.locator(
-				'.commands-command-menu__container [role="option"]:first-child'
-			)
-		).toBeVisible();
-		// Press Escape key to close command bar
-		await page.keyboard.press( 'Escape' );
+			// Check command bar button
+			await editorLocator.locator( '.editor-document-bar' ).click();
+			// Fill command bar input with 'a' to get some results
+			await page
+				.locator( '.commands-command-menu__header input' )
+				.fill( 'a' );
+			// Check command selected selector
+			await expect(
+				page.locator(
+					'.commands-command-menu__container [role="option"]:first-child'
+				)
+			).toBeVisible();
+			// Press Escape key to close command bar
+			await page.keyboard.press( 'Escape' );

-		// Check header block tools toggle button
-		// Enable header block tools
-		await editorLocator
-			.locator(
-				'.components-dropdown-menu__toggle[aria-label="Options"]'
-			)
-			.click();
-		// Check header preview dropdown preview in new tab selected
-		// Click header preview dropdown preview in new tab
-		await page
-			.locator( '.components-popover' )
-			.getByText( 'Top toolbar' )
-			.click();
-		// Click canvas to select a block to make top toolbar visible
-		await page
-			.frameLocator( 'iframe[name="editor-canvas"]' )
-			.locator( '.wp-block-heading' )
-			.first()
-			.click();
-		await expect(
-			editorLocator.locator( '.editor-collapsible-block-toolbar__toggle' )
-		).toBeVisible();
-	} );
-} );
+			// Check header block tools toggle button
+			// Enable header block tools
+			await editorLocator
+				.locator(
+					'.components-dropdown-menu__toggle[aria-label="Options"]'
+				)
+				.click();
+			// Check header preview dropdown preview in new tab selected
+			// Click header preview dropdown preview in new tab
+			await page
+				.locator( '.components-popover' )
+				.getByText( 'Top toolbar' )
+				.click();
+			// Click canvas to select a block to make top toolbar visible
+			await page
+				.frameLocator( 'iframe[name="editor-canvas"]' )
+				.locator( '.wp-block-heading' )
+				.first()
+				.click();
+			await expect(
+				editorLocator.locator(
+					'.editor-collapsible-block-toolbar__toggle'
+				)
+			).toBeVisible();
+		} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email/order-emails-block-editor.spec.ts b/plugins/woocommerce/tests/e2e/tests/email/order-emails-block-editor.spec.ts
index e84062e7380..dd5dcf75b8d 100644
--- a/plugins/woocommerce/tests/e2e/tests/email/order-emails-block-editor.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email/order-emails-block-editor.spec.ts
@@ -13,7 +13,7 @@ import {
  * Internal dependencies
  */
 import { ADMIN_STATE_PATH } from '../../playwright.config';
-import { expect, test as baseTest } from '../../fixtures/fixtures';
+import { expect, test as baseTest, locks } from '../../fixtures/fixtures';
 import { admin } from '../../test-data/data';
 import { deleteOption, setOption } from '../../utils/options';
 import { accessTheEmailEditor, expectEmail } from '../../utils/email';
@@ -125,70 +125,73 @@ const triggerOrderEmailAndOpenLog = async ( page, restApi ) => {
 	return modalContent.locator( 'iframe' ).contentFrame();
 };

-test( 'saved email is sent from the customized post', async ( {
-	page,
-	restApi,
-} ) => {
-	// Opening the editor creates this email type's draft scratchpad from the
-	// canonical file template.
-	await accessTheEmailEditor( page, EMAIL_LISTING_TITLE );
-	await expect(
-		page
-			.locator( 'iframe[name="editor-canvas"]' )
-			.contentFrame()
-			.getByText( 'Thank you for your order' )
-	).toBeVisible();
-
-	const drafts = await restApi.get( `${ WP_API_PATH }/woo_email`, {
-		status: 'draft',
-		context: 'edit',
-		per_page: 100,
-	} );
-	const draft = drafts.data.find( ( post ) =>
-		( post.slug as string ).startsWith( EMAIL_TYPE )
-	);
-	expect( draft ).toBeTruthy();
-	expect( Number.isSafeInteger( draft.id ) && draft.id > 0 ).toBe( true );
-	await restApi.post( `${ WP_API_PATH }/woo_email/${ draft.id }`, {
-		content: `${ draft.content.raw }\n<!-- wp:paragraph --><p>${ DRAFT_MARKER }</p><!-- /wp:paragraph -->`,
-	} );
-
-	// The real listing must reopen that same edited scratchpad before Save.
-	await accessTheEmailEditor( page, EMAIL_LISTING_TITLE );
-	await expect(
-		page
-			.locator( 'iframe[name="editor-canvas"]' )
-			.contentFrame()
-			.getByText( DRAFT_MARKER )
-	).toBeVisible();
+test(
+	'saved email is sent from the customized post',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	async ( { page, restApi } ) => {
+		// Opening the editor creates this email type's draft scratchpad from the
+		// canonical file template.
+		await accessTheEmailEditor( page, EMAIL_LISTING_TITLE );
+		await expect(
+			page
+				.locator( 'iframe[name="editor-canvas"]' )
+				.contentFrame()
+				.getByText( 'Thank you for your order' )
+		).toBeVisible();
+
+		const drafts = await restApi.get( `${ WP_API_PATH }/woo_email`, {
+			status: 'draft',
+			context: 'edit',
+			per_page: 100,
+		} );
+		const draft = drafts.data.find( ( post ) =>
+			( post.slug as string ).startsWith( EMAIL_TYPE )
+		);
+		expect( draft ).toBeTruthy();
+		expect( Number.isSafeInteger( draft.id ) && draft.id > 0 ).toBe( true );
+		await restApi.post( `${ WP_API_PATH }/woo_email/${ draft.id }`, {
+			content: `${ draft.content.raw }\n<!-- wp:paragraph --><p>${ DRAFT_MARKER }</p><!-- /wp:paragraph -->`,
+		} );

-	// Save publishes the draft in the background, making it the rendering
-	// source. Observe the exact post write and then poll the same draft ID.
-	const saveResponse = page.waitForResponse( ( response ) => {
-		const url = new URL( response.url() );
-		return (
-			url.pathname.endsWith( `/wp/v2/woo_email/${ draft.id }` ) &&
-			[ 'POST', 'PUT' ].includes( response.request().method() ) &&
-			response.ok()
+		// The real listing must reopen that same edited scratchpad before Save.
+		await accessTheEmailEditor( page, EMAIL_LISTING_TITLE );
+		await expect(
+			page
+				.locator( 'iframe[name="editor-canvas"]' )
+				.contentFrame()
+				.getByText( DRAFT_MARKER )
+		).toBeVisible();
+
+		// Save publishes the draft in the background, making it the rendering
+		// source. Observe the exact post write and then poll the same draft ID.
+		const saveResponse = page.waitForResponse( ( response ) => {
+			const url = new URL( response.url() );
+			return (
+				url.pathname.endsWith( `/wp/v2/woo_email/${ draft.id }` ) &&
+				[ 'POST', 'PUT' ].includes( response.request().method() ) &&
+				response.ok()
+			);
+		} );
+		await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
+		await saveResponse;
+
+		await expect
+			.poll(
+				async () => {
+					const response = await restApi.get(
+						`${ WP_API_PATH }/woo_email/${ draft.id }`,
+						{ context: 'edit' }
+					);
+					return response.data.status;
+				},
+				{ timeout: 20000 }
+			)
+			.toBe( 'publish' );
+
+		const emailBody = await triggerOrderEmailAndOpenLog( page, restApi );
+
+		await expect( emailBody.locator( 'body' ) ).toContainText(
+			DRAFT_MARKER
 		);
-	} );
-	await page.getByRole( 'button', { name: 'Save', exact: true } ).click();
-	await saveResponse;
-
-	await expect
-		.poll(
-			async () => {
-				const response = await restApi.get(
-					`${ WP_API_PATH }/woo_email/${ draft.id }`,
-					{ context: 'edit' }
-				);
-				return response.data.status;
-			},
-			{ timeout: 20000 }
-		)
-		.toBe( 'publish' );
-
-	const emailBody = await triggerOrderEmailAndOpenLog( page, restApi );
-
-	await expect( emailBody.locator( 'body' ) ).toContainText( DRAFT_MARKER );
-} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email/order-emails.spec.ts b/plugins/woocommerce/tests/e2e/tests/email/order-emails.spec.ts
index b9aa244e884..6c1296da192 100644
--- a/plugins/woocommerce/tests/e2e/tests/email/order-emails.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email/order-emails.spec.ts
@@ -9,7 +9,7 @@ import { WC_API_PATH } from '@woocommerce/e2e-utils-playwright';
  * Internal dependencies
  */
 import { ADMIN_STATE_PATH } from '../../playwright.config';
-import { expect, test as baseTest } from '../../fixtures/fixtures';
+import { expect, test as baseTest, locks } from '../../fixtures/fixtures';
 import { admin } from '../../test-data/data';
 import { expectEmail, expectEmailContent } from '../../utils/email';
 import { setFeatureEmailImprovementsFlag } from './helpers/set-email-improvements-feature-flag';
@@ -34,6 +34,10 @@ test.beforeEach( async ( { baseURL } ) => {
 	await setFeatureEmailImprovementsFlag( baseURL, 'no' );
 } );

+test.afterAll( async ( { baseURL } ) => {
+	await setFeatureEmailImprovementsFlag( baseURL, 'no' );
+} );
+
 [
 	{
 		status: 'processing',
@@ -60,108 +64,113 @@ test.beforeEach( async ( { baseURL } ) => {
 		content: 'Thanks for reading',
 	},
 ].forEach( ( { role, status, subject, content } ) => {
-	test( `${ role } receives email for ${ status } order`, async ( {
-		page,
-		restApi,
-		order,
-	} ) => {
-		// Inject the order id into the expected subject and make it a regex
-		const subjectRegex = new RegExp(
-			subject.replace( 'ORDER_ID', `${ order.id }` )
-		);
-
-		const { data: updatedOrder } = await restApi.put(
-			`${ WC_API_PATH }/orders/${ order.id }`,
-			{ status }
-		);
-
-		expect( updatedOrder.status ).toEqual( status );
-
-		const emailRow = await test.step( 'check the email exists', () =>
-			expectEmail(
-				page,
-				role === 'customer' ? order.billing.email : admin.email,
-				subjectRegex
-			) );
-
-		await test.step( 'check the email content', async () => {
-			await emailRow.getByRole( 'button', { name: 'View log' } ).click();
+	test(
+		`${ role } receives email for ${ status } order`,
+		{ lock: locks.EMAIL_FEATURE_FLAGS },
+		async ( { page, restApi, order } ) => {
+			// Inject the order id into the expected subject and make it a regex
+			const subjectRegex = new RegExp(
+				subject.replace( 'ORDER_ID', `${ order.id }` )
+			);

-			await expectEmailContent(
-				page,
-				role === 'customer' ? order.billing.email : admin.email,
-				subjectRegex,
-				content
+			const { data: updatedOrder } = await restApi.put(
+				`${ WC_API_PATH }/orders/${ order.id }`,
+				{ status }
 			);
-		} );
-	} );
-} );

-test( 'Merchant can resend order details to customer', async ( {
-	order,
-	page,
-} ) => {
-	await page.goto(
-		`wp-admin/admin.php?page=wc-orders&action=edit&id=${ order.id }`
-	);
-	await page
-		.locator( 'li#actions > select' )
-		.selectOption( 'send_order_details' );
-	await page.locator( 'button.wc-reload' ).click();
-	await expect(
-		page.locator( '#message' ).filter( { hasText: 'Order updated' } )
-	).toBeVisible();
-
-	await expectEmail(
-		page,
-		order.billing.email,
-		new RegExp( `Details for order #${ order.id }` )
+			expect( updatedOrder.status ).toEqual( status );
+
+			const emailRow = await test.step( 'check the email exists', () =>
+				expectEmail(
+					page,
+					role === 'customer' ? order.billing.email : admin.email,
+					subjectRegex
+				) );
+
+			await test.step( 'check the email content', async () => {
+				await emailRow
+					.getByRole( 'button', { name: 'View log' } )
+					.click();
+
+				await expectEmailContent(
+					page,
+					role === 'customer' ? order.billing.email : admin.email,
+					subjectRegex,
+					content
+				);
+			} );
+		}
 	);
 } );

-test( 'Zero cost shipping method keeps its title in the order email', async ( {
-	baseURL,
-	page,
-	restApi,
-} ) => {
-	const methodName = 'Shipping TBD';
-	const billingEmail = faker.internet.exampleEmail();
-
-	await setFeatureEmailImprovementsFlag( baseURL, 'yes' );
-
-	const { data: order } = await restApi.post( `${ WC_API_PATH }/orders`, {
-		status: 'processing',
-		billing: { email: billingEmail },
-		shipping_lines: [
-			{
-				method_id: 'flat_rate',
-				method_title: methodName,
-				total: '0.00',
-			},
-		],
-	} );
-
-	try {
-		const emailRow = await expectEmail(
+test(
+	'Merchant can resend order details to customer',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	async ( { order, page } ) => {
+		await page.goto(
+			`wp-admin/admin.php?page=wc-orders&action=edit&id=${ order.id }`
+		);
+		await page
+			.locator( 'li#actions > select' )
+			.selectOption( 'send_order_details' );
+		await page.locator( 'button.wc-reload' ).click();
+		await expect(
+			page.locator( '#message' ).filter( { hasText: 'Order updated' } )
+		).toBeVisible();
+
+		await expectEmail(
 			page,
-			billingEmail,
-			/order has been received/
+			order.billing.email,
+			new RegExp( `Details for order #${ order.id }` )
 		);
-		await emailRow.getByRole( 'button', { name: 'View log' } ).click();
+	}
+);

-		const shippingRow = page
-			.locator( '#wp-mail-logging-modal-content-body-content' )
-			.locator( 'iframe' )
-			.contentFrame()
-			.locator( 'tr.order-totals-shipping' );
+test(
+	'Zero cost shipping method keeps its title in the order email',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	async ( { baseURL, page, restApi } ) => {
+		const methodName = 'Shipping TBD';
+		const billingEmail = faker.internet.exampleEmail();

-		await expect( shippingRow.locator( 'td' ) ).toContainText( methodName );
-		await expect( shippingRow.locator( 'td' ) ).not.toContainText(
-			'Free!'
-		);
-	} finally {
-		await restApi.delete( `${ WC_API_PATH }/orders/${ order.id }`, {
-			force: true,
+		await setFeatureEmailImprovementsFlag( baseURL, 'yes' );
+
+		const { data: order } = await restApi.post( `${ WC_API_PATH }/orders`, {
+			status: 'processing',
+			billing: { email: billingEmail },
+			shipping_lines: [
+				{
+					method_id: 'flat_rate',
+					method_title: methodName,
+					total: '0.00',
+				},
+			],
 		} );
+
+		try {
+			const emailRow = await expectEmail(
+				page,
+				billingEmail,
+				/order has been received/
+			);
+			await emailRow.getByRole( 'button', { name: 'View log' } ).click();
+
+			const shippingRow = page
+				.locator( '#wp-mail-logging-modal-content-body-content' )
+				.locator( 'iframe' )
+				.contentFrame()
+				.locator( 'tr.order-totals-shipping' );
+
+			await expect( shippingRow.locator( 'td' ) ).toContainText(
+				methodName
+			);
+			await expect( shippingRow.locator( 'td' ) ).not.toContainText(
+				'Free!'
+			);
+		} finally {
+			await restApi.delete( `${ WC_API_PATH }/orders/${ order.id }`, {
+				force: true,
+			} );
+		}
 	}
-} );
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email/settings-email-listing.spec.ts b/plugins/woocommerce/tests/e2e/tests/email/settings-email-listing.spec.ts
index be0c6bb6732..8d7a3136e4c 100644
--- a/plugins/woocommerce/tests/e2e/tests/email/settings-email-listing.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email/settings-email-listing.spec.ts
@@ -8,6 +8,7 @@ import { test, expect, request } from '@playwright/test';
  */
 import { setOption } from '../../utils/options';
 import { ADMIN_STATE_PATH } from '../../playwright.config';
+import { locks } from '../../fixtures/fixtures';

 const setFeatureFlag = async ( baseURL: string, name: string, value: string ) =>
 	await setOption( request, baseURL, name, value );
@@ -22,165 +23,181 @@ const setBlockEmailEditorFeatureFlag = async (
 		value
 	);

-test.describe( 'WooCommerce Email Settings List View', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
+test.describe(
+	'WooCommerce Email Settings List View',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );

-	test.afterAll( async ( { baseURL } ) => {
-		await setBlockEmailEditorFeatureFlag( baseURL, 'no' );
-	} );
+		test.afterAll( async ( { baseURL } ) => {
+			await setBlockEmailEditorFeatureFlag( baseURL, 'no' );
+		} );

-	test( 'Email settings list view renders correctly and allows to edit email status and search', async ( {
-		page,
-		baseURL,
-	} ) => {
-		await setBlockEmailEditorFeatureFlag( baseURL, 'yes' );
-
-		// Navigate to WooCommerce Email Settings page
-		await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
-
-		// Check that the ListView component is present
-		const listViewLocator = page.locator(
-			'.woocommerce-email-listing-listview'
-		);
-
-		await expect( listViewLocator ).toBeVisible();
-
-		// The listing's DataViews styles ship in its lazy chunk. Without them
-		// the table falls back to the browser default (border-collapse:
-		// separate) and renders as plain, unstyled rows.
-		await expect(
-			listViewLocator.locator( '.dataviews-view-table' )
-		).toHaveCSS( 'border-collapse', 'collapse' );
-
-		// Check that "New order" email type exists within the list view
-		await expect( listViewLocator.getByText( /New order/ ) ).toBeVisible();
-
-		// Check table columns
-		// Check that Title column exists
-		await expect(
-			listViewLocator.getByRole( 'columnheader', { name: 'Title' } )
-		).toBeVisible();
-
-		// Check that Status column exists
-		await expect(
-			listViewLocator.getByRole( 'columnheader', { name: 'Status' } )
-		).toBeVisible();
-
-		// Check that Recipients column exists
-		await expect(
-			listViewLocator.getByRole( 'columnheader', {
-				name: 'Recipient(s)',
-			} )
-		).toBeVisible();
-
-		// Check that Actions column exists
-		await expect( listViewLocator.getByText( 'Actions' ) ).toBeVisible();
-
-		// Target the "New order" row explicitly so the test is independent of list ordering.
-		const newOrderRow = listViewLocator.locator( 'tr', {
-			hasText: 'New order',
+		test( 'Email settings list view renders correctly and allows to edit email status and search', async ( {
+			page,
+			baseURL,
+		} ) => {
+			await setBlockEmailEditorFeatureFlag( baseURL, 'yes' );
+
+			// Navigate to WooCommerce Email Settings page
+			await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
+
+			// Check that the ListView component is present
+			const listViewLocator = page.locator(
+				'.woocommerce-email-listing-listview'
+			);
+
+			await expect( listViewLocator ).toBeVisible();
+
+			// The listing's DataViews styles ship in its lazy chunk. Without them
+			// the table falls back to the browser default (border-collapse:
+			// separate) and renders as plain, unstyled rows.
+			await expect(
+				listViewLocator.locator( '.dataviews-view-table' )
+			).toHaveCSS( 'border-collapse', 'collapse' );
+
+			// Check that "New order" email type exists within the list view
+			await expect(
+				listViewLocator.getByText( /New order/ )
+			).toBeVisible();
+
+			// Check table columns
+			// Check that Title column exists
+			await expect(
+				listViewLocator.getByRole( 'columnheader', { name: 'Title' } )
+			).toBeVisible();
+
+			// Check that Status column exists
+			await expect(
+				listViewLocator.getByRole( 'columnheader', { name: 'Status' } )
+			).toBeVisible();
+
+			// Check that Recipients column exists
+			await expect(
+				listViewLocator.getByRole( 'columnheader', {
+					name: 'Recipient(s)',
+				} )
+			).toBeVisible();
+
+			// Check that Actions column exists
+			await expect(
+				listViewLocator.getByText( 'Actions' )
+			).toBeVisible();
+
+			// Target the "New order" row explicitly so the test is independent of list ordering.
+			const newOrderRow = listViewLocator.locator( 'tr', {
+				hasText: 'New order',
+			} );
+			await expect( newOrderRow.locator( 'td' ).nth( 2 ) ).toHaveText(
+				'Active'
+			);
+
+			// Open the row's more actions menu
+			await newOrderRow
+				.locator( '.dataviews-all-actions-button' )
+				.click();
+
+			// Check that the "Deactivate email" option is present and clickable
+			await expect(
+				page.getByRole( 'menuitem', { name: 'Deactivate email' } )
+			).toBeVisible();
+			await page
+				.getByRole( 'menuitem', { name: 'Deactivate email' } )
+				.click();
+
+			// Check that the email status is now Draft
+			await expect( newOrderRow.locator( 'td' ).nth( 2 ) ).toHaveText(
+				'Inactive'
+			);
+
+			// Open the row's more actions menu again
+			await newOrderRow
+				.locator( '.dataviews-all-actions-button' )
+				.click();
+
+			// Check that the "Activate email" option is present and clickable
+			await expect(
+				page.getByRole( 'menuitem', { name: 'Activate email' } )
+			).toBeVisible();
+			await page
+				.getByRole( 'menuitem', { name: 'Activate email' } )
+				.click();
+
+			// Check that the email status is now Active again
+			await expect( newOrderRow.locator( 'td' ).nth( 2 ) ).toHaveText(
+				'Active'
+			);
+
+			// I want to check that search works
+			await page.getByPlaceholder( 'Search' ).fill( 'Failed order' );
+			await expect(
+				listViewLocator.getByText( 'Failed order' )
+			).toBeVisible();
+
+			// Check that only one row is visible after search
+			const rows = listViewLocator.locator( 'tr' );
+			// Add 1 to account for header row
+			await expect( rows ).toHaveCount( 2 );
 		} );
-		await expect( newOrderRow.locator( 'td' ).nth( 2 ) ).toHaveText(
-			'Active'
-		);
-
-		// Open the row's more actions menu
-		await newOrderRow.locator( '.dataviews-all-actions-button' ).click();
-
-		// Check that the "Deactivate email" option is present and clickable
-		await expect(
-			page.getByRole( 'menuitem', { name: 'Deactivate email' } )
-		).toBeVisible();
-		await page
-			.getByRole( 'menuitem', { name: 'Deactivate email' } )
-			.click();
-
-		// Check that the email status is now Draft
-		await expect( newOrderRow.locator( 'td' ).nth( 2 ) ).toHaveText(
-			'Inactive'
-		);
-
-		// Open the row's more actions menu again
-		await newOrderRow.locator( '.dataviews-all-actions-button' ).click();
-
-		// Check that the "Activate email" option is present and clickable
-		await expect(
-			page.getByRole( 'menuitem', { name: 'Activate email' } )
-		).toBeVisible();
-		await page.getByRole( 'menuitem', { name: 'Activate email' } ).click();
-
-		// Check that the email status is now Active again
-		await expect( newOrderRow.locator( 'td' ).nth( 2 ) ).toHaveText(
-			'Active'
-		);
-
-		// I want to check that search works
-		await page.getByPlaceholder( 'Search' ).fill( 'Failed order' );
-		await expect(
-			listViewLocator.getByText( 'Failed order' )
-		).toBeVisible();
-
-		// Check that only one row is visible after search
-		const rows = listViewLocator.locator( 'tr' );
-		// Add 1 to account for header row
-		await expect( rows ).toHaveCount( 2 );
-	} );
-
-	test( 'Preview action renders the file template for emails without a saved post', async ( {
-		page,
-		baseURL,
-	} ) => {
-		await setBlockEmailEditorFeatureFlag( baseURL, 'yes' );
-
-		await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
-		const listViewLocator = page.locator(
-			'.woocommerce-email-listing-listview'
-		);
-		await expect( listViewLocator ).toBeVisible();
-
-		// A row no other spec creates a post for, so it renders from the file
-		// template and the Preview action must use the admin preview page.
-		const row = listViewLocator.locator( 'tr', {
-			hasText: 'Order on hold',
+
+		test( 'Preview action renders the file template for emails without a saved post', async ( {
+			page,
+			baseURL,
+		} ) => {
+			await setBlockEmailEditorFeatureFlag( baseURL, 'yes' );
+
+			await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
+			const listViewLocator = page.locator(
+				'.woocommerce-email-listing-listview'
+			);
+			await expect( listViewLocator ).toBeVisible();
+
+			// A row no other spec creates a post for, so it renders from the file
+			// template and the Preview action must use the admin preview page.
+			const row = listViewLocator.locator( 'tr', {
+				hasText: 'Order on hold',
+			} );
+			const popupPromise = page.waitForEvent( 'popup' );
+			await row.getByRole( 'button', { name: 'Preview' } ).click();
+			const popup = await popupPromise;
+
+			await expect( popup ).toHaveURL( /preview_woo_block_email/ );
+			// The wooemailtemplate chrome proves the block pipeline rendered it.
+			await expect( popup.locator( 'body' ) ).toContainText(
+				'All Rights Reserved'
+			);
 		} );
-		const popupPromise = page.waitForEvent( 'popup' );
-		await row.getByRole( 'button', { name: 'Preview' } ).click();
-		const popup = await popupPromise;
-
-		await expect( popup ).toHaveURL( /preview_woo_block_email/ );
-		// The wooemailtemplate chrome proves the block pipeline rendered it.
-		await expect( popup.locator( 'body' ) ).toContainText(
-			'All Rights Reserved'
-		);
-	} );
-
-	test( 'Email listing assets load only where the listing renders', async ( {
-		page,
-		baseURL,
-	} ) => {
-		// The chunk's stylesheet link is its durable footprint: webpack removes
-		// the chunk script element once it has run. On these pages the listing
-		// fill never registers, because its slot element is not rendered, so no
-		// code path can request the chunk at all.
-		const listingAssets = () =>
-			page.locator( 'link[href*="settings-email-listing"]' );
-
-		// Other settings tabs run the same settings-embed script but never
-		// fetch the listing chunk.
-		await setBlockEmailEditorFeatureFlag( baseURL, 'yes' );
-		await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=general' );
-		await expect(
-			page.locator( 'script[src*="settings-embed"]' )
-		).toHaveCount( 1 );
-		await expect( listingAssets() ).toHaveCount( 0 );
-
-		// With the block email editor off there is no listing slot, so the
-		// Emails tab does not fetch the chunk either.
-		await setBlockEmailEditorFeatureFlag( baseURL, 'no' );
-		await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
-		await expect(
-			page.locator( '#wc_settings_email_listing_slotfill' )
-		).toHaveCount( 0 );
-		await expect( listingAssets() ).toHaveCount( 0 );
-	} );
-} );
+
+		test( 'Email listing assets load only where the listing renders', async ( {
+			page,
+			baseURL,
+		} ) => {
+			// The chunk's stylesheet link is its durable footprint: webpack removes
+			// the chunk script element once it has run. On these pages the listing
+			// fill never registers, because its slot element is not rendered, so no
+			// code path can request the chunk at all.
+			const listingAssets = () =>
+				page.locator( 'link[href*="settings-email-listing"]' );
+
+			// Other settings tabs run the same settings-embed script but never
+			// fetch the listing chunk.
+			await setBlockEmailEditorFeatureFlag( baseURL, 'yes' );
+			await page.goto(
+				'wp-admin/admin.php?page=wc-settings&tab=general'
+			);
+			await expect(
+				page.locator( 'script[src*="settings-embed"]' )
+			).toHaveCount( 1 );
+			await expect( listingAssets() ).toHaveCount( 0 );
+
+			// With the block email editor off there is no listing slot, so the
+			// Emails tab does not fetch the chunk either.
+			await setBlockEmailEditorFeatureFlag( baseURL, 'no' );
+			await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
+			await expect(
+				page.locator( '#wc_settings_email_listing_slotfill' )
+			).toHaveCount( 0 );
+			await expect( listingAssets() ).toHaveCount( 0 );
+		} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/email/settings-email.spec.ts b/plugins/woocommerce/tests/e2e/tests/email/settings-email.spec.ts
index 558fae83d15..78a11d2f306 100644
--- a/plugins/woocommerce/tests/e2e/tests/email/settings-email.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email/settings-email.spec.ts
@@ -8,7 +8,7 @@ import { test, expect, type Page } from '@playwright/test';
  */
 import { setFeatureEmailImprovementsFlag } from './helpers/set-email-improvements-feature-flag';
 import { disableEmailEditor } from '../email-editor/helpers/enable-email-editor-feature';
-import { tags } from '../../fixtures/fixtures';
+import { tags, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';

 const pickImageFromLibrary = async ( page: Page, imageName: string ) => {
@@ -17,154 +17,171 @@ const pickImageFromLibrary = async ( page: Page, imageName: string ) => {
 	await page.getByRole( 'button', { name: 'Select', exact: true } ).click();
 };

-test.describe( 'WooCommerce Email Settings', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
+test.describe(
+	'WooCommerce Email Settings',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );

-	const storeName = 'WooCommerce Core E2E Test Suite';
+		const storeName = 'WooCommerce Core E2E Test Suite';

-	test.beforeEach( async ( { baseURL } ) => {
-		await disableEmailEditor( baseURL );
-	} );
-
-	test.afterAll( async ( { baseURL } ) => {
-		await setFeatureEmailImprovementsFlag( baseURL, 'no' );
-		await disableEmailEditor( baseURL );
-	} );
+		test.beforeEach( async ( { baseURL } ) => {
+			await disableEmailEditor( baseURL );
+		} );

-	test(
-		'Live preview when changing email settings',
-		{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
-		async ( { page, baseURL } ) => {
+		test.afterAll( async ( { baseURL } ) => {
 			await setFeatureEmailImprovementsFlag( baseURL, 'no' );
-			await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
-
-			const iframeSelector = '#wc_settings_email_preview_slotfill iframe';
-			const iframe = page.frameLocator( iframeSelector );
-			const subject = page.locator(
-				'.wc-settings-email-preview-header-subject'
-			);
-
-			const iframeContainsHtml = async ( code: string ) => {
-				const content = await iframe.locator( 'html' ).innerHTML();
-				return content.includes( code );
-			};
-
-			await expect(
-				iframe.getByText( 'Thank you for your order' )
-			).toBeVisible();
-			await expect( subject ).toContainText(
-				`Your ${ storeName } order has been received!`
-			);
-
-			await page
-				.getByLabel( 'Email preview type' )
-				.selectOption( 'Reset password' );
-			await expect(
-				iframe.getByText( 'Someone has requested a new password' )
-			).toBeVisible();
-			await expect( subject ).toContainText(
-				`Password Reset Request for ${ storeName }`
-			);
-
-			const baseColorId = 'woocommerce_email_base_color';
-			const baseColorValue = '#012345';
-
-			await page.locator( `#${ baseColorId }` ).fill( baseColorValue );
+			await disableEmailEditor( baseURL );
+		} );

-			await page.evaluate(
-				async ( args ) => {
-					const input = document.getElementById( args.baseColorId );
-					const iframeElement = document.querySelector(
-						args.iframeSelector
-					);
-					if ( ! input || ! iframeElement ) {
-						throw new Error(
-							'The live-preview inputs must be mounted.'
+		test(
+			'Live preview when changing email settings',
+			{ tag: [ tags.SKIP_ON_EXTERNAL_ENV ] },
+			async ( { page, baseURL } ) => {
+				await setFeatureEmailImprovementsFlag( baseURL, 'no' );
+				await page.goto(
+					'wp-admin/admin.php?page=wc-settings&tab=email'
+				);
+
+				const iframeSelector =
+					'#wc_settings_email_preview_slotfill iframe';
+				const iframe = page.frameLocator( iframeSelector );
+				const subject = page.locator(
+					'.wc-settings-email-preview-header-subject'
+				);
+
+				const iframeContainsHtml = async ( code: string ) => {
+					const content = await iframe.locator( 'html' ).innerHTML();
+					return content.includes( code );
+				};
+
+				await expect(
+					iframe.getByText( 'Thank you for your order' )
+				).toBeVisible();
+				await expect( subject ).toContainText(
+					`Your ${ storeName } order has been received!`
+				);
+
+				await page
+					.getByLabel( 'Email preview type' )
+					.selectOption( 'Reset password' );
+				await expect(
+					iframe.getByText( 'Someone has requested a new password' )
+				).toBeVisible();
+				await expect( subject ).toContainText(
+					`Password Reset Request for ${ storeName }`
+				);
+
+				const baseColorId = 'woocommerce_email_base_color';
+				const baseColorValue = '#012345';
+
+				await page
+					.locator( `#${ baseColorId }` )
+					.fill( baseColorValue );
+
+				await page.evaluate(
+					async ( args ) => {
+						const input = document.getElementById(
+							args.baseColorId
 						);
-					}
-
-					await Promise.all( [
-						new Promise( ( resolve ) => {
-							input.addEventListener(
-								'transient-saved',
-								() => resolve(),
-								{ once: true }
-							);
-						} ),
-						new Promise( ( resolve ) => {
-							iframeElement.addEventListener(
-								'load',
-								() => resolve(),
-								{
-									once: true,
-								}
+						const iframeElement = document.querySelector(
+							args.iframeSelector
+						);
+						if ( ! input || ! iframeElement ) {
+							throw new Error(
+								'The live-preview inputs must be mounted.'
 							);
-						} ),
-						Promise.resolve().then( () => input.blur() ),
-					] );
-				},
-				{ baseColorId, iframeSelector }
-			);
+						}
+
+						await Promise.all( [
+							new Promise( ( resolve ) => {
+								input.addEventListener(
+									'transient-saved',
+									() => resolve(),
+									{ once: true }
+								);
+							} ),
+							new Promise( ( resolve ) => {
+								iframeElement.addEventListener(
+									'load',
+									() => resolve(),
+									{
+										once: true,
+									}
+								);
+							} ),
+							Promise.resolve().then( () => input.blur() ),
+						] );
+					},
+					{ baseColorId, iframeSelector }
+				);
+
+				expect(
+					await iframeContainsHtml( baseColorValue )
+				).toBeTruthy();
+
+				await page.reload();
+				expect(
+					await iframeContainsHtml( baseColorValue )
+				).toBeFalsy();
+			}
+		);

-			expect( await iframeContainsHtml( baseColorValue ) ).toBeTruthy();
+		test( 'Send email preview', async ( { page, baseURL } ) => {
+			await setFeatureEmailImprovementsFlag( baseURL, 'no' );
+			await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );

-			await page.reload();
-			expect( await iframeContainsHtml( baseColorValue ) ).toBeFalsy();
-		}
-	);
+			// Click the "Send a test email" button
+			await page
+				.getByRole( 'button', { name: 'Send a test email' } )
+				.click();
+
+			// Verify that the modal window is open
+			const modal = page.getByRole( 'dialog' );
+			await expect( modal ).toBeVisible();
+
+			// Verify that the "Send test email" button is disabled
+			const sendButton = modal.getByRole( 'button', {
+				name: 'Send test email',
+			} );
+			await expect( sendButton ).toBeDisabled();
+
+			// Fill in the email address field
+			const email = 'test@example.com';
+			const emailInput = modal.getByLabel( 'Send to' );
+			await emailInput.fill( email );
+
+			// Verify the "Send test email" button is now enabled
+			await expect( sendButton ).toBeEnabled();
+			await sendButton.click();
+
+			// Sending fails in the test env (no mail server); the backend returns
+			// `woocommerce_rest_email_preview_not_sent`, which hits the generic fallback in friendlyEmailSendError.
+			const message = modal.locator(
+				"text=Couldn't send the test email. Check your email settings and try again."
+			);
+			await expect( message ).toBeVisible();
+		} );

-	test( 'Send email preview', async ( { page, baseURL } ) => {
-		await setFeatureEmailImprovementsFlag( baseURL, 'no' );
-		await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
+		test( 'Choose image in email image url field', async ( { page } ) => {
+			const logoImageElement = '.wc-settings-email-logo-image';
+			const uploadIconElement = '.wc-settings-email-select-image-icon';

-		// Click the "Send a test email" button
-		await page.getByRole( 'button', { name: 'Send a test email' } ).click();
+			await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );

-		// Verify that the modal window is open
-		const modal = page.getByRole( 'dialog' );
-		await expect( modal ).toBeVisible();
+			// Pick image
+			await page.locator( '.wc-settings-email-select-image' ).click();
+			await pickImageFromLibrary( page, 'image-03' );
+			await expect( page.locator( logoImageElement ) ).toBeVisible();
+			await expect( page.locator( uploadIconElement ) ).toBeHidden();

-		// Verify that the "Send test email" button is disabled
-		const sendButton = modal.getByRole( 'button', {
-			name: 'Send test email',
+			// Remove an image
+			await page
+				.locator( '#wc_settings_email_image_url_slotfill' )
+				.getByRole( 'button', { name: 'Remove', exact: true } )
+				.click();
+			await expect( page.locator( logoImageElement ) ).toBeHidden();
+			await expect( page.locator( uploadIconElement ) ).toBeVisible();
 		} );
-		await expect( sendButton ).toBeDisabled();
-
-		// Fill in the email address field
-		const email = 'test@example.com';
-		const emailInput = modal.getByLabel( 'Send to' );
-		await emailInput.fill( email );
-
-		// Verify the "Send test email" button is now enabled
-		await expect( sendButton ).toBeEnabled();
-		await sendButton.click();
-
-		// Sending fails in the test env (no mail server); the backend returns
-		// `woocommerce_rest_email_preview_not_sent`, which hits the generic fallback in friendlyEmailSendError.
-		const message = modal.locator(
-			"text=Couldn't send the test email. Check your email settings and try again."
-		);
-		await expect( message ).toBeVisible();
-	} );
-
-	test( 'Choose image in email image url field', async ( { page } ) => {
-		const logoImageElement = '.wc-settings-email-logo-image';
-		const uploadIconElement = '.wc-settings-email-select-image-icon';
-
-		await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
-
-		// Pick image
-		await page.locator( '.wc-settings-email-select-image' ).click();
-		await pickImageFromLibrary( page, 'image-03' );
-		await expect( page.locator( logoImageElement ) ).toBeVisible();
-		await expect( page.locator( uploadIconElement ) ).toBeHidden();
-
-		// Remove an image
-		await page
-			.locator( '#wc_settings_email_image_url_slotfill' )
-			.getByRole( 'button', { name: 'Remove', exact: true } )
-			.click();
-		await expect( page.locator( logoImageElement ) ).toBeHidden();
-		await expect( page.locator( uploadIconElement ) ).toBeVisible();
-	} );
-} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/tests/settings/colour-picker-swatch-height.spec.ts b/plugins/woocommerce/tests/e2e/tests/settings/colour-picker-swatch-height.spec.ts
index 4e9dc9d4d19..4bfb7e9d6db 100644
--- a/plugins/woocommerce/tests/e2e/tests/settings/colour-picker-swatch-height.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/settings/colour-picker-swatch-height.spec.ts
@@ -1,51 +1,55 @@
 /**
  * Internal dependencies
  */
-import { test, expect } from '../../fixtures/fixtures';
+import { test, expect, locks } from '../../fixtures/fixtures';
 import { ADMIN_STATE_PATH } from '../../playwright.config';

-test.describe( 'Colour picker swatch height on Email settings', () => {
-	test.use( { storageState: ADMIN_STATE_PATH } );
-
-	test( 'colour swatch is correctly sized with WP 7.0 body class', async ( {
-		page,
-	} ) => {
-		await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
-
-		await expect( page.locator( 'body' ) ).toHaveClass(
-			/wc-wp-version-gte-53/
-		);
-
-		// The swatch below is the one the settings screen renders, not a mounted
-		// fixture, so the assertions also prove the classic colour fields are still
-		// on this screen.
-		const swatch = page.locator( '.colorpickpreview' ).first();
-		await expect( swatch ).toBeVisible();
-
-		// Measure the painted box rather than the declared CSS. The swatch carries a
-		// 1px border, so the two differ wherever the rule sets no box-sizing, and
-		// making them agree at 40px is the whole point of the WP 7.0 rule.
-		await page.evaluate( () => {
-			document.body.classList.remove( 'wc-wp-version-gte-70' );
+test.describe(
+	'Colour picker swatch height on Email settings',
+	{ lock: locks.EMAIL_FEATURE_FLAGS },
+	() => {
+		test.use( { storageState: ADMIN_STATE_PATH } );
+
+		test( 'colour swatch is correctly sized with WP 7.0 body class', async ( {
+			page,
+		} ) => {
+			await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=email' );
+
+			await expect( page.locator( 'body' ) ).toHaveClass(
+				/wc-wp-version-gte-53/
+			);
+
+			// The swatch below is the one the settings screen renders, not a mounted
+			// fixture, so the assertions also prove the classic colour fields are still
+			// on this screen.
+			const swatch = page.locator( '.colorpickpreview' ).first();
+			await expect( swatch ).toBeVisible();
+
+			// Measure the painted box rather than the declared CSS. The swatch carries a
+			// 1px border, so the two differ wherever the rule sets no box-sizing, and
+			// making them agree at 40px is the whole point of the WP 7.0 rule.
+			await page.evaluate( () => {
+				document.body.classList.remove( 'wc-wp-version-gte-70' );
+			} );
+
+			// Pre-WP 7.0: 30px declared, content-box, so 32px painted. This figure is for
+			// the wide layout; the same rule has a max-width: 782px branch that declares
+			// 40px, which would paint 42px.
+			expect( await swatch.boundingBox() ).toMatchObject( {
+				height: 32,
+				width: 32,
+			} );
+
+			await page.evaluate( () => {
+				document.body.classList.add( 'wc-wp-version-gte-70' );
+			} );
+
+			// WP 7.0+: 40px declared with box-sizing: border-box, so 40px painted, which
+			// is what matches the taller WP 7.0 inputs.
+			expect( await swatch.boundingBox() ).toMatchObject( {
+				height: 40,
+				width: 40,
+			} );
 		} );
-
-		// Pre-WP 7.0: 30px declared, content-box, so 32px painted. This figure is for
-		// the wide layout; the same rule has a max-width: 782px branch that declares
-		// 40px, which would paint 42px.
-		expect( await swatch.boundingBox() ).toMatchObject( {
-			height: 32,
-			width: 32,
-		} );
-
-		await page.evaluate( () => {
-			document.body.classList.add( 'wc-wp-version-gte-70' );
-		} );
-
-		// WP 7.0+: 40px declared with box-sizing: border-box, so 40px painted, which
-		// is what matches the taller WP 7.0 inputs.
-		expect( await swatch.boundingBox() ).toMatchObject( {
-			height: 40,
-			width: 40,
-		} );
-	} );
-} );
+	}
+);
diff --git a/plugins/woocommerce/tests/e2e/utils/back-in-stock-notifications.ts b/plugins/woocommerce/tests/e2e/utils/back-in-stock-notifications.ts
index 852730c175f..dd24c870b45 100644
--- a/plugins/woocommerce/tests/e2e/utils/back-in-stock-notifications.ts
+++ b/plugins/woocommerce/tests/e2e/utils/back-in-stock-notifications.ts
@@ -554,8 +554,8 @@ export async function signUpInNewContext(
 	} );
 	const page = await context.newPage();

-	// Closed in `finally`: these specs run on a single worker, so a context
-	// left open by a failed signup would otherwise outlive the test.
+	// Closed in `finally` so a context left open by a failed signup does not
+	// outlive the test.
 	try {
 		await page.goto( permalink );