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.

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 :)-

Hi,

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

//*[@id=“dc7047d5-f0a8-4333-a35c-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

1 Like

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 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<WebElement>tableList = driver.findElements(By.xpath()));

WebUI.closeBrowser()

We have a similar concern regarding to dynamic element, which you can find here

2 Likes

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

Paul Johnson said:

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 = 100TestObject to = findTestObject('Dynamic.Objs/Page_AdvisorWeb Login/img_login')to.findProperty('xpath').setValue("//*[@id = 'login']" + changedValue)WebUI.click(to)

xpath result:

Thanks,
Dave

xpathResult.png

1 Like

You could try this:

changedValue = 100TestObject to = WebUI.modifyObjectProperty(findTestObject('Dynamic.Objs/Page_AdvisorWeb Login/img_login'), 'xpath', 'equals', '//*[@id = 'login']"+changedValue', true)
WebUI.click(to)

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))

selectorMethod.jpg

delete please

Hi Marek,

Thanks for your suggestions :wink:

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)

xpath.png

1 Like

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.

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

is not
clickable at point (214, 12). Other element would receive the click:

So I tried xpath

Using Spy Web I can find the right xpath
e.g. //*[@id=“main-menu”]/div[1]

I checked that with the verify and highlight option in the method xpath.

No scrolling is needed.

Catalogus
Item
Gefilterde items
DAM
Beeld
Extra
Help

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?

1 Like

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 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 :slight_smile:

2 Likes

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?

My problem has been solved, thanks to kazurayam, once again! :slight_smile: Look at this post for details.

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?

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)

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

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.

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. :slight_smile:

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?