Table Widget

This tutorial demonstrates the Table widget in Widgetastic.core using comprehensive examples from testing_page.html. You’ll learn to work with HTML tables, read data from rows and cells, handle embedded widgets, and manage complex table structures with merged cells.

Note

Prerequisites: Basic widgets tutorial Test Pages Used: testing/html/testing_page.html

Table Widget Fundamentals

The Table widget lets you work with HTML tables easily. Think of it as a way to read and interact with table data on web pages.

What is a Table Widget?

A table widget represents an HTML <table> element. It helps you: * Read data from table rows and cells * Fill in form inputs that are inside table cells * Find specific rows based on their content * Handle complex tables with merged cells

Let’s learn with three simple examples from the testing page.

Basic Table - Reading Data

The simplest use case: reading data from a standard table with headers.

 1"""Basic Table - Reading Data
 2
 3This example demonstrates reading data from a standard table with headers.
 4"""
 5
 6from widgetastic.widget import Table
 7
 8# Initialize the table widget
 9table = Table(parent=browser, locator="#with-thead")  # noqa: F821
10
11# Get table information
12print(f"Headers: {table.headers}")
13print(f"Number of rows: {len(list(table))}")
14
15# Read all rows
16print("All rows:")
17for row in table:
18    print([cell.text for header, cell in row])
19
20# Access a specific row (first row is index 0)
21first_row = table[0]
22print(f"First row data: {first_row.read()}")
23
24# Access a specific cell (row 0, column 0)
25cell = table[0][0]
26print(f"First cell text: {cell.text}")
27
28# Read the entire table as a list of dictionaries
29all_data = table.read()
30print(f"Table has {len(all_data)} rows")
31print(f"First row from read(): {all_data[0]}")

Accessing Cells in Different Ways

When you have a row, you can access cells in multiple ways:

 1"""Accessing Cells in Different Ways
 2
 3This example demonstrates multiple methods to access table cells.
 4"""
 5
 6from widgetastic.widget import Table
 7
 8table = Table(parent=browser, locator="#with-thead")  # noqa: F821
 9row = table[0]  # Get first row
10
11# Method 1: By index (0-based)
12cell = row[0]
13print(f"Cell by index [0]: {cell.text}")
14
15# Method 2: By column name (exact match)
16cell = row["Column 1"]
17print(f"Cell by column name 'Column 1': {cell.text}")
18
19# Method 3: By attributized name (Django-style)
20# Column names are automatically converted: "Column 1" -> "column_1"
21cell = row.column_1
22print(f"Cell by attributized name 'column_1': {cell.text}")
23
24# You can also click on cells
25print("Clicking on column_1 cell...")
26row.column_1.click()
27print("Cell clicked successfully")

Finding Rows by Content

You can search for rows that contain specific values using several methods:

 1"""Finding Rows by Content
 2
 3This example demonstrates different methods to search for specific rows.
 4"""
 5
 6from widgetastic.widget import Table
 7
 8table = Table(parent=browser, locator="#with-thead")  # noqa: F821
 9
10# Method 1: Keyword-based filtering (Django-style)
11print("Finding rows with keyword filters:")
12rows = list(table.rows(column_1="qwer"))
13print(f"Rows where Column 1 equals 'qwer': {len(rows)}")
14
15rows_containing = list(table.rows(column_2__contains="foo"))
16print(f"Rows with 'foo' in Column 2: {len(rows_containing)}")
17
18rows_starting = list(table.rows(column_2__startswith="bar"))
19print(f"Rows starting with 'bar' in Column 2: {len(rows_starting)}")
20
21rows_ending = list(table.rows(column_2__endswith="_y"))
22print(f"Rows ending with '_y' in Column 2: {len(rows_ending)}")
23
24# Find a single row (returns first match or raises RowNotFound)
25row = table.row(column_1="qwer")
26print(f"Single row where column_1='qwer': {row.read()}")
27
28# Method 2: Tuple-based filtering
29print("Tuple-based filtering:")
30rows = list(table.rows((0, "asdf")))
31print(f"Rows where column 0 equals 'asdf': {len(rows)}")
32
33rows = list(table.rows((1, "contains", "bar")))
34print(f"Rows where column 1 contains 'bar': {len(rows)}")
35
36# Method 3: Row attribute filtering
37print("Row attribute filtering:")
38rows = list(table.rows(_row__attr=("data-test", "abc-123")))
39print(f"Rows with data-test='abc-123': {len(rows)}")

Table with Embedded Widgets

Some tables have input fields (like text inputs) inside their cells. You need to tell the Table widget which columns contain widgets.

 1"""Table with Embedded Widgets
 2
 3This example demonstrates working with tables that have input fields in cells.
 4"""
 5
 6from widgetastic.widget import Table, TextInput
 7
 8# Create the table with column_widgets
 9# Tell the table which columns have widgets
10widget_table = Table(
11    parent=browser,  # noqa: F821
12    locator="#withwidgets",
13    column_widgets={
14        "Column 2": TextInput(locator="./input"),
15        "Column 3": TextInput(locator="./input"),
16    },
17)
18
19# Read the table (widgets are automatically read when present)
20data = widget_table.read()
21print(f"Table data: {data[0]}")
22
23# Access and fill a widget in a specific cell
24first_row = widget_table[0]
25column2_cell = first_row["Column 2"]
26
27# Fill the input
28column2_cell.widget.fill("new value")
29print(f"Value after filling: {column2_cell.widget.read()}")
30
31# The read() method handles this automatically
32print(f"Cell value using read(): {column2_cell.read()}")
33
34# Fill multiple rows at once
35print("Filling multiple rows:")
36widget_table.fill([{"Column 2": "value1"}, {"Column 3": "value3"}])
37print(f"After batch fill: {widget_table.read()}")

Complex Table with Merged Cells

Some tables have cells that span multiple rows or columns (rowspan/colspan). Widgetastic handles these automatically.

 1"""Complex Table with Merged Cells
 2
 3This example demonstrates handling tables with rowspan/colspan cells.
 4"""
 5
 6from widgetastic.widget import Table, TextInput
 7
 8# Create the table with column_widgets
 9complex_table = Table(
10    parent=browser,  # noqa: F821
11    locator="#rowcolspan_table",
12    column_widgets={
13        "Widget": TextInput(locator="./input"),
14    },
15)
16
17# Get headers
18print(f"Headers: {complex_table.headers}")
19
20# Read the table - merged cells are handled automatically
21data = complex_table.read()
22print(f"Table has {len(data)} rows")
23print(f"First row: {data[0]}")
24
25# Access and fill a widget in a merged cell
26row = complex_table[7]  # Access row 8
27widget_cell = row["Widget"]
28widget_cell.widget.fill("test value")
29print(f"Widget value after fill: {widget_cell.widget.read()}")

What Happens with Merged Cells?

When a cell spans multiple rows or columns, Widgetastic creates a TableReference that points to the original cell. This means: * You can still access all cells normally * Merged cells are handled transparently * Widgets in merged cells work just like regular cells

Associative Column Filling

When you have a table where one column uniquely identifies each row, you can use assoc_column to fill rows by their key value:

 1"""Associative Column Filling
 2
 3This example demonstrates filling rows using an associative column as a key.
 4"""
 5
 6from widgetastic.widget import Table, TextInput
 7
 8# The edge_case_test_table has a "Status" column with unique values
 9# Use "Status" column as the associative column
10status_table = Table(
11    parent=browser,  # noqa: F821
12    locator="#edge_case_test_table",
13    column_widgets={
14        "Input": TextInput(locator="./input"),
15    },
16    assoc_column="Status",
17)
18
19# Read the table - returns a dictionary keyed by Status value
20data = status_table.read()
21print("Table data (keyed by Status):")
22for status, row_data in data.items():
23    print(f"  {status}: {row_data}")
24
25# Fill rows by their Status value
26print("Filling rows by Status:")
27status_table.fill(
28    {"Active": {"Input": "new_active_value"}, "Inactive": {"Input": "new_inactive_value"}}
29)
30
31# Read back to verify
32updated_data = status_table.read()
33print(f"After fill - Active row Input: {updated_data['Active']['Input']}")
34print(f"After fill - Inactive row Input: {updated_data['Inactive']['Input']}")