supaguardsupaguardDocs
Use cases

E-Commerce Checkout Monitoring: Catch Revenue-Killing Bugs

Monitor your e-commerce checkout flow with Playwright synthetic tests. Learn to detect payment failures, cart issues, and conversion-blocking bugs before customers do.

Your checkout flow is where revenue happens. A broken checkout means lost sales—and customers who may never return. This guide shows you how to set up comprehensive e-commerce checkout monitoring with supaguard.

Why Monitor Checkout?

The Cost of Checkout Failures

A checkout failure has cascading effects:

ImpactCost
Lost saleImmediate revenue loss
Cart abandonmentCustomer may not return
Support ticketsTeam time spent investigating
Brand damageNegative reviews and social posts

Example: If your checkout breaks for 2 hours during business hours with $50 average order value and 100 orders/hour, you've lost $10,000 in revenue—plus the customers who won't try again.

What Can Go Wrong

Checkout flows have many failure points:

  • Cart functionality - Add to cart broken, quantity updates failing
  • Payment processing - Stripe/PayPal integration errors
  • Form validation - Address validation blocking submission
  • Inventory checks - Stock verification timing out
  • Authentication - Guest checkout vs logged-in issues
  • Third-party scripts - Analytics or chat widgets causing JS errors

Basic Checkout Test

Start with a test that covers the happy path:

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

test("complete checkout flow", async ({ page }) => {
  // Visit product page
  await page.goto("https://shop.example.com/products/sample-product");

  // Add to cart
  await page.getByRole("button", { name: "Add to Cart" }).click();
  await expect(page.getByText("Added to cart")).toBeVisible();

  // Go to cart
  await page.getByRole("link", { name: "Cart" }).click();
  await expect(page.getByRole("heading", { name: "Your Cart" })).toBeVisible();

  // Proceed to checkout
  await page.getByRole("button", { name: "Checkout" }).click();

  // Fill shipping information
  await page.getByLabel("Email").fill("test@example.com");
  await page.getByLabel("First Name").fill("Test");
  await page.getByLabel("Last Name").fill("User");
  await page.getByLabel("Address").fill("123 Test Street");
  await page.getByLabel("City").fill("San Francisco");
  await page.getByLabel("ZIP Code").fill("94102");
  await page.getByLabel("Country").selectOption("US");

  // Fill payment information (test card)
  await page.getByLabel("Card Number").fill("4242424242424242");
  await page.getByLabel("Expiration").fill("12/30");
  await page.getByLabel("CVC").fill("123");

  // Submit order
  await page.getByRole("button", { name: "Place Order" }).click();

  // Verify success
  await expect(page.getByText("Order Confirmed")).toBeVisible();
  await expect(page.getByText("Order #")).toBeVisible();
});

Handling Payment Providers

Stripe Elements

Stripe uses iframes for payment fields. Access them with Playwright's frame handling:

// Wait for Stripe iframe to load
const stripeFrame = page.frameLocator('iframe[name*="stripe"]').first();

// Fill card number in Stripe iframe
await stripeFrame.getByPlaceholder("Card number").fill("4242424242424242");
await stripeFrame.getByPlaceholder("MM / YY").fill("12/30");
await stripeFrame.getByPlaceholder("CVC").fill("123");

PayPal

PayPal opens a popup window. Handle with Playwright's multi-page support:

// Click PayPal button and wait for popup
const [popup] = await Promise.all([
  page.waitForEvent("popup"),
  page.getByRole("button", { name: "Pay with PayPal" }).click(),
]);

// Interact with PayPal popup
await popup.waitForLoadState();
await popup.getByLabel("Email").fill("test@example.com");
await popup.getByLabel("Password").fill("testpassword");
await popup.getByRole("button", { name: "Log In" }).click();

// Wait for redirect back to your site
await page.waitForURL("**/order-confirmation**");

Test Cards

Use payment provider test cards for synthetic monitoring:

ProviderTest Card Number
Stripe4242424242424242
Braintree4111111111111111
Square4532015112830366

Important: Configure your payment provider to recognize these as test transactions in production (usually via a test API key or specific test mode).

Critical Assertions

Cart Functionality

test("cart updates correctly", async ({ page }) => {
  await page.goto("/products/sample");
  await page.getByRole("button", { name: "Add to Cart" }).click();

  // Verify cart count updated
  await expect(page.getByTestId("cart-count")).toHaveText("1");

  // Verify cart total
  await page.getByRole("link", { name: "Cart" }).click();
  await expect(page.getByTestId("cart-total")).toContainText("$");
});

Price Consistency

test("prices match through checkout", async ({ page }) => {
  // Get product price
  await page.goto("/products/sample");
  const productPrice = await page.getByTestId("product-price").textContent();

  // Add to cart and check
  await page.getByRole("button", { name: "Add to Cart" }).click();
  await page.getByRole("link", { name: "Cart" }).click();
  const cartPrice = await page.getByTestId("line-item-price").textContent();

  expect(productPrice).toBe(cartPrice);
});

Order Confirmation

// Verify confirmation page has essential elements
await expect(page.getByText("Order Confirmed")).toBeVisible();
await expect(page.getByText(/Order #\d+/)).toBeVisible();
await expect(page.getByText("test@example.com")).toBeVisible();
await expect(page.getByText("Estimated delivery")).toBeVisible();

Test Data Management

Dedicated Test Account

Create a dedicated test account that:

  • Has a static email (e.g., synthetic-test@yourcompany.com)
  • Is excluded from marketing emails
  • Is excluded from revenue reporting
  • Has a recognizable name prefix (e.g., "Synthetic Test User")

Cleaning Up Test Orders

Options for handling test orders:

  1. Cancel after confirmation - Add a step to cancel the order
  2. Use zero-dollar items - Create a $0 test product
  3. Exclude by email - Filter orders from synthetic-*@ in reporting
  4. Separate environment - If possible, run against staging
// Option 1: Cancel after confirmation
await page.getByRole("button", { name: "Cancel Order" }).click();
await expect(page.getByText("Order Cancelled")).toBeVisible();

Monitoring Configuration

Check TypeFrequency
Full checkout flowEvery 5 minutes
Add to cart onlyEvery 2 minutes
Cart page loadsEvery 1 minute

Multi-Region Testing

Run checkout tests from multiple regions to catch:

  • CDN caching issues
  • Payment provider regional outages
  • Geo-blocking misconfigurations

Alert Configuration

Checkout failures should be Critical alerts:

  1. Create an alert policy for "Critical - Page Immediately"
  2. Assign PagerDuty + Slack channels
  3. Apply to all checkout-related checks

Common Issues and Solutions

Dynamic Content

// Wait for price to load (might be fetched async)
await expect(page.getByTestId("price")).not.toHaveText("Loading...");
await expect(page.getByTestId("price")).toContainText("$");

Inventory Issues

// Handle out-of-stock gracefully
const addButton = page.getByRole("button", { name: "Add to Cart" });
if (await addButton.isDisabled()) {
  // Product out of stock - this might be expected
  console.log("Product out of stock");
  return;
}
await addButton.click();

Rate Limiting

If your checkout is rate-limited:

  • Use a test account whitelisted from limits
  • Reduce test frequency
  • Test during off-peak hours

Example: Shopify Checkout

test("Shopify checkout flow", async ({ page }) => {
  await page.goto("https://your-store.myshopify.com/products/sample");

  await page.getByRole("button", { name: "Add to cart" }).click();
  await page.getByRole("link", { name: "View cart" }).click();
  await page.getByRole("button", { name: "Check out" }).click();

  // Shopify checkout
  await page.getByLabel("Email").fill("test@example.com");
  await page.getByLabel("First name").fill("Test");
  await page.getByLabel("Last name").fill("User");
  await page.getByLabel("Address").fill("123 Test St");
  await page.getByLabel("City").fill("San Francisco");
  await page.getByLabel("ZIP code").fill("94102");

  await page.getByRole("button", { name: "Continue to shipping" }).click();
  await page.getByRole("button", { name: "Continue to payment" }).click();

  // Payment (using Shopify test mode)
  await page.frameLocator('iframe[name*="card-fields"]')
    .getByPlaceholder("Card number")
    .fill("4242424242424242");

  await page.getByRole("button", { name: "Pay now" }).click();
  await expect(page.getByText("Thank you")).toBeVisible();
});

On this page