Skip to content

Commit 7550c97

Browse files
chanceaclarkclaude
andcommitted
test(core): TRAC-281 replace flaky add-to-cart toast assertions in cart and coupon E2E tests
The add-to-cart success message renders as a Sonner toast that auto-dismisses after ~4s, so asserting on it is inherently racy. The existing `toPass` retry in `cart.spec.ts` was also logically broken: the catch branch reloaded the page, which destroys client-side toast state, so retries could never succeed once the first attempt missed the toast. Drop the toast assertion in both tests and wait on `networkidle` after the add-to-cart click. The cart-page assertions on `/cart` remain as the actual verification. Additionally, rework the coupon-apply flake handling in `coupon.spec.ts`. The previous catch/reload pattern only recovered if the server had already applied the coupon (CATALYST-1685). When the action silently failed, the reload alone could not bring the coupon back. Replace it with a `toPass` retry that re-applies the coupon from a clean reloaded state if the optimistic update reverts. Refs TRAC-281 Refs CATALYST-1685 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ea7dba2 commit 7550c97

2 files changed

Lines changed: 30 additions & 42 deletions

File tree

core/tests/ui/e2e/cart.spec.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,9 @@ test('Cart page displays line item', async ({ page, catalog, currency }) => {
2020

2121
await page.goto(product.path);
2222
await page.getByRole('button', { name: t('Product.ProductDetails.Submit.addToCart') }).click();
23-
24-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
25-
const addToCartSuccessMessage = t.rich('Product.ProductDetails.successMessage', {
26-
cartItems: 1,
27-
cartLink: (chunks: React.ReactNode) => chunks,
28-
}) as string;
29-
30-
await expect(async () => {
31-
try {
32-
await expect(page.getByText(addToCartSuccessMessage)).toBeVisible();
33-
} catch {
34-
await page.reload();
35-
await expect(page.getByText(addToCartSuccessMessage)).toBeVisible();
36-
}
37-
}).toPass({ timeout: 30000, intervals: [2000] });
23+
// The success toast auto-dismisses after ~4s, so asserting on it is racy.
24+
// Wait for the add-to-cart action to settle and verify state via the /cart page instead.
25+
await page.waitForLoadState('networkidle');
3826

3927
await page.goto('/cart');
4028

core/tests/ui/e2e/coupon.spec.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,42 @@ test('Valid coupon code can be applied to the cart', async ({ page, catalog, pro
88

99
await page.goto(product.path);
1010
await page.getByRole('button', { name: t('Product.ProductDetails.Submit.addToCart') }).click();
11+
// The success toast auto-dismisses, so wait for the add-to-cart action to settle
12+
// and verify state on the /cart page.
13+
await page.waitForLoadState('networkidle');
1114

12-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
13-
const addToCartSuccessMessage = t.rich('Product.ProductDetails.successMessage', {
14-
cartItems: 1,
15-
cartLink: (chunks: React.ReactNode) => chunks,
16-
}) as string;
17-
18-
await expect(page.getByText(addToCartSuccessMessage)).toBeVisible();
1915
await page.goto('/cart');
20-
2116
await expect(page.getByRole('heading', { name: t('Cart.title') })).toBeVisible();
2217

23-
await page.getByLabel(t('Cart.CheckoutSummary.CouponCode.couponCode')).fill(coupon.code);
24-
await page.getByRole('button', { name: t('Cart.CheckoutSummary.CouponCode.apply') }).click();
25-
await page.waitForLoadState('networkidle');
18+
// TODO: Remove retry pattern when root cause of next state issue is found/resolved [CATALYST-1685]
19+
// The apply button can get stuck spinning, and the optimistic update reverts if the
20+
// server action never completes. Retry from a clean reloaded state until the coupon
21+
// sticks or the timeout is hit.
22+
await expect(async () => {
23+
if (
24+
await page
25+
.getByText(coupon.code)
26+
.isVisible()
27+
.catch(() => false)
28+
) {
29+
return;
30+
}
31+
32+
const couponInput = page.getByLabel(t('Cart.CheckoutSummary.CouponCode.couponCode'));
33+
34+
if (!(await couponInput.isVisible().catch(() => false))) {
35+
await page.reload();
36+
}
37+
38+
await couponInput.fill(coupon.code);
39+
await page.getByRole('button', { name: t('Cart.CheckoutSummary.CouponCode.apply') }).click();
40+
await page.waitForLoadState('networkidle');
2641

27-
try {
28-
await expect(page.getByText(coupon.code)).toBeVisible();
29-
await expect(
30-
page.getByRole('button', { name: t('Cart.CheckoutSummary.CouponCode.removeCouponCode') }),
31-
).toBeVisible();
32-
} catch {
33-
// TODO: Remove try/catch when root cause of next state issue is found/resolved [CATALYST-1685]
34-
// NextJS seems to have some issues when running local builds.
35-
// In this test, the coupon button will get stuck spinning forever and cause the assertions to fail.
36-
// This doesn't happen on deployed production builds, just local next builds.
37-
// To combat this, if the previous assertions fail, we hard refresh the page and then try again.
38-
await page.reload();
3942
await expect(page.getByText(coupon.code)).toBeVisible();
4043
await expect(
4144
page.getByRole('button', { name: t('Cart.CheckoutSummary.CouponCode.removeCouponCode') }),
4245
).toBeVisible();
43-
44-
// eslint-disable-next-line no-console
45-
console.warn('Coupon applied but page got stuck in loading state.');
46-
}
46+
}).toPass({ timeout: 60000, intervals: [3000] });
4747
});
4848

4949
test('Invalid coupon code cannot be applied', async ({ page, catalog }) => {

0 commit comments

Comments
 (0)