Verify multiple siblings in a list

LegacyForumLegacyForum Posts: 1,669 ✭✭
edited December 2016 in SOAtest
Verifying a list entry is present
I have looked at XML Assertors and XML Transformers in order to verify that an element appears in a list containing a particular description AND a particular id and a particular event. Value Occurrence XML Assertors let you validate a particular value is present one or more times, or not present. I have read entries talking discussing the use of tag[@child1=this and @child2=that]. When I use the evaluate XPath button, then best I can get is the value true which is not accepted in the actual XML Assertor, producing the message "An error occurred while processing an assertion: Can not convert #BOOLEAN to a Nodelist!.

I need help to figure this out. Here is a sample of what I am trying to parse:

The elements I wish to test are all children of triggerRuleSummary. The specific elements are description, eventType and id. Again, I want to verify if one of the triggerRuleSummary list elements contains the combination of description="descriptiontext" AND eventType="AR" and id=9999 (or some other number).

Here is the sample xml:


<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/";
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/";
xmlns:xsd="http://www.w3.org/2001/XMLSchema";
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">;
<soapenv:Header>
<xt:AppServer
xmlns:xt="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/">XTRAC6.5.2.1_ClusterMbr1</xt:AppServer>;
</soapenv:Header>
<soapenv:Body>
<p347:listSummariesResponse
xmlns:p347="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/">;
<p347:listSummariesReturn xmlns:p972="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types">;
<p972:moreData>0</p972:moreData>
<p972:triggerRuleSummaries>
<p972:triggerRuleSummary>
<p972:active>1</p972:active>
<p972:defaultRule>0</p972:defaultRule>
<p972:description>Test Description 42</p972:description>
<p972:editable>1</p972:editable>
<p972:eventType>AR</p972:eventType>
<p972:id>7757</p972:id>
<p972:triggerRuleCondition>
<p972:triggerRuleExpression>
<p972:ruleExpressionEvaluator>AND</p972:ruleExpressionEvaluator>
<p972:arrayOfTriggerRuleStatement>
<p972:triggerRuleStatement>
<p972:fieldKey>
<p972:id>2001</p972:id>
<p972:name>AB#</p972:name>
</p972:fieldKey>
<p972:fieldValue>TestConditionValue</p972:fieldValue>
<p972:ruleComparator>equal</p972:ruleComparator>
</p972:triggerRuleStatement>
</p972:arrayOfTriggerRuleStatement>
<p972:arrayOfTriggerRuleExpression/>
</p972:triggerRuleExpression>
</p972:triggerRuleCondition>
</p972:triggerRuleSummary>
<p972:triggerRuleSummary>
<p972:active>1</p972:active>
<p972:defaultRule>0</p972:defaultRule>
<p972:description>Test Description 42</p972:description>
<p972:editable>1</p972:editable>
<p972:eventType>AR</p972:eventType>
<p972:id>7758</p972:id>
<p972:triggerRuleCondition>
<p972:triggerRuleExpression>
<p972:ruleExpressionEvaluator>AND</p972:ruleExpressionEvaluator>
<p972:arrayOfTriggerRuleStatement>
<p972:triggerRuleStatement>
<p972:fieldKey>
<p972:id>2001</p972:id>
<p972:name>AB#</p972:name>
</p972:fieldKey>
<p972:fieldValue>TestConditionValue</p972:fieldValue>
<p972:ruleComparator>equal</p972:ruleComparator>
</p972:triggerRuleStatement>
</p972:arrayOfTriggerRuleStatement>
<p972:arrayOfTriggerRuleExpression/>
</p972:triggerRuleExpression>
</p972:triggerRuleCondition>
</p972:triggerRuleSummary>
<p972:triggerRuleSummary>
<p972:active>1</p972:active>
<p972:defaultRule>0</p972:defaultRule>
<p972:description>Test Description 42</p972:description>
<p972:editable>1</p972:editable>
<p972:eventType>AR</p972:eventType>
<p972:id>7759</p972:id>
<p972:triggerRuleCondition>
<p972:triggerRuleExpression>
<p972:ruleExpressionEvaluator>AND</p972:ruleExpressionEvaluator>
<p972:arrayOfTriggerRuleStatement>
<p972:triggerRuleStatement>
<p972:fieldKey>
<p972:id>2001</p972:id>
<p972:name>AB#</p972:name>
</p972:fieldKey>
<p972:fieldValue>TestConditionValue</p972:fieldValue>
<p972:ruleComparator>equal</p972:ruleComparator>
</p972:triggerRuleStatement>
</p972:arrayOfTriggerRuleStatement>
<p972:arrayOfTriggerRuleExpression/>
</p972:triggerRuleExpression>
</p972:triggerRuleCondition>
</p972:triggerRuleSummary>
</p972:triggerRuleSummaries>
</p347:listSummariesReturn>
</p347:listSummariesResponse>
</soapenv:Body>
</soapenv:Envelope>
Tagged:

Comments

  • LegacyForumLegacyForum Posts: 1,669 ✭✭
    Let's assume that you want to match the first "triggerRuleSummary" in the sample XML. You need to verify that there exists exactly one "triggerRuleSummary" element such that all of the following are true:
    - The "id" child element has a value of "7757".
    - The "eventType" child element has a value of "AR".
    - The "description" child element has a value of "Test Description 42".

    To verify this using an XML Assertor, create an "Occurrence Assertion" that verifies that the number of occurrences == 1.

    To create an XPath that finds a "triggerRuleSummary" that satisfies all three criteria, you can first create an XPath that finds "triggerRuleSummary" elements and then add to that XPath a predicate for the appropriate filtering.

    To find all "triggerRuleSummary" elements in the document, you can use the following XPath:

    /descendant::*[local-name()='triggerRuleSummary']

    In the editor for the Occurrence Assertion, click "Change Element" and evaluate the above XPath. You will see that it returns every "triggerRuleSummary".

    Note that this finds all "triggerRuleSummary" elements regardless of where they are in the document. If you want to require, for example, that the element is a child of a "triggerRulesSummaries" element, see the examples in this forum post on how to specify the desired parent and ancestor elements: http://forums.parasoft.com/index.php?showt...post&p=6408

    This XPath searches through all element descendants of the root node and returns a node list of all elements with a tag name in the form of "some_namespace:triggerRuleSummary" or "triggerRuleSummary" without a namespace. The "*" in "descendant::*" matches any tag name, which I use because it is easier to filter by tag names using the local-name() function when the elements use namespaces. (Alternately, you could use the name() function to match a namespace prefix as well: /descendant::*[name()='p972:triggerRuleSummary'] , but to keep this simple I'll ignore namespaces.)

    To filter the node list returned by the above XPath, you can add a predicate that establishes that the "triggerRuleSummary" has child elements with the desired values.

    /descendant::*[local-name()='triggerRuleSummary'][*[local-name()='id']='7757' and *[local-name()='eventType']='AR' and *[local-name()='description']='Test Description 42']

    In the predicate, "*[local-name()='id']" finds a child element with a tag name of "some_namespace:id" or "id". To compare the "id" element to the string "7757", the XPath engine translates the "id" element into a string. The translation of the "id" element is obvious as it does not contain any child elements. The comparison of the other child elements works similarly.

    If you want to compare the "id" and other elements against values in a data source or data bank, you can use the ${variable} syntax:

    /descendant::*[local-name()='triggerRuleSummary'][*[local-name()='id']='${foo}' and *[local-name()='eventType']='${bar}' and *[local-name()='description']='${etc}']

    Does this provide the desired behavior?

    Note that the XPath does not use the "@ such as the @child1"; in your example. The "@child1"; denotes an attribute with the name "child1". The "id", "eventType", and "description" nodes are all elements, not attributes.

    For more information on XPath, see the following:

    - In the SOAtest user's guide, the section "Reference > XPath Reference". The examples specifically mention HTML but the XPath information is applicable to any XML documents. This information and more is also in the forum post http://forums.parasoft.com/index.php?showtopic=2193

    - Another forum post on creating a custom XPath for an occurrence assertion: http://forums.parasoft.com/index.php?showtopic=2340

    - The XPath standard: http://www.w3.org/TR/xpath/
  • LegacyForumLegacyForum Posts: 1,669 ✭✭
    Thank you for your detailed response. I am working my way through the recommendation; however, I am having some trouble getting the expected results. I am probably making some wrong assumptions about the implementation of the solution.

    Your response states:

    "To create an XPath that finds a "triggerRuleSummary" that satisfies all three criteria, you can first create an XPath that finds "triggerRuleSummary" elements and then add to that XPath a predicate for the appropriate filtering.

    To find all "triggerRuleSummary" elements in the document, you can use the following XPath:

    /descendant::*[local-name()='triggerRuleSummary']

    In the editor for the Occurrence Assertion, click "Change Element" and evaluate the above XPath. You will see that it returns every "triggerRuleSummary".

    Note that this finds all "triggerRuleSummary" elements regardless of where they are in the document."


    My assumption is that your recommendation means I need to do the following:
    • Create an XML Assertor for this list operation
    • Select the Configuration tab and click the Add button
    • Select Structure Assertions > Occurrence Assertion
    • Select the following from the element tree:
      + Envelope
      + Body
      + listSummariesResponse
      + triggerRuleSummaries
    • Set the "Occurrences of the element must be" [==] "with expected value" [1]
    • Click the button "Change Element"
    • in the "Edit Element" dialog, select "Evaluate XPath" in the "Evaluate XPath" section.
    • The result is "{http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types}triggerRuleSummaries, " followed by the text of each triggerRuleSummary element.
    • Click the radio button "Edit XPath manually" above the Xpath section and completely replace the content of the XPath panel with: /descendent::*[local-name()='triggerRuleSummary']
    • Then click "Evaluate XPath"
    • This returns "Cannot evaluate. Not a valid XPath expression."
    • I have tried the same process when selecting the element triggerRuleSummary instead of the triggerRuleSummaries element (selecting
    • for all similar elements) and completely replacing that XPath.
    • I have also tried appending "/descendent::*[local-name()='triggerRuleSummary']" to the end of the XPath provided for both the triggerRuleSummaries and triggerRuleSummary element XPaths. Same result.
    • I've also tried removing /descendent from the end of the triggerRuleSummary XPath selection of triggerRuleSummary XPath, and replacing the 1 in brackets with the remainder of your XP path recommendation with the same result:
    /*[local-name(.)="Envelope" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="Body" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="listSummariesResponse" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="listSummariesReturn" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="triggerRuleSummaries" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]/*[local-name()='triggerRuleSummary'][*local-name()='id']='7994' and *[local-name()='eventType']='AR' and *[local-name]='description]='Test Description 42']

    (I did this for an existing element in the list response).

    Again, same result: "Cannot evaluate. Not a valid XPath expression."


    Evidently, I don't understand exactly how to apply your recommendation. I apologize but I need a more detailed description of the application of your suggestion.

  • LegacyForumLegacyForum Posts: 1,669 ✭✭
    Hi Bryan,

    You are correct as to the steps to take to implement this assertion: you need an XML Assertor with your XML as input and one Occurrence Assertion with expected value "==" 1. I believe that the unexpected behavior when evaluating the XPath is due to typos in your XPath expressions.

    For this XPath:

    /descendent::*[local-name()='triggerRuleSummary']

    You have misspelled the axis name: it is "descendant" (the last vowel is an "a" rather than an "e").

    Note that evaluating this XPath isn't strictly necessary: I suggest doing so to demonstrate the process of building the a more complicated XPath that will provide the desired behavior. I often build complicated XPaths iteratively to make it easier to identify what part of the expression I need to modify. For example, by evaluating your XPath and receiving the "invalid XPath" result, it is simpler to identify what part of the XPath is causing this.

    The final XPath that I suggested in my previous post works for me when evaluating it in the UI and when running the assertor:

    /descendant::*[local-name()='triggerRuleSummary'][*[local-name()='id']='7757' and *[local-name()='eventType']='AR' and *[local-name()='description']='Test Description 42']

    Regarding this XPath that you suggested, there are several typos in the predicate you added:

    /*[local-name(.)="Envelope" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="Body" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="listSummariesResponse" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="listSummariesReturn" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="triggerRuleSummaries" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]/*[local-name()='triggerRuleSummary'][*local-name()='id']='7994' and *[local-name()='eventType']='AR' and *[local-name]='description]='Test Description 42']

    Change [*local-name()='id']='7994' to *[local-name()='id']='7994' : move the "*" to outside of the "[" that starts the predicate.

    Change *[local-name]='description]='Test Description 42' to *[local-name()='description']='Test Description 42' : "local-name" is a function, so you must include parentheses as "local-name()", not a "]". Also, you need to add a terminating single-quote at the end of "'description".

    After making these changes, your XPath becomes this:

    /*[local-name(.)="Envelope" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="Body" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="listSummariesResponse" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="listSummariesReturn" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="triggerRuleSummaries" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]/*[local-name()='triggerRuleSummary'][*[local-name()='id']='7994' and *[local-name()='eventType']='AR' and *[local-name()='description']='Test Description 42']

    If you evaluate this XPath in the UI using the XML you supplied in a previous post, you will receive the result "No nodes found." because the XPath is valid but there are no elements that match. If you change the expected "id" value from '7994' to '7757', evaluating the XPath will return one element.

    Either of the XPath that I suggested starting with "/descendant::*" or the longer XPath that you suggested (once modified) will provide the desired behavior. The choice is yours. I find that the longer XPath is harder to read and therefore harder to modify to create a custom XPath. The longer XPath is longer because it is more precise: it ensures the exact structure of the document from the root node down (whether you need to require this exact structure is up to you), and by using namespace URIs it avoids ambiguity in the case (unlikely for your example) that multiple namespaces use elements with the same tag name. In my shorter XPath I made assumptions -- which seemed to be safe assumptions -- about the document for the sake of human readability. Note that I'm biased towards shorter XPaths to some degree because more of my work with XPaths has been with browser tests and HTML, where typically there are no namespaces and it is necessary to make assumptions about the document structure for the sake of creating a maintainable XPath: the structure of the HTML document is subject to change often, as the document has the goal of presenting the information to humans rather than delivering of information to other applications.

    Note that you can add namespace URIs the XPaths that you and I suggested for the purpose of avoiding tag name ambiguity:

    My XPath with namespace URIs:

    /descendant::*[local-name()='triggerRuleSummary' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"][*[local-name()='id' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]='7757'; and *[local-name()='eventType' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]='AR'; and *[local-name()='description' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]='Test Description 42']

    And your longer XPath:

    /*[local-name(.)="Envelope" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="Body" and namespace-uri(.)="http://schemas.xmlsoap.org/soap/envelope/"]/*[local-name(.)="listSummariesResponse" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="listSummariesReturn" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/"]/*[local-name(.)="triggerRuleSummaries" and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]/*[local-name()='triggerRuleSummary' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"][*[local-name()='id' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]='7994'; and *[local-name()='eventType' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]='AR'; and *[local-name()='description' and namespace-uri(.)="http://xmlns.fmr.com/systems/dev/xtrac/2004/06/types"]='Test Description 42']

    But again, how much time would it take another person working with your XML Assertor to determine the intent of this final XPath? The assumptions you want to make is up to you.
  • LegacyForumLegacyForum Posts: 1,669 ✭✭
    edited December 2016
    Thanks for noting the spelling error. Sorry for that. However, the problem persisted until I simply ran the entire suite. It appears that the test variables ${triggerRule1}, $testDescription1} and ${Event} are only available during the run of the suite. If I try to test using "Evaluate XPath", even after successfully executing the entire test suite, I will get back the response ""No nodes found".

    I appreciate all your help and I will be able to proceed from this point. However, I would like to understand what I need to do differently to get a successful response to "Evaluate XPath when using a test variable defined in the suite. As mentioned, "Evaluate XPath" does not even work after I have already successfully run the listSummaries operation.

    Thanks
  • LegacyForumLegacyForum Posts: 1,669 ✭✭
    If I understand correctly, you have an XPath that contains variable references, such as /foo[bar='${someVariable}']. When running the test, this XPath evaluates as expected, referencing the appropriate value for the variable "someVariable". However, in the UI for creating the XPath expression, when you evaluate the XPath it does not resolve "${someVariable}" to the last value used that was used for "someVariable" when running tests. You expect it to use the last value of "someVariable". You get the message "No nodes found" because the XPath uses the literal value "${someVariable}", which does not occur in the document. This is known behavior (the current version of SOAtest being 9.0) for which we have a feature request. I will note your interest. Currently there is no way to get SOAtest to resolve "${someVariable}" to a value when evaluating the XPath in the UI. To evaluate the XPath in the UI, you would need manually copy in the value and then replace the value with "${someVariable}" before you save the test, which I realize you would prefer not to do.
  • LegacyForumLegacyForum Posts: 1,669 ✭✭
    For reference...

    /descendant::*[local-name()='triggerRuleSummary'][*[local-name()='id']='7757' and *[local-name()='eventType']='AR' and *[local-name()='description']='Test Description 42']

    You can write an XPath that ignores namespaces without utilizing the verbosity of the local-name function.

    /descendant::*:triggerRuleSummary[*:id = '7757' and *:eventType = 'AR' and *:description = 'Test Description 42']

    It's much easier to understand what this XPath is trying to do.

    Using the *:tagName syntax (the "*" is a wildcard for the namespace prefix) would significantly shorten the other XPaths in this post as well.
Sign In or Register to comment.