- 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>
138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
"""
|
|
Fiche #5 - Smoke Test E2E Minimal (dry-run)
|
|
|
|
Auteur: Dom, Alice Kiro - 15 décembre 2024
|
|
Objectif: Test ultra simple qui valide le chemin critique sans cliquer pour de vrai
|
|
|
|
Valide: ScreenState + UIElements → TargetResolver → ActionExecutor (dry-run)
|
|
=> Barrière anti-régression : si ça passe, tu as le droit de respirer !
|
|
"""
|
|
|
|
import pytest
|
|
|
|
# Marquer tous les tests de ce fichier comme smoke tests
|
|
pytestmark = pytest.mark.smoke
|
|
from datetime import datetime
|
|
|
|
from core.models.screen_state import ScreenState, RawLevel, PerceptionLevel, ContextLevel, WindowContext, EmbeddingRef
|
|
from core.models.ui_element import UIElement, UIElementEmbeddings, VisualFeatures
|
|
from core.models.workflow_graph import TargetSpec, Action, ActionType, WorkflowEdge, EdgeConstraints, PostConditions
|
|
from core.execution.target_resolver import TargetResolver
|
|
from core.execution.action_executor import ActionExecutor, ExecutionStatus
|
|
from core.execution.error_handler import ErrorHandler
|
|
|
|
|
|
def _mk_elem(eid, etype, role, x, y, w, h, label, conf=0.95):
|
|
"""Helper pour créer un UIElement rapidement"""
|
|
return UIElement(
|
|
element_id=eid,
|
|
type=etype,
|
|
role=role,
|
|
bbox=(x, y, w, h), # XYWH
|
|
center=(x + w // 2, y + h // 2),
|
|
label=label,
|
|
label_confidence=1.0,
|
|
embeddings=UIElementEmbeddings(image=None, text=None),
|
|
visual_features=VisualFeatures(
|
|
dominant_color="gray",
|
|
has_icon=False,
|
|
shape="rectangle",
|
|
size_category="medium",
|
|
),
|
|
confidence=conf,
|
|
tags=[],
|
|
metadata={},
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def screen_state_login():
|
|
"""ScreenState de test avec écran de login"""
|
|
ui = [
|
|
_mk_elem("lbl_user", "label", "data_display", 100, 100, 120, 20, "Username"),
|
|
_mk_elem("inp_user", "text_input", "form_input", 240, 95, 260, 30, ""),
|
|
_mk_elem("lbl_pass", "label", "data_display", 100, 160, 120, 20, "Password"),
|
|
_mk_elem("inp_pass", "text_input", "form_input", 240, 155, 260, 30, ""),
|
|
_mk_elem("btn_signin", "button", "submit", 240, 220, 140, 35, "Sign in"),
|
|
]
|
|
|
|
return ScreenState(
|
|
screen_state_id="ss_login_001",
|
|
timestamp=datetime.now(),
|
|
session_id="sess_001",
|
|
window=WindowContext(app_name="TestApp", window_title="Login", screen_resolution=[1920, 1080]),
|
|
raw=RawLevel(screenshot_path="N/A", capture_method="test", file_size_bytes=0),
|
|
perception=PerceptionLevel(
|
|
embedding=EmbeddingRef(provider="test", vector_id="N/A", dimensions=1),
|
|
detected_text=["Username", "Password", "Sign in"],
|
|
text_detection_method="test",
|
|
confidence_avg=1.0,
|
|
),
|
|
context=ContextLevel(user_id="test_user"),
|
|
ui_elements=ui,
|
|
)
|
|
|
|
|
|
def test_smoke_resolver_by_text(screen_state_login):
|
|
"""Test que TargetResolver trouve un élément par texte"""
|
|
r = TargetResolver()
|
|
t = TargetSpec(by_text="Sign in")
|
|
resolved = r.resolve_target(t, screen_state_login)
|
|
assert resolved is not None
|
|
assert resolved.element.element_id == "btn_signin"
|
|
|
|
|
|
def test_smoke_executor_click_dry_run(monkeypatch, tmp_path, screen_state_login):
|
|
"""Test ActionExecutor clic en mode dry-run (sans cliquer pour de vrai)"""
|
|
# Force no real click + no sleep
|
|
import core.execution.action_executor as ae
|
|
monkeypatch.setattr(ae, "PYAUTOGUI_AVAILABLE", False)
|
|
monkeypatch.setattr(ae.time, "sleep", lambda *_: None)
|
|
|
|
err = ErrorHandler(error_log_dir=str(tmp_path / "errors"))
|
|
ex = ActionExecutor(error_handler=err, verify_postconditions=False)
|
|
|
|
edge = WorkflowEdge(
|
|
edge_id="e1",
|
|
from_node="n0",
|
|
to_node="n1",
|
|
action=Action(
|
|
type=ActionType.MOUSE_CLICK,
|
|
target=TargetSpec(by_text="Sign in"),
|
|
parameters={"wait_after_ms": 0},
|
|
),
|
|
constraints=EdgeConstraints(),
|
|
post_conditions=PostConditions(),
|
|
)
|
|
|
|
res = ex.execute_edge(edge, screen_state_login)
|
|
assert res.status == ExecutionStatus.SUCCESS
|
|
assert res.target_resolved is not None
|
|
assert res.target_resolved.element.element_id == "btn_signin"
|
|
|
|
|
|
def test_smoke_executor_text_input_dry_run(monkeypatch, tmp_path, screen_state_login):
|
|
"""Test ActionExecutor saisie de texte en mode dry-run"""
|
|
import core.execution.action_executor as ae
|
|
monkeypatch.setattr(ae, "PYAUTOGUI_AVAILABLE", False)
|
|
monkeypatch.setattr(ae.time, "sleep", lambda *_: None)
|
|
|
|
err = ErrorHandler(error_log_dir=str(tmp_path / "errors"))
|
|
ex = ActionExecutor(error_handler=err, verify_postconditions=False)
|
|
|
|
edge = WorkflowEdge(
|
|
edge_id="e2",
|
|
from_node="n0",
|
|
to_node="n0",
|
|
action=Action(
|
|
type=ActionType.TEXT_INPUT,
|
|
target=TargetSpec(by_role="form_input", selection_policy="first"),
|
|
parameters={"text": "hello", "wait_after_ms": 0},
|
|
),
|
|
constraints=EdgeConstraints(),
|
|
post_conditions=PostConditions(),
|
|
)
|
|
|
|
res = ex.execute_edge(edge, screen_state_login)
|
|
assert res.status == ExecutionStatus.SUCCESS
|
|
assert res.target_resolved is not None |