Initial commit
This commit is contained in:
147
rpa_vision_v3/tests/integration/test_error_recovery.py
Normal file
147
rpa_vision_v3/tests/integration/test_error_recovery.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
Tests d'intégration pour la récupération d'erreurs
|
||||
|
||||
Teste les scénarios complets de gestion d'erreurs :
|
||||
- Récupération après échec de matching
|
||||
- Récupération après target introuvable avec fallbacks
|
||||
- Récupération après violation de post-conditions
|
||||
- Détection et gestion de changements UI
|
||||
- Rollback d'actions
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from unittest.mock import Mock, patch
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
from core.execution.error_handler import ErrorHandler, RecoveryStrategy
|
||||
from core.execution.action_executor import ActionExecutor, ExecutionStatus
|
||||
from core.execution.target_resolver import TargetResolver
|
||||
from core.graph.node_matcher import NodeMatcher
|
||||
from core.models.screen_state import ScreenState, RawLevel, PerceptionLevel
|
||||
from core.models.workflow_graph import WorkflowNode, WorkflowEdge, Action, ActionType
|
||||
from core.models.ui_element import UIElement
|
||||
from core.embedding.state_embedding_builder import StateEmbeddingBuilder
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_dirs():
|
||||
"""Créer des répertoires temporaires pour les tests."""
|
||||
error_dir = tempfile.mkdtemp()
|
||||
failed_matches_dir = tempfile.mkdtemp()
|
||||
yield error_dir, failed_matches_dir
|
||||
shutil.rmtree(error_dir)
|
||||
shutil.rmtree(failed_matches_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def error_handler(temp_dirs):
|
||||
"""Créer ErrorHandler pour les tests."""
|
||||
error_dir, _ = temp_dirs
|
||||
return ErrorHandler(error_log_dir=error_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def action_executor(error_handler):
|
||||
"""Créer ActionExecutor avec ErrorHandler."""
|
||||
target_resolver = TargetResolver()
|
||||
return ActionExecutor(
|
||||
target_resolver=target_resolver,
|
||||
error_handler=error_handler,
|
||||
verify_postconditions=True
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def node_matcher(error_handler, temp_dirs):
|
||||
"""Créer NodeMatcher avec ErrorHandler."""
|
||||
_, failed_matches_dir = temp_dirs
|
||||
embedding_builder = StateEmbeddingBuilder()
|
||||
return NodeMatcher(
|
||||
embedding_builder=embedding_builder,
|
||||
error_handler=error_handler,
|
||||
failed_matches_dir=failed_matches_dir
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_screen_state_with_button():
|
||||
"""Créer un ScreenState avec un bouton."""
|
||||
raw_level = RawLevel(
|
||||
window_title="Test App",
|
||||
screenshot_path=Path("/tmp/test.png"),
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
button = UIElement(
|
||||
element_id="btn_1",
|
||||
role="button",
|
||||
text="Submit",
|
||||
bbox=(100, 100, 200, 150)
|
||||
)
|
||||
|
||||
perception_level = PerceptionLevel(
|
||||
ui_elements=[button],
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
return ScreenState(
|
||||
raw_level=raw_level,
|
||||
perception_level=perception_level
|
||||
)
|
||||
|
||||
|
||||
class TestActionExecutorErrorRecovery:
|
||||
"""Tests de récupération d'erreurs dans ActionExecutor."""
|
||||
|
||||
def test_target_not_found_triggers_error_handler(
|
||||
self, action_executor
|
||||
):
|
||||
"""Test que target introuvable déclenche ErrorHandler."""
|
||||
# Créer un état sans le bouton attendu
|
||||
raw_level = RawLevel(
|
||||
window_title="Test App",
|
||||
screenshot_path=Path("/tmp/test.png"),
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
perception_level = PerceptionLevel(ui_elements=[], timestamp=datetime.now())
|
||||
screen_state = ScreenState(raw_level=raw_level, perception_level=perception_level)
|
||||
|
||||
action = Action(
|
||||
type=ActionType.MOUSE_CLICK,
|
||||
target=Mock(role="button", text_pattern="Submit")
|
||||
)
|
||||
edge = WorkflowEdge(from_node="node_1", to_node="node_2", action=action)
|
||||
|
||||
result = action_executor.execute_edge(edge=edge, screen_state=screen_state)
|
||||
|
||||
assert result.status == ExecutionStatus.TARGET_NOT_FOUND
|
||||
stats = action_executor.get_error_statistics()
|
||||
assert stats['total_errors'] >= 1
|
||||
|
||||
|
||||
class TestNodeMatcherErrorRecovery:
|
||||
"""Tests de récupération d'erreurs dans NodeMatcher."""
|
||||
|
||||
def test_matching_failure_triggers_error_handler(
|
||||
self, node_matcher, mock_screen_state_with_button
|
||||
):
|
||||
"""Test que l'échec de matching déclenche ErrorHandler."""
|
||||
node = WorkflowNode(node_id="node_1", label="Different State")
|
||||
node.matches = Mock(return_value=(False, 0.50))
|
||||
|
||||
result = node_matcher.match(
|
||||
current_state=mock_screen_state_with_button,
|
||||
candidate_nodes=[node]
|
||||
)
|
||||
|
||||
assert result is None
|
||||
stats = node_matcher.get_error_statistics()
|
||||
assert stats['total_errors'] >= 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
Reference in New Issue
Block a user