v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution

- Frontend v4 accessible sur réseau local (192.168.1.40)
- Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard)
- Ollama GPU fonctionnel
- Self-healing interactif
- Dashboard confiance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-29 11:23:51 +01:00
parent 21bfa3b337
commit a27b74cf22
1595 changed files with 412691 additions and 400 deletions

View File

@@ -0,0 +1,296 @@
#!/usr/bin/env python3
"""
Test Action Execution - Phase 6
Tests the ActionExecutor and TargetResolver with synthetic data.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import logging
from dataclasses import dataclass
from core.models.screen_state import ScreenState, RawLevel, PerceptionLevel
from core.models.ui_element import UIElement, UIElementEmbeddings, VisualFeatures
from core.models.workflow_graph import (
WorkflowEdge, Action, ActionType, TargetSpec, WindowConstraint
)
from core.execution.action_executor import ActionExecutor
from core.execution.target_resolver import TargetResolver
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_test_screen_state() -> ScreenState:
"""Create a synthetic ScreenState for testing."""
# Create UI elements
ui_elements = [
UIElement(
element_id="btn_submit",
type="button",
bbox=(100, 100, 200, 150),
center=(150, 125),
label="Submit",
label_confidence=0.95,
role="submit_button",
confidence=0.95,
embeddings=UIElementEmbeddings(),
visual_features=VisualFeatures(
dominant_color="blue",
has_icon=False,
shape="rectangle",
size_category="medium"
)
),
UIElement(
element_id="input_email",
type="text_input",
bbox=(100, 50, 300, 80),
center=(200, 65),
label="",
label_confidence=0.92,
role="email_input",
confidence=0.92,
embeddings=UIElementEmbeddings(),
visual_features=VisualFeatures(
dominant_color="white",
has_icon=False,
shape="rectangle",
size_category="medium"
)
),
UIElement(
element_id="btn_cancel",
type="button",
bbox=(220, 100, 320, 150),
center=(270, 125),
label="Cancel",
label_confidence=0.90,
role="cancel_button",
confidence=0.90,
embeddings=UIElementEmbeddings(),
visual_features=VisualFeatures(
dominant_color="gray",
has_icon=False,
shape="rectangle",
size_category="medium"
)
)
]
# Create ScreenState
raw = RawLevel(
screenshot_path="test_screenshot.png",
capture_method="test",
file_size_bytes=1024
)
perception = PerceptionLevel(
ui_elements=ui_elements,
ocr_text="Submit Cancel",
detected_at=1234567890.0
)
return ScreenState(
id="test_state_1",
raw=raw,
perception=perception
)
def test_target_resolver():
"""Test TargetResolver with different strategies."""
logger.info("\n=== Testing TargetResolver ===")
screen_state = create_test_screen_state()
resolver = TargetResolver()
# Test 1: Resolve by role
logger.info("\nTest 1: Resolve by role (submit_button)")
target_spec = TargetSpec(
by_role="submit_button",
selection_policy="first"
)
result = resolver.resolve_target(target_spec, screen_state)
if result:
logger.info(f"✓ Resolved: {result.element.text} (confidence={result.confidence:.2f})")
logger.info(f" Strategy: {result.strategy_used}, Fallback: {result.fallback_applied}")
else:
logger.error("✗ Failed to resolve target")
# Test 2: Resolve by text
logger.info("\nTest 2: Resolve by text (Cancel)")
target_spec = TargetSpec(
by_text="Cancel",
selection_policy="first"
)
result = resolver.resolve_target(target_spec, screen_state)
if result:
logger.info(f"✓ Resolved: {result.element.text} (confidence={result.confidence:.2f})")
logger.info(f" Strategy: {result.strategy_used}, Fallback: {result.fallback_applied}")
else:
logger.error("✗ Failed to resolve target")
# Test 3: Resolve by position
logger.info("\nTest 3: Resolve by position (150, 125)")
target_spec = TargetSpec(
by_position=(150, 125),
selection_policy="first"
)
result = resolver.resolve_target(target_spec, screen_state)
if result:
logger.info(f"✓ Resolved: {result.element.text} (confidence={result.confidence:.2f})")
logger.info(f" Strategy: {result.strategy_used}, Fallback: {result.fallback_applied}")
else:
logger.error("✗ Failed to resolve target")
# Test 4: Selection policy - last
logger.info("\nTest 4: Selection policy (last button)")
target_spec = TargetSpec(
by_role="cancel_button",
selection_policy="last"
)
result = resolver.resolve_target(target_spec, screen_state)
if result:
logger.info(f"✓ Resolved: {result.element.text} (confidence={result.confidence:.2f})")
else:
logger.error("✗ Failed to resolve target")
def test_action_executor_dry_run():
"""Test ActionExecutor without actually executing (dry run)."""
logger.info("\n=== Testing ActionExecutor (Dry Run) ===")
screen_state = create_test_screen_state()
executor = ActionExecutor(verify_postconditions=False)
# Test 1: Mouse click action
logger.info("\nTest 1: Mouse click action")
action = Action(
type=ActionType.MOUSE_CLICK,
target=TargetSpec(
by_role="submit_button",
selection_policy="first"
),
params={'wait_after_ms': 100}
)
edge = WorkflowEdge(
from_node="node_1",
to_node="node_2",
action=action
)
# Note: This will actually try to click if pyautogui is available
# In production, we'd mock pyautogui for testing
logger.info(" Action configured: mouse_click on submit_button")
logger.info(" (Skipping actual execution in test)")
# Test 2: Text input action
logger.info("\nTest 2: Text input action")
action = Action(
type=ActionType.TEXT_INPUT,
target=TargetSpec(
by_role="email_input",
selection_policy="first"
),
params={
'text': 'test@example.com',
'wait_after_ms': 100
}
)
edge = WorkflowEdge(
from_node="node_2",
to_node="node_3",
action=action
)
logger.info(" Action configured: text_input on email_input")
logger.info(" Text: test@example.com")
logger.info(" (Skipping actual execution in test)")
# Test 3: Window constraints
logger.info("\nTest 3: Window constraint validation")
window_constraint = WindowConstraint(title_contains='Test Form')
matches = window_constraint.matches('Test Form', 'test_process')
logger.info(f" Window constraint check: {'✓ PASS' if matches else '✗ FAIL'}")
# Test 4: Window constraint failure
logger.info("\nTest 4: Window constraint failure")
window_constraint_bad = WindowConstraint(title_contains='Wrong Title')
matches = window_constraint_bad.matches('Test Form', 'test_process')
logger.info(f" Window constraint check: {'✓ PASS' if matches else '✗ FAIL (expected)'}")
def test_compound_action():
"""Test compound action structure."""
logger.info("\n=== Testing Compound Action ===")
# Create compound action
sub_action1 = Action(
type=ActionType.MOUSE_CLICK,
target=TargetSpec(by_role="email_input", selection_policy="first")
)
sub_action2 = Action(
type=ActionType.TEXT_INPUT,
target=TargetSpec(by_role="email_input", selection_policy="first"),
params={'text': 'test@example.com'}
)
sub_action3 = Action(
type=ActionType.MOUSE_CLICK,
target=TargetSpec(by_role="submit_button", selection_policy="first")
)
compound = Action(
type=ActionType.COMPOUND,
target=TargetSpec(by_role="form", selection_policy="first"),
params={
'actions': [sub_action1, sub_action2, sub_action3],
'repeat_policy': 'all'
}
)
logger.info("Compound action created:")
logger.info(f" - {len(compound.params['actions'])} sub-actions")
logger.info(f" - Repeat policy: {compound.params['repeat_policy']}")
logger.info(" Steps:")
logger.info(" 1. Click email input")
logger.info(" 2. Type email")
logger.info(" 3. Click submit")
def main():
"""Run all tests."""
logger.info("=" * 60)
logger.info("Phase 6 - Action Execution Tests")
logger.info("=" * 60)
try:
test_target_resolver()
test_action_executor_dry_run()
test_compound_action()
logger.info("\n" + "=" * 60)
logger.info("✓ All tests completed successfully")
logger.info("=" * 60)
except Exception as e:
logger.error(f"\n✗ Test failed: {e}", exc_info=True)
return 1
return 0
if __name__ == '__main__':
sys.exit(main())