Table Steps

Table steps inspect and assert the contents of HTML tables - row counts, column counts, header names, sort order, and cell values. They are the natural fit for reports, dashboards, admin listings, and any screen where data is rendered as <table> markup. Every step in this group is a Then assertion: it reads the table exactly as rendered in the browser and fails with a descriptive message that includes what was actually found (for example, Table "#users" has 3 rows, expected 5).

Each step takes a CSS selector for the table - an id (#users), a class (table.orders), or an attribute selector ([data-testid=products]) all work. The step reads the first matching table, collects header cells (<th>, from <thead> when present) and body rows (any <tr> that contains at least one <td>), and trims whitespace from every cell before comparing. Header-only rows are never counted as body rows, so "5 rows" always means five data rows.

Because webship-js automatically settles the page after every state-changing action (see wait steps), a table assertion placed right after a click or filter action usually sees the finished table. If the table is populated by a slow AJAX call, add an explicit edge wait such as When I wait until at least 3 elements match "tr" first, or use a web-first count assertion.

Quick reference

StepWhat it does
Then the table "#users" should have 5 rowsAssert a table has exactly N body rows.
Then the table "#users" should have 4 columnsAssert a table has exactly N columns.
Then the table "#users" should contain the following columns:Assert the table header contains every column listed in the data table.
Then the table "#users" should be emptyAssert a table has zero body rows.
Then the table "#users" should not be emptyAssert a table has at least one body row.
Then the table "#users" should be sorted by "Name" in "ascending" orderAssert a table is sorted ascending or descending by a header column.
Then the table "#users" should contain the following rows:Assert a table contains every row described by the data table (substring match per cell).
Then the "Alice" row should contain the following:Assert a row containing a text fragment also contains every listed cell value.

Steps in detail

Then the table "<selector>" should have <N> row(s)

Asserts the table has exactly N body rows. Only <tr> elements that contain at least one <td> count - the header row is excluded. The step accepts both row and rows, so should have 1 row and should have 0 rows are both valid phrasings.

Given I am on "/table.html"
Then the table "#users" should have 3 rows
And the table "table.orders" should have 1 row
And the table "#audit-log" should have 0 rows

Then the table "<selector>" should have <N> column(s)

Asserts the table has exactly N columns. The count comes from the first body row when one exists, otherwise from the header cells, so it reflects what a user actually sees. Accepts column or columns.

Given I am on "/table.html"
Then the table "#users" should have 3 columns
And the table "[data-testid=products]" should have 5 columns

Then the table "<selector>" should contain the following columns:

Takes a Gherkin data table of column names and asserts each one appears in the table header (<th> cells). Matching is exact on the trimmed header text, and the listed columns may appear in any order - extra columns in the table are fine. On failure the message lists the headers that were actually found. You can list the expected columns one per line or all on one row; both data-table shapes are flattened.

Then the table "#users" should contain the following columns:
  | Name   |
  | Role   |
  | Status |

Then the table "<selector>" should be empty / should not be empty

The positive form asserts zero body rows - useful after applying a filter that should match nothing, or on a fresh listing with no data. The negated form asserts at least one body row, which is the quickest smoke check that a listing rendered data at all.

Given I am on "/table.html"
Then the table "#empty" should be empty
And the table "#users" should not be empty

Then the table "<selector>" should be sorted by "<column>" in "<direction>" order

Finds the named header column, extracts that column's value from every body row, and asserts the values are in sorted order. Direction accepts ascending, asc, descending, or desc. The column name must match a header exactly; if it does not, the step fails with Column "<name>" not found before comparing anything. Note the comparison is lexicographic (string sort), so numeric columns compare as text - "10" sorts before "9".

Then the table "#users" should be sorted by "Name" in "ascending" order
And the table "table.orders" should be sorted by "Total" in "desc" order

Then the table "<selector>" should contain the following rows:

Takes a data table where each line describes one expected row. A row passes when some body row contains every listed cell value as a substring - you do not have to list every column, and cell order does not need to match the table's column order. This makes it ideal for "these records exist" checks that survive cosmetic column changes.

Then the table "#users" should contain the following rows:
  | Alice | Admin  |
  | Bob   | User   |

Then the "<text>" row should contain the following:

A selector-free variant: scans every <tr> on the page for the first row whose visible text contains the given fragment, then asserts that same row also contains each listed value. Use a distinctive fragment (a username, an order number, an email address) to pin down the row. Because it searches all rows on the page, it works without knowing the table's selector - but on pages with several tables, prefer the selector-based row step above.

Then the "Bob" row should contain the following:
  | User   |
  | Active |

Complete example

Adapted from tests/features/table.feature, which runs against the fixture page examples/table.html:

Feature: Table step definitions

  Scenario: Inspect table content
    Given I am on "/table.html"
    Then the table "#users" should have 3 rows
    And the table "#users" should have 3 columns
    And the table "#users" should not be empty
    And the table "#empty" should be empty
    And the table "#users" should contain the following columns:
      | Name   |
      | Role   |
      | Status |
    And the table "#users" should be sorted by "Name" in "ascending" order
    And the table "#users" should contain the following rows:
      | Alice | Admin |
    And the "Bob" row should contain the following:
      | User   |
      | Active |

Tips

  • All cell and header text is trimmed before comparison, so surrounding whitespace in the markup never causes a mismatch.
  • Row-content matching is substring-based per cell: | ship | matches a cell reading Shipped only if the case matches - matching is case-sensitive.
  • If several tables match the selector, only the first match is inspected. Use an id or a data-testid attribute to disambiguate.
  • For dynamically loaded tables, pair with When I wait until at least N elements match "tr" from the wait steps group, or use Then "tr" should have a count of N from the web-first assertions group, which auto-retries.

Back to all step groups