Ecommerce Testing

Shopping Cart Testing: Verify Items, Pricing, Promotions, and Persistence

Verify shopping carts by testing cart lines, quantities, totals, promotions, guest persistence, account-cart merging, stale stock, ownership and checkout handoff.

WrightTest TeamApproximately 18 min read
Shopping cart testing flow from selected product through cart line, quantity, pricing, promotions, stored cart and checkout handoff.
Shopping cart testing proves that visible cart actions match the stored cart and checkout input.

A customer adds one jacket priced at $80.00, changes the quantity to two, and applies a $10.00 discount.

The cart displays:

Quantity: 2
Subtotal: $160.00
Discount: −$10.00
Total: $150.00

The customer clicks Checkout.

Checkout receives:

Quantity: 1
Subtotal: $80.00
Discount: $0.00
Total: $80.00

Every cart control appeared to work. The quantity changed. The coupon produced a success message. The total looked correct.

None of those changes reached the authoritative cart.

Shopping cart testing must connect the customer-visible state with the business object stored by the application:

Selected product
→ cart line
→ quantity
→ price
→ promotions
→ estimated charges
→ stored cart
→ checkout input

The purpose of cart testing is not to confirm that a list of products appears on screen. It is to prove that the application preserves the intended products, configurations, quantities, commercial rules, ownership, and totals until checkout begins.

For the wider strategy, see Ecommerce Testing.

For the product-to-cart boundary, see Product Page Testing.

For release-level coverage across the full purchase flow, use the Ecommerce Website Testing Checklist.

For a broader test library, see Ecommerce Test Cases.

What Shopping Cart Testing Must Prove

A shopping cart is not only a visual list. It is a server-side business object that may contain:

  • a cart identifier;
  • a customer or anonymous session;
  • product and variant identifiers;
  • quantities;
  • unit prices;
  • discounts and credits;
  • estimated shipping and tax;
  • currency;
  • seller or fulfilment source;
  • a version or update timestamp;
  • eligibility decisions;
  • expiration rules.

A useful test compares four representations:

RepresentationExample
Customer actionChange quantity from 1 to 2
Visible cartQuantity 2, subtotal $160.00
Stored cartSKU JACKET-BLUE-M, quantity 2, subtotal 16000
Checkout inputThe same line and accepted calculation

The central invariant is:

The products, variants, quantities, prices, discounts, and customer context shown in the cart must match the authoritative cart state and the data handed to checkout.

In most cart regression suites, the highest-value checks are:

  1. Cart lines preserve the intended SKU and configuration.
  2. Quantity and removal actions persist.
  3. Totals can be reconstructed from their components.
  4. Promotions follow their real eligibility and stacking rules.
  5. Guest and account carts merge without silent loss or duplication.
  6. Stale price and stock are reconciled before checkout.
  7. Another customer cannot access or modify the cart.
  8. Checkout receives the same authoritative cart the customer accepted.

Separate UI assertions for every subtotal label add limited value when the same calculation can be verified against one authoritative cart response.

Define the Cart Contract and Controlled Data

Before writing browser tests, document what the cart stores and which system owns each value.

A simplified cart contract might look like this:

{
  "cartId": "CART-20418",
  "version": 7,
  "customerId": "CUSTOMER-101",
  "currency": "USD",
  "lines": [
    {
      "lineId": "LINE-1",
      "productId": "PROD-20418",
      "sku": "JACKET-BLUE-M",
      "quantity": 2,
      "unitPrice": 8000,
      "lineSubtotal": 16000,
      "discount": 1000,
      "lineTotal": 15000
    }
  ],
  "subtotal": 16000,
  "discountTotal": 1000,
  "shippingEstimate": 0,
  "taxEstimate": 0,
  "total": 15000
}

The contract should answer:

  • How is a guest cart identified?
  • Can a customer have more than one active cart?
  • Does adding the same SKU merge an existing line?
  • Are two customised products allowed to share one line?
  • Is price stored or recalculated on each request?
  • When are promotions evaluated?
  • Can promotions stack?
  • Are shipping and tax final or estimated?
  • How are gift cards and credits represented?
  • What happens when price or inventory changes?
  • How are guest and account carts merged?
  • Can an expired cart be restored?
  • Does the application use cart versioning?
  • Which exact state is passed to checkout?

Without those rules, phrases such as “correct total” and “correct merge” remain ambiguous.

Controlled fixtures

The core suite should not depend on random products from a shared catalogue.

Prepare fixtures for the business states you need to reproduce:

FixturePurpose
Standard in-stock productBasic cart operations
Multi-variant productSKU and option preservation
Product with quantity limitsBoundary validation
Last-unit productStock reconciliation
Promotion-eligible productDiscount calculation
Promotion-excluded productEligibility boundary
Products from different sellersFulfilment grouping
Taxable and non-taxable productsTax estimation
Guest customerAnonymous persistence
Registered customer with a saved cartCart merging
Customer in another regionCurrency and availability changes
Expiring promotionStale calculation recovery

Example purchase data:

{
  "customerType": "guest",
  "currency": "USD",
  "items": [
    {
      "sku": "JACKET-BLUE-M",
      "quantity": 2,
      "unitPrice": 8000
    }
  ],
  "coupon": "SAVE10",
  "expectedSubtotal": 16000,
  "expectedDiscount": 1000,
  "expectedTotal": 15000
}

Browser contexts isolate cookies and local storage. They do not automatically isolate product inventory, promotions, customer accounts, or server-side cart records.

Tests that modify shared commercial data still need independent fixtures and cleanup.

Cart Lines and Quantity

The first group of tests should prove that each line represents the sellable item the customer intended to add.

A cart line can depend on more than a product ID:

Product ID
+ SKU
+ selected options
+ customisation
+ seller
+ purchase mode
+ fulfilment method
=
cart line identity

Two products with the same title do not necessarily belong in the same line.

Line identity

After Add to cart, compare:

  • product ID;
  • SKU;
  • selected options;
  • seller;
  • quantity;
  • unit price;
  • currency;
  • customisation;
  • subscription or one-time purchase mode.

A success toast or an increased cart badge proves only that the interface reacted.

The stronger assertion is that the accepted response and stored cart contain the intended line.

Applications use different rules when the same product is added twice:

  • merge into one line;
  • retain separate lines;
  • reject the second addition;
  • merge only identical configurations;
  • separate products from different sellers;
  • separate subscription and one-time purchases.

The test should follow the product’s real policy.

For example, a jacket customised with ALICE should not automatically merge with the same SKU customised with BOB.

Quantity and removal

After changing quantity, inspect:

  • requested quantity;
  • accepted quantity;
  • unit price;
  • line subtotal;
  • discounts;
  • cart count;
  • estimated charges;
  • final total;
  • updated cart version.

Test meaningful boundaries:

0
−1
minimum − 1
minimum
maximum
maximum + 1
available stock
available stock + 1
decimal quantity
non-numeric input
very large value

The browser and server should enforce compatible rules.

Silently changing quantity 11 to 10 may hide what the application accepted. Where automatic adjustment is intentional, the new value should be communicated clearly.

When removing one line from a multi-line cart, confirm that:

  • the intended line is removed;
  • other lines remain unchanged;
  • promotions are re-evaluated;
  • shipping and tax estimates update where required;
  • totals are recalculated;
  • the change persists after refresh.

Removing the final line should produce a deliberate empty-cart state rather than a broken layout.

Concurrent updates

Quantity controls often fail when requests overlap.

A customer may click + three times before the first request finishes.

One failure pattern is:

Visible quantity: 4
Stored quantity: 2

Another appears when responses return out of order:

Request 1: quantity 2
Request 2: quantity 3

Response 2 returns
→ page shows 3

Response 1 returns late
→ page incorrectly returns to 2

Delay update responses and perform rapid interactions.

The final visible line and stored cart should represent the latest accepted action according to the application’s concurrency policy.

A cart may use:

  • version checking;
  • last-write-wins;
  • line-level reconciliation;
  • request cancellation;
  • conflict responses.

The important point is that a stale response must not silently overwrite newer cart state.

Pricing and Promotions

Shopping cart pricing and promotions testing comparing subtotal, discounts, credits, estimated charges and final total.
Cart pricing checks should reconstruct the total from every component rather than assert only the final number.

A cart total should be explainable.

A common formula is:

Item subtotal
− item discounts
− order discounts
− credits
+ estimated shipping
+ estimated tax
=
cart total

The exact order of operations is application-specific. Tax, discounts, rounding, and shipping thresholds may depend on it.

Reconstruct the total

Do not assert only the final number.

Capture each component:

{
  "subtotal": 16000,
  "itemDiscounts": 0,
  "orderDiscounts": 1000,
  "credits": 0,
  "shippingEstimate": 500,
  "taxEstimate": 1235,
  "total": 16735
}

Then verify that the visible and stored total follow the documented formula.

This makes failures diagnosable. A $5.00 difference could originate from shipping, tax, promotion eligibility, rounding, or credit application.

A final-total assertion alone cannot show which component is wrong.

Price changes

A product may remain in the cart long enough for its price to change.

Test the full transition:

  1. Add the product at the initial price.
  2. Change the authoritative price.
  3. Refresh or update the cart.
  4. Continue toward checkout.

The product may:

  • retain its original price for a documented period;
  • update immediately;
  • update only before checkout;
  • require customer acknowledgement;
  • become unavailable under the old offer.

Any of those policies can be valid.

What should not happen is a cart displaying one amount while checkout silently uses another.

Test combined commercial changes as well:

Price: $80 → $85
Stock: 3 → 1
Promotion: eligible → excluded

The cart should reconcile the complete state rather than fixing one field and preserving two stale ones.

Promotion eligibility and stacking

A promotion decision can depend on:

  • eligible products;
  • minimum spend;
  • quantity;
  • customer group;
  • region;
  • currency;
  • start and end time;
  • usage limits;
  • previous customer use;
  • maximum discount;
  • stacking policy.

A visible Coupon applied message is not enough when the stored discount remains zero.

Test representative browser journeys for:

  • valid coupon;
  • expired coupon;
  • not-yet-active coupon;
  • excluded product;
  • ineligible customer;
  • minimum spend not reached;
  • maximum uses reached;
  • wrong region or currency;
  • promotion removed after quantity decreases.

Do not run every possible promotion combination through the browser.

The full rule matrix usually belongs closer to the pricing or promotion service. Keep a smaller set of browser tests that prove the customer journey and visible recovery.

Where stacking exists, verify combinations of:

  • item discount;
  • order discount;
  • coupon;
  • free shipping;
  • loyalty credit;
  • gift card.

When stacking is rejected, the cart should explain which benefit remained and which one was removed.

Persistence, Merging, and Ownership

Shopping cart persistence and merge testing for guest carts, account carts, stale sessions, ownership and checkout handoff.
Persistence and merge tests should prove that cart ownership, line identity and totals survive session changes without silent loss.

Cart persistence involves identity, session state, and server-side ownership.

A cart may survive through:

  • a session cookie;
  • local storage;
  • an anonymous server record;
  • login;
  • another device;
  • a saved-cart link;
  • an account-level cart.

The expected duration should be documented.

Longer persistence is not always better. On a shared device, an indefinitely retained cart can expose another customer’s activity.

Guest persistence and account merge

For a guest cart, test:

  • page refresh;
  • navigation away and back;
  • browser restart where supported;
  • session expiration;
  • cookie removal;
  • local-storage removal;
  • expired cart tokens.

Then test login with products on both sides.

Example:

Guest cart:
- SKU-A × 1
- SKU-B × 2

Account cart:
- SKU-A × 2
- SKU-C × 1

A merge policy might produce:

- SKU-A × 3
- SKU-B × 2
- SKU-C × 1

Another product may keep the account cart, keep the guest cart, or ask the customer to choose.

Whichever policy applies, verify:

  • no silent product loss;
  • no unintended duplication;
  • quantity limits;
  • current prices;
  • current stock;
  • promotion re-evaluation;
  • currency compatibility;
  • seller and customisation identity;
  • ownership of the resulting cart;
  • invalidation of obsolete guest tokens.

The visible merge and the server-side owner should both be checked.

Multiple sessions and stale carts

Open the same cart in two tabs or sessions.

Then:

  1. Change quantity in the first view.
  2. Remove or update the same line in the second.
  3. Return to the first.
  4. Perform another action or start checkout.

A stale view should not overwrite a newer cart without following the documented conflict policy.

Useful evidence includes:

Cart ID
Cart version
Line ID
Requested version
Accepted version
Conflict response
Final stored state

This scenario deserves integration or API coverage in addition to a representative browser journey. Browser-only assertions can show the symptom but may not explain the version conflict.

Cart ownership

A cart identifier must not grant access by itself.

Authenticate as another customer and attempt to:

  • read the cart;
  • add a line;
  • change quantity;
  • remove a line;
  • apply a credit;
  • start checkout;
  • access a saved-cart link;
  • attach the cart to another account.

Every operation should verify ownership or authorised access.

Do not rely only on unpredictable identifiers. A long random cart ID is not a substitute for object-level authorisation.

Also verify that failure responses do not expose product lines, customer data, discounts, saved addresses, or internal identifiers.

Stale Inventory and Checkout Handoff

Adding an item to a cart does not always reserve stock.

A cart line may later become:

  • low stock;
  • out of stock;
  • discontinued;
  • restricted in the customer’s region;
  • unavailable from the selected seller;
  • limited to a smaller quantity;
  • available only through another fulfilment source.

The application needs a deliberate reconciliation policy.

Inventory changes

Consider this transition:

Cart quantity: 3
Current stock: 1

The cart may:

  • reduce the quantity after acknowledgement;
  • block checkout;
  • mark the line unavailable;
  • split fulfilment;
  • offer backorder.

It should not continue presenting quantity 3 as immediately purchasable when only one unit remains.

When a line becomes invalid:

  • identify the affected product;
  • preserve unaffected lines;
  • remove invalid discounts;
  • recalculate totals;
  • explain the next action;
  • stop stale data from reaching checkout.

Automatically deleting a line without explanation can make the customer believe it was never added.

Last-unit concurrency should not be proven through browser automation alone. The browser can reproduce competing customer journeys, but inventory and order records determine whether the business invariant held.

Checkout handoff

Cart testing ends when the authoritative cart is handed to checkout.

It does not need to prove payment or order creation. It must prove that checkout starts from the cart the customer accepted.

Compare:

CartCheckout input
Cart IDSame logical cart or purchase intent
Product linesSame SKUs and configurations
QuantitiesSame accepted values
CurrencySame commercial context
DiscountsSame eligibility or explicit recalculation
Estimated chargesRetained or clearly recalculated
TotalSame value or disclosed change

Test the handoff after:

  • quantity updates;
  • promotion application;
  • guest-to-account merge;
  • price changes;
  • stock reconciliation;
  • currency changes;
  • stale-session recovery.

A route change to /checkout proves navigation. It does not prove that checkout received the correct cart.

Checkout may legitimately recalculate shipping, tax, stock, or price. Any authoritative change should be visible before payment.

Cart Experience

The cart is a highly interactive page. Quantity controls, remove actions, coupon forms, estimates, and totals often update without a full reload.

These interactions need clear feedback and predictable behaviour.

Accessibility and mobile behaviour

Status changes should be communicated explicitly:

Quantity updated to 2
Jacket removed from cart
SAVE10 applied
Coupon expired
Only 1 item remains in stock
Cart total updated to $150.00

Check that:

  • success does not rely only on colour;
  • quantity errors identify the affected line;
  • promotion errors explain the rejected input;
  • dynamic updates are exposed as status messages;
  • focus remains predictable after removal;
  • keyboard users can reach every action;
  • disabled controls expose their state;
  • the product variant remains identifiable.

After removing a line, focus should move to a meaningful nearby control or cart heading rather than disappearing.

On mobile, verify that:

  • product and variant details remain readable;
  • quantity controls remain operable;
  • remove actions do not overlap other controls;
  • totals remain visible;
  • the sticky checkout control uses the current total;
  • promotion input is not hidden by the keyboard;
  • horizontal scrolling is unnecessary;
  • validation appears near the affected line;
  • orientation changes preserve the cart.

A common defect is a sticky checkout button displaying an old total after quantity or promotion updates.

Interaction feedback and performance

The cart should distinguish between:

Update pending
Update accepted
Update rejected

Test under delayed:

  • pricing;
  • promotions;
  • inventory;
  • shipping estimates;
  • tax estimates.

Disabling the entire cart for every request can prevent conflicts but create poor usability.

Keeping every control active without request coordination may corrupt the cart.

The expected solution depends on the cart’s concurrency model, but the customer should always understand whether the last action succeeded.

Interaction responsiveness matters more here than initial rendering alone. A quickly loaded cart can still be unusable when quantity updates take several seconds without feedback.

Core Shopping Cart Test Cases

The following 20 scenarios provide focused coverage without repeating the complete ecommerce test-case library.

Cart Lines and Quantities

IDPri.ScenarioExpected resultEvidence
CART-001P0Add the selected product variantStored line matches product ID, SKU, options, quantity, price, and currencyAdd-to-cart response and cart API
CART-002P1Add the same configuration twiceLines merge or remain separate according to policyFinal cart lines
CART-003P1Add the same SKU with different customisationDistinct configurations are not merged incorrectlyLine IDs and selected options
CART-004P1Increase and decrease quantityAccepted quantity and dependent totals update correctlyCart response and calculation
CART-005P1Submit invalid quantity boundariesInvalid values are rejected without corrupting the stored lineValidation and cart state
CART-006P1Remove one line from a multi-line cartOnly the intended line is removed and totals are recalculatedCart before and after
CART-007P2Remove the final productA deliberate empty-cart state appears and persistsBrowser and cart API

Pricing and Promotions

IDPri.ScenarioExpected resultEvidence
CART-008P0Reconstruct the complete cart totalSubtotal, discounts, credits, estimates, rounding, and total reconcileCalculation components
CART-009P1Apply a valid promotionCorrect lines, thresholds, amount, and stacking rule are usedPromotion decision and cart
CART-010P1Apply an expired or ineligible promotionPromotion is rejected truthfully and does not alter the stored totalError and cart response
CART-011P1Remove a line that made the cart eligiblePromotion and totals are re-evaluatedCart before and after
CART-012P0Change the authoritative price after additionThe documented price policy is applied before checkoutPricing and cart state

Persistence, Ownership, and Inventory

IDPri.ScenarioExpected resultEvidence
CART-013P1Refresh and restore a guest cartLines persist for the documented durationCart ID, session, and stored lines
CART-014P0Log in with guest and account cartsMerge follows policy without silent loss or duplicationPre-merge and final carts
CART-015P0Access or modify another customer’s cartEvery operation is denied without exposing cart dataAuthorisation responses
CART-016P1Modify the same cart from stale sessionsConflict or reconciliation follows the version policyCart versions and final state
CART-017P0Reduce stock below cart quantityThe customer sees a truthful adjustment or blocking stateInventory and cart response
CART-018P1Make one line unavailableUnaffected lines remain and totals are recalculatedFinal cart state

Handoff and Experience

IDPri.ScenarioExpected resultEvidence
CART-019P0Start checkout after quantity, promotion, and stock changesCheckout receives the accepted lines and commercial contextCart and checkout comparison
CART-020P1Complete key cart actions on mobile and by keyboardControls, messages, totals, and focus remain usable and accurateMobile trace and accessibility review

Automating Shopping Cart Tests with Playwright

Cart tests usually need browser and API evidence.

The browser reproduces the customer journey:

Add product
→ open cart
→ change quantity
→ apply promotion
→ inspect total
→ start checkout

The API confirms the business state:

Cart owner
→ stored lines
→ accepted quantities
→ calculation components
→ cart version
→ checkout input

Example:

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

test('quantity, promotion, and total persist before checkout', async ({
  page,
  request,
}) => {
  const fixtureResponse = await request.post('/test-support/products', {
    data: {
      fixture: 'promotion-eligible-product',
      sku: 'JACKET-BLUE-M',
      unitPrice: 8000,
      availableQuantity: 5,
    },
  });

  expect(fixtureResponse.ok()).toBeTruthy();

  const product = await fixtureResponse.json();

  await page.goto(`/products/${product.slug}`);

  await page.getByRole('button', { name: 'Blue' }).click();
  await page.getByRole('button', { name: 'Medium' }).click();
  await page.getByRole('button', { name: 'Add to cart' }).click();
  await page.getByRole('link', { name: 'Cart' }).click();

  const cartLine = page
    .getByRole('listitem')
    .filter({ hasText: product.name });

  await expect(cartLine.getByText('Blue')).toBeVisible();
  await expect(cartLine.getByText('Medium')).toBeVisible();

  const quantity = cartLine.getByLabel('Quantity');

  const [quantityResponse] = await Promise.all([
    page.waitForResponse((response) =>
      response.url().includes('/api/cart') &&
      response.request().method() === 'PATCH',
    ),
    quantity.fill('2'),
  ]);

  expect(quantityResponse.ok()).toBeTruthy();

  await expect(quantity).toHaveValue('2');

  await page
    .getByLabel('Promotion code')
    .fill('SAVE10');

  const [promotionResponse] = await Promise.all([
    page.waitForResponse((response) =>
      response.url().includes('/api/cart/promotions') &&
      response.request().method() === 'POST',
    ),
    page
      .getByRole('button', { name: 'Apply promotion' })
      .click(),
  ]);

  expect(promotionResponse.ok()).toBeTruthy();

  await expect(page.getByTestId('cart-subtotal'))
    .toHaveText('$160.00');

  await expect(page.getByTestId('cart-discount'))
    .toHaveText('−$10.00');

  await expect(page.getByTestId('cart-total'))
    .toHaveText('$150.00');

  const cartResponse = await request.get('/api/cart/current');

  expect(cartResponse.ok()).toBeTruthy();

  const cart = await cartResponse.json();

  expect(cart.lines).toEqual([
    expect.objectContaining({
      sku: 'JACKET-BLUE-M',
      quantity: 2,
      unitPrice: 8000,
      lineSubtotal: 16000,
    }),
  ]);

  expect(cart.subtotal).toBe(16000);
  expect(cart.discountTotal).toBe(1000);
  expect(cart.total).toBe(15000);

  await page
    .getByRole('button', { name: 'Checkout' })
    .click();

  await expect(page.getByTestId('checkout-cart-total'))
    .toHaveText('$150.00');
});

The routes, selectors, and promotion rules are application-specific.

The verification pattern remains:

Customer action
→ accepted response
→ persisted cart
→ reconciled calculation
→ matching checkout input

Avoid fixed sleeps for quantity, promotion, and estimate updates. Wait for the relevant response or observable state.

For guest merging, ownership, and stale-session scenarios, use independent browser contexts with independent server-side identities.

A useful failure report should retain:

Cart ID
Cart version
Customer or session ID
Line IDs
SKU
Promotion decision
Request and response
Visible totals
Checkout input
Screenshot
Playwright trace

Without those identifiers, an assertion such as Expected $150.00, received $160.00 reveals little about the failed calculation.

Where WrightTest Fits

WrightTest provides a visual interface around Playwright for recording, editing, running, scheduling, and debugging browser tests.

A team can record one cart journey:

Add product
→ open cart
→ change quantity
→ apply promotion
→ validate totals
→ start checkout

Fixed values can then be replaced with variables:

Customer type
Product
SKU
Initial quantity
Updated quantity
Promotion
Currency
Expected subtotal
Expected discount
Expected total
Expected merge result
Expected stock result

The same journey can cover:

  • empty and populated carts;
  • duplicate additions;
  • quantity boundaries;
  • valid and expired promotions;
  • guest persistence;
  • account-cart merging;
  • stale price and stock;
  • mobile layouts;
  • checkout handoff.

Each run can retain its inputs, failed step, screenshots, and Playwright trace.

WrightTest covers the repeatable browser journey. Pricing rules, promotion decisions, cart ownership, inventory reconciliation, and persistence still require the appropriate API, service, integration, or database evidence.

Final Acceptance Rule

A cart should not pass because:

The products were visible and the total looked correct.

A stronger result is:

The cart stored the intended products and configurations, persisted every accepted quantity change, applied the documented price and promotion rules, protected ownership, reconciled stale stock, exposed an explainable total, and passed the same authoritative state to checkout.

That is the boundary shopping cart testing must verify.

Technical References