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']}")