Chapter 5: Correlation and Parameterization
Here is where everything comes together. In real performance testing, you almost never use correlation alone or parameterization alone. You combine them. The flow is: parameterize the input (different users), correlate the dynamic response (tokens), and use both in subsequent requests. Let me walk you through a complete login-to-checkout flow that uses every technique we have covered.
Test Plan
├── User Defined Variables
│ ├── BASE_URL = staging-api.example.com
│ ├── PROTOCOL = https
│ └── THINK_TIME = 2000
│
├── Thread Group (${__P(users,10)} threads, ${__P(rampup,30)}s ramp-up)
│ ├── CSV Data Set Config: users.csv
│ │ (username, password, first_name, shipping_address)
│ │
│ ├── HTTP Cookie Manager (auto-manage cookies)
│ ├── HTTP Header Manager
│ │ └── Content-Type: application/json
│ │
│ ├── Transaction Controller: "TC_01_Login"
│ │ ├── GET /login
│ │ │ └── CSS Extractor
│ │ │ Variable: csrf_token
│ │ │ Selector: meta[name=csrf-token]
│ │ │ Attribute: content
│ │ │ Default: CSRF_NOT_FOUND
│ │ │
│ │ └── POST /api/auth/login
│ │ Body: {"username":"${username}","password":"${password}","_csrf":"${csrf_token}"}
│ │ └── JSON Extractor
│ │ Variables: auth_token;user_id
│ │ JSONPath: $.token;$.user.id
│ │ Defaults: TOKEN_NOT_FOUND;ID_NOT_FOUND
│ │
│ ├── HTTP Header Manager (added after login)
│ │ └── Authorization: Bearer ${auth_token}
│ │
│ ├── Constant Timer: ${THINK_TIME}
│ │
│ ├── Transaction Controller: "TC_02_Browse"
│ │ ├── GET /api/products?page=1&limit=20
│ │ │ └── JSON Extractor
│ │ │ Variable: product_id
│ │ │ JSONPath: $.products[*].id
│ │ │ Match No: 0 (random product)
│ │ │ Default: PRODUCT_NOT_FOUND
│ │ │
│ │ └── GET /api/products/${product_id}
│ │ └── JSON Extractor
│ │ Variable: product_price
│ │ JSONPath: $.price
│ │ Default: 0
│ │
│ ├── Constant Timer: ${THINK_TIME}
│ │
│ ├── Transaction Controller: "TC_03_Add_to_Cart"
│ │ └── POST /api/cart
│ │ Body: {"productId":"${product_id}","quantity":${__Random(1,5)}}
│ │ └── JSON Extractor
│ │ Variable: cart_id
│ │ JSONPath: $.cartId
│ │ Default: CART_NOT_FOUND
│ │
│ ├── Constant Timer: ${THINK_TIME}
│ │
│ └── Transaction Controller: "TC_04_Checkout"
│ └── POST /api/checkout
│ Body: {"cartId":"${cart_id}","address":"${shipping_address}","paymentRef":"PAY-${__UUID()}"}
│ └── Response Assertion
│ Contains: "orderConfirmed":true
│
│ └── View Results Tree (debug only, disable for actual load)Let me trace how data flows through this test for Thread 1, Iteration 1. This is important to understand because debugging means tracing values through the chain.
# PARAMETERIZATION: CSV provides input data
# username = priya.sharma@test.com (from CSV row 1)
# password = Priya@123 (from CSV row 1)
# shipping_address = 42 MG Road, Mumbai (from CSV row 1)
# REQUEST 1: GET /login
# -> Response HTML contains: <meta name="csrf-token" content="xK9mP2nQ" />
# CORRELATION: CSS Extractor captures csrf_token = xK9mP2nQ
# REQUEST 2: POST /api/auth/login
# -> Sends: {"username":"priya.sharma@test.com","password":"Priya@123","_csrf":"xK9mP2nQ"}
# (username, password from CSV | csrf_token from correlation)
# -> Response: {"token":"eyJhbGci...","user":{"id":42,"name":"Priya Sharma"}}
# CORRELATION: JSON Extractor captures auth_token = eyJhbGci..., user_id = 42
# REQUEST 3: GET /api/products?page=1&limit=20
# -> Header: Authorization: Bearer eyJhbGci... (auth_token from correlation)
# -> Response: {"products":[{"id":"P001",...},{"id":"P002",...},...]}
# CORRELATION: JSON Extractor captures product_id = P002 (random match)
# REQUEST 4: GET /api/products/P002
# -> Uses product_id from correlation
# CORRELATION: JSON Extractor captures product_price = 1299
# REQUEST 5: POST /api/cart
# -> Sends: {"productId":"P002","quantity":3}
# (product_id from correlation | quantity from __Random())
# CORRELATION: JSON Extractor captures cart_id = CART-789
# REQUEST 6: POST /api/checkout
# -> Sends: {"cartId":"CART-789","address":"42 MG Road, Mumbai","paymentRef":"PAY-a1b2c3..."}
# (cart_id from correlation | address from CSV | paymentRef from __UUID())
# Thread 2 gets DIFFERENT CSV data (user2) and DIFFERENT correlated tokensWhat happens when correlation fails mid-flow? If the login response does not contain a token (maybe the credentials were wrong), every subsequent request will fail. In a real test with 500 users, this cascading failure generates thousands of errors that hide the actual problem. You need guardrails.
// Add as JSR223 Assertion after the JSON Extractor on POST /login
// This stops the thread immediately if login correlation failed
def token = vars.get("auth_token")
if (token == null || token == "TOKEN_NOT_FOUND" || token.isEmpty()) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage(
"Login correlation failed for user: ${vars.get('username')}. " +
"Response code: ${prev.getResponseCode()}. " +
"Check credentials in CSV or server availability."
)
// Optionally stop this thread to prevent cascading failures
// ctx.getThread().stop()
log.error("CORRELATION FAILURE: auth_token not found for user ${vars.get('username')}")
}Wrap your test logic in Transaction Controllers and name them like TC_01_Login, TC_02_Browse. This gives you per-transaction response times in the report. Without Transaction Controllers, you only see individual request times and cannot measure end-to-end flow performance.
# Skip checkout if cart is empty (correlation returned no cart_id)
If Controller: "${cart_id}" != "CART_NOT_FOUND"
└── POST /api/checkout
# Different flow for premium vs standard users (from CSV)
If Controller: "${account_type}" == "premium"
├── GET /api/premium/offers
└── POST /api/premium/apply-discount
If Controller: "${account_type}" == "standard"
└── GET /api/standard/deals
# Conditional based on server response (from correlation)
# Extract status code from JSON: $.status
If Controller: "${response_status}" == "requires_2fa"
├── GET /api/2fa/challenge
│ └── JSON Extractor: otp_session_id
└── POST /api/2fa/verify
Body: {"sessionId":"${otp_session_id}","code":"${__Random(100000,999999)}"}Q: Walk me through how you would set up a performance test for a login flow that needs both correlation and parameterization.
A: First, I prepare the test data: a CSV file with unique usernames and passwords for each virtual user. Then I build the flow: (1) Add a CSV Data Set Config with sharing mode "All threads" so each thread gets a unique user. (2) Start with a GET request to the login page and add a CSS or Regex Extractor as a child to capture the CSRF token. (3) Add the POST login request using ${username} and ${password} from CSV and ${csrf_token} from the extractor. (4) Add a JSON Extractor on the POST response to capture the auth token. (5) Add an HTTP Header Manager after login with "Authorization: Bearer ${auth_token}". (6) All subsequent requests automatically use the auth token. I also set meaningful default values like "NOT_FOUND" on every extractor and add an If Controller or assertion after login to fail fast if correlation breaks. Finally, I wrap logical groups in Transaction Controllers for meaningful reporting.
Key Point: Real performance tests combine parameterization (unique user data from CSV) with correlation (dynamic tokens from responses). Trace the data flow through your test and add guardrails for correlation failures.