Forum Discussion

smilnik's avatar
smilnik
Occasional Contributor
10 months ago

SoapUi Groovy conditional property transfer

I'm automating some tests and i need your help to do a groovy script with conditional property transfer

<OfferItem>

               <OfferItemID>wanted_id_1</OfferItemID>

               <Service>

                              <ServiceDetails>

                                            <PaxJourneyRefID>PJ1</PaxJourneyRefID>

                              </ServiceDetails>

               </Service>

               <Service>

                              <ServiceDetails>

                                            <ServiceDetailsRef>

                                                           <PSRID>S1</PSRID>

                                                           <ServiceID>SR_YES</ServiceID>

                                            </ServiceDetailsRef>

                              </ServiceDetails>

               </Service>

</OfferItem>

<OfferItem>

               <OfferItemID>not_id_1</OfferItemID>

               <Service>

                              <ServiceDetails>

                                            <PaxJourneyRefID>PJ1</PaxJourneyRefID>

                              </ServiceDetails>

               </Service>

               <Service>

                              <ServiceDetails>

                                            <ServiceDetailsRef>

                                                           <PSRID>S1</PSRID>

                                                           <ServiceID>SR_NO</ServiceID>

                                            </ServiceDetailsRef>

                              </ServiceDetails>

               </Service>

</OfferItem>

My XML exemple : i need to transfer the offerItemID when ServiceID == SR_YES

 

Thank you in advance for help.

  • smilnik 

    There is another approach which finds offer id for matching service def and doesn't use xpath.  You may use which ever is comfortable for you.

    NOTE: have used context.response as place holder value for xml data which inside parseText(), feel free to change as needed. 

    Follow the comments in-line.

    //Define the match / search criteria, element name and value 
    def match = [ key:'ServiceDefinitionRefID',value:'SRV-SAF']
    
    //Find all OfferItem 's
    def oItems = new XmlSlurper().parseText(context.response).'**'.findAll {it.name() == 'OfferItem'}
    
    //Get the Offer Item Id by applying search criteria defined above in match variable, returns null if no match
    def getOfferItemIdForMatchingElement = { oItem, name, value -> oItem.'**'.find { it.name() == name && it.text()==value } ? oItem.OfferItemID.text() : null }
    
    //Get offer item id for all Offer items, of course, there will be one as we are expecting, so filter null values
    def offerItemId = oItems.collect { oItem -> getOfferItemIdForMatchingElement (oItem, match['key'], match['value']) }.find {it}
    
    log.info "Offer item id: $offerItemId"
    assert !offerItemId, 'No offer item id found for defined match criteria'
    
    context.testCase.setPropertyValue('ITEMID', offerItemId)
    
    
  • I would extract that value by utilizing Groovy XML slurper in Groovy script test step. When you return value in Groovy script test step, you can utilize this value in succeeding test steps and make logic based on whether return value is not empty.

    Property transfer test step could also be a viable option with XPATH but it can be more tedious with more complex xml.

    • TNeuschwanger's avatar
      TNeuschwanger
      Champion Level 1

      Hello smilnik 

      Here is groovy code for your example.  It can be dropped into a "Groovy Script" test step.  It performs property transfer to a value in a "Properties" test step within the same test case.  There is probably better code examples to 'find' a value in xml, but this was handy.

      Regards,

      Todd

      log.info "Test Step ${testRunner.runContext.currentStep.name} start...";
      
      xmlStr = """
      <catalog>
      <OfferItem>
        <OfferItemID>not_id_1</OfferItemID>
        <Service>
          <ServiceDetails>
            <PaxJourneyRefID>PJ1</PaxJourneyRefID>
          </ServiceDetails>
        </Service>
        <Service>
          <ServiceDetails>
            <ServiceDetailsRef>
              <PSRID>S1</PSRID>
              <ServiceID>SR_MAYBE</ServiceID>
            </ServiceDetailsRef>
          </ServiceDetails>
        </Service>
      </OfferItem>
      
      <OfferItem>
        <OfferItemID>wanted_id_1</OfferItemID>
        <Service>
          <ServiceDetails>
            <PaxJourneyRefID>PJ1</PaxJourneyRefID>
          </ServiceDetails>
        </Service>
        <Service>
          <ServiceDetails>
            <ServiceDetailsRef>
              <PSRID>S1</PSRID>
              <ServiceID>SR_YES</ServiceID>
            </ServiceDetailsRef>
          </ServiceDetails>
        </Service>
      </OfferItem>
      
      <OfferItem>
        <OfferItemID>not_id_1</OfferItemID>
        <Service>
          <ServiceDetails>
            <PaxJourneyRefID>PJ1</PaxJourneyRefID>
          </ServiceDetails>
        </Service>
        <Service>
          <ServiceDetails>
            <ServiceDetailsRef>
              <PSRID>S1</PSRID>
              <ServiceID>SR_NO</ServiceID>
            </ServiceDetailsRef>
          </ServiceDetails>
        </Service>
      </OfferItem>
      </catalog>
      """;
      
      def foundValue = '';
      def findStr = 'SR_YES';
      def catalog = new XmlSlurper().parseText(xmlStr);
      catalog.OfferItem.Service.ServiceDetails.ServiceDetailsRef.ServiceID.eachWithIndex {valueStr, indx ->
         log.info "indx=$indx      valueStr=$valueStr";
         if (valueStr == findStr) {
            foundValue = catalog.OfferItem.OfferItemID[indx].toString();
            log.info "foundValue=$foundValue";
         };
      };
      
      testRunner.testCase.testSteps['Properties'].setPropertyValue('offerItemID', foundValue);
      
      log.info "Test Step ${testRunner.runContext.currentStep.name} done...";
  • nmrao's avatar
    nmrao
    Champion Level 3

    Have you tried any script? what error are you seeing?

  • nmrao's avatar
    nmrao
    Champion Level 3

    If one have to capture the OfferItemID, no need to create another properties step and add it. Instead, just add it test case if the usage is limited to test case. Alternatively, can be saved to test suite or project level properties.

    Here is simple way to get the value using xpath. The advantage is just change the  xpath for different xml source, the remaining logic stays. Of course, assert what is needed accordingly.

    import javax.xml.xpath.XPathFactory
    import org.xml.sax.InputSource
    // Replace the original xml here or some logic to get from previous step
    def xmlString = ' Xml goes here'
    
    //Value extraction logic
    def xpath = "//OfferItem[Service/ServiceDetails/ServiceDetailsRef/ServiceID='SR_YES']/OfferItemID/text()"
    def xpathInstance = XPathFactory.newInstance().newXPath()
    def offerItemId = xpathInstance.evaluate(xpath, new InputSource(new StringReader(xmlString)))
    log.info "OfferItemID: $offerItemId"
    
    //If expecting a value
    assert offerItemId == 'wanted_id_1'
    
    //Store offer item id into test case custom properties for later user in other steps
    context.testCase.setPropertyValue('ITEMID', offerItemId)
    
    
    • smilnik's avatar
      smilnik
      Occasional Contributor

      Thank you for answer.

      i tried but i have this error :

      javax.xml.xpath.XPathExpressionException: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 2; Content is not allowed in prolog. error at line: 9

      • nmrao's avatar
        nmrao
        Champion Level 3

        Please check, some copy paste issue. Or you might forgot to put xml which i did not put.

        You can quickly verify here

  • smilnik's avatar
    smilnik
    Occasional Contributor

    Thank you so much it's working for this exemple.

    i'm doing property transfer from webservice response (before groovy script) to another webservice request after groovy scrit (the real xml is longer then i shared so i can't add it in the script). i taked note of TNeuschwanger comment and yours to do it but not working (sorry mybe is something easy but i am new in soapui)

    import javax.xml.xpath.XPathFactory

    import org.xml.sax.InputSource

    //i added this part

    def xmlString = new XmlSlurper().parseText(context.response);

    def xpathExpression = "//Response/OffersGroup/CarrierOffers/Offer/OfferItem[Service/ServiceAssociations/ServiceDefinitionRef/ServiceDefinitionRefID='SRV-SAF']/OfferItemID/text()"

    def xpath = XPathFactory.newInstance().newXPath()

    def result = xpath.evaluate(xpathExpression, new InputSource(new StringReader(xmlString.response)))

    log.info "OfferItemID: $result"

     

    and i have this error : groovy.lang.GroovyRuntimeException: Could not find matching constructor for: java.io.StringReader(groovy.util.slurpersupport.NodeChildren) error at line: 11 (def result line)
     

    • nmrao's avatar
      nmrao
      Champion Level 3

      smilnik 

      Can you list the test steps and it's type or screen shot of your test case please? 

      It is possible that the xpath may be different in original. It is important that the solution needs to be adopted to your data.

      Data is important to look into further. 

      By the way, you should just try assigning as shown below, assuming that it gets desired xml in string.

      def xmlString = context.response

       

      As the error says, StringReader is expecting a string , not XmlSlurper / NodeChild.

  • smilnik's avatar
    smilnik
    Occasional Contributor
    Hello nmrao thank you please find all logs bellow : 
    - We want to transfer to offerID with ServiceDefinitionRefID == SRV-SAF
     
    - to transfer this value from AirshoppingRS response to offerPriceRQ (//*:OfferRefID)
     
    The actual groovy script :
     
    import javax.xml.xpath.XPathFactory
    import org.xml.sax.InputSource
     
    def xmlString = context.response;
     
    // Create XPath expression
    def xpathExpression = "//Response/OffersGroup/CarrierOffers/Offer/OfferItem[Service/ServiceAssociations/ServiceDefinitionRef/ServiceDefinitionRefID='SRV-SAF']/OfferItemID/text()"
    // Create XPath object
    def xpath = XPathFactory.newInstance().newXPath()
     
    // Evaluate XPath expression
    def result = xpath.evaluate(xpathExpression, new InputSource(new StringReader(xmlString)))
    log.info "OfferItemID: $result"
     
     
     
    • nmrao's avatar
      nmrao
      Champion Level 3

      "//Response/OffersGroup/CarrierOffers/Offer/OfferItem[Service/ServiceAssociations/ServiceDefinitionRef/ServiceDefinitionRefID='SRV-SAF']/OfferItemID/text()

      smilnik, With above xpath, is it showing the desired offer Id ?

       

       

       

    • nmrao's avatar
      nmrao
      Champion Level 3

      smilnik 

      Got, the problem is that the xml is namespace aware, so the xpath you were using did not work.

      You can use below xpath instead of what you have currently and quickly verify as well here

      //*[local-name()='ServiceDefinitionRefID'][.='SRV-SAF']/ancestor::*[local-name()='OfferItem']/*[local-name()='OfferItemID']/text()

      then store value into custom test case custom property as suggested

      context.testCase.setPropertyValue('ITEMID', offerItemId)

      In order to get the above value into request is just change in offerPriceRQ

      <OfferRefID>cb31b206-1119-4f89-b1b2-0611196f0001</OfferRefID>

      to

      <OfferRefID>${#TestCase#ITEMID}</OfferRefID>

    • nmrao's avatar
      nmrao
      Champion Level 3

      smilnik 

      There is another approach which finds offer id for matching service def and doesn't use xpath.  You may use which ever is comfortable for you.

      NOTE: have used context.response as place holder value for xml data which inside parseText(), feel free to change as needed. 

      Follow the comments in-line.

      //Define the match / search criteria, element name and value 
      def match = [ key:'ServiceDefinitionRefID',value:'SRV-SAF']
      
      //Find all OfferItem 's
      def oItems = new XmlSlurper().parseText(context.response).'**'.findAll {it.name() == 'OfferItem'}
      
      //Get the Offer Item Id by applying search criteria defined above in match variable, returns null if no match
      def getOfferItemIdForMatchingElement = { oItem, name, value -> oItem.'**'.find { it.name() == name && it.text()==value } ? oItem.OfferItemID.text() : null }
      
      //Get offer item id for all Offer items, of course, there will be one as we are expecting, so filter null values
      def offerItemId = oItems.collect { oItem -> getOfferItemIdForMatchingElement (oItem, match['key'], match['value']) }.find {it}
      
      log.info "Offer item id: $offerItemId"
      assert !offerItemId, 'No offer item id found for defined match criteria'
      
      context.testCase.setPropertyValue('ITEMID', offerItemId)
      
      
  • smilnik's avatar
    smilnik
    Occasional Contributor

    Thank you so much it's working with

    new XmlSlurper().parseText(context.response)

    • nmrao's avatar
      nmrao
      Champion Level 3

      Hope you checked the new xpath i suggested which is also good.