tapQA 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:

Screenshot from: https://www.w3schools.com/html/html_tables.asp
Screenshot from: https://www.w3schools.com/html/html_tables.asp

If we inspect the HTML table, here is the code behind it:

<div class="w3-example">
<div class="w3-white w3-padding notranslate w3-padding-16">
<table id="customers">
    <td>Alfreds Futterkiste</td>
    <td>Maria Anders</td>
    <td>Centro comercial Moctezuma</td>
    <td>Francisco Chang</td>
    <td>Ernst Handel</td>
    <td>Roland Mendel</td>
    <td>Island Trading</td>
    <td>Helen Bennett</td>
    <td>Laughing Bacchus Winecellars</td>
    <td>Yoshi Tannamuri</td>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Giovanni Rovelli</td>
<a class="w3-btn w3-margin-top w3-margin-bottom" href="tryit.asp?filename=tryhtml_table_intro" target="_blank">Try it Yourself »</a>
(HTML code credit: w3schools.com)

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);


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 H

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!

Invalid Email