Katalon Studio v5.8 - It's time for the all-new API testing experience
Drunda Nibel
Katalon Ambassador
05/15/2018

No XPath access to text nodes

Hi,

it seems to me, that I have no access to text nodes with XPath in Katalon Studio. Though I can identify an element by the content of its child text nodes, I am not able to catch the text nodes themselves. The following example illustrates this:
<div id="node">Text 1<img src="sample.jpg" />Text 2</div>
Actually, I think, here should work ...
id("node")/text()[1] 
... to capture the first text node "Text 1". But it's not. Is XPath only incompletely implemented in Katalon Studio?

Thanks + regards
Upvote
Quote

Comments

  • kazurayam
    Katalon Evangelist
    05/16/2018
    edited May 16
    Drunda,

    I can answer to your question and propose a solution. I will explain in detail taking examples from the following demo project:
    https://github.com/kazurayam/KatalonDiscussion6790

    # Your problem reproduced

    I made a Test Object named 'Page_sample/dev_node_text1' with a xpath selector:
    id('node')/text()[1]
    I made a Test Case named TC2, which is as follows:
    import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
    import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
    WebUI.openBrowser('')
    WebUI.navigateToUrl('http://demoaut-mimic.kazurayam.com/6790_testbed.html')
    def text = WebUI.getText(findTestObject('Page_sample/div_node_text1'))
    WebUI.verifyEqual(text, 'Text 1')
    WebUI.closeBrowser()
    When I run this, it fails. The verifyEqual keyword fails with error a message like this:
    Test Cases/TC2 FAILED because (of) Unable to get text of object 
    (Root cause: java.lang.IllegalArgumentException: Object is null)
    Test Cases/TC2.run:26
    This message tells you that the expression findTestObject('Page_sample/div_node_text1') is evaluated to null. Why null?

    # Background knowledge about XPath

    The XPath 1.0 specification, 5 Data Model defines 7 types of nodes:
    • root nodes
    • element nodes
    • text nodes
    • attribute nodes
    • namespace nodes
    • processing instruction nodes
    • comment nodes
    A single XPath expression may return one of these types of nodes.

    Your expression :
    id("node")/text()[1]
    is valid as XPath. If you apply this expression to the problem HTML document using a full-stack XPath engine such as Jaxen, I am sure, this expression would return "Text 1" as you expect. 

    However, we are not using Jaxen now. We are using Katalon Studio's findTestObject. So we need to look at findTestObject(String) method. According to the API document, the findTestObject(String) is designed to return an instance of TestObject. What is a TestObject?

    I think a TestObject is designed to wrap an element node only; it can not wrap either a text node nor an attribute node. 

    This implicit constraint is not described anywhere in the Katalon documentation, but I have found it by hands-on experiences.

    ## Rule of thumb:

    Xpath selector for a Test Object is required to be a valid xpath expression which returns an 'element node' as a meaning of XPath. 
    If you set selector with an xpath which returns a text node, then  findTestObject(String <test object name>) would return null. A xpath which returns an attribute node would result in null as well. 



    # Solution proposed

    I made a Test Object named 'Page_sample/dev_node' with a selector in xpath:
    id('node')
    I made a Test Case named TS1, which is as follows:
    import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
    import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI.openBrowser('')
    WebUI.navigateToUrl('http://demoaut-mimic.kazurayam.com/6790_testbed.html')
    def text = WebUI.getText(findTestObject('Page_sample/div_node'))
    WebUI.verifyEqual(text, 'Text 1Text 2')
    def leadingPart = text.substring(0, text.indexOf('Text 2'))
    WebUI.verifyEqual(leadingPart, 'Text 1')
    WebUI.closeBrowser()
    This test case runs successful.

    What you can get by WebUI.getText() is 'Text 1Text 2'.  The <img> element between 'Text 1' and 'Text 2' disappears when WebUI.getText('Page_sample/div_node') is evaluated.

    You need to parse the 'Text 1Text 2' string for yourself with Groovy's String object methods.

    The variable named leadingPart contains 'Text 1'. This is what you wanted.


    Hope this helps.
    Upvote
    Quote
  • Drunda Nibel
    Katalon Ambassador
    05/16/2018
    Hi kazurayam

    thanks for your great effort!

    But, without having tried it out, if I understand your solution correctly, I would already have to know the complete content of both text nodes, even if I want to access only the first one (say, for example, I want to click it), wouldn't I?

    Thanks + regards!
    Upvote
    Quote
  • Russ Thomas
    Katalon Evangelist
    05/16/2018
    Another awesome post from Chief Inspector Kazurayum.  :)
    Upvote
    Quote
  • Drunda Nibel
    Katalon Ambassador
    05/16/2018
    kazurayam,

    Maybe your post in this Angular-related thread will also help me in my XPath case, allowing me to simply use the native XPath capabilities of Selenium?

    Or does your above approach also allow generic access to any text node (i.e. without knowing its content)? I don't see how yet.

    Regards

    Upvote
    Quote
  • kazurayam
    Katalon Evangelist
    05/17/2018
    edited May 17
    Maybe your post in this Angular-related thread will also help me in my XPath case, allowing me to simply use the native XPath capabilities of Selenium?
    No. 
    The Selenium WebDriver interface supports findElement(By) method. This is the only method it supports to get access to the HTML content nodes. The findElement() method returns an instance of WebElement which wraps a element-node of XPath. This is the very reason why Katalon Studio's Test Object is desined to wrap only the element-node.

    You can not get access to the text nodes in the meaning of XPath via the WebDriver API. In other words, WebDriver API does not expose native XPath capabilities.
    Upvote
    Quote
  • kazurayam
    Katalon Evangelist
    05/17/2018
    edited May 17
    A workaround. If you are able to modify the target web app, you want to change the HTML document :
    <div id="node">
      <span>Text 1</span>
      <img src="sample.jpg" />
      <span>Text 2</span>
    </div>
    
    Then the following xpath would be your easiest solution:
    id("node")/span[1]
    Sometimes changing the target web app is the best solution for testing problems. We should design our web app with testing in mind. ;)
    Upvote
    Quote
  • Drunda Nibel
    Katalon Ambassador
    05/17/2018
    Hi kazurayam,
    A workaround. If you are able to modify the target web app, you want to change the HTML document :
    [...]
    Thank you, of course, I was aware of that. ;)
    And as a consequence, I will probably include this hint in our coding guide. In my actual case it was fortunately sufficient to use just clickOffset instead of only click. :)

    Thanks + regards
    Upvote
    Quote
Sign In or Register to comment.