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:
226
tests/unit/test_self_healing.py
Normal file
226
tests/unit/test_self_healing.py
Normal file
@@ -0,0 +1,226 @@
|
||||
"""Unit tests for self-healing workflows."""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from core.healing.healing_engine import SelfHealingEngine
|
||||
from core.healing.learning_repository import LearningRepository
|
||||
from core.healing.confidence_scorer import ConfidenceScorer
|
||||
from core.healing.models import RecoveryContext, RecoveryResult, RecoveryPattern
|
||||
from core.healing.strategies import (
|
||||
SemanticVariantStrategy,
|
||||
SpatialFallbackStrategy,
|
||||
TimingAdaptationStrategy,
|
||||
FormatTransformationStrategy
|
||||
)
|
||||
|
||||
|
||||
class TestConfidenceScorer:
|
||||
"""Tests for confidence scorer."""
|
||||
|
||||
def test_confidence_score_range(self):
|
||||
"""Confidence scores should be between 0 and 1."""
|
||||
scorer = ConfidenceScorer()
|
||||
|
||||
context = RecoveryContext(
|
||||
original_action='click',
|
||||
target_element='button',
|
||||
failure_reason='element_not_found',
|
||||
screenshot_path='/tmp/test.png',
|
||||
workflow_id='test_wf',
|
||||
node_id='node1',
|
||||
attempt_count=1
|
||||
)
|
||||
|
||||
confidence = scorer.calculate_recovery_confidence(
|
||||
'semantic_variant',
|
||||
context,
|
||||
0.5
|
||||
)
|
||||
|
||||
assert 0.0 <= confidence <= 1.0
|
||||
|
||||
def test_text_similarity(self):
|
||||
"""Text similarity should work correctly."""
|
||||
scorer = ConfidenceScorer()
|
||||
|
||||
# Exact match
|
||||
assert scorer._text_similarity('submit', 'submit') == 1.0
|
||||
|
||||
# Similar
|
||||
similarity = scorer._text_similarity('submit', 'send')
|
||||
assert 0.0 < similarity < 1.0
|
||||
|
||||
# Different
|
||||
similarity = scorer._text_similarity('submit', 'xyz')
|
||||
assert similarity < 0.5
|
||||
|
||||
|
||||
class TestLearningRepository:
|
||||
"""Tests for learning repository."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test repository."""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.repo = LearningRepository(Path(self.temp_dir))
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup test repository."""
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_store_and_retrieve_pattern(self):
|
||||
"""Should store and retrieve patterns."""
|
||||
context = RecoveryContext(
|
||||
original_action='click',
|
||||
target_element='button',
|
||||
failure_reason='element_not_found',
|
||||
screenshot_path='/tmp/test.png',
|
||||
workflow_id='test_wf',
|
||||
node_id='node1',
|
||||
attempt_count=1
|
||||
)
|
||||
|
||||
result = RecoveryResult(
|
||||
success=True,
|
||||
strategy_used='semantic_variant',
|
||||
confidence_score=0.85
|
||||
)
|
||||
|
||||
# Store pattern
|
||||
self.repo.store_pattern(context, result)
|
||||
|
||||
# Retrieve patterns
|
||||
patterns = self.repo.get_all_patterns()
|
||||
assert len(patterns) == 1
|
||||
assert patterns[0].recovery_strategy == 'semantic_variant'
|
||||
|
||||
def test_pattern_matching(self):
|
||||
"""Should match patterns correctly."""
|
||||
context1 = RecoveryContext(
|
||||
original_action='click',
|
||||
target_element='button1',
|
||||
failure_reason='element_not_found',
|
||||
screenshot_path='/tmp/test.png',
|
||||
workflow_id='test_wf',
|
||||
node_id='node1',
|
||||
attempt_count=1,
|
||||
metadata={'element_type': 'button'}
|
||||
)
|
||||
|
||||
result = RecoveryResult(
|
||||
success=True,
|
||||
strategy_used='semantic_variant',
|
||||
confidence_score=0.85
|
||||
)
|
||||
|
||||
self.repo.store_pattern(context1, result)
|
||||
|
||||
# Similar context
|
||||
context2 = RecoveryContext(
|
||||
original_action='click',
|
||||
target_element='button2',
|
||||
failure_reason='element_not_found',
|
||||
screenshot_path='/tmp/test2.png',
|
||||
workflow_id='test_wf',
|
||||
node_id='node2',
|
||||
attempt_count=1,
|
||||
metadata={'element_type': 'button'}
|
||||
)
|
||||
|
||||
matching = self.repo.get_matching_patterns(context2)
|
||||
assert len(matching) > 0
|
||||
|
||||
|
||||
class TestSemanticVariantStrategy:
|
||||
"""Tests for semantic variant strategy."""
|
||||
|
||||
def test_can_handle(self):
|
||||
"""Should handle element_not_found failures."""
|
||||
strategy = SemanticVariantStrategy()
|
||||
|
||||
context = RecoveryContext(
|
||||
original_action='click',
|
||||
target_element='button',
|
||||
failure_reason='element_not_found',
|
||||
screenshot_path='/tmp/test.png',
|
||||
workflow_id='test_wf',
|
||||
node_id='node1',
|
||||
attempt_count=1
|
||||
)
|
||||
|
||||
assert strategy.can_handle(context)
|
||||
|
||||
def test_get_semantic_variants(self):
|
||||
"""Should get semantic variants."""
|
||||
strategy = SemanticVariantStrategy()
|
||||
|
||||
variants = strategy._get_semantic_variants('submit')
|
||||
assert 'send' in variants
|
||||
assert 'ok' in variants
|
||||
assert 'confirm' in variants
|
||||
|
||||
|
||||
class TestSelfHealingEngine:
|
||||
"""Tests for self-healing engine."""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test engine."""
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.engine = SelfHealingEngine(storage_path=Path(self.temp_dir))
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup test engine."""
|
||||
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Engine should initialize correctly."""
|
||||
assert self.engine.learning_repo is not None
|
||||
assert self.engine.confidence_scorer is not None
|
||||
assert len(self.engine.recovery_strategies) > 0
|
||||
|
||||
def test_max_attempts_exceeded(self):
|
||||
"""Should fail when max attempts exceeded."""
|
||||
context = RecoveryContext(
|
||||
original_action='click',
|
||||
target_element='button',
|
||||
failure_reason='element_not_found',
|
||||
screenshot_path='/tmp/test.png',
|
||||
workflow_id='test_wf',
|
||||
node_id='node1',
|
||||
attempt_count=5,
|
||||
max_attempts=3
|
||||
)
|
||||
|
||||
result = self.engine.attempt_recovery(context)
|
||||
assert not result.success
|
||||
assert 'Max recovery attempts' in result.error_message
|
||||
|
||||
def test_learn_from_success(self):
|
||||
"""Should learn from successful recovery."""
|
||||
context = RecoveryContext(
|
||||
original_action='click',
|
||||
target_element='button',
|
||||
failure_reason='element_not_found',
|
||||
screenshot_path='/tmp/test.png',
|
||||
workflow_id='test_wf',
|
||||
node_id='node1',
|
||||
attempt_count=1
|
||||
)
|
||||
|
||||
result = RecoveryResult(
|
||||
success=True,
|
||||
strategy_used='semantic_variant',
|
||||
confidence_score=0.85
|
||||
)
|
||||
|
||||
self.engine.learn_from_success(context, result)
|
||||
|
||||
patterns = self.engine.learning_repo.get_all_patterns()
|
||||
assert len(patterns) > 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
Reference in New Issue
Block a user