I have added a Beanshell post processor in a Jmeter thread group and I have multiple HTTP requests in the same thread group that uses the post processor. I am creating a Hashmap and putting values into the map after execution of every HTTP request and then creating a json object of the map. The issue is that after every HTTP request, the map values are getting replaced by only the latest values of the last HTTP request that was executed. How to avoid this?
The code in my postprocessor is as follows:
HashMap map = new HashMap();
map.put(extID,intID);
object = new JSONObject(map);
vars.put("idMap" , object.toString());
JSONParser parser = new JSONParser();
JSONObject json = (JSONObject)parser.parse(vars.get("idMap"));
You can create a map and share with all threads using props, example:
Map<String, String> map = props.get("map")
if (map == null) {
map = new HashMap<>()
} else {
// in your case map.put(extID,intID);
String uuid = UUID.randomUUID().toString().replace("-", "")
map.put(uuid,uuid)
}
props.put("map", map)
vars.putObject("map", map)
JMeter's Post-Processors obey JMeter Scoping Rules so if you place the PostProcessor at the same level as Samplers - the PostProcessor will be executed after each sampler
If you want it to be executed only after a specific Sampler - make the PostProcessor a direct child of this sampler
Also be aware that since JMeter 3.1 it's recommended to use JSR223 Test Elements and Groovy language for scripting so it worth considering migrating, you won't have to change a single line of your code if you want, or alternatively Groovy has built-in JSON support
Related
I want to extract the json response in jmeter and if the response is not as expected, i need to print it to a csv file.
i tried using the contains method to check if the response contains an expected keyword but it doesn't seem to work. Is there anyother way I can do this?
Sample Code here:
log.info(ctx.getPreviousResult().getResponseDataAsString());
r = ctx.getPreviousResult().getResponseCode();
d = ctx.getPreviousResult().getResponseDataAsString();
if(!d.contains("valid")){
p.println(r +","+ vars.get("email") +",");
}
This is my json response
{
"isBlueLinkServicePinValid": "valid"
}
I'm checking for the keyword "valid"
if(!d.contains("valid"))
But it doesn't seem to work?
TIA
Since JMeter 3.1 it is not recommended to use Beanshell for scripting, you should go for JSR223 Test Elements and Groovy language instead. The main reason is that Groovy has much better performance than Beanshell does.
Groovy has built-in JSON support therefore you can extract isBlueLinkServicePinValid attribute value in an easy way:
String response = prev.getResponseDataAsString();
log.info("Response: " + response)
String valid = new groovy.json.JsonSlurper().parseText(response).isBlueLinkServicePinValid
log.info("Valid: " + valid);
if (valid.equals("valid")) {
log.info("Do something");
}
else {
log.info("Do something else");
}
Demo:
Below is a script that helps me build an extentreport for jmeter. It is a JSR223 PostProcessor element. It's working nicely however, the problem is that I have it duplicated after every HTTP Request in the script. I have several scripts with 100's of HTTP requests that would need essentially a copy of the same PostProcessor groovy script. This = hard to maintain!
I have tried splitting common parts into an external groovy script that I tried calling on the JSR223 PostProcessor. I also tried chunking up the bits of the script and putting the values into a csv so that I could just update the csv values if anything changed.
I'm sure there's a cleaner/better way to do this but I'm still learning so I'm not sure of the best way to make this easier to maintain. Here's the JSR223 PostProcessor. The only bit that changes with each http request is the "//test result" section
import com.relevantcodes.extentreports.ExtentReports;
import com.relevantcodes.extentreports.ExtentTest;
import com.relevantcodes.extentreports.LogStatus;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
//configure object for response data
def response = prev.getResponseDataAsString();
//configure extentreports objects
ExtentReports report;
ExtentTest testLogger;
//set location for file and report config
String resultsPath = "C:/portalQA/Automation/Results/";
String configPath = "C:/portalQA/Automation/JMeter/TestReportConfig/";
String reportPath =
resultsPath+"Login_Results_${reportDate}_${currentTime}_${testenv}.html";
File file = new File(reportPath);
if (!file.exists()) {
//if file does not exist, create it
report = new ExtentReports(reportPath, true);
report.loadConfig( new File(configPath+"extent-config.xml"));
} else {
//else append to existing report
report = new ExtentReports(reportPath, false);
report.loadConfig( new File(configPath+"extent-config.xml"));
}
//test result
testLogger = report.startTest("Authenticate");
testLogger.assignCategory("Initialize Session");
if (response.contains("com.blah.portal.model.User")) {
testLogger.log(LogStatus.PASS, "Logged in with: ${username}");
testLogger.log(LogStatus.INFO, response);
} else {
testLogger.log(LogStatus.FAIL, "Could not authenticate session");
testLogger.log(LogStatus.INFO, response);
}
log.info("Authenticate");
print("Authenticate print");
report.endTest(testLogger);
report.flush();
I see two options:
I suggest using JSR223 Listener instead. First of all, that way you will only have 1 listener in your script, which resolves your original problem, but it is a better option for writing into file in general, since listener has only one instance for all running threads, so you won't be creating a race condition when writing to file.
If you rather have a post-processor, you can put it on higher level (not under any particular sampler) which will cause it to run after each request within the same scope or below.
For example, configuration like
Thread Group
Post-processor
Sampler 1
...
Sampler N
Will cause Post-processor to run after each Sampler 1...Sampler N
In both cases you may need to check which sampler you are processing, and skip those you don't want to add to your report (easiest way to do it, is to come up with some name convention for excluded samplers)
I also faced the same challenge. In my case I need to check if JSON response from REST service was correct. I solved it in the following way.
I've created a JSR223 PreProcessor under the script root. It contains my custom class to handle JSON parsing and asserts.
import groovy.json.JsonSlurper
import org.apache.jmeter.assertions.AssertionResult
class CustomAssert {
def parseResponse(json) {
def jsonSlurper = new JsonSlurper()
return jsonSlurper.parseText(json)
}
def assertResult(assertionResult, expectedResult, actualResult) {
if (!expectedResult.equals(actualResult)) {
assertionResult.setFailure(true);
assertionResult.setFailureMessage("Expected ${expectedResult} but was ${actualResult}");
}
}
}
vars.putObject('customAssert', new CustomAssert())
Note the last line:
vars.putObject('customAssert', new CustomAssert())
I put an instance of my CustomAssert to vars.
Then under my HTTP Requests I've added JSR233 Assertion
def a = vars.getObject('customAssert')
def response = a.parseResponse(prev.getResponseDataAsString())
a.assertResult(AssertionResult, 'DRY', response.sensorResultHolderUIs[0].result.toString())
a.assertResult(AssertionResult, 'DRY', response.sensorResultHolderUIs[1].result.toString())
a.assertResult(AssertionResult, 'DRY', response.sensorResultHolderUIs[2].result.toString())
It basically retrieves the instance of CustomAssert from vars and calls its methods. I can put as many JSR233 Assertions as I want. The only code that is copied is those two lines on top:
def a = vars.getObject('customAssert')
def response = a.parseResponse(prev.getResponseDataAsString())
To sum up:
Take the common part of your code (that doesn't have to be copied).
Wrap it in a class.
Put the class in JSR233 PreProcessor under the root and export its instance via vars
Take the rest of your code and adjust it to use class defined in 2.
Put that code in as many JSR233 Assertions as you want remembering to retrieve the instance created in 3. from vars
Thank you user1053510. Your advice lead me to build my own JSR223 Listener that renders the report. Below is the code in my JSR223 Listener:
import com.aventstack.extentreports.*;
import com.aventstack.extentreports.reporter.*;
import com.aventstack.extentreports.markuputils.*;
ExtentHtmlReporter htmlReporter;
ExtentReports extent;
ExtentTest test;
// create the HtmlReporter
htmlReporter = new ExtentHtmlReporter("C:/AUTO_Results/Results_${testApp}_${reportDate}_${currentTime}_${testenv}.html");
//configure report
htmlReporter.config().setCreateOfflineReport(true);
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setDocumentTitle("${testApp} Results");
htmlReporter.config().setEncoding("utf-8");
htmlReporter.config().setReportName("${testApp} Results ${reportDate}_${currentTime}_${testenv}");
htmlReporter.setAppendExisting(true);
// create ExtentReports
extent = new ExtentReports();
// attach reporter to ExtentReports
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
// Show Env section and set data on dashboard
extent.setSystemInfo("Tool","JMeter");
extent.setSystemInfo("Test Env","${testenv}");
extent.setSystemInfo("Test Date","${reportDate}");
extent.setSystemInfo("Test Time","${currentTime}");
//stringify test info
String threadName = sampler.getThreadName();
String samplerName = sampler.getName();
String requestData = props.get("propRequestData");
String respCode = props.get("propRespCode");
String respMessage = props.get("propRespMessage");
String responseData = props.get("propResponse");
// create test
test = extent.createTest(threadName+" - "+samplerName);
//test.assignCategory("API Testing");
// analyze sampler result
if (vars.get("JMeterThread.last_sample_ok") == "false") {
log.error("FAILED: "+samplerName);
print("FAILED: "+samplerName);
test.fail(MarkupHelper.createLabel("FAILED: "+sampler.getName(),ExtentColor.RED));
} else if (vars.get("JMeterThread.last_sample_ok") == "true") {
if(responseData.contains("#error")) {
log.info("FAILED: "+sampler.getName());
print("FAILED: "+sampler.getName());
test.fail(MarkupHelper.createLabel("FAILED: "+sampler.getName(),ExtentColor.RED));
} else if (responseData.contains("{")) {
log.info("Passed: "+sampler.getName());
print("Passed: "+sampler.getName());
test.pass(MarkupHelper.createLabel("Passed: "+sampler.getName(),ExtentColor.GREEN));
}
} else {
log.error("Something is really wonky");
print("Something is really wonky");
test.fatal("Something is really wonky");
}
//info messages
test.info("RequestData: "+requestData);
test.info("Response Code and Message: "+respCode+" "+respMessage);
test.info("ResponseData: "+responseData);
//playing around
//markupify json into code blocks
//Markup m = MarkupHelper.createCodeBlock(requestData);
//test.info(MarkupHelper.createModal("Modal text"));
//Markup mCard = MarkupHelper.createCard(requestData, ExtentColor.CYAN);
// test.info("Request "+m);
// test.info(mCard);
// test.info("Response Data: "+MarkupHelper.createCodeBlock(props.get("propResponse")));
// test.info("ASSERTION MESSAGE: "+props.get("propAssertion"));
// end the reporting and save the file
extent.flush();
Then in each threadgroup I have a BeanShell Assertion with these lines:
//request data
String requestData = new String(prev.SamplerData);
//String requestData = new String(requestData);
props.put("propRequestData", requestData);
//response data
String respData = new String(prev.ResponseData);
//String respData = new String(prev.getResponseDataAsString());
props.put("propResponse", respData);
//response code
String respCode = new String(prev.ResponseCode);
props.put("propRespCode",respCode);
//response message
String respMessage = new String(prev.ResponseMessage);
props.put("propRespMessage",respMessage);
I'm facing the issue while writing the scenario in JMeter. It's API using GET Method and requires JSON BODY.
It's very easy if the method is POST/PUT. But I don't know how to do with method GET. I tried: Add HTTP Header Manager with Content-Type:application/json, but nothing help.
As I know, using BODY with GET request is the not the good way, but the developer team has implemented like that, and it can be worked with curl.
So I would like to know can we config this in JMeter or not? and How?
Thanks in advance.
In fact sending request body with HTTP GET request is not supported by Apache HttpComponents and hence in JMeter, you should be able to send a GET request with JSON body using JSR223 Sampler and the following code (assumes Groovy language):
import org.apache.http.HttpResponse
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClientBuilder
import org.apache.http.util.EntityUtils
public class HttpGetWithBody extends HttpEntityEnclosingRequestBase {
public final static String METHOD_NAME = "GET";
#Override
public String getMethod() {
return METHOD_NAME;
}
}
def client = HttpClientBuilder.create().build();
def getRequest = new HttpGetWithBody();
getRequest.setURI(new URL("http://example.com").toURI());
def json = "{\"employees\":[\n" +
" {\"firstName\":\"John\", \"lastName\":\"Doe\"},\n" +
" {\"firstName\":\"Anna\", \"lastName\":\"Smith\"},\n" +
" {\"firstName\":\"Peter\", \"lastName\":\"Jones\"}\n" +
"]}";
def body = new StringEntity(json, "application/json", "UTF-8");
getRequest.addHeader("Content-Type", "application/json");
getRequest.setEntity(body);
def response = client.execute(getRequest);
def result = EntityUtils.toString(response.getEntity());
log.info(result);
See Beanshell vs JSR223 vs Java JMeter Scripting: The Performance-Off You've Been Waiting For! article for more information on using JSR223 test elements and groovy language and scripting best practices.
There is a reported bug in JMeter for this https://bz.apache.org/bugzilla/show_bug.cgi?id=60358 and looks like the fix is on the way. Hopefully this issue would be fixed soon.
I am looking for a way to add dynamic parameters[name:value] to http sampler.
i have found it for headers but not for parameters .
i have json file which contains method,parameters, headers,body, authorization according to the values i need to construct and send the http request.
I succeeded till setting HOST,PORT,PATH,HEADERS..
I want to set it for body,authorization,parameters before sending the http request.
The below is the groovy code in which i have parsed the json content to jsonparser from json parser i am setting the above http .
String fileContents = new File('../../src/resources/testInput.txt').text
def slurper = new JsonSlurper()
def inputjson = slurper.parseText fileContents
String httpmethod = inputjson.Method
sampler.setMethod(httpmethod);
inputjson.Headers.each{log.info it.each{
key,value -> log.info key
log.info value
sampler.getHeaderManager().add(new Header(key,value ));
}
}
Yea , I have found partial answer for query parameters.
inputjson.QueryParams.each{ it.each{
key,value ->
sampler.addArgument(key,value);
}
Hi I need to pass the output of BSF sampler as input to the Bean shell pre-processor below are my programs
BSF sampler:
function makeid()
{
var ts = new Date().getTime();
var digits = 10e10;
//var timestamp = ts.toString() + Math.floor(Math.random() *digits ).toString();
var timestamp = new Date().getTime().toString() + Math.floor(Math.random() *digits ).toString();
return timestamp;
}
function test()
{
var uniqueId=makeid();
var numCopies = 20,status = 'done printing',timestampDonePrintingAttr = "timestamp done printing",Title='demo.jpg',Username='keshavka'
date='1429036296',printstatus='OK',time='1429036296';
var joblog = {};
joblog[uniqueId] = {};
joblog[uniqueId]['NumCopies'] = numCopies;
joblog[uniqueId]['Status'] = status;
joblog[uniqueId]['Title'] = Title;
joblog[uniqueId]['Username'] = Username;
joblog[uniqueId]['date'] = date;
joblog[uniqueId]['print status'] = printstatus;
joblog[uniqueId]['time'] = time;
joblog[uniqueId]['timestampDonePrintingAttr'] = new Date().getTime();
//console.log(i);
var json = JSON.stringify(joblog);
return json;
}
Bean Shell Program :
FileWriter fstream = new FileWriter("C:\\apache-jmeter-2.13\\detail_log7.txt",false);
BufferedWriter out = new BufferedWriter(fstream);
out.write(${test});
out.close();
BSF sampler gives me a JSON and i need to create a File using Bean shell scripting and write content to it .
please help me through this
Approaches may differ depending on where BSF Sampler and Beanshell PreProcessor live.
If Beanshell PreProcessor is a child of BSF Sampler - it won't play as PreProcessors are executed before samplers.
For other cases:
If you look at the top of "Script" input you'll see some pre-defined variables names like ctx, vars, props, etc.
For instance, vars stands for JMeterVariables class instance, so you can set variable in BSF Sampler and access it in Beanshell PreProcessor as:
In BSF:
vars.put('json', json);
In Beanshell:
out.write(vars.get("json"));
If above approach won't help update the question with screenshot of your test plan.
NB:
You don't need to switch to Beanshell just for writing JSON to file. See Using Java From Scripts guide to learn how you can access Java classes from JavaScript and vice versa
Pre-defined variables like ctx, vars, props, etc. are described in detail in the How to use BeanShell: JMeter's favorite built-in component guide.
Both JavaScript and Beanshell have performance problems so if this code is executed with more than i.e. 10 threads it is recommended to use JSR223 test elements and Groovy for scripting where required.