How do I click a specific 'Delete' button when there are 10 identical ones on the page?

I’m trying to automate a test case where I need to click the fifth ‘Delete’ button on a dashboard page. The problem is, there are 10 ‘Delete’ buttons in total, and they all look exactly the same in the HTML.

Here is what I’ve tried so far:

  • I used the Web Recorder to spy and capture the button. It created a Test Object, but when I run the script, it always just clicks the very first ‘Delete’ button on the page, not the fifth one.

  • I tried changing the Selection Method to Attributes and checked different boxes, but since the IDs are dynamic (they change every time the page reloads) and the class names are identical, Katalon either clicks the first one or throws an ElementNotVisibleException.

Can you paste the html here ,

If you can post even the UI image it would clear the picture.
Solution
But in the situation which you are describing, I assume the delete button would be in table in each specific rows. If this is the case then create locator with placeholder, and the placeholder will be targeting to some specific unique item in the table row. This will resolve the issue and hit the dedicated button delete.

But to get more precise answer, kindly clarify the issue

Please let us know

To click the fifth ‘Delete’ button among 10 identical ones in Katalon Studio, you need to use an XPath locator with an index or target a unique parent element in the same row.

Solution 1: XPath with Index (Quick Fix)

Modify your Test Object’s selection method to use XPath with the index 5:

// In your Test Object, set Selection Method to "XPath"
// Use this XPath: (//button[@class='delete-btn'])[5]

WebUI.click(findTestObject('Page_Dashboard/btn_DeleteFifth'))

The parentheses around the XPath and the [5] at the end ensure Katalon clicks the 5th matching element, not the first.

Solution 2: Target Unique Parent Element (More Reliable)

If the Delete buttons are in a table with rows, use a unique identifier from that row (like an ID, name, or email) instead of index:

// Example: Target the Delete button in the row containing "John Doe"
// XPath: //tr[td[contains(text(),'John Doe')]]//button[@class='delete-btn']

WebUI.click(findTestObject('Page_Dashboard/btn_DeleteJohnDoe'))

This approach is more stable because:

  • It doesn’t depend on button order (which might change if rows are added/removed)
  • It directly targets the specific data row you want

Solution 3: JavaScript Click (If Others Fail)

If the element still throws ElementNotVisibleException, use JavaScript to click:

WebElement element = WebUiCommonHelper.findWebElement(findTestObject('Page_Dashboard/btn_DeleteFifth'), 30)
WebUI.executeJavaScript("arguments[0].click()", Arrays.asList(element))

Which Should You Use?

Situation Best Solution
Buttons in fixed order, no unique row data XPath with index [5]
Buttons in table rows with unique data (name, ID) Target unique parent element
Element hidden/overlapped despite correct locator JavaScript click

Recommendation: If your Delete buttons are in table rows, use Solution 2 (unique parent) — it’s the most robust approach mentioned in the forum

Katalon’s TestObject allows you to use CSS Selector as its locator as well as XPath. If you are new to both of CSS Selector and XPath, and if you want to choose one to get started, I would recommend you to learn CSS Selector rather than XPath.

If you want to learn CSS Selector, please read the following article:

There is an error when I click on this link Mr @kazurayam - saying the site was forbidden.

it seems issue at your end , it opens properly

our team members also cannot open it :face_with_monocle:

can you try opening it in incognito mode ?

Didn’t work either, probably some restrictions on my ends

By wrapping an XPath in parentheses and appending an index, we can target any specific instance: css=(//button[text()='Delete'])[5] or xpath=(//a[contains(@class,'btn-delete')])[5]

Modify the Test Object:

  • Open your ‘Delete’ button Test Object in Object Repository.
  • Set the Selection Method to Attributes.
  • Add a property named xpath.
  • Set its value to: (//button[@string()='Delete' or text()='Delete'])[${index}]
  • Check the Detect object by? box for this xpath property.

Add a Custom Keyword for Clean Execution:

To keep your test scripts readable and reusable across your framework, wrap this logic into a Custom Keyword.

package com.architecture.keywords

import com.kms.katalon.core.annotation.Keyword
import com.kms.katalon.core.testobject.ObjectRepository
import com.kms.katalon.core.testobject.TestObject
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI

public class WebTableUtils {

	/**
	 * Clicks an element from a list of identical elements based on its 1-based index.
	 * @param repositoryObjectId The path of the Test Object in the repository (e.g., 'Page_Dashboard/btn_Delete')
	 * @param index The 1-based position of the element you want to click (e.g., 5)
	 */
	@Keyword
	def clickElementByIndex(String repositoryObjectId, int index) {
		// Find the base test object from the repository
		TestObject baseObject = ObjectRepository.findTestObject(repositoryObjectId)
		
		// Dynamically pass the index parameter to the parameterized XPath
		TestObject indexedObject = WebUI.convertTO(baseObject)
		indexedObject.addProperty("xpath", com.kms.katalon.core.testobject.ConditionType.EQUALS, "((//button[text()='Delete'])[${index}])")
		
		WebUI.comment("Attempting to click the element at index: " + index)
		WebUI.waitForElementVisible(indexedObject, 10)
		WebUI.click(indexedObject)
	}
}

How to use it in your Script View:

Once the keyword is saved, you can cleanly call it in your Test Case script like this:

// Clicks the 5th delete button safely
CustomKeywords.'com.architecture.keywords.WebTableUtils.clickElementByIndex'('Object Repository/Page_Dashboard/btn_Delete', 5)

This keeps your repository clean with only one Test Object, ensures your script remains readable, and allows you to target the 1st, 5th, or 10th button effortlessly.

This is better approach (than any other provided above) (my personal opinion).

I’ve been using this kind of workflow.

Only that creating a separate keyword for only such functionality seem bit too much to me (reason being this might be not be used that much).

So instead, create a parameterized xpath and directly use that in the test case providing the parameterized value at runtim.

I’d also avoid choosing the button only by index unless the row order is very stable. If each row has some unique text, ID, email, title, or status, it’s usually safer to build the XPath around that row and then click the Delete button inside it. That way the test still clicks the right item even if a new row gets added above it.

Index works fine for quick cases, but for regression tests I’d rather target the row first, then the action button inside that row. It makes the test easier to trust later.

yeah , may be

hi @ndubuque

use an XPath with parentheses and an index, like (//button[text()='Delete'])[5]. The parentheses are key, without them the [5] won’t grab the 5th match globally.

indexing by position breaks easily though. If these buttons are in table rows, target the row first with unique text instead: //tr[td[contains(text(), 'SomeUniqueValue')]]//button[text()='Delete']

if you get ElementNotVisibleException, the button might be hidden behind something. Force a click with JavaScript:

WebElement el = WebUiCommonHelper.findWebElement(findTestObject('YourObject'), 10)
WebUI.executeJavaScript("arguments[0].click()", Arrays.asList(el))

Tried any of the above suggestions? did it work?

Very good article on locator. All beginners should definitely go through this

Use placeholder in the locator, your problem will be solved. Did you try?