jmeter icon indicating copy to clipboard operation
jmeter copied to clipboard

Error when using __FileToString to set GraphQL request body

Open asfimport opened this issue 3 years ago • 4 comments

Mateus Dadalto (Bug 66122): I'm trying to set my body based on what is in a file and it fails saying the JSON is invalid. But when I set the request body manually the same content the request works. In the results tree view I can see that the only difference is that the manual request is "encoded", it has /n instead of visual newlines.

Steps to reproduce:

-1: Download and open the .jmx attached to this bug -2: Create a file with the content being the body from the first request ("With Body Set Manually") -3: change the path on the second request ("With Body From File") to your file path -4: Run and see in the View Results Tree that the first one works and the second one fails

Created attachment Jmeter%20GraphQl%20Bug.jmx: A sample project to reproduce the bug

Jmeter%20GraphQl%20Bug.jmx
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
      <stringProp name="TestPlan.comments"></stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
          <boolProp name="LoopController.continue_forever">false</boolProp>
          <stringProp name="LoopController.loops">1</stringProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">1</stringProp>
        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
        <boolProp name="ThreadGroup.scheduler">false</boolProp>
        <stringProp name="ThreadGroup.duration"></stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
      </ThreadGroup>
      <hashTree>
        <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/>
        <HTTPSamplerProxy guiclass="GraphQLHTTPSamplerGui" testclass="HTTPSamplerProxy" testname="With Body Set Manually" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
            <collectionProp name="Arguments.arguments">
              <elementProp name="" elementType="HTTPArgument" enabled="true">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">{&quot;operationName&quot;:null,&quot;query&quot;:&quot;query {\n  allFilms {\n    films {\n      title\n      director\n      releaseDate\n      speciesConnection {\n        species {\n          name\n          classification\n          homeworld {\n            name\n          }\n        }\n      }\n    }\n  }\n}&quot;}</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
              </elementProp>
            </collectionProp>
          </elementProp>
          <stringProp name="HTTPSampler.domain">swapi-graphql.netlify.app</stringProp>
          <stringProp name="HTTPSampler.port"></stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path">.netlify/functions/index</stringProp>
          <stringProp name="HTTPSampler.method">POST</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <stringProp name="GraphQLHTTPSampler.operationName"></stringProp>
          <stringProp name="GraphQLHTTPSampler.query">query {
  allFilms {
    films {
      title
      director
      releaseDate
      speciesConnection {
        species {
          name
          classification
          homeworld {
            name
          }
        }
      }
    }
  }
}
</stringProp>
          <stringProp name="GraphQLHTTPSampler.variables"></stringProp>
          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
        </HTTPSamplerProxy>
        <hashTree/>
        <HTTPSamplerProxy guiclass="GraphQLHTTPSamplerGui" testclass="HTTPSamplerProxy" testname="With Body From File" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
            <collectionProp name="Arguments.arguments">
              <elementProp name="" elementType="HTTPArgument" enabled="true">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">{&quot;operationName&quot;:null,&quot;query&quot;:&quot;${__FileToString(your-file-path,,)}&quot;}</stringProp>
                <stringProp name="Argument.metadata">=</stringProp>
                <boolProp name="HTTPArgument.use_equals">true</boolProp>
              </elementProp>
            </collectionProp>
          </elementProp>
          <stringProp name="HTTPSampler.domain">swapi-graphql.netlify.app</stringProp>
          <stringProp name="HTTPSampler.port"></stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path">.netlify/functions/index</stringProp>
          <stringProp name="HTTPSampler.method">POST</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <stringProp name="GraphQLHTTPSampler.operationName"></stringProp>
          <stringProp name="GraphQLHTTPSampler.query">${__FileToString(your-file-path,,)}</stringProp>
          <stringProp name="GraphQLHTTPSampler.variables"></stringProp>
          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
        </HTTPSamplerProxy>
        <hashTree/>
      </hashTree>
      <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
        <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/>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

Severity: normal OS: Linux

asfimport avatar Jun 15 '22 13:06 asfimport

@FSchumacher (migrated from Bugzilla): I can reproduce the problem with JMeter 5.5 on Linux. Thanks for the detailed bug description.

asfimport avatar Jun 15 '22 15:06 asfimport

@FSchumacher (migrated from Bugzilla): Well, after trying out to replace the newlines (\n) with the string values "\n", it became clear to me, that you already found the solution yourself :)

When you are using giving the GraphQL Sampler the Query manually, JMeter knows, that this is something, that has to be encoded for usage in JSON. If you store the content in a file and include it, JMeter does not know, if it should do the encoding, or not and does not do the encoding itself.

The POST data without the escaped newlines will break the JSON stuff.

So for the moment, you will have to encode it yourself (might be done with a Pre-Processor). I am not sure, whether we can safely do the escaping automatically. Will have to think about it (not sorry, if someone else provides a good solution :))

asfimport avatar Jun 15 '22 15:06 asfimport

Mateus Dadalto (migrated from Bugzilla): I see what you mean... This will happen with anything that either comes from a function or it's stored in a variable. I still hope that this behavior gets fixed in future versions (IMHO it is not a user-friendly behavior).

I just want to add that you'll have to deal with everything that might break a JSON (graphQL syntax has quotes in places that can cause this). E.G.:

mutation { createCustomerV2( input: { firstname: "Bob" lastname: "Loblaw" email: "[email protected]" password: "b0bl0bl@w" is_subscribed: true } ) { customer { firstname lastname email is_subscribed } } }


For now the solution I came up with was to add a JSR223 PreProcessor and just treat these cases myself. Here's the PreProcessor code in case someone needs it.

def content = new File(vars.get('fileName')).text; vars.put('queryContent', content.replaceAll(/(\r\n|\r|\n)/, /\n/).replaceAll(/"/, /\"/))

asfimport avatar Jun 15 '22 15:06 asfimport