A dynamic table is a table where the number of rows and columns can change frequently. To manage these changes, advanced techniques are required to locate and interact with elements within a dynamic table in Selenium.
Automate Dynamic Tables using Selenium
In this blog we will focus on automating dynamic tables. The challenge with dynamic tables is dealing with complex locators (mostly XPaths), loops, and most importantly dealing with the dynamic data.
Sample table to be considered for automation:
Source: Fluent UI React Components
Building the WebElement locators
There are many good resources available to learn about XPaths. As a beginner, I found this blog to be very useful.
For dynamic tables it's important to focus on building WebElement locators (mostly XPaths).
Locating the Table Rows
var rows = driver.findElements(By.xpath("//div[@id='story--components-table--focusable-elements-in-cells']//tbody//tr[not(th)]"));
- An XPath is used to locate all the table rows.
- tr[not(th)] - this ensures that the headers if present are not included as part of the table rows because at this point we are only interested in the data rendered.
- This will return a list of locators to identify all the rows of the table except the table headers.
Locating the cells in the table
var cells = row.findElements(By.cssSelector("td"));
For every row, the above locators helps find the child element - td
Initialise the expected data
Note: For this example alone we will use static initialization, but this is NOT recommended.
var expectedData = List.of(
new String[]{"Meeting notes", "Max Mustermann", "7h ago"},
new String[]{"Thursday presentation", "Erika Mustermann", "Yesterday at 1:45 PM"},
new String[]{"Training recording", "John Doe", "Yesterday at 1:45 PM"},
new String[]{"Purchase order", "Jane Doe", "Tue at 9:30 AM"}
);
You will now have to loop through all the table rows and cells to validate the data.
Here's the complete test code
@Test
public void validateTableData() {
// Load the URL
driver.get("https://react.fluentui.dev/?path=/docs/components-table--default#focusable-elements-in-cells");
// Initialise the expected data set
var expectedData = List.of(
new String[]{"Meeting notes", "Max Mustermann", "7h ago"},
new String[]{"Thursday presentation", "Erika Mustermann", "Yesterday at 1:45 PM"},
new String[]{"Training recording", "John Doe", "Yesterday at 1:45 PM"},
new String[]{"Purchase order", "Jane Doe", "Tue at 9:30 AM"}
);
// Locate the table rows
var rows = driver.findElements(By.xpath("//div[@id='story--components-table--focusable-elements-in-cells']//tbody//tr[not(th)]"));
// Validate the table data
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
var row = rows.get(rowIndex);
var cells = row.findElements(By.cssSelector("td"));
for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
var cell = cells.get(cellIndex);
// Get the cell value
var text = cell.getText().trim();
// Validate
Assert.assertEquals(text, expectedData.get(rowIndex)[cellIndex], "Mismatch in column (" + cellIndex + ") at row " + rowIndex);
}
}
}
There is a major issue in the above code sample -
The expected data is hardcoded or is static. Ideally this should be obtained from the database or using APIs, which would require writing wrappers and a few lines of code as well. This will be handled as part of an upcoming blog.
There is another solution, by the use of Dynamic XPaths.
What are dynamic XPaths
Dynamic XPath is an advanced concept in Selenium WebDriver, crafted to handle web elements that change their attributes dynamically.
You can learn more about dynamic XPaths here.
public WebElement getTableRow(String file, String author, String updated) {
return driver.findElement(By.xpath("//div[@id='story--components-table--focusable-elements-in-cells']//tbody//tr[.//td//*[text()='"+file+"']]//td//*[text()='"+author+"']//ancestor::td//following-sibling::td[text()='"+updated+"']"));
}
Here's the complete code with dynamic XPaths
@Test
public void validateTableDataWithDynamicXpath() {
// Load the URL
driver.get("https://react.fluentui.dev/?path=/docs/components-table--default#focusable-elements-in-cells");
// Initialise the expected data set
var expectedData = List.of(
new String[]{"Meeting notes", "Max Mustermann", "7h ago"},
new String[]{"Thursday presentation", "Erika Mustermann", "Yesterday at 1:45 PM"},
new String[]{"Training recording", "John Doe", "Yesterday at 1:45 PM"},
new String[]{"Purchase order", "Jane Doe", "Tue at 9:30 AM"}
);
for (String[] data : expectedData) {
// Get the WebElement based on test data
WebElement row = getTableRow(data[0], data[1], data[2]);
// Validate presence
Assert.assertTrue(row.isDisplayed(), "Row not found");
}
}
Which of the 2 approaches is better?
That depends on what needs to be validated. If it's for a single row or single value, approach #2 works.
But with approach #2 the XPaths need to be as simplified as possible to enable ease of maintenance and this will not work if the table's columns keep changing.
For entire table validations, I would prefer approach #1, again the key here is to simplify the XPaths as much as possible for maintainability.
Automating components with Dynamic Locators using DevAssure
With DevAssure you can automate components with dynamic locators with ease. You will have a custom automation framework that align with your product requirements without the overhead of maintaining the framework itself.
Top comments (0)