Skip to content

End-to-end journey load

import Screenshot from ‘@components/Screenshot.astro’;

Most load tests cover a single endpoint. A BFCM readiness test must cover the entire customer journey from the moment a user lands on the homepage to the moment their payment is confirmed — because that is the path that generates revenue, and it is the path that fails in cascading ways when any single step becomes a bottleneck.

  • You have completed capacity planning and traffic modeling and have per-stage VU weights derived from your conversion funnel.
  • A smoke test has passed for each individual endpoint in the journey.
  • You have test user accounts (or anonymous session support) for the checkout flow.
  • Your staging environment has a realistic product catalog — sparse catalogs return 404s before the real problem endpoints are even hit.

A checkout-only load test misses the most common BFCM failure pattern: the bottleneck is not payment, it is a slow product page that prevents 98 % of users from ever reaching checkout. Running load against the full funnel, with realistic weights at each step, reveals where the system actually breaks under customer-shaped traffic.

Real BFCM failures are rarely “payment is down.” They are:

  • Homepage CDN cache expires under traffic, hammering origin
  • Search index returns timeouts because every arriving user searches immediately
  • PDP personalization service (frequently skipped in load tests) becomes the bottleneck
  • Session store hits its connection limit when cart-add requests spike
  • Checkout service rate-limits payment processor calls, creating a queue that backs up into cart

Full-funnel testing surfaces all of these.

The journey has six stages. Each stage calls specific endpoints, and the VU weight at each stage reflects conversion rates:

StageEndpointsRelative traffic
1. Landing / category browseGET /, GET /category/:id, GET /nav100 %
2. SearchGET /search?q=..., GET /search/autocomplete40 %
3. Product detail (PDP)GET /product/:id, GET /product/:id/reviews, GET /product/:id/stock60 %
4. Add to cartPOST /cart/items, GET /cart15 %
5. CheckoutGET /checkout, PUT /checkout/address, PUT /checkout/shipping8 %
6. PaymentPOST /checkout/payment, GET /order/:id/confirmation2 %

In a Taurus scenario, you implement this as a single scenario with all requests in sequence and think time between steps. The per-stage weights emerge naturally from the think time and scenario branching — or you can use multiple scenarios with weighted VU allocation.

execution:
- scenario: browse-to-confirm
concurrency: 1360
ramp-up: 5m
hold-for: 20m
scenarios:
browse-to-confirm:
think-time: 3s
requests:
# Stage 1: Landing page
- url: https://api.staging.example.com/v1/homepage
label: GET /homepage
think-time: 4s
# Stage 2: Search (40 % of users search — model via data file randomization)
- url: https://api.staging.example.com/v1/search?q=${SEARCH_TERM}
label: GET /search
think-time: 3s
# Stage 3: Product detail
- url: https://api.staging.example.com/v1/products/${PRODUCT_ID}
label: GET /product/:id
- url: https://api.staging.example.com/v1/products/${PRODUCT_ID}/stock
label: GET /product/:id/stock
think-time: 5s
# Stage 4: Add to cart
- url: https://api.staging.example.com/v1/cart/items
label: POST /cart/items
method: POST
headers:
Content-Type: application/json
Authorization: Bearer ${SESSION_TOKEN}
body: '{"product_id": "${PRODUCT_ID}", "qty": 1}'
think-time: 2s
# Stage 5: Checkout
- url: https://api.staging.example.com/v1/checkout/start
label: POST /checkout/start
method: POST
headers:
Authorization: Bearer ${SESSION_TOKEN}
body: '{"cart_id": "${CART_ID}"}'
think-time: 6s
# Stage 6: Payment (only ~25% of checkout starters complete payment)
- url: https://api.staging.example.com/v1/checkout/payment
label: POST /checkout/payment
method: POST
headers:
Content-Type: application/json
Authorization: Bearer ${SESSION_TOKEN}
body: '{"payment_method": "card", "token": "${PAYMENT_TOKEN}"}'

Use a CSV data entity (see Parameterise tests with CSV data) to supply realistic PRODUCT_ID, SESSION_TOKEN, CART_ID, and SEARCH_TERM values. Do not use the same product ID for every virtual user — you will create artificial cache-hit rates that are not present in production.

Set failure criteria at the per-label level so each stage has its own SLO:

Request labelp95 thresholdError rate threshold
GET /homepage300 ms0.1 %
GET /search500 ms0.5 %
GET /product/:id400 ms0.1 %
GET /product/:id/stock200 ms1 %
POST /cart/items600 ms0.5 %
POST /checkout/start800 ms0.5 %
POST /checkout/payment1200 ms0.2 %

Payment gets the most generous latency budget (customers will wait longer to pay) but the tightest error rate budget (payment failures directly reduce revenue).

Open the Overview tab and switch to the per-label breakdown:

  • Check that each label appears in the results — a missing label means that part of the journey was not exercised.
  • Sort by p95 latency to find the slowest stage.
  • Sort by error rate to find the most error-prone stage.
  • Look at throughput per label — the ratio RPS(homepage) / RPS(payment) should be close to your conversion funnel ratio (100 : 2). If payment RPS is higher relative to homepage, your test scenario is over-weighting checkout.

The payment endpoint under realistic funnel load is often faster than it appears in isolation tests — because only 2 % of your VUs reach it. If payment p95 is slow in a full-funnel run, that is a real problem, not test artifact.

DoDon’t
Weight scenario steps by real conversion funnel dataSend all VUs to the payment endpoint — it is not a realistic workload
Use randomized product IDs and search terms from a CSVUse the same product/session in every VU — artificial cache behavior
Set per-label failure criteria on critical endpointsUse only a global error-rate criterion — it hides per-stage failures
Verify every journey stage appears in the per-label breakdownTrust that all steps ran just because the run finished
Run the funnel test before stress or spike testsSpike-test an endpoint that was never part of a funnel test