Automatically run guides and gather screenshots of the steps#139
Automatically run guides and gather screenshots of the steps#139TrentHouliston wants to merge 26 commits intomainfrom
Conversation
There was a problem hiding this comment.
Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit
ruff lint
Module level import not at top of file
haeo/tests/guides/sigenergy/test_sigenergy_guide.py
Lines 25 to 29 in 8df5fd4
print found
print found
Shebang is present but file is not executable
haeo/tests/guides/test_ha_runner.py
Line 1 in 8df5fd4
Import block is un-sorted or un-formatted
haeo/tests/guides/test_ha_runner.py
Lines 7 to 8 in 8df5fd4
Module level import not at top of file
haeo/tests/guides/test_ha_runner.py
Line 14 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 19 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 22 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 23 in 8df5fd4
import should be at the top-level of a file
haeo/tests/guides/test_ha_runner.py
Line 26 in 8df5fd4
Import block is un-sorted or un-formatted
haeo/tests/guides/test_ha_runner.py
Lines 26 to 27 in 8df5fd4
import should be at the top-level of a file
haeo/tests/guides/test_ha_runner.py
Line 27 in 8df5fd4
Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.
haeo/tests/guides/test_ha_runner.py
Line 30 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 31 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 33 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 36 in 8df5fd4
Missing return type annotation for private function check_states
haeo/tests/guides/test_ha_runner.py
Line 61 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 67 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 68 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 71 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 73 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 76 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 82 in 8df5fd4
import should be at the top-level of a file
haeo/tests/guides/test_ha_runner.py
Line 85 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 87 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 91 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 100 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 103 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 105 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 115 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 116 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 117 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 125 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 135 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 141 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 143 in 8df5fd4
print found
haeo/tests/guides/test_ha_runner.py
Line 146 in 8df5fd4
❌ 2 Tests Failed:
View the top 2 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
- Delete singlefile_capture.py (SingleFile JS injection for HTML snapshots) - Remove single-file-cli from package.json devDependencies - Strip HTML capture code from run_guide.py - Keep PNG screenshot capture using Playwright's native screenshot() - Add .gitignore for guide test outputs (logs, screenshots)
Resolved conflicts: - pyproject.toml: Keep both playwright and networkx dependencies - docs/user-guide/examples/sigenergy-system.md: Accept main version (better formatting) - uv.lock: Regenerated with all dependencies
- Add dark mode parameter to test and run_guide - Update ha_runner inject_auth to set theme preference - Add aiohttp NotAppKeyWarning suppression for compatibility - Update field labels to match current translations (en.json): - Hub: 'System Name' instead of 'Network Name' - Integration search uses 'HAEO' - Battery/Solar/Grid/Load field labels aligned with translations - Fix two-step config flows: - Grid: submit step 1 for entities, step 2 for limits - Load: use 'HAEO Configurable' entity and step 2 for constant value - Battery: add wait and check for step 2 values form - Fix close_element_dialog to try multiple button names and wait properly - Add LONG_WAIT after submit for async processing
- Use outline + box-shadow for element highlighting (not clipped by Shadow DOM) - Add z-index and position adjustments for visibility above siblings - Handle ha-integration-list-item, role=listitem, role=option elements - Add capture_name support to add_another_entity() function - Skip fill_textbox when field already contains target value - Capture all solar forecast selections (4 total) - Capture all grid price entity selections (2 import, 2 export) Addresses feedback: - Screenshots now captured for all entity selections - 'Add entity' button clicks are now captured - Highlighting surrounds elements properly via outline - Pre-filled name fields are skipped
There was a problem hiding this comment.
Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit
ruff lint
import should be at the top-level of a file
haeo/tests/guides/test_ha_runner.py
Line 27 in 2f847ce
Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.
haeo/tests/guides/test_ha_runner.py
Line 30 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 31 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 33 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 36 in 2f847ce
Missing return type annotation for private function check_states
haeo/tests/guides/test_ha_runner.py
Line 61 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 67 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 68 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 71 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 73 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 76 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 82 in 2f847ce
import should be at the top-level of a file
haeo/tests/guides/test_ha_runner.py
Line 85 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 87 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 91 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 100 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 103 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 105 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 115 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 116 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 117 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 125 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 135 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 141 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 143 in 2f847ce
print found
haeo/tests/guides/test_ha_runner.py
Line 146 in 2f847ce
- Replace inline element styling with overlay positioned at bounding box - Use popover API for top-layer placement (avoids parent overflow clipping) - Focus elements before highlighting to update UI state - Target label.mdc-text-field for text input highlighting - Target .combo-box-row for entity picker item highlighting - Capture element border-radius for rounded overlay corners - Remove unused _ensure_click_indicator_styles and _CLICK_INDICATOR_STYLE
- Remove debug_ha.py (standalone debug script) - Remove test_ha_runner.py (manual test script not needed in CI) - Add .ruff.toml to ignore intentional patterns (late imports, etc.) - Fix type annotations in conftest.py, ha_runner.py, test_sigenergy_guide.py - Replace print statements with logging - Format all files with ruff
- Extract JavaScript click indicator to tests/guides/js/click_indicator.js for better maintainability - Remove blanket .ruff.toml ignore file; add inline noqa comments with explanations for intentional late imports (PLC0415) - Convert asyncio.sleep loop pattern to proper asyncio.Event signaling: - Use asyncio.Event instead of threading.Event for stop signal - Signal via loop.call_soon_threadsafe() for thread-safe shutdown - Remove sys.path manipulation from run_guide.py and test_sigenergy_guide.py; imports work correctly without it using fully qualified paths - Add pyright:ignore[reportAttributeAccessIssue] for async_add_auth call where Home Assistant type stubs don't include the subclass method
Two-layer architecture: - HAPage: Low-level HA UI primitives (may change between HA versions) - HAEO primitives: High-level element functions accepting schema TypedDicts Features: - Screenshot capture with click indicators - Form interactions (textbox, spinbutton, combobox) - Entity picker dialog handling - Integration search and setup
- Add guide.py using HAPage primitives (~370 lines vs ~1000) - Delete run_guide.py (superseded by guide.py) - Update test import to use new guide module Keeps Sigenergy-specific entity names and search terms in guide.py while delegating HA UI interactions to the primitives package.
JavaScript for click indicator overlay is now inlined as _CLICK_INDICATOR_JS constant to avoid file loading issues and keep related code together.
- haeo.py: Add typed config dataclasses (InverterConfig, BatteryConfig, etc.) with Entity tuples for (search_term, display_name) - guide.py: Now just defines Sigenergy config data and calls primitives (~170 lines vs ~370 lines) - Primitives handle all HA UI interactions - Guide just specifies what to configure, not how
- Add ScreenshotContext with OrderedDict and stack-based naming - Add @guide_step decorator for automatic scope management - Move click indicator JS to external file (primitives/js/) - Always capture screenshots when context is active - HAPage methods use context.scope() for hierarchical names - HAEO primitives all decorated with @guide_step - Guide.py uses inline arguments for walkthrough style - Screenshots named like: add_grid.entity_Import_Price.search_results - Rename screenshots.py to capture.py (avoid gitignore pattern)
| "ha-list-item, mwc-list-item, md-list-item, " + | ||
| '[role="listitem"], [role="option"]' |
There was a problem hiding this comment.
[prettier] reported by reviewdog 🐶
| "ha-list-item, mwc-list-item, md-list-item, " + | |
| '[role="listitem"], [role="option"]' | |
| "ha-list-item, mwc-list-item, md-list-item, " + '[role="listitem"], [role="option"]' |
| const listItemParent = entityListItem.closest( | ||
| "ha-list-item, mwc-list-item, md-list-item, md-item" | ||
| ); |
There was a problem hiding this comment.
[prettier] reported by reviewdog 🐶
| const listItemParent = entityListItem.closest( | |
| "ha-list-item, mwc-list-item, md-list-item, md-item" | |
| ); | |
| const listItemParent = entityListItem.closest("ha-list-item, mwc-list-item, md-list-item, md-item"); |
| const haWrapper = roleItem.closest( | ||
| "ha-list-item, md-item, mwc-list-item, .combo-box-row" | ||
| ); |
There was a problem hiding this comment.
[prettier] reported by reviewdog 🐶
| const haWrapper = roleItem.closest( | |
| "ha-list-item, md-item, mwc-list-item, .combo-box-row" | |
| ); | |
| const haWrapper = roleItem.closest("ha-list-item, md-item, mwc-list-item, .combo-box-row"); |
| from tests.guides.primitives.ha_page import HAPage | ||
| from tests.guides.primitives.haeo import ( | ||
| Entity, | ||
| add_battery, | ||
| add_grid, | ||
| add_integration, | ||
| add_inverter, | ||
| add_load, | ||
| add_node, | ||
| add_solar, | ||
| login, | ||
| verify_setup, | ||
| ) | ||
| from tests.guides.primitives.capture import ( | ||
| ScreenshotContext, | ||
| guide_step, | ||
| screenshot_context, | ||
| ) | ||
|
|
||
| __all__ = [ |
There was a problem hiding this comment.
Import block is un-sorted or un-formatted
| from tests.guides.primitives.ha_page import HAPage | |
| from tests.guides.primitives.haeo import ( | |
| Entity, | |
| add_battery, | |
| add_grid, | |
| add_integration, | |
| add_inverter, | |
| add_load, | |
| add_node, | |
| add_solar, | |
| login, | |
| verify_setup, | |
| ) | |
| from tests.guides.primitives.capture import ( | |
| ScreenshotContext, | |
| guide_step, | |
| screenshot_context, | |
| ) | |
| __all__ = [ | |
| from tests.guides.primitives.capture import ScreenshotContext, guide_step, screenshot_context | |
| from tests.guides.primitives.ha_page import HAPage | |
| from tests.guides.primitives.haeo import ( | |
| Entity, | |
| add_battery, | |
| add_grid, | |
| add_integration, | |
| add_inverter, | |
| add_load, | |
| add_node, | |
| add_solar, | |
| login, | |
| verify_setup, | |
| ) | |
| __all__ = [ |
| __all__ = [ | ||
| # Screenshot context | ||
| "ScreenshotContext", | ||
| "guide_step", | ||
| "screenshot_context", | ||
| # Low-level primitives | ||
| "HAPage", | ||
| # Entity type | ||
| "Entity", | ||
| # HAEO element primitives | ||
| "add_battery", | ||
| "add_grid", | ||
| "add_integration", | ||
| "add_inverter", | ||
| "add_load", | ||
| "add_node", | ||
| "add_solar", | ||
| "login", | ||
| "verify_setup", | ||
| ] |
There was a problem hiding this comment.
__all__ is not sorted
| __all__ = [ | |
| # Screenshot context | |
| "ScreenshotContext", | |
| "guide_step", | |
| "screenshot_context", | |
| # Low-level primitives | |
| "HAPage", | |
| # Entity type | |
| "Entity", | |
| # HAEO element primitives | |
| "add_battery", | |
| "add_grid", | |
| "add_integration", | |
| "add_inverter", | |
| "add_load", | |
| "add_node", | |
| "add_solar", | |
| "login", | |
| "verify_setup", | |
| ] | |
| __all__ = [ | |
| # Entity type | |
| "Entity", | |
| # Low-level primitives | |
| "HAPage", | |
| # Screenshot context | |
| "ScreenshotContext", | |
| # HAEO element primitives | |
| "add_battery", | |
| "add_grid", | |
| "add_integration", | |
| "add_inverter", | |
| "add_load", | |
| "add_node", | |
| "add_solar", | |
| "guide_step", | |
| "login", | |
| "screenshot_context", | |
| "verify_setup", | |
| ] |
| from dataclasses import dataclass, field | ||
| from functools import wraps | ||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING, Any, Callable, TypeVar |
| # ctx.screenshots contains all captured screenshots | ||
|
|
||
| """ | ||
| global _current_context |
| # ctx.screenshots contains all captured screenshots | ||
|
|
||
| """ | ||
| global _current_context |
| _current_context = previous | ||
|
|
||
|
|
||
| def guide_step(func: F) -> F: |
| """Decorator for HAEO primitive functions. | ||
|
|
||
| Wraps the function to push its name onto the screenshot context stack, | ||
| so all screenshots taken within the function get hierarchical names. | ||
|
|
||
| Usage: | ||
| @guide_step | ||
| def add_battery(page: HAPage, name: str, ...): | ||
| page.fill_textbox("Battery Name", name) # → "add_battery.fill_textbox.Battery_Name" | ||
| ... | ||
|
|
||
| """ |
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from collections import OrderedDict | ||
| from pathlib import Path | ||
| import shutil | ||
| import sys | ||
| from typing import Any | ||
|
|
||
| from playwright.sync_api import sync_playwright | ||
|
|
||
| from tests.guides.ha_runner import LiveHomeAssistant, live_home_assistant | ||
| from tests.guides.primitives import ( | ||
| HAPage, | ||
| add_battery, | ||
| add_grid, | ||
| add_integration, | ||
| add_inverter, | ||
| add_load, | ||
| add_solar, | ||
| login, | ||
| screenshot_context, | ||
| verify_setup, | ||
| ) | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) |
There was a problem hiding this comment.
Import block is un-sorted or un-formatted
| from __future__ import annotations | |
| import logging | |
| from collections import OrderedDict | |
| from pathlib import Path | |
| import shutil | |
| import sys | |
| from typing import Any | |
| from playwright.sync_api import sync_playwright | |
| from tests.guides.ha_runner import LiveHomeAssistant, live_home_assistant | |
| from tests.guides.primitives import ( | |
| HAPage, | |
| add_battery, | |
| add_grid, | |
| add_integration, | |
| add_inverter, | |
| add_load, | |
| add_solar, | |
| login, | |
| screenshot_context, | |
| verify_setup, | |
| ) | |
| _LOGGER = logging.getLogger(__name__) | |
| from __future__ import annotations | |
| from collections import OrderedDict | |
| import logging | |
| from pathlib import Path | |
| import shutil | |
| import sys | |
| from typing import Any | |
| from playwright.sync_api import sync_playwright | |
| from tests.guides.ha_runner import LiveHomeAssistant, live_home_assistant | |
| from tests.guides.primitives import ( | |
| HAPage, | |
| add_battery, | |
| add_grid, | |
| add_integration, | |
| add_inverter, | |
| add_load, | |
| add_solar, | |
| login, | |
| screenshot_context, | |
| verify_setup, | |
| ) | |
| _LOGGER = logging.getLogger(__name__) |
| from typing import Any | ||
|
|
Guides can get out of date super easily as things change.
These changes are working towards making sure that any guides we make in the future are self executing and can be self healing with the help of AI MCP rules.
Also it lets us take screenshots of each step so that we can make pretty step by step instructions without having to gather all those screenshots ourselves!