Back to BlogTroubleshooting

Cart and Wishlist Persistence Bugs That Quietly Lose Sales

Rupak Amin

Founder & Lead Engineer, RAITHub

9 min read

Cart and wishlist persistence bugs lose sales silently, because nothing throws an error: the cart empties on login, the wishlist vanishes on a new device, or a merge doubles the quantities. The usual causes are a merge that overwrites instead of combining, a cart kept only in the browser, and stale prices on reload. Each is reproducible, and each deserves a Playwright test that fails before the fix.

If you would rather have these found and guarded for you, see how RAITHub would test this below. First, the bugs and how to reproduce them.

Why do these bugs lose sales without anyone noticing?

Because the shopper does not report them; they just leave. A cart that empties at the login step looks to your logs like a shopper who changed their mind. There is no exception, no 500, no alert, so the bug survives for months while conversion quietly bleeds. The only way to catch it is to script the exact journey a real shopper takes, across the guest-to-account boundary and across devices, and assert the cart is still right. That is what the tests below do.

BugWhat the shopper seesUsual cause
Cart empties on loginItems added as a guest are gone after signing inLogin replaces the guest cart with the (empty) account cart instead of merging
Merge doubles quantitiesTwo of everything after loginThe merge adds guest lines to account lines without combining the same item
Cart or wishlist lost on a new deviceNothing saved when they switch phone or browserState is kept only in the browser, never tied to the account on the server
Prices or stock staleAn item is out of stock or a different price at checkoutThe cart stores a snapshot and never re-checks the catalogue

Where should the cart actually live?

A guest cart can live in the browser, but an account cart must live on the server, tied to the customer, or it cannot follow them to another device. The clean model is: a guest has a cart keyed by an anonymous token in a cookie; when they log in, that guest cart is merged into their account cart on the server; from then on the server is the source of truth and the browser is a cache. A wishlist, which is meant to last, should be on the server from the moment a logged-in customer saves to it.

The reload rule matters as much as the storage rule: a cart line stores the item and quantity, not the price. Price and stock are re-read from the catalogue on every view and at checkout, so a cart that sat overnight shows today's truth, and the final charge is computed on the server, never from a number the browser kept.

How do you fix the guest-to-login merge?

Merge, do not replace, and combine matching items by adding quantities rather than duplicating lines. The merge runs once, server-side, at the moment of login, and is idempotent so a repeated login does not merge twice.

// Merge a guest cart into the account cart at login. Combine matching
// variants by summing quantity; never overwrite the account cart.
export async function mergeGuestCart(db: Db, customerId: number, guestToken: string) {
  const guest = await db.carts.findByToken(guestToken)
  if (!guest) return
  const account = await db.carts.findOrCreateForCustomer(customerId)
  for (const line of guest.lines) {
    const existing = account.lines.find((l) => l.variantId === line.variantId)
    if (existing) existing.qty += line.qty          // combine, do not duplicate
    else account.lines.push(line)
  }
  await db.carts.save(account)
  await db.carts.delete(guest.id)                   // consume the guest cart once
}

How do you reproduce and guard these with Playwright?

Script the journeys the bugs live in. The guest-to-login test is the one that catches the most expensive bug; the cross-device test needs two browser contexts sharing an account.

import { test, expect } from '@playwright/test'

test('guest cart survives login and merges without doubling', async ({ page }) => {
  await page.goto('/product/blue-shirt')
  await page.getByRole('button', { name: 'Add to cart' }).click()   // 1 as guest
  await signIn(page, 'returning@example.com')                       // account already has 1
  await page.goto('/cart')
  // Expect the quantities to combine to 2, not replace to 0 or duplicate to 1+1 lines.
  await expect(page.getByTestId('cart-qty-blue-shirt')).toHaveText('2')
  await expect(page.getByTestId('cart-line')).toHaveCount(1)
})

test('wishlist appears on a second device', async ({ browser }) => {
  const phone = await browser.newContext()
  const laptop = await browser.newContext()
  const p1 = await phone.newPage(); await signIn(p1, 'shopper@example.com')
  await p1.goto('/product/blue-shirt'); await p1.getByRole('button', { name: 'Save' }).click()
  const p2 = await laptop.newPage(); await signIn(p2, 'shopper@example.com')
  await p2.goto('/wishlist')
  await expect(p2.getByText('Blue shirt')).toBeVisible()            // synced via the server
})

test('a cart line shows today price, not the price when it was added', async ({ page }) => {
  await addToCartThenChangeCatalogPrice(page, 'blue-shirt', 25)
  await page.goto('/cart')
  await expect(page.getByTestId('cart-price-blue-shirt')).toHaveText('$25.00')
})

Run these in CI so the merge cannot regress the next time someone touches login. Testing the journey a real user takes, rather than the code in isolation, is the point of the pre-launch QA checklist, and a lost cart is exactly the kind of silent loss covered in what a checkout bug quietly costs.

Buy, build or hire?

OptionChoose this whenThe catch
Trust the platform's cartA hosted store whose built-in cart handles login and syncCustom login, headless front ends and your own wishlist often reintroduce the merge bug
Fix it in-house from this postYou can reproduce the bug and know the codeWithout tests in CI, the next login change breaks it again
Hire QA to reproduce and guardConversion is dropping at login and no one can say why, or a headless build needs the journeys lockedA one-off audit finds them; keeping them fixed needs the tests in CI

How long does it take to fix yourself, and what is the risk?

If you already have an account cart on the server, fixing the merge and adding the tests is 1 to 3 days. If the cart lives only in the browser, moving it to the server first is more like 1 to 2 weeks. The main risk is fixing the merge once and letting it regress: login is touched often, and without the Playwright test in CI the same bug returns. The second risk is storing price in the cart line; store the item and quantity, and re-read price and stock, or you trade one silent bug for another.

Why RAITHub for this

  • Journey testing in production. PropDesk, a property-management SaaS RAITHub built, carries 1,024 tests covering real user journeys, gated in CI. See the PropDesk case study.
  • Carts and sessions across roles. On TheSkinProof, the founder's own venture built and run by RAITHub, sessions and account state are tested across 5 portals and 750+ tests.
  • The testers also fix. Each bug comes with a failing test and a suggested fix, because the same engineers test and build.

When you don't need us

  • The platform handles it. If your hosted cart merges and syncs correctly, you may need only a couple of your own tests.
  • You can reproduce and fix it. The tests above are a fair start for your own developer.
  • No accounts yet. A guest-only store has no merge to get wrong.

How RAITHub would test this

  • Reproduce first: write the failing Playwright test for each reported loss, across the guest-to-login boundary and two devices.
  • Fix the merge: combine matching items, never overwrite, idempotent at login, server as the source of truth.
  • Harden reload: store item and quantity only, re-read price and stock, charge from the server.
  • Guard in CI: the journey tests run on every change so login edits cannot regress the cart.

Timeline: as a fixed-scope one-off audit this is a short engagement; keeping the journeys guarded runs as a monthly QA plan. See QA as a Service, QA and test automation and the ecommerce industry page.

You receive: the Playwright tests in your repository, CI gates that block a regression, a report of what was found, and full IP in your name under NDA.

Next step: book the free 15-minute technical audit with the drop-off you have seen, and we will follow up with a written fixed quote.

Frequently asked questions

Why does my cart empty when a shopper logs in?

Because login replaces the guest cart with the account cart instead of merging them. Merge at login, server-side, combining matching items by adding quantities, and make the merge idempotent so a repeated login does not run it twice.

Why does the wishlist disappear on a new phone?

Because it is stored only in the browser. A wishlist that should last belongs on the server, tied to the account, so it follows the customer to any device. The browser should be a cache of that server state, not the only copy.

Should the cart store the price of each item?

No. Store the item and the quantity, and re-read price and stock from the catalogue on every view and at checkout. Storing price gives a stale number when prices change, and the final charge must be computed on the server anyway.

How do I test the guest-to-login cart merge?

With a Playwright test that adds an item as a guest, signs in to an account that already holds that item, and asserts the quantities combine into one line, rather than emptying or duplicating. Run it in CI so login changes cannot regress it.

Where should a guest cart live?

In the browser, keyed by an anonymous token in a cookie, until the shopper logs in. At login it merges into the account cart on the server, which then becomes the source of truth. A guest cart that is never persisted is fine; a logged-in cart that is not on the server is a bug.

Is this a one-off audit or ongoing testing?

Both are offered. A one-off audit reproduces and fixes the losses now. Because login and cart code change often, keeping the journeys guarded in CI as a monthly QA plan is what stops the same bug returning.

cart persistencewishlist persistenceguest cart mergecross-device syncplaywright testingecommerce bugs

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.