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 );