- Add PageUrlState utility for URL ↔ state serialization - Add column `id` field for unique column identification in URLs - Update idSelector to return String for URL compatibility - All DataGrid pages now support URL params: search, sort, order, id - Browser URL updates via replaceState (no GoRouter rebuilds) - Add FilterPanelSemantics for filter panel semantic IDs - Add TESTING.md documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
9.3 KiB
Testing & Automation Guide
This document covers automated testing patterns for Tatlock UI, focusing on semantic identifiers that enable reliable UI automation.
Overview
Tatlock UI uses Flutter's Semantics tree to expose stable identifiers for automated testing. These identifiers are accessible to:
- Puppeteer (via Chrome DevTools accessibility API)
- Appium (via accessibility labels)
- WebDriver (via ARIA attributes)
- Flutter integration tests
The semantics system is enabled on web in main.dart:
if (kIsWeb) {
SemanticsBinding.instance.ensureSemantics();
}
Semantic Identifiers
All semantic IDs are centralized in lib/core/semantics/semantic_ids.dart. This provides:
- Stable selectors - IDs don't change with UI refactoring
- Type safety - Compile-time verification of ID usage
- Discoverability - Single source of truth for automation targets
Available ID Classes
| Class | Purpose | Example IDs |
|---|---|---|
ProfileSemantics |
User profile dropdown | profile_button, profile_menu_settings |
RoomTabSemantics |
Main navigation tabs | roomTab_frontHall, roomTab_controlRoom |
NavSemantics |
Side navigation panel | nav_panel, nav_item_{id} |
DataGridSemantics |
Data tables | dataGrid_row_{id}, dataGrid_search |
DialogSemantics |
Modal dialogs | dialog_confirm, dialog_cancel |
SettingsSemantics |
Settings page | settings_theme, settings_defaultRoom |
StateSemantics |
Loading/error states | state_auth_loading, snackbar_{type} |
ID Naming Convention
{area}_{component}_{identifier}
- area: Feature or section (e.g.,
profile,nav,dataGrid) - component: Widget type (e.g.,
menu,button,row) - identifier: Specific item (e.g.,
light,settings,selectAll)
Examples:
profile_menu_theme_dark- Dark theme option in profile menudataGrid_row_abc123- Row with ID "abc123" in data gridnav_item_containers- Containers nav item
Adding Semantics to Widgets
Method 1: Direct Semantics Widget
Use Flutter's Semantics widget with the identifier property:
import 'package:tatlock_ui/core/semantics/semantic_ids.dart';
Semantics(
identifier: ProfileSemantics.button,
label: 'Open profile menu',
button: true,
child: IconButton(
icon: Icon(Icons.person),
onPressed: () => ...,
),
)
Method 2: SemanticWidget Wrapper
Use the convenience wrapper from lib/core/semantics/semantic_widget.dart:
import 'package:tatlock_ui/core/semantics/semantic_ids.dart';
import 'package:tatlock_ui/core/semantics/semantic_widget.dart';
SemanticWidget(
id: DataGridSemantics.search,
label: 'Search data grid',
textField: true,
child: TextField(
decoration: InputDecoration(hintText: 'Search...'),
),
)
Method 3: Extension Method
Use the withSemantics extension for inline wrapping:
TextField(
decoration: InputDecoration(hintText: 'Search...'),
).withSemantics(
id: DataGridSemantics.search,
label: 'Search data grid',
)
Dynamic IDs
For lists and grids, use the generator methods:
// Row in a data grid
Semantics(
identifier: DataGridSemantics.row(item.id), // "dataGrid_row_abc123"
child: DataGridRow(item: item),
)
// Navigation item
Semantics(
identifier: NavSemantics.item(route.id), // "nav_item_containers"
child: NavItem(route: route),
)
// Bulk action button
Semantics(
identifier: DataGridSemantics.bulkAction('delete'), // "dataGrid_bulk_delete"
child: IconButton(icon: Icon(Icons.delete), ...),
)
Querying from Puppeteer
Puppeteer can query semantic identifiers via Chrome's accessibility tree:
// Connect to Chrome with DevTools protocol
const browser = await puppeteer.connect({
browserURL: 'http://localhost:9222'
});
const page = await browser.newPage();
// Get accessibility snapshot
const snapshot = await page.accessibility.snapshot({ interestingOnly: false });
// Find element by semantic identifier
function findBySemanticId(node, id) {
if (node.name === id || node.description === id) {
return node;
}
for (const child of node.children || []) {
const found = findBySemanticId(child, id);
if (found) return found;
}
return null;
}
// Example: Find profile button
const profileButton = findBySemanticId(snapshot, 'profile_button');
// Example: Find a specific data grid row
const row = findBySemanticId(snapshot, 'dataGrid_row_abc123');
Using Chrome DevTools MCP
With the Chrome DevTools MCP server, you can query semantics directly:
// Take a snapshot (returns accessibility tree)
const snapshot = await mcp__chrome_devtools__take_snapshot();
// Click by semantic ID (uid in snapshot)
await mcp__chrome_devtools__click({ uid: 'profile_button' });
// Fill input by semantic ID
await mcp__chrome_devtools__fill({
uid: 'dataGrid_search',
value: 'my search query'
});
Best Practices
1. Add Semantics to Interactive Elements
Every clickable, tappable, or input element should have a semantic identifier:
// Buttons
Semantics(
identifier: 'myFeature_submit',
button: true,
label: 'Submit form',
child: ElevatedButton(...),
)
// Text fields
Semantics(
identifier: 'myFeature_email',
textField: true,
label: 'Email address',
child: TextField(...),
)
// Checkboxes
Semantics(
identifier: 'myFeature_rememberMe',
checked: isChecked,
label: 'Remember me',
child: Checkbox(...),
)
2. Use Meaningful Labels
Labels help both accessibility tools and test debugging:
// Good - descriptive label
Semantics(
identifier: DataGridSemantics.rowAction(item.id, 'delete'),
label: 'Delete ${item.name}',
button: true,
child: ...,
)
// Bad - no context
Semantics(
identifier: 'btn1',
child: ...,
)
3. Register New IDs Centrally
Always add new semantic IDs to semantic_ids.dart:
/// My new feature IDs.
abstract class MyFeatureSemantics {
static const submitButton = 'myFeature_submit';
static const cancelButton = 'myFeature_cancel';
static const nameField = 'myFeature_name';
/// Generate ID for a list item.
static String item(String id) => 'myFeature_item_$id';
}
4. Test ID Stability
Semantic IDs should remain stable across releases. When refactoring:
- Keep existing IDs unchanged
- Add deprecation comments if IDs must change
- Update automation tests when IDs change
5. Exclude Decorative Elements
Don't add semantic IDs to purely decorative elements:
// Decorative icon - no semantics needed
Icon(Icons.star, color: Colors.yellow)
// Interactive icon - needs semantics
Semantics(
identifier: 'rating_star_3',
button: true,
label: 'Rate 3 stars',
child: IconButton(
icon: Icon(Icons.star),
onPressed: () => rate(3),
),
)
DataGrid Semantic Patterns
The DataGrid component has comprehensive semantic coverage:
dataGrid - The grid container
dataGrid_search - Search input field
dataGrid_search_clear - Clear search button
dataGrid_selectAll - Select all checkbox
dataGrid_header_{columnId} - Column header (sortable)
dataGrid_row_{itemId} - Row container
dataGrid_row_{itemId}_checkbox - Row selection checkbox
dataGrid_row_{itemId}_actions - Row actions menu trigger
dataGrid_row_{itemId}_action_{actionId} - Specific row action
dataGrid_bulk_{actionId} - Bulk action button
dataGrid_bulk_clear - Clear selection button
dataGrid_loading - Loading indicator
dataGrid_empty - Empty state message
dataGrid_error - Error state message
dataGrid_refresh - Refresh button
Example: Automating DataGrid Selection
// Select all rows
await click('dataGrid_selectAll');
// Select specific row
await click('dataGrid_row_abc123_checkbox');
// Perform bulk delete
await click('dataGrid_bulk_delete');
// Confirm in dialog
await click('dialog_confirm');
Debugging Semantics
Flutter DevTools
- Open Flutter DevTools
- Go to "Inspector" tab
- Enable "Semantics" overlay
- Click widgets to see their semantic properties
Chrome DevTools
- Open DevTools (F12)
- Go to "Accessibility" tab
- Inspect the accessibility tree
- Search for semantic identifiers
Programmatic Inspection
// In a test, dump the semantics tree
debugDumpSemanticsTree();
// Check if semantics are enabled
print('Semantics enabled: ${SemanticsBinding.instance.semanticsEnabled}');
Integration with URL Routing
For deep-linkable test scenarios, semantic IDs work with URL query parameters:
/control-room/containers?selected=abc123
Automation can:
- Navigate to URL with query params
- Verify selection state via
dataGrid_row_abc123_checkbox(checked: true) - Interact with selected rows via semantic IDs
See URL Routing section for query parameter patterns.
Checklist for New Features
When adding a new feature, ensure semantic coverage:
- Add semantic ID class to
semantic_ids.dart - Wrap all buttons with
Semantics+identifier - Wrap all inputs with
Semantics+identifier - Wrap list/grid items with dynamic IDs
- Add labels for accessibility
- Test that IDs appear in accessibility snapshot
- Document IDs in this file if they establish new patterns