Not able to validate SOAP call response

Hello,

I’m hitting another roadblock trying to assert some results for a SOAP call.
Please find the sample response below:

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsi="" xmlns:ns="" xmlns:ns2="" xmlns:ns1="" xmlns:env="">
  <env:Header>
    <wsse:Security xmlns:wsse="">
      <wsu:Timestamp wsu:Id="" xmlns:wsu="">
        <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>
      <ns:Receipt>
        <ns1:DataKey>1234
        </ns1:DataKey>
        <ns1:CustomerId>123456
        </ns1:CustomerId>
        <ns1:PaymentId>1234567
        </ns1:PaymentId>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
        <ns1:MaskedCreditCardNumber>hi
        </ns1:MaskedCreditCardNumber>
        <ns1:ExpirationDate>hey
        </ns1:ExpirationDate>
        <ns1:CardDataSource>right
        </ns1:CardDataSource>
        <ns1:AuthorizationCode>683295
        </ns1:AuthorizationCode>
        <ns1:TransactionTime>15:23:11
        </ns1:TransactionTime>
        <ns1:TransactionDate>2021-09-15
        </ns1:TransactionDate>
        <ns1:TransactionType>01
        </ns1:TransactionType>
        <ns1:TransactionAmount>24.00
        </ns1:TransactionAmount>
        <ns1:CreditCardType>M
        </ns1:CreditCardType>
        <ns1:TransactionId>47490-0_20
        </ns1:TransactionId>
        <ns1:CvdResultCode>M
        </ns1:CvdResultCode>
        <ns1:PaymentType>cc
        </ns1:PaymentType>
        <ns2:RiskInquiryResponse>
          <ns2:TransactionId>D6TK02CBPQQQ
          </ns2:TransactionId>
          <ns2:Result>A
          </ns2:Result>
          <ns2:Score>34
          </ns2:Score>
        </ns2:RiskInquiryResponse>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>

I’m trying to make an assertion for <ns1:TransactionResult>Approved</ns1:TransactionResult>
which I’m sending the following
WS.verifyElementText(response, 'PreAuthorizeResponse.Receipt.TransactionResult', 'Approved')

seems to be failing with the following verification error

2021-09-14 18:02:40.692 INFO  c.k.k.core.main.WSVerificationExecutor   - --------------------
2021-09-14 18:02:40.695 INFO  c.k.k.core.main.WSVerificationExecutor   - START Verification
2021-09-14 18:02:41.649 ERROR c.k.k.core.keyword.internal.KeywordMain  - ? Expected text is 'Approved' but actual element text is: 
2021-09-14 18:02:41.657 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: 
    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.call(Unknown Source)
    at WSVerification1631656960771.run(WSVerification1631656960771:15)
    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.WSVerificationExecutor.runScript(WSVerificationExecutor.java:178)
    at com.kms.katalon.core.main.WSVerificationExecutor.doExecute(WSVerificationExecutor.java:172)
    at com.kms.katalon.core.main.WSVerificationExecutor.processExecutionPhase(WSVerificationExecutor.java:155)
    at com.kms.katalon.core.main.WSVerificationExecutor.accessMainPhase(WSVerificationExecutor.java:147)
    at com.kms.katalon.core.main.WSVerificationExecutor.execute(WSVerificationExecutor.java:129)
    at com.kms.katalon.core.main.TestCaseMain.runWSVerificationScript(TestCaseMain.java:155)
    at com.kms.katalon.core.main.TestCaseMain$runWSVerificationScript$0.call(Unknown Source)
    at TempTestCase1631656954390.run(TempTestCase1631656954390.groovy:25)
)
2021-09-14 18:02:41.661 ERROR c.k.k.core.main.WSVerificationExecutor   - ? Verification FAILED.
Reason:
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.call(Unknown Source)
    at WSVerification1631656960771.run(WSVerification1631656960771:15)
    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.WSVerificationExecutor.runScript(WSVerificationExecutor.java:178)
    at com.kms.katalon.core.main.WSVerificationExecutor.doExecute(WSVerificationExecutor.java:172)
    at com.kms.katalon.core.main.WSVerificationExecutor.processExecutionPhase(WSVerificationExecutor.java:155)
    at com.kms.katalon.core.main.WSVerificationExecutor.accessMainPhase(WSVerificationExecutor.java:147)
    at com.kms.katalon.core.main.WSVerificationExecutor.execute(WSVerificationExecutor.java:129)
    at com.kms.katalon.core.main.TestCaseMain.runWSVerificationScript(TestCaseMain.java:155)
    at com.kms.katalon.core.main.TestCaseMain$runWSVerificationScript$0.call(Unknown Source)
    at TempTestCase1631656954390.run(TempTestCase1631656954390.groovy:25)
2021-09-14 18:02:41.666 INFO  c.k.k.core.main.WSVerificationExecutor   - END Verification

Also when trying to use CTRL+K for TransactionResult it sends me the following which is the wrong path of the object

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

Please let me know if there is an alternative way for me to get this assertion working, maybe by parsing the xml to a string and validating it? any help would be appreciated.

Thanks,

Katalon Studio implicitly assumes that the response XML is a valid SOAP message. But your XML response has empty XML Namespace.

This XML is not a valid SOAP message at all. This should rather be something like:

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ns="foo" xmlns:ns1="baz" xmlns:ns2="bar">

Because the response XML isn’t a valid SOAP message, Katalon Studio can’t process it as expected.

(I regret that Katalon Studio is not robust enough against poorly-formed XML => @ThanhTo )

I hope your development team understands what I am saying: XML Namespace matters. I guess, your server-side app is still in early stage of development; so that your app is not yet ready to test using tools.


Yes, there would be some other ways, but it requires you to have seasoned programming skill and enough knowledge about XML data processing. It is not easy. Also, once the XML Namespace problem is fixed, your interim code for test will become totally useless. I think it is not the way you should take before fixing the aforementioned problem.

I noticed it as well.

It is likely that CTRL+K has got crazy because of the empty XML Namespace declarations in this response XML instance. You should fix the XML Namespace problem first, then try CTRL+K. I suppose it will work if the response XML instances is a valid SOAP message.

I suppose it is a bug in Katalon Studio.

c/c @ThanhTo

In the actual response all those fields have proper values, for the sake of the example I removed the values. My mistake.
It is more like

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns="beep" xmlns:ns2="bop" xmlns:ns1="foo" xmlns:env="be">
  <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>
      <ns:Receipt>
        <ns1:DataKey>1234
        </ns1:DataKey>
        <ns1:CustomerId>12345
        </ns1:CustomerId>
        <ns1:PaymentId>123456
        </ns1:PaymentId>
        <ns1:TransactionResult>Approved
        </ns1:TransactionResult>
        <ns1:MaskedCreditCardNumber>123456788
        </ns1:MaskedCreditCardNumber>
        <ns1:ExpirationDate>3012
        </ns1:ExpirationDate>
        <ns1:CardDataSource>Ecommerce
        </ns1:CardDataSource>
        <ns1:AuthorizationCode>683295
        </ns1:AuthorizationCode>
        <ns1:TransactionTime>15:23:11
        </ns1:TransactionTime>
        <ns1:TransactionDate>2021-09-15
        </ns1:TransactionDate>
        <ns1:TransactionType>01
        </ns1:TransactionType>
        <ns1:TransactionAmount>24.00
        </ns1:TransactionAmount>
        <ns1:CreditCardType>M
        </ns1:CreditCardType>
        <ns1:TransactionId>47490-0_20
        </ns1:TransactionId>
        <ns1:CvdResultCode>M
        </ns1:CvdResultCode>
        <ns1:PaymentType>cc
        </ns1:PaymentType>
        <ns2:RiskInquiryResponse>
          <ns2:TransactionId>D6TK02CBPQQQ
          </ns2:TransactionId>
          <ns2:Result>A
          </ns2:Result>
          <ns2:Score>34
          </ns2:Score>
        </ns2:RiskInquiryResponse>
      </ns:Receipt>
    </ns:PreAuthorizeResponse>
  </env:Body>
</env:Envelope>

“be” is not the XML Namespace for SOAP Envelope.

It should be:

xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"

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.