JMeter XPath getting multiple values and odd output order - xpath

Preamble
I have a JMeter script with an XPath Extractor, in this I have specified a query that gets multiple values from the XML document. This all works fine
XML
<?xml version="1.0" encoding="UTF-8"?>
<InventoryAvailabilityAdvice>
<Warehouse>WFC2</Warehouse>
<Timestamp>2019-07-31T23:00:02.177</Timestamp>
<InventoryItem>
<ItemNumber>80903</ItemNumber>
<AvailableQuantity UnitOfMeasure="EA">13</AvailableQuantity>
</InventoryItem>
<InventoryItem>
<ItemNumber>80901</ItemNumber>
<AvailableQuantity UnitOfMeasure="EA">17</AvailableQuantity>
</InventoryItem>
</InventoryAvailabilityAdvice>
Problem
When I then try to get these values in a loop using a JSR232 Sampler they don't seem to come out in the order declared in the XPath Query.
I expected theData_2 to contain the UnitOfMeasure attribute and theData_3 to contain the quantity, but as you can see they are reversed.
Question
Is this expected behavior? If so, when an element has multiple attributes how do I know which order those will be made available as?
Thanks

The order of XPath nodesets produced by union operator is not guaranteed, you can see putValuesForXPathInList() function for implementation details
Actually if you've decided to go for Groovy - you don't even need the XPath Extractor, you can use XmlSlurper class for parsing the XML response.
Example code:
def response = new XmlSlurper().parseText(prev.getResponseDataAsString())
response.InventoryItem.eachWithIndex { item, index ->
log.info('Item: ' + index)
log.info('ItemNumber: ' + item.ItemNumber)
log.info('AvailableQuantiry: ' + item.AvailableQuantity)
log.info('UnitOfMeasure:' + item.AvailableQuantity.#UnitOfMeasure)
}
Demo:
References:
Groovy: Processing XML
Apache Groovy - Why and How You Should Use It

Related

How to invoke "dynamic" XPaths with count() in Karate? [duplicate]

This question already has answers here:
Dynamically set a XML tag value while building payload
(2 answers)
Closed 1 year ago.
I'm trying to invoke a "dynamic" XPath in Karate that uses the XPath count() function to return a number (or string representation).
[With Karate 0.9.2] I'm trying to invoke "dynamic" XPath expressions (originally read from a JSON-based configuration file) on an XML document.
There are (potentially) multiple XPath expressions so I am using Karate's karate.forEach() to invoke an XPath utility Javascript function repeatedly within Karate.
Within the embedded Javascript function I use karate.xmlPath() to invoke the "dynamic" XPath expression string.
This works fine for retrieving single nodes, node lists etc but it fails when the expression uses XPath's count() function as the result is a number rather than an XML node or XML NodeList.
Feature: General XPath based evaluator
Scenario: ....
# Omitting details around performing HTTP request to obtain XML response....
* xml payload = ..... $.requests[0].body ...
#
# A JS Function to invoke each XPath Query in our query dictionary
#
# queryDictionaryItem has a single XPath query in it with an expected
value
# { "xpath": <query>, "expectedValue", <string> }
#
* def checkXPathQueryFn =
"""
function(queryDictionaryItem) {
var requestXML = karate.get("payload");
var xpathQuery = queryDictionaryItem.xpath;
var expectedValue = queryDictionaryItem.expectedValue;
// [!!] This will blow up if the xpathQuery is of the form:
// "count(........)"
// --> Cannot return a NUMERIC value rather than a NODELIST
var actualValue = karate.xmlPath( requestXML, xpathQuery );
var match = karate.match( actualValue, expectedValue );
if (!match.pass)
{
karate.abort("Failed to match expectation..."); }
}
"""
# queryDictionary is a list of JSON objects of the form:
# { "xpath": <query>, "expectedValue", <string> }
* eval karate.forEach(queryDictionary, checkXPathQueryFn)
Expected result:
Receive a String/Number when an XPath based on count() is dynamically invoked.
Actual outcome:
Error:
javax.xml.xpath.XPathExpressionException: com.sun.org.apache.xpath.internal.XPathException: Can not convert #NUMBER to a NodeList!
javascript evaluation failed: karate.forEach(requestExpectations, oldCheckExpectation), javax.xml.xpath.XPathExpressionException: com.sun.org.apache.xpath.internal.XPathException: Can not convert #NUMBER to a NodeList!
For the Intuit Karate developers: [#ptrthomas]
In the v0.9.2 version of karate-core, there are provisions for use of count() in XPaths within Script#evalXmlPathOnXmlNode():
https://github.com/intuit/karate/blob/master/karate-core/src/main/java/com/intuit/karate/Script.java#L367 L367
but as we're using dynamic XPath, the call sequence does not use that "safeguard" and instead uses ScriptBridge#xmlPath()
https://github.com/intuit/karate/blob/master/karate-core/src/main/java/com/intuit/karate/core/ScriptBridge.java#L230 L230
This method has the line:
Node result = XmlUtils.getNodeByPath((Node) o, path, false);
which throws RuntimeExceptions when XPath expressions do not return NODESET shaped data.
https://github.com/intuit/karate/blob/master/karate-core/src/main/java/com/intuit/karate/XmlUtils.java#L152 L152.
Confirming this Karate Framework issue is fixed with the latest (as at 2019-04-23) Karate Core (Development branch build).
This fix is scheduled for release in Intuit Karate v0.9.3.
Java/Karate source further detailing the issue and an alternative (interim), Java-native workaround via direct Java XPath interop is listed at:
https://github.com/mhavilah/karateDynamicXPath
NB: The behaviour of the XPathHelper in the above project is slightly different to that of the karate.xmlPath() DSL service.
In particular for retrieving single XML elements, the karate DSL auto extracts the underlying text() node, whereas the Java native Helper requires explicit reference to the embedded text node within the XML element.

XPath2: A sequence of more than one item is not allowed as the first argument of string()

I have this xml that contains certificates and its attributes. Here is the xml over which I am running the extraction query
<Certificates>
<CertificateAndChain>
<Certificate>
<FriendlyName />
<Thumbprint>EE8B375347FCADDC25547FDDF61866E07D5B6A71</Thumbprint>
<SerialNumber>256BB11DB5BE889E46EBCD85608DA849</SerialNumber>
<Version>3</Version>
<SignatureAlgorithm>sha1RSA</SignatureAlgorithm>
<Issuer>CN=certname</Issuer>
<Subject>CN=certname</Subject>
<NotAfter>2040-09-26T18:30:00Z</NotAfter>
<NotBefore>2016-09-27T18:30:00Z</NotBefore>
<IsVerified>false</IsVerified>
</Certificate>
.
.
.(continued)...
I have written another test over this xml that needs to extract thumbprints of all the certificates that are about to get expired in next 10 days.
As part of the test, , I have written the Extraction query as:
<XPath2ResponseParameter Name="TPName"><ExtractionQuery><![CDATA[string(//CertificateAndChain/Certificate[NotAfter[text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))]]/Thumbprint)]]></ExtractionQuery></XPath2ResponseParameter>
The test is in xml file as well. However, when I run the test, I get this exception:
A sequence of more than one item is not allowed as the first argument of string().
This is probably happening because there are multiple certificate elements present in the xml. So, I couldn't figure out the way to extract all the thumbprints failing the mentioned condition(there could be more than 1 and I need to extract all of them)
Can anyone help me fix this?
You can invoke function in axis when using XPath 2.0. So, try to move string() function to the end of the inner XPath (formatted for readability) :
//CertificateAndChain
/Certificate[
NotAfter[
text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))
]
]/Thumbprint
/string()
UPDATE :
Since CDATA can't receive multiple strings in this context, according to the error message in your first comment, then you need to combine those strings into one, presumably using string-join() :
string-join(
//CertificateAndChain
/Certificate[
NotAfter[
text() <= (current-dateTime() + xs:dayTimeDuration('P10D'))
]
]/Thumbprint
/string()
,' ')

Looping through JSON response + in JMETER

I am using Jmeter for performance testing and stuck at following point:
I am getting a JSON response from Webapi as follows:
PersonInfoList:
Person
[0]
{
id: 1
name: Steve
}
[1]
Person
{
id: 2
name: Mark
}
I need to get the ids based on the count of this JSON array and create a comma separated string as ("Expected value" = 1,2)
I know how to read a particular element using JSON Post processor or Regex processor but am unable to loop through the array and create a string as explained so that I can use this value in my next sampler request.
Please help me out with this: I am using Jmeter 3.0 and if this could be achieved without using external third party libs that would be great. Sorry for the JSON syntax above
Actually similar functionality comes with JSON Path PostProcessor which appeared in JMeter 3.0. In order to get all the values in a single variable configure JSON Path PostProcessor as follows:
Variable Names: anything meaningful, i.e. id
JSON Path Expressions: $..id or whatever you use to extract the ids
Match Numbers: -1
Compute concatenation var (suffix _ALL): check
As a result you'll get id_ALL variable which will contain all JSON Path expression matches (comma-separated)
More "universal" answer which will be applicable for any other extractor types and in fact will allow to concatenate any arbitrary JMeter Variables is using scripting (besides if you need this "expected value and parentheses)
In order to concatenate all variables which names start with "id" into a single string add Beanshell PostProcessor somewhere after JSON Path PostProcessor and put the following code into "Script" area
StringBuilder result = new StringBuilder();
result.append("(\"Expected value\" = ");
Iterator iterator = vars.getIterator();
while (iterator.hasNext()) {
Map.Entry e = (Map.Entry) iterator.next();
if (e.getKey().matches("id_(\\d+)")) {
result.append(e.getValue());
result.append(",");
}
}
result.append(")");
vars.put("expected_value", result.toString());
Above code will store the resulting string into ${expected value} JMeter Variable. See How to Use BeanShell: JMeter's Favorite Built-in Component article for more information regarding bypassing JMeter limitations using scripting and using JMeter and Java API from Beanshell test elements.
Demo:

Use Datasource Properties in XPath Expression of SoapUI

I need to know whether it is possible to use a datasource property in XPath Expression panel of XPath Match Configuration. For instance, if we have the following XML document:
<ns1:Ions>
<ns1:Ion>UI</ns1:Ion>
<ns1:IonType>X</ns1:IonType>
<ns1:StartDate>2010-05-10</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>HH</ns1:Ion>
<ns1:IonType>RI</ns1:IonType>
<ns1:StartDate>1998-11-23</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>CF</ns1:Ion>
<ns1:IonType>A</ns1:IonType>
<ns1:StartDate>2000-06-10</ns1:StartDate>
</ns1:Ions>
I need to evaluate to see whether a content of IonType is 'A' only if its sibling node, Ion, has a value of 'CF'. I was hoping to accomplish this by setting XPath Match Configuration as following:
XPath Expression (DataSourceInput#ION is 'CF')
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()=${DataSourceInput#ION}]]/ns1:IonType/text()
Expected Results (DataSourceInput#ION_TYPE is 'A')
${DataSourceInput#ION_TYPE}
Running the test would result in SoapUI [Pro] to error the following, Missing content for xpath declare. If I replace ${DataSourceInput#ION} with an actual value, i.e. 'CF', the test works accordingly (I even tried place single quotes around ${DataSourceInput#ION}, but it didn't work).
Is there another way of accomplish this in SoapUI?
I try what you do and it works for me if I put single quotes around the property:
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()='${DataSourceInput#ION}']]/ns1:IonType/text()
Did you check that testStep name is exactly DataSourceInput? If there are spaces in the TestStep name (i.e your testStep name is Data Source Input you have to put ${Data Source Input#ION}).
Anyway I give you another way to do so, you can add a testStep of type groovy script after the testStep where you are getting the <Ions>response, and check the assert here like follows:
// get xml holder
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context);
def ionsHolder = groovyUtils.getXmlHolder("IonsTestStepName#response");
// generate xpath expression
def xpathExpression = "//*:Ions[*:Ion[text()='" + context.expand('${DataSourceInput#ION}') + "']]/*:IonType/text()";
log.info xpathExpression;
// get the node value
def nodeValue = ionsHolder.getNodeValue(xpathExpression);
// check expected value
assert nodeValue == context.expand('${DataSourceInput#ION_TYPE}'),'ERROR IONS VALUE';
Hope this helps,

MockHttpServletResponse : Checking xml content

I am testing a controller using MockMvc. This is what the response looks like:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[text/xml]}
Content type = text/xml
Body = <?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:diagnosisCode xmlns:ns2="http://schemas.mycompany.co.za/health" effectiveStartDate="2014-03-05T00:00:00+02:00" effectiveEndDate="2014-03-05T23:59:59.999+02:00" diagnosisId="1"><diagnosisCodeId><codingSchemaCode>irrelevant schema</codingSchemaCode><diagnosisCode>irrelevant code</diagnosisCode></diagnosisCodeId></ns2:diagnosisCode>
Forwarded URL = null
Redirected URL = null
Cookies = []
Pretty-printed version of the Body line:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:diagnosisCode xmlns:ns2="http://schemas.mycompany.co.za/health" effectiveStartDate="2014-03-05T00:00:00+02:00" effectiveEndDate="2014-03-05T23:59:59.999+02:00" diagnosisId="1">
<diagnosisCodeId>
<codingSchemaCode>irrelevant schema</codingSchemaCode>
<diagnosisCode>irrelevant code</diagnosisCode>
</diagnosisCodeId>
</ns2:diagnosisCode>
The call on MockMvc looks like
mockMvc.perform(
get("/diagnostic/diagnosisCodes/{schema}/{code}", IRRELEVANT_SCHEMA, IRRELEVANT_CODE).accept(MediaType.TEXT_XML))
.andDo(print())
.andExpect(content().contentType(MediaType.TEXT_XML))
.andExpect(status().isOk())
.andExpect(xpath("diagnosisCodeId/diagnosisCode").string(IRRELEVANT_CODE))
.andExpect(xpath("diagnosisCodeId/codingSchemaCode").string(IRRELEVANT_SCHEMA));
I am pretty sure I am misunderstanding how I'm supposed to use XPath here, but why is this assertion failing? What should my expectation look like?
java.lang.AssertionError: XPath diagnosisCode expected:<irrelevant code> but was:<>
I'm not totally sure what the XPath context is (or whether it is the document node), but I see two possible problems and guess both apply:
You try to match < diagnosisCodeId/> elements that are the root element. There are none, but they're children of <diagnosisCode>. Either include an axis step for the root node (probably better way) or use the descendant-or-self axis step // in front of the query.
/diagnosisCode/diagnosisCodeId/diagnosisCode
//diagnosisCodeId/diagnosisCode
The document uses namespaces (for the root element). In addition to the root element problem described above, either register that namespace (better solution, but I don't know how to do this in spring MVC) or ignore it using following workaround:
/*[local-name() = 'diagnosisCode']/diagnosisCodeId/diagnosisCode
Which first matches all child nodes, but then limits to the ones having the apropriate element name (ignoring namespaces).
By adding XPath 2.0 support (for example by including Saxon as library), you can also use the wildcard namespace matcher:
/*:diagnosisCode/diagnosisCodeId/diagnosisCode
If you register the namespace URI http://schemas.mycompany.co.za/health as ns2, the query would look like
/ns2:diagnosisCode/diagnosisCodeId/diagnosisCode
There is an overload for xpath that takes a Map<String, String> of namespaces:
Map<String, String> ns = Map.of("ns2", "http://schemas.mycompany.co.za/health");
mockMvc.perform(get("/diagnostic/diagnosisCodes/{schema}/{code}", IRRELEVANT_SCHEMA, IRRELEVANT_CODE)
.accept(MediaType.TEXT_XML))
.andExpect(xpath("ns2:diagnosisCodeId/diagnosisCode", ns).string(IRRELEVANT_CODE))
.andExpect(xpath("ns2:diagnosisCodeId/codingSchemaCode", ns).string(IRRELEVANT_SCHEMA));

Resources