Transforming Test Object on the fly

I have made a demo project on GitHub:

This project was developed to propose a solution to a question raised in the Katalon Forum: Customizing XPath generation. The originator Michal.Pachucki worte:

The website i am trying to test using Katalon Studio generates id that are partialy static and partialy dynamic: " //button[@id='staticId1:dynamicId:staticId2']/span ". But there are never two elements that have the same staticId1/2 and different dynamicId. So i would like to somehow generate xpath " //button[starts-with(@id, 'staticId1') and endswith('staticId2', @id)]/span ". I know I can edit the xpath manually, but I will have hundreds of them, so I would prefer that it would be automatic. Is there a way of suppying my own xpath generator?


Here I propose a Custom Keyword named my.TestObjectTransformer . The class has a public static method named toMichalPachuckiXpath() . The method takes an instance of com.kms.katalon.core.testobject.TestObject as an argument, and will return another TestObject instance as result.

The following table shows some examples of source xpath and generated xpath pairs.

source xpath generated xpath
//button[@id='staticId1:dynamicId:staticId2']/span //button[starts-with(@id,"staticId1") and (substring(@id,string-length(@id)-string-length("staticId2")+1)="staticId2")]/span"}]}
//button[@id='abc:96423857:EVEREST']/span"}]} //button[starts-with(@id,"abc") and (substring(@id,string-length(@id)-string-length("EVEREST")+1)="EVEREST")]/span"}]}
//button[@id='abc:49238765:KILIMANJARO']/span"}]} //button[starts-with(@id,"abc") and (substring(@id,string-length(@id)-string-length("KILIMANJARO")+1)="KILIMANJARO")]/span"}]}

Applicability and extensibility of my proposal

The rule of xpath transformation implemented as the toMichalPachuckiXpath() method is designed specifically to satisfy the need of Michal.Pachucki’s case. The method is not applicable to other cases.

However the class my.TestObjectTransformer is extensible. If you read the code of my.TestObjectTransformer you would find a way to add another method which implements your way of generating TestObjects with your customized XPath.


Custom Keyword my.TestObjectTransformer

I have developed a custom keyword:

This keyword is tested by a TestCase:

This test case has the following code snipet:

def performTransformingTestObject(TestObject source) {
	assert source != null
	println "------------------------------------------------------------------------------"
	println "source is " + CustomKeywords.'my.TestObjectFormatter.format'(source)

	// transform a Test Object into another as Michal.Pachuski wanted
	// see
	TestObject target =

	assert target != null
	println "target is " + CustomKeywords.'my.TestObjectFormatter.format'(target)


When you run the test case, you would see the following output in the Console:

source is {"objectId":"Object Repository/Page_15801_testbed/button_staticId2","selectorMethod":"XPATH","activeXpaths":[{"name":"xpath:attributes","value":"//button[@id='staticId1:dynamicId:staticId2']/span"}]}

target is {"objectId":"Object Repository/Page_15801_testbed/button_staticId2*","selectorMethod":"XPATH","activeXpaths":[{"name":"xpath:attributes","value":"//button[starts-with(@id,"staticId1") and (substring(@id,string-length(@id)-string-length("staticId2")+1)="staticId2")]/span"}]}

You can see that a customized XPath expression is generated by the keyword. The customized xpath would satisfy the need of Michal.Pachucki.

TestCase TC1

I wrote a Test Case TestCases/TC1:

WebUI.verifyElementPresent(findTestObject('Object Repository/Page_15801_testbed/div_main'), 10)

TestObject staticId2 = findTestObject('Page_15801_testbed/button_staticId2')
WebUI.verifyElementPresent(staticId2, 3)

TestObject staticId2aster = CustomKeywords.'my.TestObjectTransformer.toMichalPachuckiXpath'(

WebUI.verifyElementPresent(staticId2aster, 3)

I expect the TC1 should pass as follows:

2018-12-25 11:20:07.321 INFO c.k.katalon.core.main.TestCaseExecutor   -START Test Cases/TC1
2018-12-25 11:20:08.954 INFO c.k.k.core.webui.driver.DriverFactory    -Starting 'Firefox' driver
2018-12-25 11:20:09.088 INFO c.k.k.core.webui.driver.DriverFactory    -Action delay is set to 0 seconds
2018-12-25 11:20:14.362 INFO c.k.k.core.webui.driver.DriverFactory    -sessionId = 279b3f12-c732-4f7b-8565-b4bc4cb9acea
2018-12-25 11:20:14.389 INFO c.k.k.core.webui.driver.DriverFactory    -browser = Firefox 63.0
2018-12-25 11:20:14.390 INFO c.k.k.core.webui.driver.DriverFactory    -platform = Windows 7
2018-12-25 11:20:14.393 INFO c.k.k.core.webui.driver.DriverFactory    -seleniumVersion = 3.7.1

2018-12-25 11:20:15.011 INFO k.k.c.m.CustomKeywordDelegatingMetaClass -my.TestObjectTransformer.toMichalPachuckiXpath is PASSED

2018-12-25 11:20:16.502 INFO c.k.katalon.core.main.TestCaseExecutor   -END Test Cases/TC1

But in fact, the TC1 fails due to a problem in Katalon Studio : “Cannot find elements when xpath expression is null”](Cannot find elements when XPath expression is null). It fails in Katalon Studio 5.7~5.10.0 with the following error messages in the Console.

2018-12-25 11:34:37.877 ERROR c.k.katalon.core.main.TestCaseExecutor   - �[❌ Test Cases/TC1 FAILED.
com.kms.katalon.core.exception.StepFailedException: Unable to verify object 'Object Repository/Page_15801_testbed/button_staticId2*' is present (Root cause: java.lang.IllegalArgumentException: Cannot find elements when the XPath expression is null.)
	at com.kms.katalon.core.keyword.internal.KeywordMain.stepFailed(KeywordMain.groovy:36)
	at com.kms.katalon.core.webui.keyword.internal.WebUIKeywordMain.stepFailed(WebUIKeywordMain.groovy:65)
	at com.kms.katalon.core.webui.keyword.internal.WebUIKeywordMain.runKeyword(WebUIKeywordMain.groovy:27)
	at com.kms.katalon.core.webui.keyword.builtin.VerifyElementPresentKeyword.verifyElementPresent(VerifyElementPresentKeyword.groovy:92)


I think I could propose a solution to the problem raised, but due to a problem in Katalon Studio, I can not prove my solution effective. I will wait for Katalon Team to fix the problem.