eBook > The JMeter Playbook: Build, Scale, and Optimize Performance Tests
Appendix: Example Test Plan
This appendix provides a complete, annotated JMeter test plan that ties together every concept from this playbook. The test targets the free Demoblaze online store.
Back to topScenario
Simulate a user who:
- Opens the homepage of the demoblaze.com online store.
- Lists products by calling the backend API.
- Extracts the product ID from the response using a JSON Extractor.
- Opens product details page for the selected product.
- Adds the product to the cart via a POST request with dynamic parameters.
- Verifies the add-to-cart request returns a successful response.
Test Plan Tree
Test Plan
└── Thread Group ← Chapter 2: Thread Groups
│ Threads : ${__P(threads,1)} (parameterized via properties)
│ Ramp-Up : ${__P(ramp-up,1)}
│ Loops : ${__P(loops,1)}
│ Duration: ${__P(duration,60)}
│
├── HTTP Request Defaults ← Chapter 2: Samplers
│ Protocol: https, Server: demoblaze.com
│ Retrieve All Embedded Resources (6 parallel)
│
├── HTTP Cookie Manager ← Chapter 2: Samplers
│
├── HTTP Cache Manager ← Chapter 2: Samplers
│
├── CSV Data Set Config (products.csv) ← Chapter 3: CSV Data Set
│ Variable: product (read from header row)
│
├── open homepage - GET / ← Chapter 2: Samplers
│
├── get products - GET /entries ← Chapter 2: Samplers
│ (server: api.demoblaze.com)
│ └── JSON Extractor → ${productId} ← Chapter 3: Extractors
│ JSONPath: $.Items[?(@.title=='${product}')].id
│
├── product details - GET /prod.html?idp_=${productId} ← Chapter 3: Correlation
│
├── add to cart - POST /addtocart ← Chapter 2: Samplers
│ (server: api.demoblaze.com)
│ Body: {"id":"${__UUID()}","cookie":"user=${__UUID()}",
│ "prod_id":${productId},"flag":false}
│ ├── HTTP Header Manager ← Chapter 2: Samplers
│ │ Content-Type: application/json
│ └── Response Assertion ← Chapter 4: Assertions
│ Response Code Equals 200
│
├── View Results Tree (disabled) ← Chapter 2: Listeners
├── Summary Report (disabled) ← Chapter 2: Listeners
└── Backend Listener (disabled) ← Chapter 5: Results & Reporting
InfluxDB: http://localhost:8086/write?db=jmeter
Back to top
Sample CSV File (products.csv)
product
Samsung galaxy s6
Nokia lumia 1520
Nexus 6
Samsung galaxy s7
Back to topNote: The header row (
product) is used by JMeter to automatically create the${product}variable. Leave the Variable Names field empty and set Ignore first line toFalsein the CSV Data Set Config.
The JMX File
Below is the complete .jmx file you can save as example-test-plan.jmx and open in JMeter:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments"
guiclass="ArgumentsPanel" testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
</TestPlan>
<hashTree>
<!-- ==================== THREAD GROUP ==================== -->
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup"
testname="Thread Group">
<stringProp name="ThreadGroup.num_threads">${__P(threads,1)}</stringProp>
<stringProp name="ThreadGroup.ramp_time">${__P(ramp-up,1)}</stringProp>
<stringProp name="ThreadGroup.duration">${__P(duration,60)}</stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController"
guiclass="LoopControlPanel" testclass="LoopController"
testname="Loop Controller">
<stringProp name="LoopController.loops">${__P(loops,1)}</stringProp>
<boolProp name="LoopController.continue_forever">false</boolProp>
</elementProp>
</ThreadGroup>
<hashTree>
<!-- HTTP Request Defaults -->
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement"
testname="HTTP Request Defaults">
<boolProp name="HTTPSampler.image_parser">true</boolProp>
<boolProp name="HTTPSampler.concurrentDwn">true</boolProp>
<intProp name="HTTPSampler.concurrentPool">6</intProp>
<stringProp name="HTTPSampler.domain">demoblaze.com</stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.path">/</stringProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments"
guiclass="HTTPArgumentsPanel" testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
</ConfigTestElement>
<hashTree/>
<!-- HTTP Cookie Manager -->
<CookieManager guiclass="CookiePanel" testclass="CookieManager"
testname="HTTP Cookie Manager" enabled="true">
<collectionProp name="CookieManager.cookies"/>
<boolProp name="CookieManager.clearEachIteration">false</boolProp>
<boolProp name="CookieManager.controlledByThreadGroup">false</boolProp>
</CookieManager>
<hashTree/>
<!-- HTTP Cache Manager -->
<CacheManager guiclass="CacheManagerGui" testclass="CacheManager"
testname="HTTP Cache Manager" enabled="true">
<boolProp name="clearEachIteration">false</boolProp>
<boolProp name="useExpires">true</boolProp>
<boolProp name="CacheManager.controlledByThread">false</boolProp>
</CacheManager>
<hashTree/>
<!-- CSV Data Set Config -->
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet"
testname="CSV Data Set Config" enabled="true">
<stringProp name="delimiter">,</stringProp>
<stringProp name="fileEncoding"></stringProp>
<stringProp name="filename">products.csv</stringProp>
<boolProp name="ignoreFirstLine">false</boolProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">shareMode.all</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames"></stringProp>
</CSVDataSet>
<hashTree/>
<!-- GET / - open homepage -->
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy"
testname="open homepage">
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments"
guiclass="HTTPArgumentsPanel" testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</HTTPSamplerProxy>
<hashTree/>
<!-- GET /entries - get products (server: api.demoblaze.com) -->
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy"
testname="get products">
<stringProp name="HTTPSampler.domain">api.demoblaze.com</stringProp>
<stringProp name="HTTPSampler.path">/entries</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments"
guiclass="HTTPArgumentsPanel" testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</HTTPSamplerProxy>
<hashTree>
<!-- JSON Extractor: productId -->
<JSONPostProcessor guiclass="JSONPostProcessorGui"
testclass="JSONPostProcessor"
testname="JSON Extractor" enabled="true">
<stringProp name="JSONPostProcessor.referenceNames">productId</stringProp>
<stringProp name="JSONPostProcessor.jsonPathExprs">$.Items[?(@.title=='${product}')].id</stringProp>
<stringProp name="JSONPostProcessor.match_numbers">1</stringProp>
<stringProp name="JSONPostProcessor.defaultValues">NOT_FOUND</stringProp>
</JSONPostProcessor>
<hashTree/>
</hashTree>
<!-- GET /prod.html?idp_={productId} - product details -->
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy"
testname="product details">
<stringProp name="HTTPSampler.path">/prod.html?idp_=${productId}</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments"
guiclass="HTTPArgumentsPanel" testclass="Arguments"
testname="User Defined Variables">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</HTTPSamplerProxy>
<hashTree/>
<!-- POST /addtocart - add to cart (server: api.demoblaze.com) -->
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy"
testname="add to cart">
<stringProp name="HTTPSampler.domain">api.demoblaze.com</stringProp>
<stringProp name="HTTPSampler.path">/addtocart</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{
"id": "${__UUID()}",
"cookie": "user=${__UUID()}",
"prod_id": ${productId},
"flag": false
}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
</HTTPSamplerProxy>
<hashTree>
<!-- HTTP Header Manager (child of POST only) -->
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager"
testname="HTTP Header Manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/json</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
<!-- Response Assertion: 200 OK -->
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion"
testname="Response Assertion" enabled="true">
<collectionProp name="Asserion.test_strings">
<stringProp name="49586">200</stringProp>
</collectionProp>
<stringProp name="Assertion.custom_message"></stringProp>
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
<boolProp name="Assertion.assume_success">false</boolProp>
<intProp name="Assertion.test_type">8</intProp>
</ResponseAssertion>
<hashTree/>
</hashTree>
<!-- View Results Tree (disabled for load tests) -->
<ResultCollector guiclass="ViewResultsFullVisualizer"
testclass="ResultCollector"
testname="View Results Tree" enabled="false">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<!-- Summary Report (disabled for load tests) -->
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector"
testname="Summary Report" enabled="false">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<!-- Backend Listener - InfluxDB (disabled by default) -->
<BackendListener guiclass="BackendListenerGui" testclass="BackendListener"
testname="Backend Listener" enabled="false">
<elementProp name="arguments" elementType="Arguments"
guiclass="ArgumentsPanel" testclass="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="influxdbMetricsSender" elementType="Argument">
<stringProp name="Argument.name">influxdbMetricsSender</stringProp>
<stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.influxdb.HttpMetricsSender</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="influxdbUrl" elementType="Argument">
<stringProp name="Argument.name">influxdbUrl</stringProp>
<stringProp name="Argument.value">http://localhost:8086/write?db=jmeter</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="application" elementType="Argument">
<stringProp name="Argument.name">application</stringProp>
<stringProp name="Argument.value">jmeter-essentials</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="measurement" elementType="Argument">
<stringProp name="Argument.name">measurement</stringProp>
<stringProp name="Argument.value">jmeter</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="summaryOnly" elementType="Argument">
<stringProp name="Argument.name">summaryOnly</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="samplersRegex" elementType="Argument">
<stringProp name="Argument.name">samplersRegex</stringProp>
<stringProp name="Argument.value">.*</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="percentiles" elementType="Argument">
<stringProp name="Argument.name">percentiles</stringProp>
<stringProp name="Argument.value">90;95;99</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="testTitle" elementType="Argument">
<stringProp name="Argument.name">testTitle</stringProp>
<stringProp name="Argument.value">Test name</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="eventTags" elementType="Argument">
<stringProp name="Argument.name">eventTags</stringProp>
<stringProp name="Argument.value"></stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">org.apache.jmeter.visualizers.backend.influxdb.InfluxdbBackendListenerClient</stringProp>
</BackendListener>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
Back to top
How to Run This Example
GUI Mode (for debugging)
- Save the XML above as
example-test-plan.jmx. - Save the CSV data as
products.csvin the same directory. - Open JMeter and load
example-test-plan.jmx. - Enable View Results Tree and Summary Report listeners (they are disabled by default).
- Click ▶ Start and inspect the results.
CLI Mode (for load testing)
jmeter -n \
-t example-test-plan.jmx \
-f \
-l results.jtl \
-Jthreads=50 \
-Jramp-up=150 \
-Jloops=-1 \
-Jduration=300 \
-e -o report-dashboard/
Then open report-dashboard/index.html in your browser.
Congratulations! You have completed JMeter Essentials. You now have the knowledge to design, parameterize, validate, and analyze performance tests with Apache JMeter. Happy testing! 🚀