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

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.

Related

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:

How to check xml response for dynamic array elements

I am new to groovy and soapui pro. I have below sample response that displays 2 or more array elements with dynamic data. I am wondering how to write a script assertion or xpath match to check if script passes as long as one of the elements has value 1.
<ns1:SampleTests>
<ns1:SampleTest1>
<ns1:Test>1</ns1:Test>
</ns1:SampleTest1>
<ns1:SampleTest2>
<ns1:Test>2</ns1:Test>
</ns1:SampleTest2>
</ns1:SampleTests>
I have written this in script assertion but its failing.
Supposing that you've a response like:
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<ns1:SampleTests xmlns:ns1="hola">
<ns1:SampleTest1>
<ns1:Test>1</ns1:Test>
</ns1:SampleTest1>
<ns1:SampleTest2>
<ns1:Test>2</ns1:Test>
</ns1:SampleTest2>
</ns1:SampleTests>
</Body>
</Envelope>
You can perform the follow XPath: exists(//*:Test[.=1]) to check that exists at least one <ns1:Test> element with 1 as value.
Inside an XPath Match it looks like:
If instead you prefer to use an Script assertion you can use the XmlSlurper to parse your Xml, then get all <ns1:Test> values an assert that at least one has 1 as value. Look into the follow code:
// get the response
def responseStr = messageExchange.getResponseContent()
// parse the response as slurper
def response = new XmlSlurper().parseText(responseStr)
// get all <ns1:Test> values
def results = response.'**'.findAll { it.name() == 'Test' }
// now in results list we've NodeChild class instances we will convert it to
// string in order to perform the assert
results = results.collect { it.toString() }
// check that at least one element has '1' value
assert results.contains('1'),'RESPONSE NOT CONTAINS ANY <ns1:Test>1</ns1:Test>'

Parsing xml with Go, ignoring nested elements?

I am trying to parse a html document with the Golang xml parser. I have managed it to extract all the <li>elements but if the element contains a link <a>, then the content of the link is ignored. I would like to just ignore the nested <a> and display it's content as plain text but I don't know how.
Here is my code:
d := xml.NewDecoder(resp.Body)
d.Strict = false
d.AutoClose = xml.HTMLAutoClose
d.Entity = xml.HTMLEntity
type list_item struct {
Data string `xml:",chardata"`
}
for {
t,_ := d.Token()
if t == nil {
break
}
switch se := t.(type) {
case xml.StartElement:
if se.Name.Local == "li" {
var q list_item
d.DecodeElement(&q, &se)
c.Infof("%+v\n", q)
}
}
}
Is there any way to just ignore nested elements and display their content?
Constder using specialized package for parsing HTML. In general, HTML is not XML (XHTML 1.0 is, but documents formatted using it are not very common, and that standard has been deprecated).
An even better approach in my opinion—given your apparent use case,— would be using XPath to extract the necessary information using a query.
As to the question as stated, I think there's no built-in way to do what you want: the xml.Decoder implements the Skip() method but it only allows you to skip over unneeded content; there's nothing returning "inner XML" as is. You could roll this yourself by using xml.Decoder's RawToken(): by immediately rendering whatever it returns until it returns something denoting and end element you're looking for (you'll have to implement support for handling nested elements).
I found a library that uses the jQuery style of getting html information: http://godoc.org/github.com/PuerkitoBio/goquery
I used that and it solved the problem.

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,

xerces-c 3.1 XPath evaluation

I could not find much examples of evaluate XPath using xerces-c 3.1.
Given the following sample XML input:
<abc>
<def>AAA BBB CCC</def>
</abc>
I need to retrieve the "AAA BBB CCC" string by the XPath "/abc/def/text()[0]".
The following code works:
XMLPlatformUtils::Initialize();
// create the DOM parser
XercesDOMParser *parser = new XercesDOMParser;
parser->setValidationScheme(XercesDOMParser::Val_Never);
parser->parse("test.xml");
// get the DOM representation
DOMDocument *doc = parser->getDocument();
// get the root element
DOMElement* root = doc->getDocumentElement();
// evaluate the xpath
DOMXPathResult* result=doc->evaluate(
XMLString::transcode("/abc/def"), // "/abc/def/text()[0]"
root,
NULL,
DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE, //DOMXPathResult::ANY_UNORDERED_NODE_TYPE, //DOMXPathResult::STRING_TYPE,
NULL);
// look into the xpart evaluate result
result->snapshotItem(0);
std::cout<<StrX(result->getNodeValue()->getFirstChild()->getNodeValue())<<std::endl;;
XMLPlatformUtils::Terminate();
return 0;
But I really hate that:
result->getNodeValue()->getFirstChild()->getNodeValue()
Has it to be a node set instead of the exact node I want?
I tried other format of XPath such as "/abc/def/text()[0]", and "DOMXPathResult::STRING_TYPE". xerces always thrown exception.
What did I do wrong?
I don't code with Xerces C++ but it seems to implement the W3C DOM Level 3 so based on that I would suggest to select an element node with a path like /abc/def and then simply to access result->getNodeValue()->getTextContent() to get the contents of the element (e.g. AAA BBB CCC).
As far as I understand the DOM APIs, if you want a string value then you need to use a path like string(/abc/def) and then result->getStringValue() should do (if the evaluate method requests any type or STRING_TYPE as the result type).
Other approaches if you know you are only interested in the first node in document order you could evaluate /abc/def with FIRST_ORDERED_NODE_TYPE and then access result->getNodeValue()->getTextContent().

Resources