Tables
Tables carry most of the data in a business app, and most of what a test checks. swing-ui-agent reads a table the way it is painted: rows in the order on screen, each cell as its renderer shows it. This page uses Order Desk's Orders table, its products table and its cart.
The examples run from the Order Desk folder, with UI="java -jar target/swing-ui-agent-agent.jar".
Read a table
Section titled “Read a table”read returns the columns and the rows as shown, in view order (after sorting and filtering). maxRows limits the rows:
$UI read '{"target":{"scope":["tab:Orders"],"role":"table"},"maxRows":3}'{"found":true,"node":{"ref":"e16","role":"table","state":["atTop","atBottom"], "columns":["Order","Date","Customer","Items","Total","Status"], "rows":[["ORD-1639","9 Oct 2026","Ada Shop Ltd","55","€95.52","Placed"], ["ORD-1638","7 Oct 2026","Café Lumen","8","€181.44","Placed"], ["ORD-1637","5 Oct 2026","Birch & Co","7","€141.36","Placed"]], "rowCount":12,"selected":[],"truncated":true, "scroll":{"x":0,"y":0,"width":1254,"height":566,"maxX":0,"maxY":0}}}Each cell is its renderer's text: €95.52, not the number behind it. A check box cell reads as its state. A renderer that builds a small panel reads as the text of its parts.
Which table
Section titled “Which table”- By its name: a table's caption, a label linked to it or a titled border,
{"role":"table","name":"Orders"}. In Java,ui.table("Orders"). - By its tab:
{"scope":["tab:Orders"],"role":"table"}when the tab holds one table. In Java,ui.tab("Orders").table(). - By a column only it has: a
columnwithout arownarrows to the table with that header. Order Desk's New order tab has two unnamed tables, the products and the cart; only the cart has a Qty column:
$UI read '{"target":{"scope":["tab:New order · Ada Shop Ltd"],"role":"table","column":"Qty"}}'{"found":true,"node":{"role":"table","columns":["Product","Qty","Price","Line total"], "rows":[["A4 paper, 500 sheets","3","€4.90","€14.70"],["Gel pen, black","10","€1.20","€12.00"]],"rowCount":2,"selected":[1]}}In Java, ui.tab("New order · Ada Shop Ltd").table().cell("Qty").
Rows and cells
Section titled “Rows and cells”- A row by its values:
"row":{"Order":"ORD-1640"}, one or two columns. It finds the row wherever sorting put it. In Java,.row("Order", "ORD-1640"), or.row(Rows.row("Customer", "Ada Shop Ltd", "Status", "Placed"))for two. - A row by position:
"row":{"index":0}, the first row on screen whatever its data. In Java,.rowAt(0). - A cell: a row plus a
column. In Java,.row(...).cell("Status"). - A header: a
columnwithout a row, as a target to click.
A row named by more than two columns runs, but export_test has no Java form for it.
Click, open and sort
Section titled “Click, open and sort”Click a cell to select its row. Order Desk's PlaceOrderUiIT selects products this way before Add to cart:
ui.tab("New order · Ada Shop Ltd").table().row("Product", "A4 paper, 500 sheets").cell("SKU").click();ui.tab("New order · Ada Shop Ltd").button("Add to cart").click();ui.tab("New order · Ada Shop Ltd").table().row("Product", "Gel pen, black").cell("SKU").click();ui.tab("New order · Ada Shop Ltd").button("Add to cart").click();- Open a row:
clickwith"count":2; in Java.doubleClick()or.open(). - Add to the selection:
"modifiers":["Mod"]; in Java.click("left", 0, "Mod"). - Right-click a row:
context_menuwith the row as target and the menu path (see Menus and popups); in Java.rightClick("Cancel orders…"). - Sort: click the header, as a user does:
$UI click '{"target":{"scope":["tab:Orders"],"role":"table","column":"Total"}}'The next read returns the rows in the new order. A row found by its values does not care.
Edit a cell
Section titled “Edit a cell”type on a cell clicks it, starts the edit, types and, with "commit":true, presses Enter:
$UI type '{"target":{"scope":["tab:New order · Ada Shop Ltd"],"role":"table","row":{"Product":"Gel pen, black"},"column":"Qty"},"text":"10","commit":true}'{"passed":true,"action":"type(table [tab:New order · Ada Shop Ltd] row={Product=Gel pen, black} column=Qty '10' +Enter)","message":"","ms":24}The Qty cell's editor is a spinner; type finds the text field inside it. In PlaceOrderUiIT:
ui.tab("New order · Ada Shop Ltd").table().row("Product", "Gel pen, black").cell("Qty") .type("10", null, Boolean.TRUE);- A check box cell:
checkwith"checked":trueorfalseclicks it only when it is not already in that state. In Java.check()and.uncheck(). - A value the editor refuses: the step fails
NOT_ACTIONABLE, the edit is cancelled and the cell keeps its last valid value; the message says which value it kept. If the app explains the refusal in a dialog, the dialog comes back aspending. When another cell's editor refuses to let go, typing into this cell failsNOT_ACTIONABLEand says which cell is still being edited. - Paste a block:
set_clipboardwith tab-separated cells and line-separated rows, click the anchor cell, thenpress {"keys":"Mod+V"}.
expect retries until the check holds (see Waiting). Give exactly one of these:
| Check | Locator part | Java |
|---|---|---|
| A cell's text | "cell":{"row":{"Order":"ORD-1640"},"column":"Status","expected":"Placed"} |
.hasCell(row("Order", "ORD-1640"), "Status", "Placed") |
| The row count | "rows":{"op":"gte","value":1} (eq, gt, gte, lt, lte) |
.hasRowCount("gte", 1) |
| Exactly these rows selected | "selected":[0] ([] for none) |
.hasSelectedRows(0) |
| A row selected | target with a row, "state":["selected"] |
assertThat(table.row(...)).isSelected() |
| A cell editable | target with a row and column, "state":["editable"] |
.isEditable() |
| Scrolled to the end | "state":["atBottom"] |
.hasStates("atBottom") |
PlaceOrderUiIT checks the new order's row on the Orders tab:
ui.tab("Orders").click();
assertThat(ui.tab("Orders").table().cell("Status")).hasCell(row("Order", order), "Status", "Placed");assertThat(ui.tab("Orders").table().cell("Status")).hasCell(row("Order", order), "Total", "€37.20");selected holds row positions in view order, so it changes when the table is sorted. Check a row by its values where you can.
Scroll
Section titled “Scroll”scroll moves the viewport directly, the way the scroll bar does: exact, and it fires the same change continuous paging listens for. Give exactly one of:
"to":"bottom"(ortop,left,right); in Java.scrollTo("bottom");"pages":2(negative goes back); in Java.scrollBy(2);- a
rowon the target, to bring that row into view; in Java.row(...).scrollIntoView().
$UI scroll '{"target":{"scope":["tab:New order"],"role":"table","column":"Stock"},"to":"bottom"}'$UI expect '{"target":{"scope":["tab:New order"],"role":"table","column":"Stock"},"state":["atBottom"]}'Use scroll rather than key presses such as Ctrl+End: those move the selection, not only the view.
Several columns with one header
Section titled “Several columns with one header”Some tables repeat a header, a pivot with one "Quantity" column per order, say. Tell them apart by the column's identifier: "column":"Quantity@ORD-1640", in Java .cell("Quantity", "ORD-1640").
What's next
Section titled “What's next”- Menus and popups: context menus on rows.
- Trees: the other element with rows.
Apache-2.0 · A Kormcrete project · For coding agents:llms.txt, llms-full.txt