E-Commerce Testing Guide

How to Test Shopify Add to Cart with Playwright: Complete 2026 Guide

A scenario-by-scenario walkthrough of testing Shopify add to cart flows with Playwright. Variant IDs, the AJAX Cart API, cart drawer state management, quantity updates, cart line item properties, and the hidden complexity that breaks real e-commerce test suites.

$235B+

Shopify merchants generated over $235 billion in global economic activity in 2023, with add to cart as the single most critical conversion event in every storefront.

Shopify Global Economic Impact Report 2023

0Cart scenarios covered
0API endpoints intercepted
0%Fewer lines with Assrt
0msTypical AJAX cart response

Shopify Add to Cart End-to-End Flow

BrowserProduct PageShopify AJAX APICart StateCart DrawerSelect variant + click Add to CartPOST /cart/add.js { id, quantity, properties }Update server-side cartReturn updated cart JSON{ items, total_price, item_count }Open cart drawer with new itemRender cart line items + totals

1. Why Testing Shopify Add to Cart Is Harder Than It Looks

On the surface, add to cart looks simple: the user clicks a button and a product appears in their cart. Under the hood, Shopify storefronts handle this through a surprisingly complex chain of variant resolution, AJAX API calls, client-side state management, and UI animation. Each link in that chain introduces a different class of test failure.

The first problem is variant IDs. Every Shopify product can have up to 100 variants, each with a unique numeric ID. When a customer selects “Size: Large” and “Color: Blue,” the storefront must resolve those option values to a specific variant ID before sending the add to cart request. Many themes use JavaScript to swap a hidden input value or update the URL query parameter with the selected variant ID. If your test clicks “Add to Cart” before that resolution completes, you will add the wrong variant (typically the default first variant) and your assertion will pass even though the behavior is incorrect.

The second problem is the AJAX Cart API. Modern Shopify themes rarely submit a traditional form to /cart/add. Instead, they POST to /cart/add.js and receive a JSON response containing the added item. The theme then updates the cart count badge, opens a cart drawer or sidebar, and renders the new line item, all without a full page navigation. Your test must wait for the AJAX response to complete and the DOM to update, rather than waiting for a page load.

The third problem is cart drawer state. Most Shopify themes (Dawn, Debut, Prestige, and hundreds of third-party themes) use a slide-out drawer or modal to show the cart after adding an item. This drawer has CSS transition animations, dynamically rendered line items, and interactive quantity controls. Testing that the drawer opened, that it contains the correct product, and that quantity controls work requires careful timing and the right locator strategy.

The fourth problem is cart line item properties. Shopify supports arbitrary key-value properties on each line item, used for customization fields like engraving text, gift wrapping preferences, or subscription intervals. These properties travel through the entire order pipeline and must be verified in the cart, at checkout, and in the order confirmation. Many themes render properties differently, and some do not render them at all until checkout.

The fifth problem is inventory and availability. A product variant can be in stock, out of stock, or set to “continue selling when out of stock.” The add to cart button state, error messages, and cart behavior all change based on inventory policy. A test that assumes the product is always available will break the moment inventory drops to zero in your test store.

Shopify Add to Cart Data Flow

🌐

Product Page

User selects options

⚙️

Variant Resolution

Map options to variant ID

↪️

POST /cart/add.js

Send variant ID + quantity

⚙️

Shopify Backend

Validate + update cart

🔔

JSON Response

Return cart state

🌐

Cart Drawer

Render line items

Cart Badge

Update item count

Variant Resolution Flow

🌐

Option 1

e.g., Size: Large

🌐

Option 2

e.g., Color: Blue

🌐

Option 3

e.g., Material: Cotton

⚙️

Lookup

Match combination to variant

Variant ID

e.g., 44012345678

⚙️

Hidden Input

Update form value

A thorough Shopify add to cart test suite covers all of these surfaces. The sections below walk through each scenario with runnable Playwright TypeScript you can paste into a real project.

2. Setting Up a Reliable Test Environment

Shopify provides a free development store through the Shopify Partners program. Create a dedicated development store for testing, separate from any store that serves real customers. Development stores have access to all features including the AJAX Cart API, metafields, and the full Liquid template engine without requiring a paid plan.

Shopify Test Store Setup Checklist

  • Create a Shopify Partners account and provision a development store
  • Install and publish a theme (Dawn is recommended for predictable selectors)
  • Create test products with multiple variants (Size, Color combinations)
  • Set inventory tracking on at least one product to test out-of-stock flows
  • Add a product with line item properties (customization fields)
  • Enable the cart drawer in your theme settings (not redirect to /cart)
  • Generate a Storefront API access token for programmatic cart cleanup
  • Disable password protection on the development store for CI access

Environment Variables

.env.test

Cart Cleanup Before Each Test

Shopify carts are tied to the browser session cookie. The simplest cleanup strategy is to clear all cookies before each test, which forces Shopify to create a fresh empty cart. Alternatively, you can call the /cart/clear.js endpoint to empty the cart without losing the session.

test/helpers/shopify-cart.ts

Playwright Configuration for Shopify

Shopify storefronts load third-party scripts (analytics, reviews, chat widgets) that slow down page load. Configure Playwright to block unnecessary requests in test mode and set a generous timeout for the initial page load, since development stores can be slower than production.

playwright.config.ts
Shopify Development Store Setup

3. Scenario: Simple Product Add to Cart

The simplest Shopify add to cart scenario is a product with no variants (or a single default variant). The user lands on the product page, clicks “Add to Cart,” and the item appears in the cart. Even this basic flow has subtlety: the button text often changes to “Adding...” during the AJAX request, then back to “Add to Cart” once complete. The cart count badge updates asynchronously. If your theme uses a cart drawer, it slides open with a CSS transition. Your test needs to wait for all of these state changes.

1

Simple Product Add to Cart

Straightforward

Goal

Navigate to a single-variant product, click Add to Cart, and confirm the item appears in the cart with the correct name, price, and quantity of one.

Preconditions

  • Development store accessible at APP_BASE_URL
  • A test product with a single variant exists (TEST_PRODUCT_SIMPLE)
  • Cart is empty (cookies cleared or /cart/clear.js called)

Playwright Implementation

shopify-cart.spec.ts

What to Assert Beyond the UI

Cart State Verification

  • Cart API response contains exactly one item
  • Line item handle matches the expected product
  • Line item quantity is 1
  • Cart total price is non-zero and matches product price
  • Cart count badge reflects the correct number

Simple Add to Cart: Playwright vs Assrt

import { test, expect } from '@playwright/test';
import { clearCart, getCartContents } from '../helpers/shopify-cart';

test('simple product: add to cart', async ({ page }) => {
  await page.goto('/');
  await clearCart(page);
  await page.goto(`/products/${process.env.TEST_PRODUCT_SIMPLE}`);

  const addButton = page.getByRole('button', { name: /add to cart/i });
  await expect(addButton).toBeEnabled();
  await addButton.click();

  await expect(addButton).not.toHaveText(/adding/i, { timeout: 10_000 });

  const cartBadge = page.locator('[data-cart-count], .cart-count-bubble');
  await expect(cartBadge).toHaveText('1', { timeout: 5_000 });

  const cart = await getCartContents(page);
  expect(cart.item_count).toBe(1);
  expect(cart.items[0].handle).toBe(process.env.TEST_PRODUCT_SIMPLE);
});
46% fewer lines

4. Scenario: Variant Selection Before Add to Cart

Most Shopify products have variants. A t-shirt might have Size (S, M, L, XL) and Color (Black, White, Navy) for a total of 12 variants, each with its own inventory count, price, and SKU. When the customer selects options, the theme JavaScript resolves the combination to a variant ID and updates a hidden <input name="id"> field inside the product form. If your test does not select all required options before clicking Add to Cart, Shopify will either add the default variant or reject the request entirely.

The critical testing insight is that clicking an option swatch or selecting from a dropdown is asynchronous. The theme must look up the matching variant, update the price display, check availability, swap the product image, and update the hidden input. Your test should wait for the variant resolution to complete before clicking Add to Cart. The most reliable signal is the URL query parameter (many themes append ?variant=44012345678 to the URL) or the hidden input value change.

2

Variant Selection Before Add to Cart

Moderate

Playwright Implementation

shopify-variants.spec.ts

Variant Selection: Playwright vs Assrt

test('variant: select size/color and add', async ({ page }) => {
  await page.goto(`/products/${process.env.TEST_PRODUCT_VARIANTS}`);
  await clearCart(page);

  const sizeFieldset = page.locator('fieldset').filter({ hasText: /size/i });
  await sizeFieldset.getByLabel('Large').check();

  await page.waitForFunction(() => {
    const input = document.querySelector('input[name="id"]');
    return input && input.value !== '';
  });

  const colorFieldset = page.locator('fieldset').filter({ hasText: /color/i });
  await colorFieldset.getByLabel('Blue').check();

  await page.waitForFunction(() =>
    new URL(location.href).searchParams.has('variant')
  );

  await page.getByRole('button', { name: /add to cart/i }).click();
  await page.waitForResponse(r =>
    r.url().includes('/cart/add.js') && r.status() === 200
  );

  const cart = await getCartContents(page);
  expect(cart.items[0].variant_title).toContain('Large');
  expect(cart.items[0].variant_title).toContain('Blue');
});
58% fewer lines

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

5. Scenario: Intercepting the AJAX Cart API

Shopify's AJAX Cart API is a set of endpoints that themes call with JavaScript to manage the cart without full page reloads. The three critical endpoints are /cart/add.js (add items), /cart/change.js (update quantity), and /cart/update.js (bulk update). Intercepting these requests in Playwright gives you precise control over what gets sent to Shopify and what comes back. This is essential for testing error states (like adding more items than available inventory) and for validating that the theme sends the correct payload.

Playwright's page.route() and page.waitForResponse() methods are perfectly suited for AJAX cart testing. You can intercept the request to verify its payload, mock the response to simulate error conditions, or simply wait for the response to confirm the operation completed before making assertions.

3

AJAX Cart API Interception

Moderate

Playwright Implementation

shopify-ajax-cart.spec.ts

What the AJAX Endpoints Return

Shopify AJAX Cart API Response

6. Scenario: Cart Drawer State and Animations

Most modern Shopify themes (Dawn, Prestige, Impact, and many third-party themes) display the cart as a slide-out drawer rather than a full page. After clicking Add to Cart, the drawer slides in from the right side of the screen with a CSS transition that typically takes 200 to 500 milliseconds. The drawer contains the list of cart line items, quantity selectors, subtotal, and a checkout button. Testing this component requires waiting for the animation to complete and then asserting on dynamically rendered content.

The biggest testing challenge with cart drawers is timing. The drawer opens asynchronously after the AJAX cart response completes. If you assert on drawer contents too early, the line items have not rendered yet. If the drawer uses a fade or slide animation, elements may exist in the DOM but be visually hidden during the transition. Playwright's visibility checks handle this well, but you need to target the right container element.

4

Cart Drawer State and Animations

Complex

Playwright Implementation

shopify-cart-drawer.spec.ts

Cart Drawer Testing: Playwright vs Assrt

test('cart drawer opens with correct contents', async ({ page }) => {
  await page.goto(`/products/${process.env.TEST_PRODUCT_SIMPLE}`);
  await clearCart(page);

  const productTitle = await page.locator('h1.product__title').textContent();
  await page.getByRole('button', { name: /add to cart/i }).click();

  const cartDrawer = page.locator('cart-drawer, #cart-drawer');
  await expect(cartDrawer).toBeVisible({ timeout: 10_000 });
  await expect(cartDrawer.getByText(productTitle!.trim())).toBeVisible();

  const qtyInput = cartDrawer.locator('input[name="updates[]"]');
  await expect(qtyInput.first()).toHaveValue('1');

  await expect(
    cartDrawer.getByRole('button', { name: /check ?out/i })
  ).toBeVisible();

  const subtotal = cartDrawer.locator('.totals__total-value');
  await expect(subtotal.first()).not.toBeEmpty();
});
50% fewer lines

7. Scenario: Quantity Updates and Removal

After an item is in the cart, customers frequently change quantities or remove items entirely. Shopify themes implement quantity changes through the /cart/change.js endpoint, which takes a line item key and a new quantity. Setting the quantity to zero removes the item. The tricky part for testing is that quantity changes trigger another AJAX request, the response updates the cart state, and the theme re-renders the affected line item (price recalculation, total update, possible free shipping threshold changes). Your test must wait for each of these state transitions.

Some themes also implement plus/minus buttons that increment or decrement the quantity, while others use a numeric input field. The interaction pattern differs: with plus/minus buttons you click and wait for the AJAX call, while with an input field you may need to clear the value, type a new number, and then blur or press Enter to trigger the update.

5

Quantity Updates and Item Removal

Moderate

Playwright Implementation

shopify-quantity.spec.ts

Assrt Equivalent

# scenarios/shopify-quantity-update.assrt
describe: Increase quantity and remove item from cart

given:
  - I have 1 "classic-leather-belt" in my cart
  - I am on the cart page

---
scenario: Increase quantity
steps:
  - click the plus button for the first item
  - wait for the cart to update
expect:
  - the quantity input shows 2
  - the cart total reflects the updated quantity

---
scenario: Remove item
steps:
  - click the remove button for the first item
  - wait for the cart to update
expect:
  - the cart shows "Your cart is empty"
  - the cart API returns 0 items

8. Scenario: Cart Line Item Properties (Customization)

Shopify line item properties allow merchants to collect custom data for each cart item. Common use cases include engraving text, gift messages, subscription intervals, bundle configurations, and custom file uploads. Properties are key-value pairs attached to the line item when it is added to the cart. They travel through the entire Shopify pipeline: cart, checkout, order, and fulfillment.

Testing line item properties requires verifying three things. First, that the product form collects the property values correctly. Second, that the /cart/add.js request includes the properties in its payload. Third, that the cart (drawer or page) displays the properties alongside the line item. Many themes hide properties with keys that start with an underscore (like _subscription_id), which is a Shopify convention for hidden metadata.

6

Cart Line Item Properties

Complex

Playwright Implementation

shopify-properties.spec.ts

Line Item Properties: Playwright vs Assrt

test('line item properties: engraving text', async ({ page }) => {
  await page.goto(`/products/${process.env.TEST_PRODUCT_PROPERTIES}`);
  await clearCart(page);

  const engravingInput = page.locator(
    'input[name="properties[Engraving Text]"]'
  );
  await engravingInput.fill('Happy Birthday Jane');

  const addReq = page.waitForRequest(r =>
    r.url().includes('/cart/add.js') && r.method() === 'POST'
  );
  await page.getByRole('button', { name: /add to cart/i }).click();

  const req = await addReq;
  const data = req.postDataJSON();
  expect(data.properties['Engraving Text']).toBe('Happy Birthday Jane');

  await page.waitForResponse(r =>
    r.url().includes('/cart/add.js') && r.status() === 200
  );

  const cart = await getCartContents(page);
  expect(cart.items[0].properties).toEqual(
    expect.objectContaining({ 'Engraving Text': 'Happy Birthday Jane' })
  );

  await page.goto('/cart');
  await expect(page.getByText('Happy Birthday Jane')).toBeVisible();
});
53% fewer lines

9. Common Pitfalls That Break Shopify Cart Test Suites

Racing Ahead of Variant Resolution

The single most common Shopify test failure is clicking Add to Cart before the variant has resolved. When a customer selects options on a product page, the theme JavaScript must look up the matching variant, update the hidden input[name="id"]field, and refresh the price display. This takes time, especially on themes with complex variant logic or slow JavaScript. If your test selects “Large / Blue” and immediately clicks Add to Cart, the form may still contain the default variant ID. The request succeeds, but you added the wrong variant. Always wait for the variant ID to update in the URL query parameter or the hidden input before clicking Add to Cart.

Ignoring AJAX Responses

Many Shopify test suites check only the UI (badge count, drawer contents) after adding to cart. This is fragile because themes render cart state differently and may have subtle bugs in their display logic. Always verify the cart state through the /cart.js API endpoint in addition to UI assertions. The API response is the source of truth for what Shopify actually stored. A GitHub issue on the Dawn theme (#2374) documents a case where the cart drawer displayed stale data after rapid sequential adds, while the API response was correct.

Cart Session Persistence Across Tests

Shopify ties cart state to the browser session cookie. If your tests share a browser context without clearing cookies between runs, items from previous tests persist in the cart. This causes cascade failures where a test expects one item but finds five. Always clear the cart at the start of each test, either by clearing cookies (which creates a new empty cart) or by calling /cart/clear.js.

Theme-Specific Selectors

Shopify has over 100 official and third-party themes. Each theme uses different selectors for the Add to Cart button, cart drawer, quantity inputs, and line item displays. A test written for Dawn will break on Prestige, and vice versa. If you support multiple themes or plan to change themes, use Playwright's getByRole and getByText locators wherever possible, falling back to CSS selectors only when semantic locators are not available. Alternatively, use Assrt, which resolves selectors dynamically at runtime.

Inventory Changes Between Test Runs

Development stores share inventory state across all sessions. If one test run reduces inventory to zero, subsequent runs will fail on add to cart. Use the Shopify Admin API to reset inventory levels in a globalSetupstep, or configure your test products with “Continue selling when out of stock” enabled to avoid inventory-related test failures entirely.

Anti-Patterns to Avoid

  • Clicking Add to Cart before variant resolution completes
  • Asserting only on UI elements without verifying /cart.js API
  • Sharing browser context between tests without clearing cart
  • Hardcoding theme-specific CSS selectors across the test suite
  • Ignoring inventory state in the development store
  • Using waitForTimeout instead of waitForResponse for AJAX calls
  • Testing add to cart on the default variant without selecting options
Shopify Cart Test Suite Run

10. Writing These Scenarios in Plain English with Assrt

Every scenario above requires intimate knowledge of Shopify theme selectors, the AJAX Cart API endpoint URLs, JSON response structures, and CSS animation timing. Multiply six scenarios by the two or three theme variants you might support and you have a substantial, brittle test suite. When Shopify updates Dawn (which happens several times per month), selectors shift, class names change, and your tests break even though the customer-facing behavior is identical.

Assrt lets you describe what the customer does and what should happen, without specifying how the theme implements it. The variant selection scenario from Section 4 demonstrates this clearly. In raw Playwright, you need to know about fieldset filters, hidden input names, URL query parameters, and waitForFunction callbacks. In Assrt, you describe the customer intent and let the framework resolve the implementation details at runtime.

scenarios/shopify-cart-full-suite.assrt

Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can inspect, run, and modify. When Shopify updates the Dawn theme and renames a CSS class or restructures the cart drawer markup, Assrt detects the failure, analyzes the new DOM, and opens a pull request with updated locators. Your scenario files remain untouched.

Start with the simple add to cart happy path. Once it is green in your CI, add variant selection, then cart drawer assertions, then quantity updates, then line item properties. In a single afternoon you can have complete Shopify cart coverage that adapts automatically when the theme changes.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk