Work with tables
HTML tables are one of the most common sources of brittle Selenium tests. The fix is to target structure and meaning instead of absolute row indexes: read the headers, find the row that contains a known value, then read the cell you care about.
Assert a cell value by row content
Find the row whose first cell contains a known key, then assert the value in another column:
OrdersPage.java
import io.testfly.test.BasePage;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
public class OrdersPage extends BasePage {
private static final By TABLE = By.id("orders");
public String statusOf(String orderId) {
return cellByRowText(TABLE, orderId, "Status");
}
private String cellByRowText(By table, String rowKey, String columnName) {
WebElement tableEl = find(table).first().element();
// Map header text to column index
java.util.List<WebElement> headers = tableEl.findElements(By.tagName("th"));
int columnIndex = -1;
for (int i = 0; i < headers.size(); i++) {
if (headers.get(i).getText().trim().equals(columnName)) {
columnIndex = i;
break;
}
}
if (columnIndex < 0) {
throw new AssertionError("Column not found: " + columnName);
}
// Find the row whose first cell (or any cell) contains rowKey
for (WebElement row : tableEl.findElements(By.cssSelector("tbody tr"))) {
if (row.getText().contains(rowKey)) {
return row.findElements(By.tagName("td")).get(columnIndex).getText().trim();
}
}
throw new AssertionError("Row not found: " + rowKey);
}
}
OrdersTest.java
public class OrdersTest extends BaseTest {
@Test
public void orderStatusIsShipped() {
open("/orders");
OrdersPage page = new OrdersPage();
softAssert().that(page.statusOf("ORD-1234").equals("Shipped"), "Order ORD-1234 should be Shipped");
}
}
Count rows
int visibleRows = find("#orders tbody tr").count();
softAssert().that(visibleRows > 0, "Should have at least one visible row");
Wait for a row to appear
assertThat(find("#orders tbody tr")).count(3); // auto-waits until 3 rows exist
Assert a row exists
Use withText(...) to scope the locator to the row containing the text (substring matching by default), then assert visibility:
assertThat(find("#orders tbody tr").withText("ORD-1234")).isVisible();
Tips for stable table tests
- Never hard-code row indexes. A new row from another test can shift every index.
- Use header names, not column numbers. The order of columns changes more often than their labels.
- Scope queries to the table. Use
#orders tbody trinstead of globaltrselectors. - Assert the visible text, not the DOM order. A user's bug report says "Status shows Pending," not "the third
tdis wrong."
Deeper reference: Locator — counting, filtering, and iterating over matching elements.