Now that you know every component, let us build a production-grade test plan that uses all of them. This is the kind of test plan you would create for a real project -- an e-commerce API load test with authentication, parameterization, dynamic correlation, realistic think time, proper assertions, and professional reporting.
Test Plan: "E-Commerce Load Test"
│
├── User Defined Variables
│ ├── BASE_URL = api-staging.myapp.com
│ ├── PROTOCOL = https
│ ├── SLA_API = 2000
│ ├── SLA_PAGE = 5000
│ └── API_VERSION = v2
│
├── Thread Group (100 users, 120s ramp-up, Duration: 1800s)
│ │
│ ├── HTTP Request Defaults
│ │ ├── Protocol: ${PROTOCOL}
│ │ ├── Server: ${BASE_URL}
│ │ ├── Connect Timeout: 5000
│ │ └── Response Timeout: 30000
│ │
│ ├── HTTP Header Manager
│ │ ├── Content-Type: application/json
│ │ ├── Accept: application/json
│ │ └── X-Test-Run: ${__time(yyyyMMdd-HHmm)}
│ │
│ ├── HTTP Cookie Manager (standard policy)
│ │
│ ├── CSV Data Set Config (users.csv)
│ │ └── Variables: username, password, email
│ │
│ ├── Transaction Controller: "01-Login"
│ │ ├── HTTP Request: POST /api/${API_VERSION}/auth/login
│ │ │ └── Body: {"username":"${username}","password":"${password}"}
│ │ ├── JSON Extractor: authToken ($.token)
│ │ ├── JSON Extractor: userId ($.user.id)
│ │ ├── Response Assertion: code = 200
│ │ ├── JSON Assertion: $.token exists
│ │ └── Duration Assertion: ${SLA_API}
│ │
│ ├── Gaussian Random Timer (3000ms ± 1000ms)
│ │
│ ├── HTTP Header Manager (adds Authorization: Bearer ${authToken})
│ │
│ ├── Transaction Controller: "02-Browse Products"
│ │ ├── HTTP Request: GET /api/${API_VERSION}/products?page=1&limit=20
│ │ ├── JSON Extractor: productId ($.products[0].id)
│ │ ├── JSON Assertion: $.products[0] exists
│ │ ├── Duration Assertion: ${SLA_API}
│ │ ├── Gaussian Random Timer (2000ms ± 500ms)
│ │ ├── HTTP Request: GET /api/${API_VERSION}/products/${productId}
│ │ ├── Response Assertion: code = 200
│ │ └── Duration Assertion: ${SLA_API}
│ │
│ ├── Gaussian Random Timer (5000ms ± 2000ms) ← simulates reading product details
│ │
│ ├── Transaction Controller: "03-Add to Cart"
│ │ ├── HTTP Request: POST /api/${API_VERSION}/cart
│ │ │ └── Body: {"productId":${productId},"quantity":1}
│ │ ├── JSON Extractor: cartId ($.cartId)
│ │ ├── Response Assertion: code = 201
│ │ └── Duration Assertion: ${SLA_API}
│ │
│ ├── Gaussian Random Timer (3000ms ± 1000ms)
│ │
│ ├── Transaction Controller: "04-Checkout"
│ │ ├── JSR223 PreProcessor: generate orderRef
│ │ ├── HTTP Request: POST /api/${API_VERSION}/checkout
│ │ │ └── Body: {"cartId":"${cartId}","orderRef":"${orderRef}"}
│ │ ├── JSON Extractor: orderId ($.orderId)
│ │ ├── Response Assertion: code = 201
│ │ ├── JSON Assertion: $.orderId exists
│ │ └── Duration Assertion: ${SLA_API}
│ │
│ └── Transaction Controller: "05-Order Confirmation"
│ ├── HTTP Request: GET /api/${API_VERSION}/orders/${orderId}
│ ├── Response Assertion: body contains "confirmed"
│ └── Duration Assertion: ${SLA_API}
│
├── Backend Listener (InfluxDB → Grafana)
│
└── Summary Report (Write to: results-${__time(yyyyMMdd-HHmm)}.jtl)# Step 1: Validate with 2 users (GUI mode)
jmeter -t ecommerce-load-test.jmx
# Run with 2 users, check View Results Tree for green results
# Step 2: Run the actual load test (CLI mode)
jmeter -n \
-t ecommerce-load-test.jmx \
-l results.jtl \
-e -o html-report/ \
-Jthreads=100 \
-Jrampup=120 \
-Jduration=1800
# Step 3: Open the HTML report
open html-report/index.html
# Note: -J flags pass properties that override User Defined Variables.
# In your test plan, use ${__P(threads,100)} instead of hardcoded 100.
# This lets you change user count from command line without editing the .jmx file.# In JMeter Thread Group:
Number of Threads: ${__P(threads,100)}
Ramp-Up Period: ${__P(rampup,120)}
Duration: ${__P(duration,1800)}
# In User Defined Variables:
BASE_URL = ${__P(baseurl,api-staging.myapp.com)}
# Now you can change everything from CLI:
jmeter -n -t test.jmx -l results.jtl \
-Jthreads=200 \
-Jrampup=60 \
-Jduration=3600 \
-Jbaseurl=api.myapp.com
# This is how you run the same test plan against different environments
# and with different load profiles without editing the .jmx file.Use ${__P(property,default)} in your test plan for any value that might change between runs: thread count, duration, server URL, SLA thresholds. This makes your test plan CI/CD-friendly -- Jenkins or GitHub Actions can pass different parameters for different test types (smoke: 10 users/5 min, load: 100 users/30 min, stress: 500 users/60 min).
Key Point: A production-grade test plan uses: Request Defaults, Header/Cookie Managers, CSV data, Gaussian timers, Transaction Controllers, Response + Duration Assertions, JSON Extractors, and CLI properties. Validate in GUI first, run in CLI mode.
Key Point: Production test plan = Defaults + Headers + Cookies + CSV + Timers + Transactions + Assertions + Extractors + CLI properties. Always validate in GUI before running.