diff --git a/visual_workflow_builder/backend/services/learned_workflow_bridge.py b/visual_workflow_builder/backend/services/learned_workflow_bridge.py index 2177a2031..d0c56da8f 100644 --- a/visual_workflow_builder/backend/services/learned_workflow_bridge.py +++ b/visual_workflow_builder/backend/services/learned_workflow_bridge.py @@ -166,11 +166,30 @@ def convert_learned_to_vwb_steps( ) continue + # Image d'ancre du parent : à poser sur le 1er substep cliquable. + # Les actions simples l'obtiennent déjà (branche `else` plus bas) ; + # les compound ne la propageaient pas → substeps anchor_id NULL. + compound_anchor_b64 = ( + target.get("anchor_image_base64") + or target.get("screenshot") + or (target.get("context_hints") or {}).get("anchor_image_base64") + ) + compound_anchor_attached = False + for sub_idx, sub in enumerate(sub_steps): sub_type = sub.get("type", "unknown") sub_vwb_type, sub_params = _convert_compound_substep( sub_type, sub, target ) + # Poser l'ancre du parent sur le 1er substep cliquable uniquement + # (éviter de la dupliquer sur les N substeps). + if ( + compound_anchor_b64 + and not compound_anchor_attached + and sub_vwb_type in ("click_anchor", "double_click_anchor", "right_click_anchor") + ): + sub_params["_anchor_image_base64"] = compound_anchor_b64 + compound_anchor_attached = True label = _build_step_label(sub_vwb_type, sub_params, from_name, to_name) steps.append({ "action_type": sub_vwb_type, diff --git a/visual_workflow_builder/backend/test_learned_workflow_bridge.py b/visual_workflow_builder/backend/test_learned_workflow_bridge.py new file mode 100644 index 000000000..d0608f690 --- /dev/null +++ b/visual_workflow_builder/backend/test_learned_workflow_bridge.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +Test TDD — propagation de l'image d'ancre aux substeps *compound* (SP-1 / U-B). + +Bug : `convert_learned_to_vwb_steps` ne propage `anchor_image_base64` qu'aux +actions simples (branche L226-233). Les substeps des actions *compound* +(majoritaires côté Léa) passent par `_convert_compound_substep` qui ne lit +jamais l'ancre → `anchor_id NULL` → "Ancre requise" sans image dans le VWB. + +Cible du fix : poser l'image d'ancre du parent sur le 1er substep cliquable. +""" + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +from services.learned_workflow_bridge import convert_learned_to_vwb_steps + +_CLICK_TYPES = ("click_anchor", "double_click_anchor", "right_click_anchor") + + +def _compound_workflow_with_anchor(b64: str) -> dict: + """Workflow core minimal : un edge compound dont la cible porte une image + d'ancre dans `context_hints`, avec un substep cliquable suivi d'une saisie.""" + return { + "workflow_id": "wf_test_compound_anchor", + "name": "Test compound anchor", + "entry_nodes": ["n1"], + "nodes": [ + {"node_id": "n1", "name": "Start"}, + {"node_id": "n2", "name": "End"}, + ], + "edges": [ + { + "edge_id": "e1", + "from_node": "n1", + "to_node": "n2", + "action": { + "type": "compound", + "target": { + "by_text": "Fichier", + "context_hints": {"anchor_image_base64": b64}, + }, + "parameters": { + "steps": [ + {"type": "mouse_click", "pos": [0.5, 0.5], "button": "left"}, + {"type": "text_input", "text": "bonjour"}, + ] + }, + }, + } + ], + } + + +def test_compound_substep_inherits_anchor_image(): + """Le 1er substep cliquable d'un compound doit hériter de l'image d'ancre du parent.""" + b64 = "ZmFrZV9hbmNob3I=" # "fake_anchor" encodé base64 + _meta, steps, _warnings = convert_learned_to_vwb_steps( + _compound_workflow_with_anchor(b64) + ) + + clickable = [s for s in steps if s["action_type"] in _CLICK_TYPES] + assert clickable, "le compound aurait dû produire au moins un step cliquable" + assert clickable[0]["parameters"].get("_anchor_image_base64") == b64, ( + "le 1er substep cliquable doit hériter de l'image d'ancre du parent " + "(target.context_hints.anchor_image_base64)" + )