Skip to content

Commit 46b2bd9

Browse files
author
vmarta_sfemu
committed
Sync from monorepo
Template version: 1.0.0-alpha.0 Uses NPM packages @salesforce/storefront-next-* v1.0.0-alpha.0 Synced by: vmarta_sfemu Monorepo SHA: 2e03a3c5632dc86e841fb5d4f925a0fc823afc92 Latest change: 2e03a3c56 - Backport #1889: Move full @core E2E suite to pre-merge gate [release-1.0.x] (#1902)
1 parent da03e51 commit 46b2bd9

16 files changed

Lines changed: 316 additions & 198 deletions

e2e/CLAUDE.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -320,20 +320,41 @@ Tests can be tagged for easy filtering and organization:
320320

321321
#### Test Tiers (`@smoke` vs `@core`)
322322

323-
Three tiers gate different stages of CI:
323+
Two tiers gate three pipelines. Every `@smoke` scenario also carries `@core` (via the Feature-level tag) — so `@smoke ⊂ @core`.
324324

325-
| Tier | Tag | Where it runs | What it covers |
325+
| Tier | Tag | What it covers | Wall clock |
326326
|---|---|---|---|
327-
| Smoke | `@smoke` | Every PR (sequential, ~5–6 min budget) | Critical revenue/auth paths only — must pass to merge because every merge auto-deploys to a production demo site |
328-
| Core | `@core` | Post-merge on `push` to `main` / `release-*` (queued, never cancelled), and nightly | OOTB regression set; failure here triggers a Slack notification with PR + author attribution |
329-
| Full | (no tag filter) | Manual / on demand | Everything, including non-`@core` specs (e.g., multi-site, performance) |
327+
| Smoke | `@smoke` | Critical revenue/auth paths (homepage load, PLP→PDP→cart, login, checkout place-order) | ~6 min |
328+
| Core | `@core` | OOTB regression set | ~27 min |
329+
330+
**Where each tier runs:**
331+
332+
| Pipeline | Trigger | Grep | Behavior on failure |
333+
|---|---|---|---|
334+
| Pre-merge gate (`e2e-core-pr.yml`) | every PR + `merge_group` | pass 1: `@smoke`, then pass 2: `(?=.*@core)(?!.*@smoke)` on the same pool target | pass 1 fails → pass 2 skipped (fast-fail); merge blocked until both pass. Bypass with the [`skip-e2e` label](#skip-e2e-pr-label-escape-hatch). |
335+
| Post-merge canary (`e2e-postmerge.yml`) | push to `main` / `release-*` | `@smoke` | retries once on a fresh pool target; if both attempts fail → Slack page |
336+
| Nightly (`e2e-core-nightly.yml`) | cron | `@core` | full run on a dedicated target (no pool pressure); flake baseline + drift detector |
330337

331338
**Rules for tagging:**
332339

333-
- **Add `@smoke` sparingly.** A scenario earns `@smoke` only if it gates revenue or auth — homepage load, PLP→PDP→cart, login, checkout place-order. Anything else stays in `@core`. The smoke set should fit a ~5–6 min budget at `workers: 1`.
334-
- **`@smoke` scenarios must also stay in `@core`.** All `@smoke` scenarios live inside `@core` Features, so they automatically pick up `@core` from the Feature-level tag. Don't drop the Feature tag.
340+
- **Add `@smoke` sparingly.** A scenario earns `@smoke` only if it gates revenue or auth. Anything else stays in `@core`. Keep the smoke set under a ~6 min budget at `workers: 2`.
341+
- **`@smoke` scenarios must also stay in `@core`.** All `@smoke` scenarios live inside `@core` Features and pick up `@core` from the Feature-level tag. Don't drop the Feature tag.
335342
- **If a `@smoke` scenario flakes**, treat it as P0 — either fix it or downgrade it to `@core`. Don't ship a flaky smoke gate.
336343

344+
#### `skip-e2e` PR label (escape hatch)
345+
346+
Add the `skip-e2e` label to a PR to bypass the E2E gate entirely. The `check_skip` job in `e2e-core-pr.yml` forwards the result as `bypass: true` to `e2e-runner.yml`, which causes the inner `run_e2e_tests` job to be skipped at the job level. GitHub still reports a "Skipped" status for `call_runner / run_e2e_tests`, and branch protection treats that as success.
347+
348+
**Use sparingly.** Appropriate cases:
349+
350+
- Changelog-only or docs-only PRs that don't touch product code.
351+
- Hot-revert PRs where the revert itself was already covered by the original PR's gate and the priority is restoring `main`.
352+
- Infra-only PRs that demonstrably can't affect E2E behavior (e.g., editing GitHub Actions workflows that aren't on the test path).
353+
354+
**Inappropriate cases.** Anything that touches storefront source, SDK runtime, route loaders/actions, page objects, or test infrastructure. If you're unsure, run the gate — a 27 min run is cheaper than a broken `main` and a Slack page from the post-merge canary.
355+
356+
**The label only bypasses `pull_request` runs, not the merge queue.** When a PR is added to the merge queue, GitHub fires a `merge_group` event against a synthetic merge commit (your PR + current `main`), and `e2e-core-pr.yml` re-runs against that ref. PR labels aren't attached to the merge commit, so `check_skip` always evaluates to `skip=false` on `merge_group` and the full ~27 min suite runs. This is intentional — the merge queue exists to catch semantic conflicts that can't be detected at the PR level — but expect the wait when queueing a labelled PR.
357+
337358
#### Basic Tag Usage
338359

339360
```typescript

e2e/src/specs/core/account-addresses.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@
5353

5454
Feature('Account Addresses Tests').tag('@core').tag('@account').tag('@addresses');
5555

56+
// TODO: The "Deleting default address auto-promotes remaining
57+
// address" scenario times out on `waitForInvisible` of the deleted card
58+
// (5s budget). Possibly a real regression or a timing flake; leaving the
59+
// rest of the suite running to keep coverage.
60+
const isDeleteDefaultAddressBroken = true;
61+
const deleteDefaultAddressScenario = isDeleteDefaultAddressBroken ? Scenario.skip : Scenario;
62+
5663
const { accountAddressesPage, apiLoginFlow, signupFlow, storefrontPage } = inject();
5764
import { expect } from 'chai';
5865

@@ -431,7 +438,7 @@ Scenario('User can delete non-default address', async () => {
431438
* - State management across multiple API calls
432439
* - Edge case handling in live app
433440
*/
434-
Scenario('Deleting default address auto-promotes remaining address', async () => {
441+
deleteDefaultAddressScenario('Deleting default address auto-promotes remaining address', async () => {
435442
accountAddressesPage.navigate();
436443

437444
// Ensure we have exactly 2 addresses for this test

e2e/src/specs/core/account-details.spec.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616

1717
Feature('Account Details Tests').tag('@core').tag('@account').tag('@user-account');
1818

19+
// TODO: Skipped pending fixes to (1) CheckoutPage.fillContactInfo
20+
// "Continue to Shipping Address" click timeout (the change-email scenario
21+
// routes through checkout) and (2) the SLAS 409 "Tenant has already performed
22+
// login in last 1 sec" rate-limit hit in the per-scenario Before hook.
23+
// Re-enable when both root causes are fixed.
24+
const isBroken = true;
25+
const scenarioFn = isBroken ? Scenario.skip : Scenario;
26+
1927
const { I, storefrontPage, accountDetailsPage, apiLoginFlow, signupFlow } = inject();
2028
import { expect } from 'chai';
2129

@@ -52,7 +60,7 @@ Before(async () => {
5260
* 3. Validate authenticated session cookies
5361
* 4. Verify page title and structure
5462
*/
55-
Scenario('Account details page loads successfully for authenticated user', async () => {
63+
scenarioFn('Account details page loads successfully for authenticated user', async () => {
5664
// Navigate to account details
5765
accountDetailsPage.navigate();
5866

@@ -83,7 +91,7 @@ Scenario('Account details page loads successfully for authenticated user', async
8391
* 2. Verify profile card displays user information
8492
* 3. Validate that all profile fields are visible
8593
*/
86-
Scenario('Profile information is displayed correctly', async () => {
94+
scenarioFn('Profile information is displayed correctly', async () => {
8795
accountDetailsPage.navigate();
8896

8997
// Get displayed profile data
@@ -109,7 +117,7 @@ Scenario('Profile information is displayed correctly', async () => {
109117
* 5. Verify success message
110118
* 6. Validate changes are displayed
111119
*/
112-
Scenario('User can successfully update profile information', async () => {
120+
scenarioFn('User can successfully update profile information', async () => {
113121
accountDetailsPage.navigate();
114122

115123
// Update profile with new data using helper method
@@ -143,7 +151,7 @@ Scenario('User can successfully update profile information', async () => {
143151
* 4. Click Cancel
144152
* 5. Verify changes are not saved
145153
*/
146-
Scenario('User can cancel profile editing without saving changes', async () => {
154+
scenarioFn('User can cancel profile editing without saving changes', async () => {
147155
accountDetailsPage.navigate();
148156

149157
// Get current data
@@ -214,7 +222,7 @@ Scenario('User can cancel profile editing without saving changes', async () => {
214222
* 2. Refresh the page
215223
* 3. Verify updated data persists
216224
*/
217-
Scenario('Profile changes persist after page refresh', async () => {
225+
scenarioFn('Profile changes persist after page refresh', async () => {
218226
accountDetailsPage.navigate();
219227

220228
// Edit and save profile using helper method
@@ -249,7 +257,7 @@ Scenario('Profile changes persist after page refresh', async () => {
249257
* 5. Verify success message
250258
* 6. Verify automatic re-authentication
251259
*/
252-
Scenario('User can successfully change password', async () => {
260+
scenarioFn('User can successfully change password', async () => {
253261
accountDetailsPage.navigate();
254262

255263
// Click Change Password button
@@ -308,7 +316,7 @@ Scenario('User can successfully change password', async () => {
308316
* 4. Click Cancel
309317
* 5. Verify form closes without saving
310318
*/
311-
Scenario('User can cancel password change without saving', async () => {
319+
scenarioFn('User can cancel password change without saving', async () => {
312320
accountDetailsPage.navigate();
313321

314322
// Click Change Password button
@@ -343,7 +351,7 @@ Scenario('User can cancel password change without saving', async () => {
343351
* 4. Attempt to save
344352
* 5. Verify error message
345353
*/
346-
Scenario('Password change fails with incorrect current password', () => {
354+
scenarioFn('Password change fails with incorrect current password', () => {
347355
accountDetailsPage.navigate();
348356

349357
// Click Change Password button
@@ -379,7 +387,7 @@ Scenario('Password change fails with incorrect current password', () => {
379387
* 4. Verify password requirements indicators show errors
380388
* 5. Verify Save button is disabled or save fails
381389
*/
382-
Scenario('Password change validates password strength requirements', () => {
390+
scenarioFn('Password change validates password strength requirements', () => {
383391
accountDetailsPage.navigate();
384392

385393
// Use spec-scoped credentials
@@ -415,7 +423,7 @@ Scenario('Password change validates password strength requirements', () => {
415423
* 4. Attempt to save
416424
* 5. Verify validation error
417425
*/
418-
Scenario('Password change fails when confirmation does not match', async () => {
426+
scenarioFn('Password change fails when confirmation does not match', async () => {
419427
accountDetailsPage.navigate();
420428

421429
// Use spec-scoped credentials
@@ -475,7 +483,7 @@ Scenario('Password change fails when confirmation does not match', async () => {
475483
* E2E value: Confirms section renders in the live app with real auth/customerId,
476484
* which unit tests and stories cannot verify.
477485
*/
478-
Scenario('Interests & Preferences section renders for authenticated user', () => {
486+
scenarioFn('Interests & Preferences section renders for authenticated user', () => {
479487
accountDetailsPage.navigate();
480488

481489
I.seeElement(accountDetailsPage.locators.interestsPreferencesCard);
@@ -494,7 +502,7 @@ Scenario('Interests & Preferences section renders for authenticated user', () =>
494502
*
495503
* E2E value: Verifies the flow in the real page layout with actual async timing.
496504
*/
497-
Scenario('I&P edit mode toggle — cancel restores view mode', () => {
505+
scenarioFn('I&P edit mode toggle — cancel restores view mode', () => {
498506
accountDetailsPage.navigate();
499507

500508
accountDetailsPage.clickEditInterestsPreferences();
@@ -528,7 +536,7 @@ Scenario('I&P edit mode toggle — cancel restores view mode', () => {
528536
* E2E value: Full dialog flow (open → tab switch → checkbox → save → badge)
529537
* not covered by unit tests or stories.
530538
*/
531-
Scenario('Add an interest via the tabbed dialog', async () => {
539+
scenarioFn('Add an interest via the tabbed dialog', async () => {
532540
accountDetailsPage.navigate();
533541

534542
accountDetailsPage.clickEditInterestsPreferences();
@@ -563,7 +571,7 @@ Scenario('Add an interest via the tabbed dialog', async () => {
563571
*
564572
* E2E value: Real browser aria-label button interaction not covered by unit tests.
565573
*/
566-
Scenario('Remove an interest badge in edit mode', async () => {
574+
scenarioFn('Remove an interest badge in edit mode', async () => {
567575
accountDetailsPage.navigate();
568576

569577
accountDetailsPage.clickEditInterestsPreferences();
@@ -605,7 +613,7 @@ Scenario('Remove an interest badge in edit mode', async () => {
605613
*
606614
* E2E value: Tests the second dialog type (multi-select) end-to-end.
607615
*/
608-
Scenario('Add a product category via multi-select dialog', async () => {
616+
scenarioFn('Add a product category via multi-select dialog', async () => {
609617
accountDetailsPage.navigate();
610618

611619
accountDetailsPage.clickEditInterestsPreferences();
@@ -638,7 +646,7 @@ Scenario('Add a product category via multi-select dialog', async () => {
638646
* E2E value: Full "edit → save → toast → view mode reflects changes" cycle, including
639647
* the success toast fired by the parent component — not covered by unit tests or stories.
640648
*/
641-
Scenario('I&P full save flow shows success toast and reflects state in view mode', async () => {
649+
scenarioFn('I&P full save flow shows success toast and reflects state in view mode', async () => {
642650
accountDetailsPage.navigate();
643651

644652
accountDetailsPage.clickEditInterestsPreferences();
@@ -675,7 +683,7 @@ Scenario('I&P full save flow shows success toast and reflects state in view mode
675683
*
676684
* E2E value: Verifies full discard behavior across change types in the real browser.
677685
*/
678-
Scenario('I&P cancel discards all pending changes', async () => {
686+
scenarioFn('I&P cancel discards all pending changes', async () => {
679687
accountDetailsPage.navigate();
680688

681689
accountDetailsPage.clickEditInterestsPreferences();

e2e/src/specs/core/checkout-analytics.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
Feature('Storefront Checkout Analytics Tests').tag('@core').tag('@checkout').tag('@analytics');
1818

19+
// TODO: Skipped pending fix to CheckoutPage.fillContactInfo —
20+
// "Continue to Shipping Address" click times out on pool topology since
21+
// 2026-06-01. Re-enable when the checkout team lands the fix.
22+
const isBroken = true;
23+
const scenarioFn = isBroken ? Scenario.skip : Scenario;
24+
1925
const { checkoutPage, apiCartSetupFlow, storefrontPage, beaconCaptureFlow } = inject();
2026
import { expect } from 'chai';
2127
import { TEST_PRODUCT_CATEGORIES, generateTestEmail } from '../../test-data/checkout.data';
@@ -26,7 +32,7 @@ import { TEST_PRODUCT_CATEGORIES, generateTestEmail } from '../../test-data/chec
2632
* Validates that checkout_start events include the checkoutType attribute
2733
* with value 'one-click' when sent to Einstein.
2834
*/
29-
Scenario('Checkout start event should include checkoutType attribute', async () => {
35+
scenarioFn('Checkout start event should include checkoutType attribute', async () => {
3036
await beaconCaptureFlow.setupInterception('beginCheckout');
3137

3238
storefrontPage.navigate();
@@ -52,7 +58,7 @@ Scenario('Checkout start event should include checkoutType attribute', async ()
5258
* Validates that checkout_step events include the checkoutType attribute
5359
* with value 'one-click' when sent to Einstein.
5460
*/
55-
Scenario('Checkout step event should include checkoutType attribute', async () => {
61+
scenarioFn('Checkout step event should include checkoutType attribute', async () => {
5662
await beaconCaptureFlow.setupInterception('checkoutStep');
5763

5864
storefrontPage.navigate();

e2e/src/specs/core/checkout-billing-validation.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
Feature('Checkout Billing Address Validation Tests').tag('@core').tag('@checkout').tag('@billing-validation');
1818

19+
// TODO: Skipped pending fix to CheckoutPage.fillContactInfo —
20+
// "Continue to Shipping Address" click times out on pool topology since
21+
// 2026-06-01. Re-enable when the checkout team lands the fix.
22+
const isBroken = true;
23+
const scenarioFn = isBroken ? Scenario.skip : Scenario;
24+
1925
const { checkoutPage, apiCartSetupFlow } = inject();
2026
import { expect } from 'chai';
2127
import {
@@ -25,7 +31,7 @@ import {
2531
generateTestEmail,
2632
} from '../../test-data/checkout.data';
2733

28-
Scenario('Billing address validation — required fields show errors', async () => {
34+
scenarioFn('Billing address validation — required fields show errors', async () => {
2935
const productInfo = await apiCartSetupFlow.executeAndNavigateToCheckout(TEST_PRODUCT_CATEGORIES.MENS_JACKETS);
3036
expect(productInfo).to.not.be.undefined;
3137

e2e/src/specs/core/checkout-multi-currency.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,15 @@ const siteAliases: readonly string[] = TEST_LOCALE_CURRENCIES.map((e) => e.siteA
4040
// (/:siteId/:localeId). Self-skip for all other URL configurations.
4141
const isPrefixSiteLocale = Boolean(process.env.SITE_ALIAS) && Boolean(process.env.LOCALE);
4242

43+
// TODO: completeCheckout() routes through CheckoutPage.fillContactInfo,
44+
// whose "Continue to Shipping Address" click times out on pool topology since
45+
// 2026-06-01. Re-enable when the checkout team lands the fix.
46+
const isBroken = true;
47+
4348
for (const localeCurrency of TEST_LOCALE_CURRENCIES) {
4449
const envAlias = process.env.SITE_ALIAS;
4550
const canRun =
51+
!isBroken &&
4652
isPrefixSiteLocale &&
4753
(localeCurrency.siteAlias === envAlias ||
4854
(!siteAliases.includes(envAlias as string) && localeCurrency.locale === process.env.LOCALE));

e2e/src/specs/core/checkout-multiple-items.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
Feature('Checkout Multiple Items Tests').tag('@core').tag('@checkout');
1818

19+
// TODO: Skipped pending fix to CheckoutPage.fillContactInfo —
20+
// "Continue to Shipping Address" click times out on pool topology since
21+
// 2026-06-01. Re-enable when the checkout team lands the fix.
22+
const isBroken = true;
23+
const scenarioFn = isBroken ? Scenario.skip : Scenario;
24+
1925
const { checkoutPage, addToCartFlow } = inject();
2026
import { expect } from 'chai';
2127
import {
@@ -25,7 +31,7 @@ import {
2531
generateTestEmail,
2632
} from '../../test-data/checkout.data';
2733

28-
Scenario('Guest checkout with multiple items in cart', async () => {
34+
scenarioFn('Guest checkout with multiple items in cart', async () => {
2935
const productInfo1 = await addToCartFlow.execute(TEST_PRODUCT_CATEGORIES.MENS_JACKETS);
3036
expect(productInfo1).to.not.be.undefined;
3137

e2e/src/specs/core/checkout-order-summary-shipping.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727

2828
Feature('Checkout Order Summary & Shipping Method Tests').tag('@core').tag('@checkout');
2929

30+
// TODO: Skipped pending fix to CheckoutPage.fillContactInfo —
31+
// "Continue to Shipping Address" click times out on pool topology since
32+
// 2026-06-01. Re-enable when the checkout team lands the fix.
33+
const isBroken = true;
34+
const scenarioFn = isBroken ? Scenario.skip : Scenario;
35+
3036
const { checkoutPage, apiCartSetupFlow } = inject();
3137
import { expect } from 'chai';
3238
import { TEST_SHIPPING_ADDRESS, TEST_PRODUCT_CATEGORIES, generateTestEmail } from '../../test-data/checkout.data';
@@ -42,7 +48,7 @@ import { TEST_SHIPPING_ADDRESS, TEST_PRODUCT_CATEGORIES, generateTestEmail } fro
4248
* 5. Fill through checkout to payment step
4349
* 6. Verify order summary is still visible with a non-zero total
4450
*/
45-
Scenario('Order summary displays subtotal, shipping, tax, and total', async () => {
51+
scenarioFn('Order summary displays subtotal, shipping, tax, and total', async () => {
4652
const productInfo = await apiCartSetupFlow.executeAndNavigateToCheckout(TEST_PRODUCT_CATEGORIES.MENS_JACKETS);
4753
expect(productInfo, 'Product should be added to cart').to.not.be.undefined;
4854

@@ -82,7 +88,7 @@ Scenario('Order summary displays subtotal, shipping, tax, and total', async () =
8288
* 6. Select a shipping method (second option if available, otherwise first)
8389
* 7. Verify payment step is reached
8490
*/
85-
Scenario('Guest shopper can view and select different shipping methods', async () => {
91+
scenarioFn('Guest shopper can view and select different shipping methods', async () => {
8692
const productInfo = await apiCartSetupFlow.executeAndNavigateToCheckout(TEST_PRODUCT_CATEGORIES.MENS_JACKETS);
8793
expect(productInfo, 'Product should be added to cart').to.not.be.undefined;
8894

0 commit comments

Comments
 (0)