- 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>
187 lines
8.3 KiB
Python
187 lines
8.3 KiB
Python
"""
|
|
Tests d'intégration pour la Fiche #14 - Screen signature + Cross-frame Target Memory
|
|
|
|
Auteur : Dom, Alice Kiro - 20 décembre 2024
|
|
"""
|
|
from datetime import datetime
|
|
from core.execution.target_resolver import TargetResolver, ResolutionContext
|
|
from core.models.workflow_graph import TargetSpec
|
|
from core.models.screen_state import ScreenState, RawLevel, PerceptionLevel, ContextLevel, WindowContext, EmbeddingRef
|
|
from core.models.ui_element import UIElement, UIElementEmbeddings, VisualFeatures
|
|
|
|
def create_ui_element(eid, role, bbox, label="", etype="ui"):
|
|
"""Helper pour créer des éléments UI"""
|
|
return UIElement(
|
|
element_id=eid, type=etype, role=role, bbox=bbox,
|
|
center=(bbox[0]+bbox[2]//2, bbox[1]+bbox[3]//2),
|
|
label=label, label_confidence=1.0,
|
|
embeddings=UIElementEmbeddings(image=None, text=None),
|
|
visual_features=VisualFeatures(dominant_color="n/a", has_icon=False, shape="rectangle", size_category="medium"),
|
|
confidence=0.95, tags=[], metadata={}
|
|
)
|
|
|
|
def create_screen_state(elements, state_id="s", title="Test"):
|
|
"""Helper pour créer des screen states"""
|
|
return ScreenState(
|
|
screen_state_id=state_id,
|
|
timestamp=datetime.now(),
|
|
session_id="sess",
|
|
window=WindowContext(app_name="app", window_title=title, screen_resolution=[1920,1080]),
|
|
raw=RawLevel(screenshot_path="x", capture_method="test", file_size_bytes=1),
|
|
perception=PerceptionLevel(embedding=EmbeddingRef(provider="p", vector_id="v", dimensions=1),
|
|
detected_text=[], text_detection_method="none", confidence_avg=0.0),
|
|
context=ContextLevel(),
|
|
ui_elements=elements
|
|
)
|
|
|
|
def test_cross_frame_memory_integration():
|
|
"""Test d'intégration complet du système de mémoire cross-frame"""
|
|
resolver = TargetResolver()
|
|
|
|
# Scénario : formulaire avec un élément unique identifiable
|
|
|
|
# Frame 1: Capture initiale
|
|
ui1 = [
|
|
create_ui_element("lbl_user", "label", (100, 100, 80, 20), "Username"),
|
|
create_ui_element("inp_user", "input", (200, 95, 200, 30), "search_field", "text_input"), # Texte unique
|
|
create_ui_element("btn_login", "button", (200, 200, 100, 35), "Login", "button"),
|
|
]
|
|
s1 = create_screen_state(ui1, "s1", "Login Form")
|
|
|
|
# Spec unique qui identifie clairement l'élément
|
|
spec_user = TargetSpec(by_role="input", by_text="search_field")
|
|
res1 = resolver.resolve_target(spec_user, s1, ResolutionContext(screen_state=s1))
|
|
|
|
assert res1 is not None
|
|
assert res1.element.element_id == "inp_user"
|
|
|
|
# Vérifier que le cache est peuplé
|
|
cache_size_after_first = len(resolver._cross_frame_cache)
|
|
assert cache_size_after_first > 0, "Le cache devrait être peuplé après la première résolution"
|
|
|
|
# Frame 2: Même écran mais avec variations (OCR, IDs, micro-mouvements)
|
|
ui2 = [
|
|
create_ui_element("lbl_user_2", "label", (102, 98, 80, 20), "USER NAME"),
|
|
create_ui_element("inp_user_new", "input", (202, 93, 200, 30), "search_field", "text_input"), # Même texte
|
|
create_ui_element("btn_login_2", "button", (202, 198, 100, 35), "Login", "button"),
|
|
]
|
|
s2 = create_screen_state(ui2, "s2", "Login Form")
|
|
|
|
# Résolution avec mémoire cross-frame
|
|
res2 = resolver.resolve_target(spec_user, s2, ResolutionContext(screen_state=s2))
|
|
|
|
assert res2 is not None
|
|
assert res2.element.element_id == "inp_user_new" # Nouvel ID trouvé
|
|
# Le cache peut être utilisé même si la stratégie finale est composite
|
|
assert res2.confidence > 0.8 # Bonne confiance
|
|
|
|
# Frame 3: Variations plus importantes mais même écran logique
|
|
ui3 = [
|
|
create_ui_element("label_1", "label", (95, 105, 90, 18), "Username:"),
|
|
create_ui_element("field_1", "input", (195, 100, 210, 28), "search_field", "text_input"), # Même texte
|
|
create_ui_element("submit_btn", "button", (195, 205, 110, 32), "Sign In", "button"),
|
|
]
|
|
s3 = create_screen_state(ui3, "s3", "Login Page")
|
|
|
|
# Doit encore fonctionner
|
|
res3 = resolver.resolve_target(spec_user, s3, ResolutionContext(screen_state=s3))
|
|
|
|
assert res3 is not None
|
|
assert res3.element.element_id == "field_1"
|
|
assert res3.confidence > 0.8
|
|
|
|
# Vérifier que le cache continue de grandir
|
|
final_cache_size = len(resolver._cross_frame_cache)
|
|
assert final_cache_size >= cache_size_after_first, "Le cache devrait continuer à être utilisé"
|
|
|
|
def test_screen_signature_stability():
|
|
"""Test de stabilité des signatures d'écran"""
|
|
from core.execution.screen_signature import screen_signature
|
|
|
|
# Même contenu logique avec variations
|
|
ui1 = [
|
|
create_ui_element("a", "label", (100, 100, 120, 20), "Username"),
|
|
create_ui_element("b", "input", (240, 95, 260, 30), "", "text_input"),
|
|
]
|
|
|
|
ui2 = [
|
|
create_ui_element("a_new", "label", (102, 98, 118, 22), "USER NAME"), # Variations OCR + position
|
|
create_ui_element("b_new", "input", (242, 93, 258, 32), "", "text_input"),
|
|
]
|
|
|
|
s1 = create_screen_state(ui1, "s1", "Login")
|
|
s2 = create_screen_state(ui2, "s2", " LOGIN ") # Espaces supplémentaires
|
|
|
|
# Les signatures doivent être identiques en mode layout
|
|
sig1 = screen_signature(s1, ui1, mode="layout")
|
|
sig2 = screen_signature(s2, ui2, mode="layout")
|
|
|
|
assert sig1 == sig2, "Les signatures layout doivent être identiques malgré les variations"
|
|
|
|
# Mais différentes en mode text (plus strict)
|
|
sig1_text = screen_signature(s1, ui1, mode="text")
|
|
sig2_text = screen_signature(s2, ui2, mode="text")
|
|
|
|
assert sig1_text != sig2_text, "Les signatures text doivent être différentes avec variations de texte"
|
|
|
|
def test_cache_performance():
|
|
"""Test de performance du cache cross-frame"""
|
|
resolver = TargetResolver()
|
|
|
|
# Créer un écran avec beaucoup d'éléments
|
|
ui_elements = []
|
|
for i in range(100):
|
|
ui_elements.append(create_ui_element(f"elem_{i}", "button", (i*10, i*5, 50, 20), f"Button {i}"))
|
|
|
|
# Ajouter notre élément cible avec un texte unique
|
|
ui_elements.append(create_ui_element("target", "input", (500, 300, 200, 30), "unique_search", "text_input"))
|
|
ui_elements.append(create_ui_element("label", "label", (300, 305, 80, 20), "Search"))
|
|
|
|
s1 = create_screen_state(ui_elements, "s1")
|
|
spec = TargetSpec(by_role="input", by_text="unique_search") # Spec unique
|
|
|
|
# Première résolution (sans cache)
|
|
import time
|
|
start = time.perf_counter()
|
|
res1 = resolver.resolve_target(spec, s1, ResolutionContext(screen_state=s1))
|
|
time1 = time.perf_counter() - start
|
|
|
|
assert res1 is not None
|
|
assert res1.element.element_id == "target"
|
|
|
|
# Vérifier que le cache est peuplé
|
|
cache_size = len(resolver._cross_frame_cache)
|
|
assert cache_size > 0, "Le cache devrait être peuplé"
|
|
|
|
# Deuxième résolution avec variations (avec cache)
|
|
ui_elements_2 = []
|
|
for i in range(100):
|
|
ui_elements_2.append(create_ui_element(f"new_elem_{i}", "button", (i*10+2, i*5+1, 50, 20), f"Button {i}"))
|
|
|
|
ui_elements_2.append(create_ui_element("target_new", "input", (502, 298, 200, 30), "unique_search", "text_input"))
|
|
ui_elements_2.append(create_ui_element("label_new", "label", (302, 303, 80, 20), "Search"))
|
|
|
|
s2 = create_screen_state(ui_elements_2, "s2")
|
|
|
|
start = time.perf_counter()
|
|
res2 = resolver.resolve_target(spec, s2, ResolutionContext(screen_state=s2))
|
|
time2 = time.perf_counter() - start
|
|
|
|
assert res2 is not None
|
|
assert res2.element.element_id == "target_new"
|
|
# Le cache peut être utilisé même si la stratégie finale n'est pas CROSS_FRAME_CACHE
|
|
|
|
# Vérifier que les performances sont raisonnables
|
|
print(f"Time without cache: {time1:.4f}s, with cache: {time2:.4f}s")
|
|
# Note: Le test de performance peut varier, on vérifie juste que le cache est utilisé
|
|
assert time2 < time1 * 2.0, f"Cache should not be significantly slower: {time2:.4f}s vs {time1:.4f}s"
|
|
|
|
# Vérifier que le cache continue de fonctionner
|
|
final_cache_size = len(resolver._cross_frame_cache)
|
|
assert final_cache_size >= cache_size, "Le cache devrait continuer à être utilisé"
|
|
|
|
if __name__ == "__main__":
|
|
test_cross_frame_memory_integration()
|
|
test_screen_signature_stability()
|
|
test_cache_performance()
|
|
print("✅ Tous les tests d'intégration Fiche #14 passent !") |