The phrase "the e-commerce flow" is doing a lot of work. Every site that sells anything has its own version of it, but the shape is the same one you have probably automated half a dozen times: land on a page, browse a catalog, drill into a product, add it to a cart, walk a checkout, confirm an order, look it up later.
Six moves, a handful of side-effects per move. The reason every QA portfolio test suite ends up automating this spine is that it exercises every part of a real product that breaks tests in production — auth state, currency formatting, redirects, stock changes between page load and submit, and the failure modes nobody scripts until the day a card declines and the team finds out the spec never covered it.
This page is how to walk that flow on QA Shop with a tester's eye. We have engineered the site so the flow is scriptable from the start: stable testids on every interactive element, slugs and order numbers instead of UUIDs in URLs, deterministic seed fixtures, and a developer dock at /test-tools that exposes cookies and JWT claims.
What follows is the practitioner's tour — the happy path first, then the variants you should script next, then the failure modes you should script third.
§ 01 · WHAT WE MEAN
What the e-commerce flow means here
Before we dive into checkpoints, a quick map. The canonical happy path on QA Shop has seven moves: home, products listing, product detail, add-to-cart, cart, checkout, confirmation, with order history reachable after the fact.
The page transitions you write assertions on are home → PLP, PLP → PDP, PDP → cart (via the cart icon or directly), cart → checkout, and checkout → order confirmation. The side-effects to verify along the way are concrete enough to be unambiguous: a cart-count badge that increments on add-to-cart, a session cookie that gets attached on login, a database row inserted on order placement, a redirect from /checkout to /orders/[orderNumber] on success.
That is the spine. Every test you write here either walks the spine or peels off it for a variant or a failure case. We will walk it once below — slowly, with the testid you would target at each step — and then peel off into the variants.
§ 02 · HAPPY PATH
The six checkpoints of the happy path
Six checkpoints. About a hundred words on each, written the way you would brief a teammate who is sitting down to script their first end-to-end pass.
1. Land on home
Start at /. The home route is anonymous-accessible, so your test does not need a logged-in fixture for this checkpoint. The two assertions worth making here are that the featured-product rail renders and that the header cart count is 0 for a fresh context.
Read the cart count from data-testid="cart-count" — it is the badge inside the header cart link. If you are scripting in Playwright, this is the moment to grab the page's <title> and stash it; we have shipped sites under the wrong title before, and the cheapest assertion is the one you make right after page load.
2. Browse to the product listing
Click into /products (the link is in the main nav, but a direct navigation is faster for a test). The PLP renders product cards and a search-and-filter rail. Scan for stable cards by slug rather than by index — the seed sometimes shifts ordering when we touch the catalog migration.
USB-C Hub 7-in-1 Adapter is a useful drilldown target because it has stock greater than zero and a non-trivial description. Search and filter both work via URL params, so you can drive them in your test by navigating to /products?search=hub rather than typing into the search input — same code path, faster spec, fewer flake surfaces.
3. Drill into the product detail page
From the PLP, click a card or navigate directly to /products/usb-c-hub-7-in-1-adapter. The outer wrapper is data-testid="page-pdp", the title is at data-testid="pdp-title", the price at data-testid="pdp-price". The buy box exposes the quantity stepper and the add-to-cart CTA at data-testid="pdp-add-to-cart".
Two side-effects to verify before you click: the stock value is read from the database on render (so a reseed reflects in the page), and the price is formatted in the active currency (the locale toggle in the footer changes it). Most happy-path specs only need a price-not-empty assertion here; portfolio specs do well to assert against an exact formatted string.
4. Add to cart and watch the badge
Click data-testid="pdp-add-to-cart". The user-visible side-effect is the cart count incrementing in the header — re-read data-testid="cart-count" and assert it equals 1.
There is also a toast that fires; if you assert against it, hold a wait that is comfortable enough for the toast to fade out before the next action, otherwise your next click will land on the toast and your test will look like the page froze. The test runner that handles this most gracefully out of the box is Playwright, because its locator auto-waits past transient overlays.1 In Selenium and WebdriverIO you will write the wait yourself.
5. Cart page and totals
Navigate to /cart. The line list is data-testid="cart-list"; each line carries a quantity stepper and a remove control. The summary card on the side carries totals at data-testid="cart-summary-subtotal", cart-summary-tax, cart-summary-shipping, and the headline data-testid="cart-summary-total". The promo input is data-testid="cart-summary-promo-input" with cart-summary-promo-apply to submit.
The single most useful assertion at this checkpoint is that the totals add up — subtotal plus tax plus shipping equals total — because that is the assertion that catches arithmetic regressions in the pricing layer that no individual price assertion would.
6. Checkout and order confirmation
Click data-testid="cart-summary-checkout" (the legacy alias data-testid="checkout-button" still resolves on the cart page CTA). On /checkout, the wizard step container is data-testid="checkout-step-content"; the summary panel is data-testid="checkout-summary"; any inline error renders at data-testid="checkout-error".
Walk the shipping form, then the payment form, using the fixed test card numbers we publish:
The happy-path card. Authorizes, captures, redirects to /orders/[orderNumber]. Use this one for any spec whose assertion is "the order was placed".
A generic decline from the issuer. The submission posts, the server responds with a decline, and the cart contents stay intact so the customer can retry.
Simulates an expired card. Same posture as the decline, but the error message names the expiry so your spec can assert against the specific failure reason.
On submit, the page redirects to /orders/[orderNumber] and renders the confirmation. Capture the order number from data-testid="order-detail-number" — it is the value you will fence later assertions on, and it is stable across reseeds.
§ 03 · VARIANTS
Variants you should script next
The happy path is one test. The four variants below are the ones that catch real bugs, because they exercise code paths the spine does not.
Quantity greater than one. On the PDP, increment the quantity stepper (data-testid="pdp-qty-inc") before adding to cart. The cart line should reflect the new quantity, the line total should multiply correctly, and tax should recompute on the new subtotal. The assertion that catches the most regressions here is the tax-recompute one — it is the one teams forget to write, because it is invisible until tax math drifts.
Multiple distinct items. Add a second product (try /products/mechanical-keyboard-rgb — note that this one is seeded with stock=0, so save it for the failure-mode case below; pick another in-stock product from the PLP for the multi-item happy variant).
The cart should show two line items, the subtotal should equal the sum of the line totals, and shipping calculation should reflect the combined weight (or whatever shipping rule is active). This variant is where the cart-list rendering bugs surface — duplicate keys, sort drift, totals that quietly use the first line only.
Promo code apply and clear. Enter a known promo code in data-testid="cart-summary-promo-input" and click cart-summary-promo-apply. The status should render at cart-summary-promo-status and the total should drop.
Then clear the code; the total should rise back. The assertion that catches real bugs is the symmetric one — apply, then clear, then assert the original total — because it is the assertion that catches state-not-resetting bugs that the apply-only test hides.
Logged-in versus guest. Run the spine once with no auth (anonymous browsing, anonymous cart, log in only at checkout) and once fully logged in from the start.
The cart should persist across the login step in the first case — the cart-context provider merges localStorage cart into the server-side cart on auth — and order history should be visible at /orders only in the second. If your suite covers both regimes, you exercise the auth-merge code path that most happy-path specs miss entirely.
§ 04 · FAILURES
Failure modes you should script third
The happy path passes. The variants pass. Now write the failures, because they are the tests that catch real outages.
Out-of-stock product. Navigate to /products/mechanical-keyboard-rgb. The PDP renders, but the buy box swaps the add-to-cart CTA for an out-of-stock indicator. Assert that the disabled state is present and that the cart count does not change if your test attempts to click anyway.
The reason this matters is that catalog services drift — a stock value of 0 is a normal product state, not a 404, and it is the easiest place for a regression to slip in (a button that should be disabled becoming clickable because of a refactor in the buy-box component).
Declined card. Use the test card 4000 0000 0000 0002. The checkout submission posts, the server responds with a decline, and data-testid="checkout-error" renders the failure message. The order is not placed.
Two assertions to make: the error testid is visible, and the cart contents are still intact (so the customer can retry without re-adding products). Suites that skip this case are the ones that ship a prod outage when the payment provider returns a status code the client does not recognize.
Expired card. Test card 4000 0000 0000 0069 simulates an expired card. Same checkout posture, same error testid, but with a different message. If you script both decline and expired, you cover the two most common card-failure states.
Server error simulation. The /test-tools/error-simulation page is a fixture for forcing 4xx and 5xx responses. Use it to verify that your suite handles a transient 500 gracefully — the right behavior for the customer is a retry, the right behavior for your test is to surface the failure with a stable error message rather than a crash.
Cypress, in particular, is unforgiving here because it runs your code in the browser; a thrown error in the test page will halt the spec.2
Network slowdown. The same /test-tools surface includes a delay knob — useful for the test that asserts a loading skeleton renders while the response is in flight. The skeleton is at data-testid="cart-skeleton" (and friends across the surface).
If your suite asserts only against the loaded state, you have a class of bugs you cannot see — the skeleton that renders forever when the response shape changes, the spinner that fails to clear on error.
§ 05 · AUTH
Account-aware versus anonymous flows
Two regimes of test, both worth covering. The anonymous regime is where the cart lives in the client-side context backed by localStorage and the order history page is gated behind login (so it 404s for guests). The account-aware regime is where the cart syncs to the server on auth and the order history is populated. The seam between them — guest-to-auth merge — is where the most interesting bugs live, because it is where two stores have to agree.
The fastest way to inspect the auth state from a running test is the /test-tools/state-inspector page. It exposes the active session cookie, the JWT claims (decoded), and the cart-context contents — everything your test would otherwise have to read out of the browser context manually. We use it during exploratory test authoring to confirm the state is what we think it is before committing to an assertion against it.
The Customer and Principal/Automation account split, covered in getting-started, has a practical implication for tests: keep one fixture-by-account discipline. A spec that creates its own user and tears it down is reproducible; a spec that depends on a shared account is a flake waiting to happen. Use the seeded Customer credentials for happy-path tests and the Principal/Automation pairing for CI matrices that need disposable accounts at scale.
§ 06 · ADMIN SIDE
The admin side of the same flow
The other side of the e-commerce flow is the admin view, and it is the most under-tested surface in commerce automation. An order placed by a Customer at checkout lands in the admin orders list at /admin/orders — outer wrapper data-testid="admin-orders-table", with a status filter at data-testid="admin-orders-status-filter".
From there, an admin can transition the status: pending, processing, shipped, delivered, cancelled. Each transition is a database write and a side-effect on the customer-facing order detail page.
We do not publish admin credentials in this MDX for security reasons; the seeded admin fixture is documented in the testing guide for users who need it.
Where to go next
The next step inside the help center is the testing guide — the full data-testid catalog for the surfaces this page introduces. The features page covers the selector contract in depth, and getting-started covers the account model. Outside the help center, framework-specific runnable spec files for the flow above are coming under /recipes once published; longer-form practitioner guidance lives under /learn.
Footnotes
-
Playwright Auto-waiting and actionability — https://playwright.dev/docs/actionability (verified 2026-04-28, Playwright 1.58) ↩
-
Cypress Error Handling — https://docs.cypress.io/app/references/error-messages (verified 2026-04-28, Cypress 13.6) ↩
Frequently asked questions
Last verified: