Naveen
Katalon Apprentice
04/05/2017

How to handle dynamic xpath ?

Hi Team,
I am facing difficulties in handling dynamic xpath. I have a table, the contents of table changes depending on the previous steps.

Ex : 
//[@id="aaaa.CountryRolePhasesView.readyByDate_editor.0"]
//
[@id="aaaa.CountryRolePhasesView.readyByDate_editor.1"]
//*[@id="aaaa.CountryRolePhasesView.readyByDate_editor.2"]

In above example, number changes. How can we handle this ? can we pass a variable to test objects to achieve this ? If yes, please provide the procedure for the same.

Also, how to get the number of elements matching a xpath, like "findelements" in selenium. Eg : List<WebElement> tableList = driver.findElements(By.xpath()));

Please let me know if any other information is required.
Appreciate your help!

Thanks.

Upvote
Quote

Comments

  • Vinh Nguyen
    Katalon Moderator
    04/05/2017
    Hi there,

    Suppose you have a variable called 'changedValue' and you want to pass it into 'xpath' of existing test object 'cell'Table' (the dynamic element ):
    changedValue = 0
    TestObject to = findTestObject('cellTable')

    'Change xpath property to new value'
    to.findProperty('xpath').setValue('//*[@id=\u201Daaaa.CountryRolePhasesView.readyByDate_editor\u2033].' + changedValue)
    You can refer to other functions of TestObject class in this page. After the test object's value has been changed, you can then use it freely in other step, e.g: WebUI.click(to)

    2. To get the number of elements matching a xpath, like “findelements” in selenium. Eg : List<WebElement> tableList = driver.findElements(By.xpath()));

    => You should utilize DriverFactory class and its functions

    Example code:
    WebUI.openBrowser('http://demoaut.katalon.com)

    WebDriver driver = DriverFactory.getWebDriver()
    List&lt;WebElement&gt;tableList = driver.findElements(By.xpath()));

    WebUI.closeBrowser()
    We have a similar concern regarding to dynamic element, which you can find here
    Upvote
    Quote
  • Naveen
    Katalon Apprentice
    04/07/2017
    Hi Nguyen,
    Appreciate your help here. I have tried above and still facing issue. Kindly find below as the code i have tried.

    1. Code
    WebDriver driver = DriverFactory.getWebDriver()
    List tablelist1 = driver.findElements(By.xpath('//*contains(@id,"aaaa.CountryRolePhasesView.CountryRoleAndPhase_Table:\")]'))

    Result : Test Cases/TC01_ProjectCreation FAILED because (of) Variable 'By' is not defined for test case.

    2. Code
    TestObject to = findTestObject('Unilever/Page_CreateProject/TextField/input_aaaa.CountryRolePhasesVi')
    to.findProperty('xpath').setValue('id(\"aaaa.CountryRolePhasesView.tacCode_editor4.' + RowId + '\")')
    WebUI.click(to)

    Original xpath, as in captured by Katalon : id("aaaa.CountryRolePhasesView.tacCode_editor4.0")

    Results : Success

    3. Is it possible to use existing parameters of test objects and modify the same. Let say in above case, xpath is id("aaaa.CountryRolePhasesView.tacCode_editor4.0"), now i wanted to change this to -
    id("aaaa.CountryRolePhasesView.tacCode_editor4.+rowid+"). So i can have single custom function, which can be used for different fields of the table, instead of hard coding the partial xpath.

    Kindly Help and appreciate your support :)-
    Upvote
    Quote
  • Jason Roberts
    Katalon Apprentice
    09/22/2017

    Hi,

    I have a similar issue in attempting to locate a dynamic xpath id. Here is an example:

    //*[@7dfb13b9eab5"]/div[2]/div[5]/table/tbody/tr/td

    where the above id is dynamic. The element is a calculated numeric value.

    Any ideas?

    Thanks,

    Jason Roberts

    Upvote
    Quote
  • Paul Johnson
    Katalon Apprentice
    11/13/2017

    Naveen By should be by ( not capitlised) Jason, the way i got round this was to look in the script view and define the variable, and then referecne the variable in the xpath statement. the define line appears in the manual view as a binary value. Hope this helps

    Upvote
    Quote
  • Dave Evers
    Katalon Ambassador
    04/11/2018
    edited April 11

    Naveen By should be by ( not capitlised) Jason, the way i got round this was to look in the script view and define the variable, and then referecne the variable in the xpath statement. the define line appears in the manual view as a binary value. Hope this helps

    Hi Paul or Vinh,
       Could you please include an example of how you were able to change a test object's xpath value & then use WebUI.click(to)? I've tried the code as suggested by Vinh, but I am not having any luck with changing the objects xpath values.

    My code:
    changedValue = 100
    TestObject to = findTestObject('Dynamic.Objs/Page_AdvisorWeb Login/img_login')
    to.findProperty('xpath').setValue("//*[@id = 'login']" + changedValue)
    WebUI.click(to)
    xpath result:


    Thanks,
    Dave
    Upvote
    Quote
  • Mate Mrse
    Katalon Expert
    04/12/2018
    You could try this:
    changedValue = 100
    TestObject to = WebUI.modifyObjectProperty(findTestObject('Dynamic.Objs/Page_AdvisorWeb Login/img_login'), 'xpath', 'equals', '//*[@id = 'login']"+changedValue', true)
    WebUI.click(to)
    Upvote
    Quote
  • Marek Melocik
    Katalon Expert
    04/12/2018
    Hi Dave,

    How is your TestObject defined? It is case sensitive, so if your property name is XPATH and you use lower-case version xpath in the test code, it won't work.

    Also, you may use XPATH selector type instead of Basic properties:


    In this case, the code must be slightly different. See a simple example:

    TestObject to = findTestObject("Misc/test")

    // get Map with <SelectorMethod, String> pair
    Map allSelectors = to.getSelectorCollection()

    // println original value of XPATH selector
    println allSelectors.get(SelectorMethod.XPATH)

    // update the value
    to.setSelectorValue(SelectorMethod.XPATH, "//some/other/path")

    // println new value of XPATH selector
    allSelectors = to.getSelectorCollection()
    println allSelectors.get(SelectorMethod.XPATH)

    // console output

    04-12-2018 09:17:25 AM - [START] - Start action : Statement - println(allSelectors.get(XPATH))
    //some/path
    04-12-2018 09:17:25 AM - [END] - End action : Statement - println(allSelectors.get(XPATH))
    04-12-2018 09:17:25 AM - [START] - Start action : Statement - to.setSelectorValue(XPATH, "//some/other/path")
    04-12-2018 09:17:25 AM - [END] - End action : Statement - to.setSelectorValue(XPATH, "//some/other/path")
    04-12-2018 09:17:25 AM - [START] - Start action : Statement - allSelectors = to.getSelectorCollection()
    04-12-2018 09:17:25 AM - [END] - End action : Statement - allSelectors = to.getSelectorCollection()
    04-12-2018 09:17:25 AM - [START] - Start action : Statement - println(allSelectors.get(XPATH))
    //some/other/path
    04-12-2018 09:17:25 AM - [END] - End action : Statement - println(allSelectors.get(XPATH))




    Upvote
    Quote
  • Marek Melocik
    Katalon Expert
    04/12/2018
    edited April 12
    delete please
    Upvote
    Quote
  • Dave Evers
    Katalon Ambassador
    04/12/2018
    Hi Marek,
    Thanks for your suggestions ;-)
    I am using the XPath selector type = //*[@id="login"]

    But I am not able to click on the 'login_btn' can you see where I am going wrong?

    TestObject login_btn = findTestObject("Chk.Xpath/Page_AdvisorWeb Login/img_login")
    login_btn.setSelectorValue(SelectorMethod.XPATH, '//*[@id="login001"]')
    Map allSelectors = login_btn.getSelectorCollection()
    println ('NewXpath: ' + allSelectors.get(SelectorMethod.XPATH))
    //My result= NewXpath: //*[@id="login001"]
    WebUI.click(login_btn)
    Results:
    04-12-2018 10:51:45 AM - [START]  - Start action : click
    04-12-2018 10:51:45 AM - [INFO]   - Checking object
    04-12-2018 10:51:45 AM - [INFO]   - Checking timeout
    04-12-2018 10:51:45 AM - [INFO] - Finding web element with id: 
    'Object Repository/Chk.Xpath/Page_AdvisorWeb Login/img_login' located by 'By.xpath: //*[@id="login001"]' in '30' second(s)
    04-12-2018 10:52:17 AM - [FAILED] - Unable to click on object 'Object Repository/Chk.Xpath/Page_AdvisorWeb Login/img_login' 
    (Root cause: com.kms.katalon.core.webui.exception.WebElementNotFoundException: 
    Web element with id: 'Object Repository/Chk.Xpath/Page_AdvisorWeb Login/img_login' 
    located by 'By.xpath: //*[@id="login001"]' not found)


    Upvote
    Quote
  • Marek Melocik
    Katalon Expert
    04/13/2018
    Hi Dave,

    looks like you entered incorrect XPath as there's WebElementNotFoundException - Katalon is unable to find specified object on a page.

    Make sure you are using correct path - doublecheck if you are on the correct page when you call WebUI.click() and/or open Dev console in browser and check if button ID you use is correct.
    Upvote
    Quote
  • anja.brand
    Katalon Apprentice
    04/13/2018

    Hi,

    I also want some help on this topic. I am just starting using Katalon.
    The objects on the webpage are build dynamically from scratch. 

    Using basic objects properties it can’t perform the click action:

    It's not able to find the object. The message:
    Test Cases/PIM/Menu catalogus FAILED because (of) Unable to click on object 'Object Repository/PIM/2.Menu_onderdelen/Menu_Catalogus' (Root cause: org.openqa.selenium.WebDriverException: unknown error: Element <div data-uid="1" class="item">...</div> is not clickable at point (214, 12). Other element would receive the click: <div class="src-loading-overlay"></div>

    So I tried xpath
    Using Spy Web I can find the right xpath e.g. //*[@menu"]/div[1]
    I checked that with the verify and highlight option in the method xpath.
    No scrolling is needed.

    httpsforumkataloncomuploadseditorz2nwwsc88doq6hpng

    <div class="src-ui-menubar" id="main-menu" style="left: 176px; top: 1px; width: 1157px; height: 24px;">
    <div class="item" data-uid="1"><span>Catalogus</span></div>
    <div class="item" data-uid="2"><span>Item</span></div>
    <div class="item" data-uid="3"><span>Gefilterde&nbsp;items</span></div>
    <div class="item" data-uid="4"><span>DAM</span></div>
    <div class="item" data-uid="5"><span>Beeld</span></div>
    <div class="item" data-uid="6"><span>Extra</span></div>
    <div class="item" data-uid="7"><span>Help</span></div>
    <div class="clear"></div></div>

    This testcase is also not able to perform the click action on the Testobject.
    The message:

    Test Cases/PIM/Menu catalogus - Click FAILED because (of) Unable to click on object 'Object Repository/PIM/2.Menu_onderdelen/Menu_Catalogus' (Root cause: com.kms.katalon.core.webui.exception.WebElementNotFoundException: Web element with id: 'Object Repository/PIM/2.Menu_onderdelen/Menu_Catalogus' located by 'By.xpath: ' not found)

     

    Scriptmode:

    WebUI.openBrowser('')
    WebUI.maximizeWindow()
    WebUI.navigateToUrl('vms01-t/')
    WebUI.waitForPageLoad(GlobalVariable.TimeOut)
    WebUI.sendKeys(findTestObject('PIM/1.Page_Login - SRC-PIM Regressie/username'), 'AnjaENG')
    WebUI.sendKeys(findTestObject('PIM/1.Page_Login - SRC-PIM Regressie/password'), '???????!')
    WebUI.click(findTestObject('PIM/1.Page_Login - SRC-PIM Regressie/btn_login'))
    WebUI.waitForElementVisible(findTestObject('PIM/2.Menu_onderdelen/SRC_Logo'), 0)
    WebUI.click(findTestObject('PIM/2.Menu_onderdelen/Page_SRC-PIM Regressie (1)/span_Catalogus'))

    I tried to work with the Dynamic object but I am struggling with the scriptmode. With the examples mentioned it is not clear what to do.

    Can somebody please tell me what the script has to be?


    Upvote
    Quote
  • Arnel
    Katalon Ambassador
    05/23/2018
    Hi Guys,

    Just wanted to help.

    You can try this out:

    In object repository when you create a new test object go to Basic and then click add. In the Name field type xpath. Match Condition should be equals and then the Value field is your xpath. And then
    you can put a variable on that xpath. Follow the pattern below.

    //*[@id="your id"]/div[2]/table/tbody/tr[${yourVariable}]

    Or if Id number changes you can do this

    //*[@id="id{$IdNumber}"] if it doesnt work try with single quote '' something like this
    //*[@id="id'{$IdNumber}'"]

    For number of elements we can do hard coded approach something like this:

    java.util.List<WebElement> yourVar = WebUiCommonHelper.findWebElements(findTestObject('your test object'), 30)

    I suggest to use the xpath //*[contains(@id, 'your element')]

    It will be an array so when calling it or you want to do some commands you can do this:

    yourVar[index].click() since your class is WebElement so it can inherit all the WebElement commands.

    Hope that helps :)
    Upvote
    Quote
  • Drunda Nibel
    Katalon Ambassador
    06/06/2018
    Hi,

    this syntax ...
    //*[@id="id{$IdNumber}"] if it doesnt work try with single quote '' something like this
    //*[@id="id'{$IdNumber}'"]
    ... (with and without single and double quotes ) in the object repository's properties seems no longer to work. Does anyone know, how it has to be now?
    Upvote
    Quote
  • Drunda Nibel
    Katalon Ambassador
    06/07/2018
    edited June 7
    My problem has been solved, thanks to kazurayam, once again! :) Look at this post for details.
    Upvote
    Quote
  • Joko T. Susilo Widodo
    Katalon Apprentice
    07/12/2018
    Hi, all

    i have problem in my dynamic xpath 
    my script :
    rowLevel = 1
    //Change Xpath
    new_row = WebUI.modifyObjectProperty(findTestObject('Dashboard/Installment/DynamicObject/detail'),'xpath', 'equals', '//span[@id = "appAmount'+ rowLevel + '" and (text() = "Detail" or . = "Detail")]', true)
    //Click on new_btn
    WebUI.click(new_row)
    and Result :
    Unable to click on object 'Object Repository/Dashboard/Installment/DynamicObject/detail' 
    (Root cause: com.kms.katalon.core.webui.exception.WebElementNotFoundException: 
    Web element with id: 'Object Repository/Dashboard/Installment/DynamicObject/detail' 
    located by 'By.xpath: //span[@id = "appAmount1" and (text() = "Detail" or . = "Detail")][count(. | //span[@id = 'appAmount' and (text() = 'Detail' or . = 'Detail')]) = count(//span[@id = 'appAmount' and (text() = 'Detail' or . = 'Detail')])]' not found)

    Actually i have correct xpath :
    By.xpath: //span[@id = "appAmount1" and (text() = "Detail" or . = "Detail")] 

    any idea for this case?
    Upvote
    Quote
  • Marek Melocik
    Katalon Expert
    07/12/2018
    Hello,

    why you don't create brand new test object?

    new_row = new TestObject().addProperty('xpath', ConditionType.EQUALS, '//span[@id = "appAmount'+ rowLevel + '" and (text() = "Detail" or . = "Detail")]', true)
    Upvote
    Quote
  • Joko T. Susilo Widodo
    Katalon Apprentice
    07/12/2018
    hi Marek,

    because i will use it to handle table data,
    the row of table have each id 
    ex :
    span[@id = "appAmount1"
    span[@id = "appAmount2"
    etc

    Upvote
    Quote
  • Drunda Nibel
    Katalon Ambassador
    07/12/2018
    edited July 12
    I had a similar challenge and managed by creating only a test object for the surrounding HTML table object and from there I went directly through the table structure using the Selenium XPath method. Here is my already quite generically working custom keyword, which you can probably easily customize for your purposes:

    package com.mycompany.global

    import usual.stuff

    import org.openqa.selenium.WebElement as WebElement
    import org.openqa.selenium.By
    import java.util.regex.Matcher

    public class TestObjectUtils {

        @Keyword
        static def readHtmlTable(TestObject table, Map colsTestingCriteria) {
    // colsTestingCriteria map consists of the identifiers for the expected columns as map keys,
    // each of which is assigned another nested map with two regular expressions:
    // The first to check the table header for the expected column headings and the second
    // to check the table body cells for the expected content patterns.
            def boolean isTableStructure = false
            def WebElement tableElm = WebUiBuiltInKeywords.findWebElements(table, 30)[0]
            def WebElement tableHeadElm = tableElm.findElement(By.xpath('.//thead[1]'))
            if (tableHeadElm == null) {
                tableHeadElm = tableElm.findElement(By.xpath('.//tr[1]'))
            }
            else isTableStructure = true
            def Map<String, Integer> htmlColNames = [:]
            def List<WebElement> tableHeadRowElms = tableHeadElm.findElements(By.xpath('.//*[name()="th" or name()="td"]'))

            tableHeadRowElms.eachWithIndex { colElm, colElmId ->
                for (def colTestingObj in colsTestingCriteria) {
                    def String colName = colTestingObj.key
                    def String colElmName = colElm.getText()
                    def String regExColCriterion = colsTestingCriteria[colName]['regExColCriterion']
                    if (regExColCriterion != '') {
                        def Matcher matcher = colElmName =~ regExColCriterion
                        if (matcher.size() > 0) {
                            //println '*** colName * colElmId (Kopf): ' + colName + ' * ' + colElmId
                            htmlColNames << [(colName) : colElmId]
                            break
                        }
                    }
                }
            }

            def List<WebElement> tableBodyRowElms = []
            if (isTableStructure) tableBodyRowElms = tableElm.findElements(By.xpath('.//tbody[1]//tr'))
            else tableBodyRowElms = tableElm.findElements(By.xpath('.//tr[position()!=1]'))
            def ArrayList<Map> tableContents = []
            tableBodyRowElms.eachWithIndex() { rowElm, rowElmId ->
                def List<WebElement> tableBodyColElms = rowElm.findElements(By.xpath('.//*[name()="th" or name()="td"]'))
                def Map rowContents = [:]
                def Map colsTestingCriteriaRow = deepCopy(colsTestingCriteria)

                colsTestingCriteriaRow.each { colName, colTestingObj ->
                    def String regExColCriterion = colTestingObj['regExColCriterion']
                    if (regExColCriterion != '') {
                        def String cellValue = tableBodyColElms[htmlColNames[colName]].getText()
                        def String regExCellValue = colTestingObj['regExCellValue']
                        if (regExCellValue != '') {
                            def Matcher matcher = cellValue =~ regExCellValue
                            if (matcher.size() > 0) {
                                cellValue = matcher[0]
                            }
                        }
                        colTestingObj['cellValue'] = cellValue
                    }
                    rowContents << [(colName) : colTestingObj]
                }
                tableContents << rowContents
            }
            //        println "### Contents of actual HTML table are: ${tableContents}"
            return tableContents
        }

        @Keyword
        // source: https://stackoverflow.com/questions/13155127/deep-copy-map-in-groovy
        def deepCopy(orig) {
            def bos = new ByteArrayOutputStream()
            def oos = new ObjectOutputStream(bos)
            oos.writeObject(orig); oos.flush()
            def bin = new ByteArrayInputStream(bos.toByteArray())
            def ois = new ObjectInputStream(bin)
            return ois.readObject()
        }

    }

    Have fun with that, I hope it helps.



    Upvote
    Quote
  • Drunda Nibel
    Katalon Ambassador
    07/12/2018
    edited July 12
    I'm sorry, I didn't read exactly enough. You want to address the individual web elements directly via identifiers and not at all via the generic structure of the HTML table. Then, of course, my approach was shot at sparrows with cannons, as we say in Germany. :)

    In your case it should be enough to use the Selenium Xpath method directly, as you have probably been able to do by now. So just go ahead:
        def Integer rowLevel = 1
        def String xPath = '//span[@id = "appAmount'+ rowLevel + '" and (text() = "Detail" or . = "Detail")]'
        def WebElement tableCellElm = findElement(By.xpath(xPath))
    Edit:
    I still missed something: You will first need to create a WebDriver object to which you can then apply the findElement method. Try this and report back, please:
    import org.openqa.selenium.WebElement as WebElement
    import org.openqa.selenium.By
    import org.openqa.selenium.WebDriver
    import com.kms.katalon.core.webui.driver.DriverFactory

    def Integer rowLevel = 1
    def String xPath = '//span[@id = "appAmount'+ rowLevel + '" and (text() = "Detail" or . = "Detail")]'
    def WebDriver driver = DriverFactory.getWebDriver()
    def WebElement tableCellElm = driver.findElement(By.xpath(xPath))

    Does that help you?

    Upvote
    Quote
  • Jervin
    Katalon Apprentice
    08/30/2018
    Hi Guys,

    Hope this helps.

    Try to use the condition Contains or Starts with condition in the properties of the object if the xpath or ID of the object contains at least a dynamic word.


    Upvote
    Quote
Sign In or Register to comment.