GridElement Specification¶
For the authoritative method list and signatures, see the auto-generated api-reference.md. This file focuses on behaviour and usage that signatures alone don't convey.
Overview¶
GridElement is a Playwright element wrapper for the <vaadin-grid> web component. It provides helpers for querying rows and cells, accessing cell content by row/column index or header text, and interacting with the grid's selection, sorting, and row details features. Row and cell access methods auto-scroll the grid as needed to bring virtualized rows into view.
Results are returned as Optional wrappers or inner-class elements (RowElement, CellElement, HeaderCellElement) so callers can interact with rows and cells directly rather than going back through the grid. Please note, that you should interact with these elements before searching for another row which may lead to scrolling and possibly hiding / removing element that is referenced.
Tag Name¶
vaadin-grid
Class Hierarchy¶
VaadinElement
└── GridElement
Implemented Interfaces¶
| Interface | Description |
|---|---|
FocusableElement |
Focus operations |
HasStyleElement |
CSS class support |
HasThemeElement |
Theme attribute support |
HasEnabledElement |
Enabled/disabled state |
Constants¶
| Constant | Value | Description |
|---|---|---|
FIELD_TAG_NAME |
"vaadin-grid" |
HTML tag name |
API Methods¶
Constructor¶
GridElement(Locator locator)
Creates a new GridElement from a Playwright locator.
Static Factory Methods¶
get(Page page)¶
Get the first GridElement on the page.
GridElement grid = GridElement.get(page);
get(Locator parent)¶
Get the first GridElement within a parent locator.
GridElement grid = GridElement.get(page.locator("#container"));
getById(Page page, String id)¶
Get a GridElement by its id attribute.
GridElement grid = GridElement.getById(page, "my-grid");
Properties / Getters¶
| Method | Description |
|---|---|
getTotalRowCount() |
Total number of data items |
getRenderedRowCount() |
Number of rows currently rendered in the DOM |
getColumnCount() |
Number of visible (non-hidden) columns |
isAllRowsVisible() |
Whether allRowsVisible is enabled (no vertical scroll) |
isMultiSort() |
Whether multi-sort is enabled |
isColumnReorderingAllowed() |
Whether column reordering is allowed |
findColumnIndexById(String id) |
OptionalInt index of the visible column with that id; empty if absent or hidden |
getColumnIndexById(String id) |
Same, throwing IllegalArgumentException when no visible column has that id |
Column Indexes¶
Every columnIndex / colIndex parameter counts the visible columns in
display order, starting from 0 on the left:
- hidden columns (
setVisible(false)) are skipped; - a selection column (multi-select) is a column and counts;
- after the user drags a column to a new place, the indexes follow the new
order — the same order as TestBench's
getVisibleColumns().
This matches TestBench, whose getCell(row, colIndex) and getHeaderCell(colIndex)
count the selection column too. Flow's Grid.getColumns() and browserless tests
(GridTester) do not list the selection column, so in a multi-select grid a
Flow column index is one less than the GridElement index (the selection column
is first unless the columns were reordered). To stay independent of the selection
mode, look a column up by header text (findCell(row, "Email")) or by id
(getColumnIndexById) instead of hard-coding its index.
A column with no header text cannot be found with findHeaderCellByText; give it
an id in Flow (Column.setId("email"), which lands on the <vaadin-grid-column>)
and look it up with getColumnIndexById("email"). Column.setKey(..) stays on the
server and is not visible to the browser.
int email = grid.getColumnIndexById("email");
grid.assertCellContent(0, email, "person1@example.com");
Header Access Methods¶
All findHeaderCell* methods return Optional<HeaderCellElement>.
| Method | Description |
|---|---|
findHeaderCell(int colIndex) |
Header cell by column index in the first header row |
findHeaderCell(int headerRowIndex, int colIndex) |
Header cell by header row index and column index |
findHeaderCellByText(String text) |
Header cell by text content in the first header row |
findHeaderCellByText(int headerRowIndex, String text) |
Header cell by header row index and text content |
getHeaderCellContents() |
Text content of all visible header cells |
getHeaderCells(int colIndex) |
List<HeaderCellElement>: the column's header cells in every header row, top row first |
getHeaderCells returns the cell of the column itself and those of the column
groups around it, which is how the web component renders Flow's extra
HeaderRows: a joined cell above (returned for each column it spans) or a filter
field in an appended row below. Scope a component lookup to a cell's content:
// Header rows: joined "Name" | "Last Name" with sorter | filter field
// The column has Column.setId("last-name") in Flow
var cells = grid.getHeaderCells(grid.getColumnIndexById("last-name"));
TextFieldElement filter = TextFieldElement.getById(cells.get(2).getCellContentLocator(), "last-filter");
filter.setValue("Smith");
Footer Access Methods¶
All findFooterCell* methods return Optional<FooterCellElement>.
| Method | Description |
|---|---|
findFooterCell(int colIndex) |
Footer cell by column index in the first footer row |
findFooterCell(int footerRowIndex, int colIndex) |
Footer cell by footer row index and column index |
findFooterCellByText(String text) |
Footer cell by text content in the first footer row |
findFooterCellByText(int footerRowIndex, String text) |
Footer cell by footer row index and text content |
getFooterCellContents() |
Text content of all visible footer cells |
Row Access Methods¶
| Method | Return Type | Description |
|---|---|---|
findRow(int rowIndex) |
Optional<RowElement> |
Find row by index; auto-scrolls if the row is not yet in view |
findRowIndexesWithColumnText(int columnIndex, String text) |
List<Integer> |
Row indexes where the given column cell matches the given text (reads the column a page at a time) |
getColumnTexts(int columnIndex) |
List<String> |
Text of every body cell in the column, indexed by row |
getColumnTexts (and findRowIndexesWithColumnText, built on it) reads a lazy
grid a page at a time: one script reads every rendered row, then the grid jumps
to the first unread row and waits for it to load. That is one scroll per page
instead of several round trips per row, so prefer it over looping on
findCell(row, column). To customise how a cell matches, override
matchesCellText(String cellText, String text).
Cell Access Methods¶
Both methods return Optional<CellElement> and auto-scroll the grid if the target row is not yet in view.
| Method | Description |
|---|---|
findCell(int row, int column) |
Body cell by row and column index |
findCell(int row, String columnHeaderText) |
Body cell by row index and column header text |
Selection Methods¶
| Method | Description |
|---|---|
getSelectedItemCount() |
Number of currently selected items |
select(int rowIndex) |
Select row by index |
deselect(int rowIndex) |
Deselect row by index |
isSelectAllChecked() |
Whether the select-all checkbox is checked |
isSelectAllIndeterminate() |
Whether the select-all checkbox is indeterminate |
isSelectAllUnchecked() |
Whether the select-all checkbox is unchecked |
checkSelectAll() |
Check the select-all checkbox (no-op if already checked) |
uncheckSelectAll() |
Uncheck the select-all checkbox (no-op if already unchecked) |
select and deselect click the row like a user would. In a multi-select grid
they click the selection checkbox. Otherwise they click the first cell that has a
spot where the click reaches the grid rather than a component rendered in the
cell, usually the cell padding: the grid ignores clicks on links, buttons and
other focusable content, so clicking the centre of a cell holding a link would
follow the link instead of selecting the row. A cell holding a link or a button
therefore still selects the row through its padding. Only when interactive content
covers every cell, padding included (for example a component with negative
margins, or a theme without cell padding), do they throw an
IllegalStateException; override the
protected clickRowForSelection(RowElement) or clickCellForSelection(CellElement)
to select such a row another way.
They also check the outcome. select(int) and deselect(int) throw an
IllegalArgumentException when there is no row at the index, and every select and
deselect fails with an AssertionError when the row's selected state didn't
change after the click: a grid with no selection, a single-select grid that
doesn't allow deselecting, or a server that rejected the selection.
Utility Methods¶
| Method | Description |
|---|---|
waitForGridToStopLoading() |
Wait for the grid to finish loading after a scroll or action that triggers row loading |
Inner Classes¶
CellElement¶
Wraps a single grid cell (<td>).
| Method | Description |
|---|---|
getTableCell() |
Locator for the table cell element (<td>) |
getColumnIndex() |
0-based column index; -1 for details cells |
getCellContent() |
Locator for the <vaadin-grid-cell-content> slot element |
getContentSlotName() |
Name of the slot used for cell content |
click() |
Click the cell content |
HeaderCellElement extends CellElement¶
Wraps a single grid header cell (<th>).
Extends CellElement with sorting support.
| Method | Description |
|---|---|
isSortable() |
Whether the column supports sorting |
clickSort() |
Click the sorter to cycle sort direction |
sort(SortDirection direction) |
Click the sorter until it shows direction (ASCENDING, DESCENDING, NONE); no-op if already there |
getSortDirection() |
Current GridElement.SortDirection; NONE when unsorted |
assertSortDirection(SortDirection expected) |
Assert the direction (retrying); NONE asserts unsorted |
isSortAscending() |
Whether the column is sorted ascending |
isSortDescending() |
Whether the column is sorted descending |
isNotSorted() |
Whether the column is not sorted |
FooterCellElement extends CellElement¶
Wraps a single grid footer cell (<td> in <tfoot>). Adds no methods beyond CellElement; exists as a distinct type returned by the footer access methods.
RowElement¶
Wraps a single grid row (<tr>).
| Method | Description |
|---|---|
getRow() |
Locator for the row element <tr> |
getRowIndex() |
0-based row index |
getCell(int columnIndex) |
Cell for the given column index |
getCell(String columnHeaderText) |
Cell for the given header text |
getDetailsCell() |
Cell element for the details column |
isSelected() |
Whether the row is selected |
select() |
Select this row |
deselect() |
Deselect this row |
click() |
Click the row, firing the grid's item click |
doubleClick() |
Double-click the row, firing the grid's item double click |
openDetails() |
Open this row's details panel by clicking the row |
closeDetails() |
Close this row's details panel by clicking the row |
isDetailsOpen() |
Whether this row's details panel is open |
click() and doubleClick() click the first data cell, skipping the selection
column, at a spot free of interactive content (usually the padding), so a button or
link rendered in the cell is not triggered; when interactive content covers a cell
entirely they move on to the next one, and throw an IllegalStateException when
no cell has a free spot.
openDetails() and closeDetails() click the row the same way and rely on the
grid toggling details on click, Flow's Grid.setDetailsVisibleOnClick(true) (the
default). They fail with an AssertionError naming that setting when the details
didn't open or close; override the protected openDetails(RowElement) /
closeDetails(RowElement) when the details are toggled another way.
Usage Examples¶
Basic Cell Access¶
GridElement grid = GridElement.getById(page, "my-grid");
// Row and column counts
int rows = grid.getTotalRowCount();
int cols = grid.getColumnCount();
// Headers
List<String> headers = grid.getHeaderCellContents();
// Cell content by index (auto-scrolls if needed)
var valueCellO = grid.findCell(0, 0);
assertTrue(valueCellO.isPresent());
assertThat(valueCellO.get().getCellContent()).hasText("My name");
var emailCellO = grid.findCell(0, "Email");
assertTrue(emailCellO.isPresent());
assertThat(emailCellO.get().getCellContent()).hasText("my@email.com");
Single Selection¶
GridElement grid = GridElement.getById(page, "single-select-grid");
grid.select(0);
var row0 = grid.findRow(0);
assertTrue(row0.isPresent());
assertTrue(row0.get().isSelected());
grid.select(1);
assertFalse(row0.get().isSelected());
Multi Selection¶
GridElement grid = GridElement.getById(page, "multi-select-grid");
grid.select(0);
grid.select(1);
assertEquals(2, grid.getSelectedItemCount());
grid.checkSelectAll();
assertTrue(grid.isSelectAllChecked());
grid.uncheckSelectAll();
assertEquals(0, grid.getSelectedItemCount());
Sorting¶
GridElement grid = GridElement.getById(page, "sortable-grid");
var headerCell = grid.findHeaderCellByText("Name");
assertTrue(headerCell.isPresent());
headerCell.get().clickSort();
assertTrue(headerCell.get().isSortAscending());
headerCell.get().clickSort();
assertTrue(headerCell.get().isSortDescending());
headerCell.get().clickSort();
assertTrue(headerCell.get().isNotSorted());
// Or go straight to a direction (at most two clicks, waits for the grid to load)
headerCell.get().sort(GridElement.SortDirection.DESCENDING);
headerCell.get().assertSortDirection(GridElement.SortDirection.DESCENDING);
sort, getSortDirection and clickSort throw IllegalStateException on a
header cell without a sorter, such as a joined group cell.
Row Details¶
GridElement grid = GridElement.getById(page, "details-grid");
var row = grid.findRow(0);
assertTrue(row.isPresent());
row.get().openDetails();
assertTrue(row.get().isDetailsOpen());
row.get().closeDetails();
assertFalse(row.get().isDetailsOpen());
Component Renderer¶
GridElement grid = GridElement.getById(page, "component-grid");
var actionCell = grid.findCell(0, "Action");
assertTrue(actionCell.isPresent());
Locator button = actionCell.get().getCellContent().locator("vaadin-button");
assertThat(button).hasText("Click First1");
button.click();
LitRenderer¶
GridElement grid = GridElement.getById(page, "lit-renderer-grid");
// Read text from a LitRenderer cell (auto-scrolls if needed)
var firstNameCell = grid.findCell(0, "First Name");
assertTrue(firstNameCell.isPresent());
assertThat(firstNameCell.get().getCellContent().locator(".badge")).hasText("First1");
// Click a LitRenderer button
var actionCell = grid.findCell(0, "Action");
assertTrue(actionCell.isPresent());
actionCell.get().getCellContent().locator("vaadin-button").click();
Lazy Loading¶
GridElement grid = GridElement.getById(page, "lazy-grid");
assertEquals(10000, grid.getTotalRowCount());
// findRow auto-scrolls to row 9000 before returning
var row = grid.findRow(9000);
assertTrue(row.isPresent());
assertThat(row.get().getCell(0).getCellContent()).hasText("First9001");
Searching Across Rows¶
GridElement grid = GridElement.getById(page, "my-grid");
// Find all row indexes where the "Status" column contains "Active"
List<Integer> activeRows = grid.findRowIndexesWithColumnText(2, "Active");
Related Elements¶
VirtualListElement- Virtualized scrollable list (simpler, no columns)