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 whole customer journey, from the homepage landing to the payment confirmation. That path earns the revenue, and when any one step on it becomes a bottleneck, the failure cascades down the rest of it.

  • 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. A sparse catalog returns 404s before the test reaches the endpoints that actually break.

A checkout-only load test misses the most common BFCM failure: a slow product page that stops 98 % of users from reaching checkout at all. Put load on the full funnel, with realistic weights at each step, and you see where the system breaks under traffic shaped like your customers.

Real BFCM failures are rarely “payment is down.” More often they look like this:

  • 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

A full-funnel test shows all of these.

The journey has six stages. Each stage calls its own endpoints, and the VU weight at each stage follows the 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 Taurus, you write this as one scenario with all requests in sequence and think time between steps. The per-stage weights come from the think time and scenario branching. You can also use several scenarios with a weighted VU split.

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. Give each virtual user its own product ID. One shared ID produces cache-hit rates that production never sees.

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 never ran.
  • Sort by p95 latency to find the slowest stage.
  • Sort by error rate to find the stage with the most errors.
  • 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 scenario over-weights checkout.

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

DoDon’t
Weight scenario steps by real conversion funnel dataSend all VUs to the payment endpoint, which is not a realistic workload
Use randomized product IDs and search terms from a CSVUse the same product/session in every VU and get artificial cache behavior
Set per-label failure criteria on critical endpointsUse only a global error-rate criterion, which 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