Not able to validate SOAP call response

Using your sample XML, I have studied the source code of com.kms.katalon.core.testobject.ResponseObject and other related classes.

I believe that I have found out problems (bad design) in Katalon Studio. Due to that problem, your test code:

WS.verifyElementText(response, 'PreAuthorizeResponse.Receipt.TransactionResult', 'Approved')

will inevitably encounter :

ERROR c.k.k.core.keyword.internal.KeywordMain  - ? Expected text is 'Approved' but actual element text is: 

I will report what I have found out later.


Reading the source code, I found that the source code of Katalon Studio is careless about the XML Namespace. Therefore now I think that Katalon Studio is not good for testing SOAP services. I think, you had better look at other test tools which have good reputation for testing SOAP services.

@milad.abbasi

Your XML response contains env:Header element with content elements.

<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns="beep" xmlns:ns2="bop" xmlns:ns1="foo" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsu:Timestamp wsu:Id="timestamp" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        <wsu:Created>2021-09-15T19:23:13.688Z
        </wsu:Created>
        <wsu:Expires>2021-09-15T19:28:13.688Z
        </wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>
  </env:Header>
  <env:Body>
    <ns:PreAutorizeResponse>
      ...

The <env:Header> element makes WS.verifyElementText keyword confused. Surprisingly enough, it expects the SOAP response to have <env:Body> only.

Let me be precise. See https://github.com/katalon-studio/katalon-studio-testing-framework/blob/master/Include/scripts/groovy/com/kms/katalon/core/testobject/ResponseObject.java, Line #77:

  Node node = (Node) xPath.evaluate("//*//*//*", doc, XPathConstants.NODE);

This is where the problem comes from. When the response XML contains no <env:Header> element, the node variable will be

<ns:PreAuthorizeResponse>....

which is as you exepect. But when the response XML contains a <env:Header> element, the node variable will be

<wsse:Security> ...

which is not what you expected. Here you would get an error:

? Expected text is 'Approved' but actual element text is: 

I am sure that the Katalon programer who wrote WS.verifyElementText keyword ignored a case where a SOAP response has a <env:Header> element.

c/c @duyluong

When I made a search in this forum with keyword “WS.verifyElementText”, I found quite a lot of questions. People claimed that they can not get the expected value of XML nodes via this keyword.

These questions are unresolved, outstanding. I am afraid that nobody has ever realised how bad the line #77 of https://github.com/katalon-studio/katalon-studio-testing-framework/blob/master/Include/scripts/groovy/com/kms/katalon/core/testobject/ResponseObject.java is. It will continue confusing people in future.

Also, I would argue that the keyword name WS.verifyElementText() is inappropriate. This name does not express what it does actually. I would propose to Katalon Team

  1. it should be separated into 2 keywords: one for JSON response (e.g, WS.verifyJsonElementText()), another for SOAP response.
  2. the keyword for SOAP response should be named “WS.verifySOAPBodyElementText()”, and clearly state that it works only with a SOAP response without a Header element.
  3. also the documentation should clearly state that WS.verifySOAPBodyElementText() does not work for arbitrary XML responses like <data><a>A</a><b>B</b></data>.

As for JSON response, WS.verifyElementText might be OK. But for XML response, this keyword does not help. You shouldn’t use it.

If Katalon Team finds it difficult to change the codebase for the backward-compatibility concern, then, I would suggest, you should improve the documentation so that people do not waste their time.


@milad.abbasi

What else can you do?

You should avoid the builtin WS.verifyElementText keyword as long as your XML contains <env:Header> element.

In Katalon Studio, you can get access to the XML response body, parse it and extract text content out of the XML elements; then you can make assertions: do these by your Groovy scripts (Test Case and Custom Keywords)

By the way, I looked at other API Testing tool Postman. I found that you have to write a bunch of custom codes (in Node.js) to process and verify SOAP messages in Postman. Now a question to you would be; which tool (which scripting language) you like?


Here is a sample Test Case script that can verify your XML Response.

import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject

import com.kms.katalon.core.testobject.RequestObject
import com.kms.katalon.core.testobject.ResponseObject
import com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords as WS

import groovy.util.slurpersupport.GPathResult

ResponseObject response = WS.sendRequest((RequestObject)findTestObject('data'))

println "response.getResponseText() : " + response.getResponseText()

GPathResult envelope = new XmlSlurper().parseText(response.getResponseText())
                .declareNamespace(env: "http://schemas.xmlsoap.org/soap/envelope/",
					wsse: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
					wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
					ns: "beep", ns2: "bop", ns1: "foo")

assert envelope.'env:Header'.'wsse:Security'.'wsu:Timestamp'.'wsu:Created'.text().contains('2021-09-15T19:23:13.688Z')

assert envelope.'env:Body'.'ns:PreAuthorizeResponse'.'ns:Receipt'.'ns1:TransactionResult'.text().contains('Approved')

The following post shows you an alternative approach to verify XML respose using XPath.

I think that a Test Case that uses XPath is the best approach to verify a SOAP message.

Thanks for your responses. I will give this a shot.

I’m curious, wouldn’t it make more sense to take the response XML and removing the <env:Header> node from it then running it through WS.verifyElementText?
Wouldn’t that be an easier solution.

Thanks

You can take any workaround for you, of course.

I just wanted to let Katalon Team be aware that the WS.verifyElementText keyword is problematic, it is confusing more and more people, it requires some fix. I just hope Katalon Team to address this issue.

@duyluong, @ThanhTo

Thanks @kazurayam
I have been working on a workaround for this issue and this is how far I have got … I was wondering if you have any inputs for me

import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
import internal.GlobalVariable as GlobalVariable
import org.openqa.selenium.Keys as Keys
import com.kms.katalon.core.testobject.RequestObject
import com.kms.katalon.core.testobject.ResponseObject
import com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords as WS
import com.kms.katalon.core.logging.KeywordLogger
import org.json.JSONException;
import org.json.JSONObject;
import org.json.XML;
import com.kms.katalon.core.logging.KeywordLogger
import groovy.util.slurpersupport.GPathResult

ResponseObject response = WS.sendRequest((RequestObject)findTestObject('data'))
private String stripTag(String xmlData, String startTag, String endTag) {
    if (xmlData.contains(startTag) && xmlData.contains(endTag)) {
        return "${xmlData.substring(0, xmlData.indexOf(startTag))}${xmlData.substring(xmlData.indexOf(startTag) + startTag.length(), xmlData.indexOf(endTag))}${xmlData.substring(xmlData.indexOf(endTag) + endTag.length(), xmlData.length())}";
    }
    return xml;
}
KeywordLogger log = new KeywordLogger();
String xmlData = response.getResponseText();
ResponseObject strippedResponse = response;
strippedResponse.setResponseText(stripTag(xmlData, '<env:Header>\n', '</env:Header>\n'))
WS.verifyElementText(strippedResponse, 'PreAuthorizeResponse.Receipt.TransactionResult', 'Approved');
=============== ROOT CAUSE =====================


For trouble shooting, please visit: https://docs.katalon.com/katalon-studio/docs/troubleshooting.html
================================================

09-21-2021 06:37:57 PM verifyElementText(strippedResponse, "PreAuthorizeResponse.Receipt.TransactionResult", "Approved")

Elapsed time: 0.334s

Unable to verify element text (Root cause: com.kms.katalon.core.exception.StepFailedException: Expected text is 'Approved' but actual element text is: 
	at com.kms.katalon.core.keyword.internal.KeywordMain.stepFailed(KeywordMain.groovy:50)
	at com.kms.katalon.core.webservice.keyword.builtin.VerifyElementTextKeyword$_verifyElementText_closure1.doCall(VerifyElementTextKeyword.groovy:52)
	at com.kms.katalon.core.webservice.keyword.builtin.VerifyElementTextKeyword$_verifyElementText_closure1.call(VerifyElementTextKeyword.groovy)
	at com.kms.katalon.core.keyword.internal.KeywordMain.runKeyword(KeywordMain.groovy:74)
	at com.kms.katalon.core.webservice.keyword.builtin.VerifyElementTextKeyword.verifyElementText(VerifyElementTextKeyword.groovy:45)
	at com.kms.katalon.core.webservice.keyword.builtin.VerifyElementTextKeyword.execute(VerifyElementTextKeyword.groovy:40)
	at com.kms.katalon.core.keyword.internal.KeywordExecutor.executeKeywordForPlatform(KeywordExecutor.groovy:74)
	at com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords.verifyElementText(WSBuiltInKeywords.groovy:253)
	at com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords$verifyElementText$0.call(Unknown Source)
	at CreditCardPreAuth.run(CreditCardPreAuth:30)
	at com.kms.katalon.core.main.ScriptEngine.run(ScriptEngine.java:194)
	at com.kms.katalon.core.main.ScriptEngine.runScriptAsRawText(ScriptEngine.java:119)
	at com.kms.katalon.core.main.TestCaseExecutor.runScript(TestCaseExecutor.java:430)
	at com.kms.katalon.core.main.TestCaseExecutor.doExecute(TestCaseExecutor.java:421)
	at com.kms.katalon.core.main.TestCaseExecutor.processExecutionPhase(TestCaseExecutor.java:400)
	at com.kms.katalon.core.main.TestCaseExecutor.accessMainPhase(TestCaseExecutor.java:392)
	at com.kms.katalon.core.main.TestCaseExecutor.execute(TestCaseExecutor.java:273)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:142)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:133)
	at com.kms.katalon.core.main.TestCaseMain$runTestCase$0.call(Unknown Source)
	at TempTestCase1632263866285.run(TempTestCase1632263866285.groovy:25)
)

Thanks

I am afraid you still do not understand how WS.verifyElementText() is causing a problem for you.

I would refain.

WS.verifyElementText() keyword calls ResponseObject.getResponseBodyContent() to get access to the response text. The getResponseBodyContent() method has a careless code at #LINE 77.

@milad.abbasi

Do you understand what the following line of code does?

May be you don’t. If you don’t understand what this XPath expression does, then, I am afraid, you would not understand my discussion. But it’s OK. An usual Katalon Studio user do not need to.

But I hope that Katalon developers understand how the Line#77 https://github.com/katalon-studio/katalon-studio-testing-framework/blob/master/Include/scripts/groovy/com/kms/katalon/core/testobject/ResponseObject.java works bad.

c/c
@duyluong

Hi @milad.abbasi,

This is an issue that KS doesn’t recognize namespace in SOAP Envelope Body. It was planned to fix in KS v8.2.0 that will release in Nov 18th.

The workaround, for now, is to copy the folder Include/scripts/groovy of the attachment project and paste it to the user’s project. If everything is OK, they can see the added scripts look like the image below:
dd5bc6bb-d8b2-42f2-b381-c44c1d403222

HELPDESK-690.zip (177.7 KB)

@kazurayam,

Back to your question about the below code:

Node node = (Node) xPath.evaluate("//*//*//*", doc, XPathConstants.NODE);

I can confirm that this code is not handle namespace in XML DOM child elements when parsing the SOAP response body.

No, that is not the point.

The worst thing about WS.verifyElementTtext() is that the official document https://docs.katalon.com/katalon-studio/docs/webui-verify-element-text.html explains NOTHING how this keyword behaves.

The document MUST clearly describe why it applies the magical XPath //*//*//*. What is the intension. What this keyword assumes the response text to be like.

I believe the programmer who wrote the WS.verifyElementText() keyword implicitly assumed that, in case the response is a XML, the document is a SOAP <env:Envelope> with a <env:Body> element and without a<env:Head> element. The keyword fails to process a SOAP message with an <env:Head> element, Also this keyword fails to process non-SOAP messages.

The name of the keyword “verifyElementText” is not appropriate. The name “verifyElementText” sounds generic. The name sounds as if the keyword would solve every problems. But actually the keyword is narrow scoped. It does not support non-SOAP XML messages. The keyword should rather be called “verifySOAPBodyElementTextInCaseOfSOAPMessageWithoutHead” to express what it does.


I will show you an experiment. I made a TestCase:

import com.kms.katalon.core.testobject.ResponseObject

String responseTextWithoutHead = '''<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns2="bop" xmlns:ns1="foo" >
      <ns:Receipt>
        <ns1:TransactionResult>Approved</ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>
'''

ResponseObject responseObject1 = new ResponseObject()
responseObject1.setContentType("application/xml")
responseObject1.setResponseText(responseTextWithoutHead)
String nodeStr1 = responseObject1.getResponseBodyContent()
println "nodeStr1: " + nodeStr1

//-----------------------------------------------------------------------

String responseTextWithHead = '''<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsu:Timestamp wsu:Id="timestamp" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        <wsu:Created>2021-09-15T19:23:13.688Z
        </wsu:Created>
        <wsu:Expires>2021-09-15T19:28:13.688Z
        </wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>
  </env:Header>
  <env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns2="bop" xmlns:ns1="foo" >
      <ns:Receipt>
        <ns1:TransactionResult>Approved</ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>
'''

ResponseObject responseObject2 = new ResponseObject()
responseObject2.setContentType("application/xml")
responseObject2.setResponseText(responseTextWithHead)
String nodeStr2 = responseObject2.getResponseBodyContent()
println "nodeStr2: " + nodeStr2

When I ran this, it gave following output in the console:

nodeStr1: 
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns1="foo" xmlns:ns2="bop">
      <ns:Receipt>
        <ns1:TransactionResult>Approved</ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>

and

nodeStr2: 
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="timestamp">
        <wsu:Created>2021-09-15T19:23:13.688Z
        </wsu:Created>
        <wsu:Expires>2021-09-15T19:28:13.688Z
        </wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>

The above experiment shows that the ReponseObject.getResponseBodyContent() returns

  1. the descendant nodes of <env:Body> element of the Response text when the response text does NOT have <env:Head> element.

  2. the descendant nodes of <env:Head> element of the Response text when the response text has a <env:Head> element.

I feel this behaviour is very strange. It is difficult to see why ResponseObject.getResponseBody() behaves like this. I suppose that it should rather return the the descendant nodes of <env:Body> element of the Response text regardless if the response text has a <env:Head> element or not. — But in fact, this is not the case.

This behaviour of ResponseObject.getResponseBody() affects WS.verifyElementText().

I think that, regardless if this behaviour is as intended or not, the document of WS.verifyElementText() MUST describe the keyword will behave as this.

Let me show you another experiment. This experiment studies how the magical XPath //*//*//* works.

import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import com.kms.katalon.util.DocumentBuilderProvider;
import com.kms.katalon.util.TransformerFactoryProvider;

String responseTextWithoutHead = '''<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns2="bop" xmlns:ns1="foo" >
      <ns:Receipt>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>
'''

String responseTextWithHead = '''<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsu:Timestamp wsu:Id="timestamp" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        <wsu:Created>2021-09-15T19:23:13.688Z
        </wsu:Created>
        <wsu:Expires>2021-09-15T19:28:13.688Z
        </wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>
  </env:Header>
  <env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns2="bop" xmlns:ns1="foo" >
      <ns:Receipt>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>
'''

println """getXPathResult(responseTextWithoutHead, "//*"):\n""" + getXPathResult(responseTextWithoutHead, "//*") + "\n"
println """getXPathResult(responseTextWithoutHead, "//*//*"):\n""" + getXPathResult(responseTextWithoutHead, "//*//*") + "\n"
println """getXPathResult(responseTextWithoutHead, "//*//*//*"):\n""" + getXPathResult(responseTextWithoutHead, "//*//*//*") + "\n"
println """getXPathResult(responseTextWithHead, "//*"):\n""" + getXPathResult(responseTextWithHead, "//*") + "\n"
println """getXPathResult(responseTextWithHead, "//*//*"):\n""" + getXPathResult(responseTextWithHead, "//*//*") + "\n"
println """getXPathResult(responseTextWithHead, "//*//*//*"):\n""" + getXPathResult(responseTextWithHead, "//*//*//*") + "\n"
  
 
 String getXPathResult(String responseText, String xpath) {
	 DocumentBuilder db = DocumentBuilderProvider.newBuilderInstance();
	 Document doc = db.parse(new InputSource(new StringReader(responseText)));
	 XPath xPath = XPathFactory.newInstance().newXPath();
	 Node node = (Node) xPath.evaluate(xpath, doc, XPathConstants.NODE);
	 return nodeToString(node);
 }
 
 String nodeToString(Node node) throws TransformerException {
	 StringWriter writer = new StringWriter();
	 TransformerFactory tf = TransformerFactoryProvider.newInstance();
	 Transformer xform = tf.newTransformer();
	 xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
	 xform.transform(new DOMSource(node), new StreamResult(writer));
	 return writer.toString();
 }

This gives you the following output:

getXPathResult(responseTextWithoutHead, "//*"):
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns1="foo" xmlns:ns2="bop">
      <ns:Receipt>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>

getXPathResult(responseTextWithoutHead, "//*//*"):
<env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns1="foo" xmlns:ns2="bop">
      <ns:Receipt>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>

getXPathResult(responseTextWithoutHead, "//*//*//*"):
<ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns1="foo" xmlns:ns2="bop">
      <ns:Receipt>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>

getXPathResult(responseTextWithHead, "//*"):
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="timestamp">
        <wsu:Created>2021-09-15T19:23:13.688Z
        </wsu:Created>
        <wsu:Expires>2021-09-15T19:28:13.688Z
        </wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>
  </env:Header>
  <env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns1="foo" xmlns:ns2="bop">
      <ns:Receipt>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>

getXPathResult(responseTextWithHead, "//*//*"):
<env:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="timestamp">
        <wsu:Created>2021-09-15T19:23:13.688Z
        </wsu:Created>
        <wsu:Expires>2021-09-15T19:28:13.688Z
        </wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>
  </env:Header>

getXPathResult(responseTextWithHead, "//*//*//*"):
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="timestamp">
        <wsu:Created>2021-09-15T19:23:13.688Z
        </wsu:Created>
        <wsu:Expires>2021-09-15T19:28:13.688Z
        </wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>

So, I think that the author wrote the magical XPath //*//*//* intending to select the child node set of a SOAP <env:Body> element. But he carelessly neglected the case where the SOAP message contains a <env:Head> element preceding to the <env:Body>. This was his mistake.

Quote from the offical document: https://docs.katalon.com/katalon-studio/docs/handle-response-messages.html

XML data are similar both in the structure and the way we define element locator , our expected info may come from: the attribute of XML tag (in example below, " no" is an attribute of " contacts" tag) or from inner child tag, you can use keywords for handling text (e.g. verifyElementText) or property (e.g. verifyElementPropertyValue) respectively.

In the screenshot, you can find a sample code

  • verifyElementText(responseObject, "GetEmployeeResponse.employee.contacts.email", "email:kms-techn...)

The locator in this sample silently skips <soap:Envelope> and <soap:Body> element ( The magical XPath //*//*//* does this skipping). In other words, you are NOT supposed to write like this:

  • verifyElementText(responseObject, "Envelope.Body.GetEmployeeResponse.employee.contacts.email", "email:kms-techn...)

I think that the former locator is mysterious. Why Envelope element and Body element are skipped in the locator? It it right or not?

I think that the latter locator is intuitive and definitive. Here I should write an absolute GPath starting from the Document root (Envelope) going down the layers of nodes (Body.GetEmployeeResponse.employee.contacts) to the element of my interest(email). I find no mystery here.

I got confused: Which way the keyword requires? I read the official document, but it does not answer to my question.

Therefore I would argue that users should not use the WS.verifyElementText() keyword for XML because of the poor documentation.

Hi everyone,

The issue that KS doesn’t recognize namespace in SOAP Envelope Body has been fixed on KS 8.2.0 beta.
Please refer to Release Notes for the other improvements.

Happy Testing!

Nam Nguyen.

@nam.nguyen

I hope Katalon Team to spare time to improve the official document of WS.verifyElementText

I tried 8.2.0beta. I confirmed that WS.verifyElementText() works for a SOAP response with Header element.


I would remind you of an outstanding issue.

Provided that the response SOAP XML is like this:

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Body>
    <ns:PreAuthorizeResponse xmlns:ns="beep" xmlns:ns2="bop" xmlns:ns1="foo" >
      <ns:Receipt>
        <ns1:DataKey>123</ns1:DataKey>
        <ns1:CustomerId>12345</ns1:CustomerId>
        <ns1:PaymentId>123456
        </ns1:PaymentId>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
...

I tried the following test:

WS.verifyElementText(response, 
    'PreAuthorizeResponse.Receipt.TransactionResult',
    'Approved')

This failed as follows:

2021-10-08 18:36:01.105 ERROR c.k.k.core.keyword.internal.KeywordMain  - ❌ Expected text is 'Approved' but actual element text is: Approved
        
2021-10-08 18:36:01.122 ERROR c.k.k.core.keyword.internal.KeywordMain  - ❌ Unable to verify element text (Root cause: com.kms.katalon.core.exception.StepFailedException: Expected text is 'Approved' but actual element text is: Approved
        
	at com.kms.katalon.core.keyword.internal.KeywordMain.stepFailed(KeywordMain.groovy:50)
...

I amended the test script as follows:

WS.verifyElementText(response, 
    'PreAuthorizeResponse.Receipt.TransactionResult',
    '''Approved
        ''')

Please note that the 3rd argument

    '''Approved
        '''

contains a NEWLINE character plus 8 whitespaces.

The amended test script passed.

This example shows that the WS.verifyElementText() keyword is difficult to use. It checks if the XML element content text is strictly equal to the 3rd argument string. Any NEWLINEs and whitespace characters easily break the equality test. Unfortunately the keyword offers no way to make the verification tolerant of whitespaces.

The official documentation explains nothing about the strictness for whitespaces. I am afraid that many people would get stuck with it.

Therefore I would still argue, users should not rely on this keyword. They should rather parse the XML in the Web Service Response themselves to look up elements of their interest while trimming (or ignoring) whitespaces in the content text as appropriate.

1 Like

Yes, sure @kazurayam.
Thanks for your suggestion.

Regards,
Nam Nguyen.

I would like to suggest a code change:

Current Line#50

            boolean isEqual = (text.equals(String.valueOf(retValue)))

I would propose to change this to:

            boolean found = String.valueOf(retValue).contains(text.trim())

This change would not break existing scripts of users.

And the keyword WebUI.verifyElementText() will become tolerant for whitespaces in the response body.

The current official document writes:

Returns

  • true , if your element text is found, otherwise; false .

This description is ambiguous enough. The aforementioned change would not break the doc. :wink:

1 Like

Hi @kazurayam,

We will verify the solution and apply it if it works perfectly.
Really appreciate your idea!

Thanks,
Nam Nguyen.