Unable to find a web table


#1

I get an error when my script is unable to find a web table. When the table is present, it finds it and gives me the right results. Please see the following code and suggest what I need to change:

This is what I have in the Keywords:

class FavoritesKeywords {

	@Keyword
	def locateTable(){

		// creating a variable 'driver' and assigning its value
		WebDriver driver = DriverFactory.getWebDriver()
		'To locate table'
		WebElement Table = driver.findElement(By.xpath("//table[@class='cart']"))

	}

This is the test case:

int numcolumns
String columns

WebUI.callTestCase(findTestCase('Smoke Testing/Login'), [:], FailureHandling.STOP_ON_FAILURE)

WebUI.click(findTestObject('Master Object Repository/B2B - My_CES Dropdown/My CES - B2B'))

WebUI.click(findTestObject('Master Object Repository/B2B - My_CES Dropdown/Favorites - B2B'))

// since the table needs to be located first, it should check for its existence
Boolean favtable = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateTable'()

numcolumns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.allHeadersFavorites'()

columns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.columnNames'()

	
	if (favtable == true){
	 
		println ('No. of Columns: ' + numcolumns)
		
		println ('Names of the columns are: ' + columns)
				
	}
	else
		println('Favorites is empty!')
		
			
WebUI.closeBrowser()

When I run it, I get:

no such element: Unable to locate element: {“method”:“xpath”,“selector”:"//table[@class=‘cart’]"}

Thanks


#2

I believe with this line of code:

Boolean favtable = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateTable'()

you are simply trying to validate that the table exists, yet your keyword does not return a boolean value (it returns nothing if the table is found, and it will throw an error if it is not found):

@Keyword
def locateTable(){

	// creating a variable 'driver' and assigning its value
	WebDriver driver = DriverFactory.getWebDriver()
	'To locate table'
	WebElement Table = driver.findElement(By.xpath("//table[@class='cart']"))

}

Using the standard way (using webdriver) to verify the presence of any given element, your keyword should instead look something like:

@Keyword
public boolean locateTable() {
    WebDriver driver = DriverFactory.getWebDriver()
    return driver.findElements(By.xpath("//table[@class='cart']")).size() > 0
}

and your test code like:

def exists = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateTable'()

Bonus Round

Instead of having one keyword that looks specifically for that table, why not make it generic, and check for the presence of ANY element you want:

@Keyword
public boolean locateElement(String xpath) {
    WebDriver driver = DriverFactory.getWebDriver()
    return driver.findElements(By.xpath(xpath)).size() > 0
}

This way you can reuse that keyword in many many places.

Hope this helps :slight_smile:


#3

Hi Brandon

I tried your suggestion but Katalon started complaining about the class. The following are the changes I made:

class FavoritesKeywords {

	@Keyword
	public boolean locateTable(){

		// creating a variable 'driver' and assigning its value
		WebDriver driver = DriverFactory.getWebDriver()
		
		'To locate table and set the keyword to return a boolean value'
		return driver.findElements(By.xpath("//table[@class='cart']")).size() > 0
			
		}
		

	}

Test case:

def exists = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateTable'()

numcolumns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.allHeadersFavorites'()

columns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.columnNames'()

	
	if (exists == true){
	 
		println ('No. of Columns: ' + numcolumns)
		
		println ('Names of the columns are: ' + columns)
				
	}
	else
		println ('Favorites is Empty')
		
			
WebUI.closeBrowser()

It gives this error for the class FavoritesKeywords “Multiple markers at this line
- The type FavoritesKeywords is already defined”

Any idea why this is happening?


#4

Hmmm you may need to just use Groovy syntax instead (my example uses Java):

@Keyword
def boolean locateTable() {
    WebDriver driver = DriverFactory.getWebDriver()
    return driver.findElements(By.xpath("//table[@class='cart']")).size() > 0
}

If an error is still thrown, please post the entire error.


#5

I changed it to use def instead of public (assuming that was the only change) and still got the same error. Here is the full error:

Multiple markers at this line
- The type FavoritesKeywords is already defined
- Groovy:Invalid duplicate class definition of class com.ces.cesonline.favorites.FavoritesKeywords : The source C:\Users\mj\QA\Keywords\com\ces\cesonline\favorites\FavoritesKeywords.groovy contains at least two
definitions of the class com.ces.cesonline.favorites.FavoritesKeywords.


#6

Can you please copy/paste the entire FavoritesKeywords.groovy class?


#7

I think Katalon was acting up and did not recognize the changes quickly. That error went away but the script is failing again with the same error.

Here is the class:

class FavoritesKeywords {

	@Keyword
	def boolean locateTable(){

		// creating a variable 'driver' and assigning its value
		WebDriver driver = DriverFactory.getWebDriver()
		
		//To locate the table and set the keyword to return a boolean value
		return driver.findElements(By.xpath("//table[@class='cart']")).size() > 0
			
		}
		

Test case:

def exists = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateTable'()

numcolumns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.allHeadersFavorites'()

columns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.columnNames'()

	
	if (exists == true){
	 
		println ('No. of Columns: ' + numcolumns)
		
		println ('Names of the columns are: ' + columns)
				
	}
	else
		println ('Favorites is Empty')

#8

That’s not the entire class. The error is stating that you have defined FavoritesKeywords within itself:

The source C:\Users\mj\QA\Keywords\com\ces\cesonline\favorites\FavoritesKeywords.groovy contains at least two
definitions of the class com.ces.cesonline.favorites.FavoritesKeywords.

#9

Since my class had other keywords also, I did not want to include those but here is the full class:

class FavoritesKeywords {

	@Keyword
	def boolean locateTable(){

		// creating a variable 'driver' and assigning its value
		WebDriver driver = DriverFactory.getWebDriver()
		
		//To locate the table and set the keyword to return a boolean value
		return driver.findElements(By.xpath("//table[@class='cart']")).size() > 0
			
		}
		

	// Printing all the headers of the Favorites table assuming first row as a header
	@Keyword
	def allHeadersFavorites(){

		//String columnNames = []

		// creating a variable 'driver' and assigning its value
		WebDriver driver = DriverFactory.getWebDriver()

		// get the first row in the headers from the table and store this in a variable ‘th’ of type web element.
		WebElement th = driver.findElement(By.xpath("//table[@class='cart']//thead"))

		//Get all the headers with tag name ‘th’ and store all the elements in a list of web elements.
		//Now all the elements with tag ‘th’ are stored in ‘headers’ list.
		List<WebElement> headers = th.findElements(By.tagName('th'))

		//storing the header size so that it can be used in the test for printing
		return headers.size()

	}


	@Keyword
	def columnNames (){


		WebDriver driver = DriverFactory.getWebDriver()

		WebElement th = driver.findElement(By.xpath("//table[@class='cart']//thead"))

		List<WebElement> headers = th.findElements(By.tagName('th'))

		//traversing through the column headers, index 0 will be the first column name and so on

		def retvalue = '' //using def keyword to define a variable for storing values

		//iterating through the list headers
		for (WebElement e : headers){

			retvalue += e.getText() + ';'

		}
		return retvalue
	}
}

#10

Do you have multiple FavoritesKeywords classes defined somewhere? Also, can you please open the Console tab and copy/paste the error from there (the entire error). It’s tough to get to the root of things if I can’t see the actual error log.


#11

Found out what the problem was. I had to comment out the following since it was trying to find the table

numcolumns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.allHeadersFavorites'()

columns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.columnNames'()

I commented out the above so that it could only run the following and I got my expected result of “Favorites is empty”

def exists = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateTable'()

But I did not understand why we did certain things. For example, does the line

return driver.findElements(By.xpath("//table[@class='cart']")).size() > 0

mean that the size of the table is greater than 0? Or in other words, does the table exist?


#12

Close. It means that when we look in the HTML for a table located by “//table[@class=‘cart’]”, do we get one (or more) result back? This is the same as asking the question “does the element exist?”


#13

Got it. Since I wanted to cover the scenario of the existence of the table also, I changed my keyword to the following:

class FavoritesKeywords {

	@Keyword
	def boolean locateTable(){

		// creating a variable 'driver' and assigning its value
		WebDriver driver = DriverFactory.getWebDriver()

		//To locate the table and set the keyword to return a boolean value
		return driver.findElements(By.xpath("//table[@class='cart']")).size() > 0
		return driver.findElements(By.xpath("//table[@class='cart']//thead")).size() > 0

	}
}

Here is the error I got:

2019-08-13 13:54:47.970 ERROR k.k.c.m.CustomKeywordDelegatingMetaClass - ❌ Unable to locate element: //table[@class='cart']//thead
For documentation on this error, please visit: https://www.seleniumhq.org/exceptions/no_such_element.html
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:25:53'
System info: host: 'CES-FS7JG72', ip: '10.120.70.125', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_181'
Driver info: com.kms.katalon.core.webui.driver.firefox.CGeckoDriver
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 60.0.1, javascriptEnabled: true, moz:accessibilityChecks: false, moz:geckodriverVersion: 0.23.0, moz:headless: false, moz:processID: 25012, moz:profile: C:\Users\M\Ap..., moz:useNonSpecCompliantPointerOrigin: false, moz:webdriverClick: true, pageLoadStrategy: normal, platform: XP, platformName: XP, platformVersion: 10.0, proxy: Proxy(direct), rotatable: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}}
Session ID: 0174af81-bdfb-4a8d-8637-68c80e6636d2
*** Element info: {Using=xpath, value=//table[@class='cart']//thead}
2019-08-13 13:54:47.975 ERROR c.k.katalon.core.main.TestCaseExecutor   - ❌ Test Cases/mj_regression/Favorites_all_headers FAILED.
Reason:
org.openqa.selenium.NoSuchElementException: Unable to locate element: //table[@class='cart']//thead
For documentation on this error, please visit: https://www.seleniumhq.org/exceptions/no_such_element.html
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:25:53'
System info: host: 'CES-FS7JG72', ip: '10.120.70.125', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_181'
Driver info: com.kms.katalon.core.webui.driver.firefox.CGeckoDriver
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 60.0.1, javascriptEnabled: true, moz:accessibilityChecks: false, moz:geckodriverVersion: 0.23.0, moz:headless: false, moz:processID: 25012, moz:profile: C:\Users\M\Ap..., moz:useNonSpecCompliantPointerOrigin: false, moz:webdriverClick: true, pageLoadStrategy: normal, platform: XP, platformName: XP, platformVersion: 10.0, proxy: Proxy(direct), rotatable: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}}
Session ID: 0174af81-bdfb-4a8d-8637-68c80e6636d2
*** Element info: {Using=xpath, value=//table[@class='cart']//thead}

It is trying to find the headers of the table but is unable to do because the table is not there. What can I change to cover this scenario also?


#14

A method can only return one object, so you cannot do two return statements consecutively like that. These are all fundamental programming skills though (nothing to do with Katalon), so I would recommend that you run through a beginner’s tutorial of either the Groovy language, or the Java language (they are basically the same).

To answer your question though, I will ask you another question:

Can a table header exist if the table it is part of doesn’t exist?

Hopefully your answer is “obviously not”. So, in other words, there’s no need to check for both in the same method.

This is also why I provided you with a bonus suggestion in my original answer. Now you are talking about checking the existence of multiple different elements, so why not create a generic keyword that takes the xpath as an argument, and then you can pass in whatever xpath you want to locate?


#15

I tried the bonus suggestion but got the error for the following keyword and test case

class FavoritesKeywords {

	@Keyword
	def boolean locateElement(String xpath) {
		WebDriver driver = DriverFactory.getWebDriver()
		return driver.findElements(By.xpath("//table[@class='cart']", "//table[@class='cart']//thead")).size() > 0
	}
}
def locateElement = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateElement'()

	
	if (locateElement == true){
	 
		println ('No. of Columns: ' + numcolumns)
		
		println ('Names of the columns are: ' + columns)
				
	}
	else
		println ('Favorites is empty!')

This is the error I got:

org.codehaus.groovy.runtime.InvokerInvocationException: groovy.lang.MissingMethodException: No signature of method: static org.openqa.selenium.By.xpath() is applicable for argument types: (java.lang.String, java.lang.String) values: [//table[@class='cart'], //table[@class='cart']//thead]
Possible solutions: xpath(java.lang.String), wait(), any(), name(java.lang.String), with(groovy.lang.Closure), each(groovy.lang.Closure)

Any ideas?


#16

Sorry, you are missing the point. The bonus suggestion is to be used by passing in your desired xpath from your script, then the keyword should operate on whatever xpath that you give it. This way, in your script, you can use the method multiple times, using ANY xpath you want:

Keyword:

def boolean locateElement(String xpath) {
    WebDriver driver = DriverFactory.getWebDriver()
    return driver.findElements(By.xpath(xpath)).size() > 0
}

Script:

def tablePresent = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateElement'("//table[@class='cart']")
def tableHeaderPresent = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateElement'("//table[@class='cart']//thead")

if(tablePresent && tableHeaderPresent) {

}

#17

This is what I have:

@Keyword
	def boolean locateElement(String xpath) {
		WebDriver driver = DriverFactory.getWebDriver()
		return driver.findElements(By.xpath(xpath)).size() > 0
	}
def tablePresent = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateElement'("//table[@class='cart']")
def tableHeaderPresent = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateElement'("//table[@class='cart']//thead")


	if(tablePresent && tableHeaderPresent == true) {
		println ('No. of Columns: ' + tablePresent)
	
		println ('Names of the columns are: ' + tableHeaderPresent)
			
}
	else
		println ('Favorites is empty!')

When the table was present, I got the result of:

No. of Columns: true

Names of the columns are: true

When the table was not present, I correctly got “Favorites is empty”

What can I do to get the actual number of columns and the names of the columns?


#18

The keyword we have been talking about is specifically designed to find the presence of any element you define.

You will need a separate keyword to get the names of the columns, then call that in your if statement.


#19

I created two separate keywords but still I am not getting the expected result when I call from my statement

@Keyword
	def allHeadersFavorites(){

		// creating a variable 'driver' and assigning its value
		WebDriver driver = DriverFactory.getWebDriver()

		// get the first row in the headers from the table and store this in a variable ‘th’ of type web element.
		WebElement th = driver.findElement(By.xpath("//table[@class='cart']//thead"))

		//Get all the headers with tag name ‘th’ and store all the elements in a list of web elements.
		//Now all the elements with tag ‘th’ are stored in ‘headers’ list.
		List<WebElement> headers = th.findElements(By.tagName('th'))

		//storing the header size so that it can be used in the test for printing
		return headers.size()

	}


	@Keyword
	def columnNames (){

		WebDriver driver = DriverFactory.getWebDriver()

		WebElement th = driver.findElement(By.xpath("//table[@class='cart']//thead"))

		List<WebElement> headers = th.findElements(By.tagName('th'))

		//traversing through the column headers, index 0 will be the first column name and so on

		def retvalue = '' //using def keyword to define a variable for storing values

		//iterating through the list headers
		for (WebElement e : headers){

			retvalue += e.getText() + ';'

		}
		return retvalue
	}
def tablePresent = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateElement'("//table[@class='cart']")
def tableHeaderPresent = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.locateElement'("//table[@class='cart']//thead")
def numColumns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.allHeadersFavorites'("//table[@class='cart']//thead")
def nameColumns = CustomKeywords.'com.ces.cesonline.favorites.FavoritesKeywords.columnNames'("//table[@class='cart']//thead")

	if(tablePresent && tableHeaderPresent == true) {
		println ('No. of Columns: ' + numColumns)
	
		println ('Names of the columns are: ' + nameColumns)
			
}
	else
		println ('Favorites is empty!')
	

I got this error

The current scope already contains a variable of the name numColumns
The current scope already contains a variable of the name nameColumns

#20

Variable names must be unique within their scope. You have

def numColumns

and

def nameColumns

twice in your script again. Please research these issues yourself before posting when something doesn’t work. Google is a wonderful tool and the answers to most of your questions likely already exists somewhere. You will also learn much more if you do the work yourself… Closing this topic for now, as the main question was answered.