# 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`: ```dart if (kIsWeb) { SemanticsBinding.instance.ensureSemantics(); } ``` ## Semantic Identifiers All semantic IDs are centralized in `lib/core/semantics/semantic_ids.dart`. This provides: 1. **Stable selectors** - IDs don't change with UI refactoring 2. **Type safety** - Compile-time verification of ID usage 3. **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 menu - `dataGrid_row_abc123` - Row with ID "abc123" in data grid - `nav_item_containers` - Containers nav item ## Adding Semantics to Widgets ### Method 1: Direct Semantics Widget Use Flutter's `Semantics` widget with the `identifier` property: ```dart 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`: ```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: ```dart TextField( decoration: InputDecoration(hintText: 'Search...'), ).withSemantics( id: DataGridSemantics.search, label: 'Search data grid', ) ``` ### Dynamic IDs For lists and grids, use the generator methods: ```dart // 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: ```javascript // 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: ```javascript // 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: ```dart // 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: ```dart // 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`: ```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: ```dart // 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 ```javascript // 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 1. Open Flutter DevTools 2. Go to "Inspector" tab 3. Enable "Semantics" overlay 4. Click widgets to see their semantic properties ### Chrome DevTools 1. Open DevTools (F12) 2. Go to "Accessibility" tab 3. Inspect the accessibility tree 4. Search for semantic identifiers ### Programmatic Inspection ```dart // 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: 1. Navigate to URL with query params 2. Verify selection state via `dataGrid_row_abc123_checkbox` (checked: true) 3. Interact with selected rows via semantic IDs See [URL Routing](#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