How to Validate HTML Tables Using Cucumber DataTables and Selenium
A common use case for automated tests is validating data in an HTML table.
In this blog post, I will share one technique to do this using Cucumber DataTables and Selenium WebDriver.
Let’s say we are looking to validate the entries in the following example table from w3schools.com:
If we inspect the HTML table, here is the code behind it:
<div class="w3-example"> <h3>Example</h3> <div class="w3-white w3-padding notranslate w3-padding-16"> <table id="customers"> <tbody><tr> <th>Company</th> <th>Contact</th> <th>Country</th> </tr> <tr> <td>Alfreds Futterkiste</td> <td>Maria Anders</td> <td>Germany</td> </tr> <tr> <td>Centro comercial Moctezuma</td> <td>Francisco Chang</td> <td>Mexico</td> </tr> <tr> <td>Ernst Handel</td> <td>Roland Mendel</td> <td>Austria</td> </tr> <tr> <td>Island Trading</td> <td>Helen Bennett</td> <td>UK</td> </tr> <tr> <td>Laughing Bacchus Winecellars</td> <td>Yoshi Tannamuri</td> <td>Canada</td> </tr> <tr> <td>Magazzini Alimentari Riuniti</td> <td>Giovanni Rovelli</td> <td>Italy</td> </tr> </tbody></table> </div> <a class="w3-btn w3-margin-top w3-margin-bottom" href="tryit.asp?filename=tryhtml_table_intro" target="_blank">Try it Yourself »</a> </div>
The example table is made up of 7 rows (the first row being the header row) where each row has three columns. How do we validate this? Thankfully, Cucumber includes a handy data type to do this cleanly called a "DataTable" that we can pass via Cucumber step definitions.
For simplicity's sake, let's assume we are not going to be validating the "header" row of the table, just the rows containing the data. We'll start by writing the Cucumber step definition. Here is one way that could look:
Scenario: Validate an HTML table Given I navigate to the URL the HTML table is on Then I verify the HTML table contains the following values | Alfreds Futterkiste | Maria Anders | Germany | | Centro comercial Moctezuma | Francisco Chang | Mexico | | Ernst Handel | Roland Mendel | Austria | | Island Trading | Helen Bennett | UK | | Laughing Bacchus Winecellars | Yoshi Tannamuri | Canada | | Magazzini Alimentari Riuniti | Giovanni Rovelli | Italy |
We will be passing a Cucumber DataTable to our validation code in the "Then" step. Below is the Java code for the "Then" step:
@Then("I verify the HTML table contains the following values") public void i_verify_the_html_table_contains_the_following_values(DataTable dataTable) { HtmlTableValidationPage htmlTableValidationPage = new HtmlTableValidationPage(webDriver); htmlTableValidationPage.verifyHtmlTableData(dataTable); }
We can define the data text below the "Then" step as a Cucumber DataTable by declaring the input parameter of that step definition as a DataTable type (I'm importing the package io.cucumber.datatable.DataTable to do that). The Cucumber DataTable object will allow us to work with the "expected" data in a row by row, column by column basis which works great for validating data within an HTML table.
Next, let's take a look at the Java code used to verify the HTML table. Below is the code for the Class HtmlTableValidationPage.java:
import io.cucumber.datatable.DataTable; import org.junit.Assert; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import java.util.List; public class HtmlTableValidationPage { private final WebDriver webDriver; public HtmlTableValidationPage(WebDriver webDriver) { this.webDriver = webDriver; } public void verifyHtmlTableData(DataTable dataTable) { WebElement htmlTableElement = webDriver.findElement(By.xpath("//table[@id="customers"]")); //get the table WebElement List<WebElement> rowElements = htmlTableElement.findElements(By.xpath(".//tr")); //get all of the row WebElements from the table rowElements.remove(0); //remove the "header" row from the list of row WebElements List<List<String>> dataTableRows = dataTable.asLists(); //outer List<> is rows, inner List<> is cells for (List<String> row : dataTableRows) { //loop through every row in the DataTable input int rowIdx = dataTableRows.indexOf(row); WebElement rowElem = rowElements.get(rowIdx); //get the row WebElement based on the index of the current row in the DataTable List<WebElement> cellElements = rowElem.findElements(By.xpath(".//td")); //get all of the cells from the row WebElement for (String expectedCell : row) { //loop through every cell in the current DataTable row int cellIdx = row.indexOf(expectedCell); String actualCell = cellElements.get(cellIdx).getText(); /* System.out.println for demonstration purposes */ System.out.println("DataTable row " + rowIdx + ", cell " + cellIdx + ": " + expectedCell); System.out.println("Actual value on the page: " + actualCell); Assert.assertEquals("Expected value of cell should match actual value of cell", expectedCell, actualCell); } } } }
The code is commented to describe its functionality. In short, all I'm doing is looping through every row and every cell (column) in the DataTable, grabbing the matching row and cell from the HTML table, and validating that the text contents are the same. Below is the resulting console output:
DataTable row 0, cell 0: Alfreds Futterkiste Actual value on the page: Alfreds Futterkiste DataTable row 0, cell 1: Maria Anders Actual value on the page: Maria Anders DataTable row 0, cell 2: Germany Actual value on the page: Germany DataTable row 1, cell 0: Centro comercial Moctezuma Actual value on the page: Centro comercial Moctezuma DataTable row 1, cell 1: Francisco Chang Actual value on the page: Francisco Chang DataTable row 1, cell 2: Mexico Actual value on the page: Mexico DataTable row 2, cell 0: Ernst Handel Actual value on the page: Ernst Handel DataTable row 2, cell 1: Roland Mendel Actual value on the page: Roland Mendel DataTable row 2, cell 2: Austria Actual value on the page: Austria DataTable row 3, cell 0: Island Trading Actual value on the page: Island Trading DataTable row 3, cell 1: Helen Bennett Actual value on the page: Helen Bennett DataTable row 3, cell 2: UK Actual value on the page: UK DataTable row 4, cell 0: Laughing Bacchus Winecellars Actual value on the page: Laughing Bacchus Winecellars DataTable row 4, cell 1: Yoshi Tannamuri Actual value on the page: Yoshi Tannamuri DataTable row 4, cell 2: Canada Actual value on the page: Canada DataTable row 5, cell 0: Magazzini Alimentari Riuniti Actual value on the page: Magazzini Alimentari Riuniti DataTable row 5, cell 1: Giovanni Rovelli Actual value on the page: Giovanni Rovelli DataTable row 5, cell 2: Italy Actual value on the page: Italy
Additionally, if you'd like to see all Assertion failures when validating a table instead of just the first one, view my post on Multiple JUnit Assertions.
Jared Hoernemann
Jared Hoernemann is a Senior Automation Engineer specializing in designing and coding test automation scripts and frameworks from the ground up, as well as mentoring other developers. Jared designs, implements, and executes automated tests for web browsers, APIs of all kinds, and databases.
Have a QA question?
Our team would love to help!