I'm using the JMeter Property file reader successfully to get values from a file.
Then I use a simple Groovy snippet to save those values in variables.
Why, then, is JMeter still thinking the variable is not defined?
Output to the console shows that the variables are being stored.
for (i in props)
if (i.key.startsWith('testData')) {
vars.put(i.key, i.value)
log.info(vars.get(i.key))
}
tigapo.prop:
testData.getUserHistoryPageUsingGETFile=HistoryControllerApi.csv
testData.createBillingUsingPOSTFile=BillingControllerApi.csv
testData.getCreditExchangeInfoUsingGETFile=BillingControllerApi.csv
testData.handleTagDetectionUsingPOSTFile=TagDetectionControllerApi.csv
testData.getSettingsUsingGETFile=SettingsControllerApi.csv
testData.createUserUsingPOSTFile=UserControllerApi.csv
With all steps disabled except one, no errors, and I get the vars in the output.
When I enable one step that uses a variable, the error is displayed.
And here is the CSV Data Config:
To make my comment easier to read:
Did what was suggested.
And verified that:
But still getting:
2022-05-10 16:32:46,656 INFO o.a.j.s.FileServer: Stored: ${testData.getUserUsingGETFile} Alias: ${testData.getUserUsingGETFile}#11286416
2022-05-10 16:32:46,656 ERROR o.a.j.t.JMeterThread: Test failed!
java.lang.IllegalArgumentException: Could not read file header line for file ${testData.getUserUsingGETFile}
at org.apache.jmeter.services.FileServer.reserveFile(FileServer.java:283) ~[ApacheJMeter_core.jar:5.4.3]
at org.apache.jmeter.config.CSVDataSet.initVars(CSVDataSet.java:208) ~[ApacheJMeter_components.jar:5.4.3]
at org.apache.jmeter.config.CSVDataSet.iterationStart(CSVDataSet.java:171) ~[ApacheJMeter_components.jar:5.4.3]
at org.apache.jmeter.control.GenericController.fireIterationStart(GenericController.java:399) ~[ApacheJMeter_core.jar:5.4.3]
at org.apache.jmeter.control.GenericController.fireIterEvents(GenericController.java:391) ~[ApacheJMeter_core.jar:5.4.3]
at org.apache.jmeter.control.GenericController.next(GenericController.java:160) ~[ApacheJMeter_core.jar:5.4.3]
at org.apache.jmeter.control.LoopController.next(LoopController.java:134) ~[ApacheJMeter_core.jar:5.4.3]
at org.apache.jmeter.threads.AbstractThreadGroup.next(AbstractThreadGroup.java:91) ~[ApacheJMeter_core.jar:5.4.3]
at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:254) [ApacheJMeter_core.jar:5.4.3]
at java.lang.Thread.run(Unknown Source) [?:1.8.0_311]
Caused by: java.lang.IllegalArgumentException: File ${testData.getUserUsingGETFile} must exist and be readable
testData.getUserUsingGETFile=UserControllerApi.csv
Because CSV Data Set Config is being initialized before JSR223 PreProcessor, take a look at JMeter test elements execution order:
0. Configuration elements
1. Pre-Processors
2. Timers
3. Sampler
4. Post-Processors (unless SampleResult is null)
5. Assertions (unless SampleResult is null)
6. Listeners (unless SampleResult is null)
CSV Data Set Config is a configuration element hence it's being run before anything else.
so you need to:
Convert JSR223 PreProcessor to JSR223 Sampler
Put it under setUp Thread Group
Related
I am trying to test some APIs but first I need to get session keys for each customer I use in the test. I have a CSV file with customer login information and read it with each thread.
I have the following form in my JMeter file.
CSV Data Set Config - User Login Info
Set username, password - one for each iteration
Setup Thread Group
BeanShell Sampler to delete the sessionKeys.csv
File file = new File("C:/user/sessionKeys.csv");
if (file.exists() && file.isFile()) {
file.delete();
}
Login Thread - ThreadCount = threadCount, Loop = 1
Login Request
BeanShell PostPrecessor to create file and append Session Keys to sessionKeys.csv
if("${sessionKey}" != "not_found")
{
File file = new File("C:/user/sessionKeys.csv");
FileWriter fWriter = new FileWriter(file, true);
BufferedWriter buff = new BufferedWriter(fWriter);
buff.write("${sessionKey}\n");
buff.close();
fWriter.close();
}
CSV Data Set Config - Session Keys
API Call Thread - ThreadCount = threadCount, Loop = loop
GetData Request
And I noticed that even if the file is actually deleted, created and filled with new sessionKeys, first a few requests uses the old sessionKeys in the file before it was deleted.
I have tried adding constant timer or changing the structure of the JMeter file but nothing worked.
Take a look at JMeter Test Elements Execution Order
Configuration elements
Pre-Processors
Timers
Sampler
Post-Processors (unless SampleResult is null)
Assertions (unless SampleResult is null)
Listeners (unless SampleResult is null)
CSV Data Set Config is a Configuration Element hence it's executed long before the Beanshell Sampler and this perfectly explains the behaviour you're facing.
So if you need to do some pre-processing of the CSV file you will need to do it in setUp Thread Group
Also be aware that starting from JMeter 3.1 you're supposed to be using JSR223 Test Elements and Groovy language for scripting so it makes sense considering migrating.
It looks like the CSV Config element in your test plan exists outside of the thread group and so will be called first before the file is deleted and recreated.
In your case it might be simpler to just store the session key as a JMeter property so that it can be accessed in all thread groups. You can store it using Groovy like props.put("${sessionKey}", sessionKey) or via JMeter functions like ${__setProperty("sessionKey",${sessionKey})}.
The property can then be accessed again using the property function like ${__P(sessionKey,)}.
I have a Test Plan containing one Thread Group whith one HttpRequest sampler, JRS223PreProcessor and one csv data set config. I need to read from csv, at run time,the current value of column 2 and use it in my JSR223 PreProcessor. In order to do this, I defined a variable on Test Plan:
name ${__CSVRead(C:/Users/marial/Desktop/csvs/csv_hotelCodeReq.txt,2)
In JSR223 PreProcessor I am taking it like this:
String name= new String(vars.get("name"));
I would expect this value to change on each line readed, but it didn't, it always take the first value encountered. Does anyone know why?
To be more specific, if I have the csv file :
1,2,firstName1:lastName1
3,2,firstName2:lastName2
and loop count = 2, users=1 than the values of name are:
loop1: firstName1:lastName1
loop2: firstName1:lastName1
The other values are correctly handled, so it goes to the next line.
According to User Defined Variables documentation:
Note that all the UDV elements in a test plan - no matter where they are - are processed at the start.
So your __CSVRead() function is evaluated only during test startup and only once
The solution would be moving the function into "Parameters" section of the JSR223 PreProcessor and you will be able to access the function output as Parameters in your Groovy script like:
String name = Parameters
Demo:
This way the __CSVRead() function will be executed each time the JSR223 PreProcessor is called. Check out Apache Groovy - Why and How You Should Use It article to learn more about Groovy scripting in JMeter
I'm using Apache JMeter 3.2 r1790748 on Mac.
I have a setUp Thread Group making an authentication call. The call works and outputs the tokens correctly. Now I need to pass that token to the HTTP Header Manager for all the calls I'm making.
First of all, here's my token json output:
{
"access_token": "aaaaaa555555555",
"token_type": "Access",
"user_id": "5555"
}
Here's what my HTTP Header manager looks like:
1 value: Authorization : Bearer ${access_token}
My network call:
GET https://my_server.com/some_path
GET data:
[no cookies]
Request Headers:
Connection: close
Authorization: Bearer ${access_token}
Host: my_server.com
User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_91)
As you can see, the variable access_token is not being replaced with the value from the setup call.
What I've tried:
BeanShell PostProcessor:
I created this script, and it actually parses and outputs the access_token properly:
import org.apache.jmeter.protocol.http.control.Header;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
String jsonString = prev.getResponseDataAsString();
log.info("jsonString = " + jsonString);
JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
JSONObject json = (JSONObject) parser.parse(jsonString);
String access_token = json.getAsString("access_token");
log.info("access_token = " + access_token);
vars.put("access_token", access_token);
JSON Extractor:
Apply to: Main sample and sub-samples
Variable names: access_token
JSON Path expressions: access_token
Match No. (0 for Random): 1
Compute concatenation var (suffix _ALL): unchecked
Default Values: none
Any ideas as to why the header manager is not applying the value of the access_token result?
Thanks!
Since you set a variable in setUp Thread Group, you cannot use it in another thread groups, since thread groups don't share variables, only properties.
So in order to pass authentication, you need to save it as a property:
${__setProperty(access_token, ${access_token})};
In this example I am using value of variable named access_token (already set, but only available in setUp thread group) to set property with the same name, which will be available across thread groups. Or change BeanShell post-processor, add:
props.put("access_token", access_token);
And then in the other thread group, you retrieve it using __P or __property function:
${__P(access_token)}
Also keep in mind that HTTP Header Manager initializes before any thread starts, so you can't use variables there for that reason too. Check this question for instance.
If you still see empty value, I recommend adding Debug Sampler (with both JMeter Properties and JMeter Variables enabled) in both thread groups, and checking where the breakage is (on saving or retrieving).
As per Functions and Variables chapter of the JMeter User Manual
Variables are local to a thread; properties are common to all threads, and need to be referenced using the __P or __property function
So the variable you define in the setUp Thread Group cannot be accessed by:
other threads in the same Thread Group
other threads outside the Thread Group
So my recommendations are:
Switch to JMeter Properties instead of Jmeter Variables, JMeter Properties are global to all threads and in fact the whole JVM instance
Switch to JSR223 PostProcessor with Groovy language instead of Beanshell PostProcessor, JSR223 Elements performance is much better, moreover Groovy has built-in JSON support.
So:
The relevant Groovy code for getting access_token attribute value and storing it into the relevant property would be :
props.put('access_token', new groovy.json.JsonSlurper().parse(prev.getResponseData()).access_token)
You can refer the value in the HTTP Header Manager (or wherever you require) as:
${__P(access_token,)}
I have a screen with a form contaning invoices I want to notify the user with. The screen is used as bodyScreenLocation for the EmailTemplate. Up to here, it works. I wanted to add a link for each invoice so that the user can open the document right from withing the message. How to construct the path? This way it does not work:
This field is created in a form, that is included in a screen. This screen is used in a template called using
<service-call name="org.moqui.impl.EmailServices.send#EmailTemplate" async="true">
Added 18.10.2016
I attempted to use your advice.
<actions>
<entity-find entity-name="mantle.account.invoice.Invoice" list="invoiceList"/>
<script>
import org.moqui.impl.context.WebFacadeImpl
def httpUrl = WebFacadeImpl.getWebappRootUrl('webroot',null,false,false,ec)
</script>
</actions>
This is the log:
21:47:18.996 WARN oquiWorker-2 o.moqui.i.c.TransactionFacadeImpl Transaction set rollback only. The rollback was originally caused by: Error rendering screen [component://HS-data/screen/Notification/PayablesDue.xml]
java.lang.NullPointerException: null
at org.moqui.impl.context.WebFacadeImpl.makeWebappHost(WebFacadeImpl.groovy:477) ~[moqui-framework-2.0.0.jar:2.0.0]
at org.moqui.impl.context.WebFacadeImpl.makeWebappRootUrl(WebFacadeImpl.groovy:520) ~[moqui-framework-2.0.0.jar:2.0.0]
at org.moqui.impl.context.WebFacadeImpl.getWebappRootUrl(WebFacadeImpl.groovy:467) ~[moqui-framework-2.0.0.jar:2.0.0]
at org.moqui.impl.context.WebFacadeImpl$getWebappRootUrl.call(Unknown Source) ~[?:?]
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) ~[groovy-2.4.7.jar:2.4.7]
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) ~[groovy-2.4.7.jar:2.4.7]
at PayablesDue_xml_screen_actions.run(PayablesDue_xml_screen_actions:11) ~[script:?]
What you're looking for is the WebFacade.getWebappRootUrl() method. Use a call that is something like this in your template to get the base URL:
ec.web.getWebappRootUrl(true, null)
When there is no WebFacade in place, such as when running in an async service, you'll have to drop a level lower and there is a static method in WebFacadeImpl for this (it's not so convenient when you don't have the standard interfaces to go through), here is the method signature:
String getWebappRootUrl(String webappName, String servletContextPath, boolean requireFullUrl, Boolean useEncryption, ExecutionContextImpl eci)
The standard webappName is 'webroot' and unless you're doing something fancy in your Moqui Conf XML file this can be a static String. The servletContextPath can be null or an empty String if your Moqui webapp is mounted on the root (commonly the case; without a request where we can get to the ServletContext to see where it is mounted this can't be determined automatically).
I have an app build with jodd 3.6.6 and when I try to run it on jetty9 it cannot initialize Madvoc :
1377 [ERROR] j.m.Madvoc.startNewWebApplication:161 - Madvoc startup failure.
jodd.madvoc.MadvocException: Scan classpath error; <--- java.lang.ClassNotFoundException: Class not found: org.eclipse.jetty.client.api.Result
at jodd.madvoc.config.AutomagicMadvocConfigurator.configure(AutomagicMadvocConfigurator.java:85)
at jodd.madvoc.config.AutomagicMadvocConfigurator.configure(AutomagicMadvocConfigurator.java:65)
at jodd.madvoc.WebApplication.configure(WebApplication.java:255)
at jodd.madvoc.Madvoc.start(Madvoc.java:238)
at jodd.madvoc.Madvoc.startNewWebApplication(Madvoc.java:157)
at jodd.madvoc.MadvocServletFilter.init(MadvocServletFilter.java:45)
at org.eclipse.jetty.servlet.FilterHolder.initialize(FilterHolder.java:138)
at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:852)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:298)
at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1349)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1342)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:741)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:505)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:132)
at org.eclipse.jetty.server.Server.start(Server.java:387)
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:114)
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:61)
at org.eclipse.jetty.server.Server.doStart(Server.java:354)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at net.sourceforge.eclipsejetty.starter.jetty9.Jetty9Adapter.start(Jetty9Adapter.java:68)
at net.sourceforge.eclipsejetty.starter.common.AbstractJettyLauncherMain.launch(AbstractJettyLauncherMain.java:85)
at net.sourceforge.eclipsejetty.starter.jetty9.Jetty9LauncherMain.main(Jetty9LauncherMain.java:42)
Caused by: jodd.io.findfile.FindFileException: Scan entry error: EntryData{org.eclipse.jetty.client.api.Result'}; <--- java.lang.ClassNotFoundException: Class not found: org.eclipse.jetty.client.api.Result
at jodd.io.findfile.ClassFinder.scanEntry(ClassFinder.java:391)
at jodd.io.findfile.ClassFinder.scanJarFile(ClassFinder.java:292)
at jodd.io.findfile.ClassFinder.scanPath(ClassFinder.java:261)
at jodd.io.findfile.ClassFinder.scanPaths(ClassFinder.java:226)
at jodd.madvoc.config.AutomagicMadvocConfigurator.configure(AutomagicMadvocConfigurator.java:83)
... 22 more
Caused by: jodd.madvoc.MadvocException: Invalid Madvoc result class: org.eclipse.jetty.client.api.Result; <--- java.lang.ClassNotFoundException: Class not found: org.eclipse.jetty.client.api.Result
at jodd.madvoc.config.AutomagicMadvocConfigurator.onEntry(AutomagicMadvocConfigurator.java:108)
at jodd.io.findfile.ClassFinder.scanEntry(ClassFinder.java:389)
... 26 more
The same app runs without a problem on tomcat. Any idea ?
Ok, it should be simple: just skip the scanning of netty classes.
Here is the explanation: In Madvoc you can optionally use the Result class (source) to work with the results (documentation). In short, if you have a field with Result type, Madvoc will use it for this feature.
So it looks like Netty has the same class name, hence the error. To prevent this (until we make a better recognition), just skip scanning the full class path, and make the scanner scan only your classes - that will improve the startup performances too. Here is how to do that:
By default, the AutomagicMadvocConfigurator is used for configuring the actions (by scanning the path). Get this instance (in WebApplication for example) and configure it (since it is a ClassFinder subclass). So what I do is:
classFinder.setExcludeAllEntries(true);
classFinder.setIncludedEntries(myapp.getClass().getPackage().getName() + ".*");
classFinder.setIncludedJars("somejar.jar", "myapp*.jar");
This will narrow down the classpath that is being searched.
Let me know if you need more help with this. Meanwhile, we will definitely make changes so this never happens again!