Files
rpa_vision_v3/.kiro/specs/visual-workflow-builder/design.md
Dom a7de6a488b feat: replay E2E fonctionnel — 25/25 actions, 0 retries, SomEngine via serveur
Validé sur PC Windows (DESKTOP-58D5CAC, 2560x1600) :
- 8 clics résolus visuellement (1 anchor_template, 1 som_text_match, 6 som_vlm)
- Score moyen 0.75, temps moyen 1.6s
- Texte tapé correctement (bonjour, test word, date, email)
- 0 retries, 2 actions non vérifiées (OK)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:04:41 +02:00

47 KiB

Document de Design: Visual Workflow Builder

Vue d'Ensemble

Le Visual Workflow Builder est une interface graphique révolutionnaire qui permet de créer des workflows RPA par glisser-déposer, sans écrire une seule ligne de code. Cette solution démocratise l'automatisation RPA en la rendant accessible aux utilisateurs non-techniques tout en conservant la puissance nécessaire aux développeurs experts.

Objectifs de Design

  1. Simplicité d'Usage: Interface intuitive inspirée de Scratch et Node-RED
  2. Puissance Technique: Accès à toutes les fonctionnalités RPA Vision V3
  3. Performance: Rendu fluide à 60fps même avec 100+ nodes
  4. Intégration: Compatibilité totale avec ExecutionLoop, Analytics, Self-Healing
  5. Extensibilité: Architecture modulaire pour ajouter facilement de nouveaux types de nodes

Principes de Design

  • WYSIWYG: Ce que vous voyez est ce que vous obtenez
  • Feedback Immédiat: Test et validation en temps réel
  • Tolérance aux Erreurs: Validation continue et récupération gracieuse
  • Accessibilité: Support clavier, screen readers, high contrast
  • Collaboration: Partage et versioning des workflows

Architecture Globale

Vue d'Ensemble de l'Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    Visual Workflow Builder                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │
│  │   Frontend      │  │   Backend API   │  │   Integration   │ │
│  │   (React/Vue)   │  │   (Flask)       │  │   Layer         │ │
│  │                 │  │                 │  │                 │ │
│  │ • Canvas        │  │ • Serialization │  │ • WorkflowGraph │ │
│  │ • Palette       │  │ • Validation    │  │ • ExecutionLoop │ │
│  │ • Properties    │  │ • Templates     │  │ • Analytics     │ │
│  │ • Target Select │  │ • Export/Import │  │ • Self-Healing  │ │
│  │ • Real-time     │  │ • Version Ctrl  │  │ • Storage       │ │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘ │
│           │                     │                     │         │
│           └─────────────────────┼─────────────────────┘         │
│                                 │                               │
├─────────────────────────────────┼─────────────────────────────────┤
│              Communication Layer (WebSocket + REST)            │
├─────────────────────────────────┼─────────────────────────────────┤
│                                 │                               │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │              RPA Vision V3 Core System                 │   │
│  │                                                         │   │
│  │  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐      │   │
│  │  │ExecutionLoop│ │ Analytics   │ │Self-Healing │      │   │
│  │  └─────────────┘ └─────────────┘ └─────────────┘      │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Couches Architecturales

1. Couche Présentation (Frontend)

  • Framework: React avec TypeScript
  • Canvas: react-flow-renderer pour le rendu des graphes
  • Drag & Drop: react-dnd pour les interactions
  • UI Components: Material-UI ou Ant Design
  • State Management: Redux Toolkit
  • Real-time: Socket.IO client

2. Couche API (Backend)

  • Framework: Flask avec Flask-SocketIO
  • Sérialisation: JSON Schema validation
  • Base de Données: SQLite pour les workflows, PostgreSQL pour production
  • Cache: Redis pour les sessions et templates
  • Files: Système de fichiers pour les exports

3. Couche Intégration

  • Conversion: Visual JSON → WorkflowGraph
  • Exécution: Interface avec ExecutionLoop
  • Monitoring: Hooks vers Analytics
  • Recovery: Intégration Self-Healing

Composants et Interfaces

1. Canvas Component

Le Canvas est le composant central où les utilisateurs construisent visuellement leurs workflows.

Responsabilités

  • Rendu des nodes et edges
  • Gestion des interactions (sélection, déplacement)
  • Zoom et panoramique
  • Grille d'alignement
  • Minimap pour navigation

Interface TypeScript

interface CanvasProps {
  nodes: VisualNode[];
  edges: VisualEdge[];
  onNodeSelect: (nodeId: string) => void;
  onNodeMove: (nodeId: string, position: Position) => void;
  onEdgeCreate: (source: string, target: string) => void;
  onEdgeDelete: (edgeId: string) => void;
  readonly?: boolean;
}

interface CanvasState {
  selectedNodes: string[];
  selectedEdges: string[];
  viewport: Viewport;
  dragState: DragState;
}

2. Node Palette

La palette affiche tous les types de nodes disponibles organisés par catégorie.

Responsabilités

  • Affichage des types de nodes disponibles
  • Recherche et filtrage
  • Catégorisation (Actions, Logic, Data, etc.)
  • Drag source pour création de nodes

Structure

interface NodeType {
  id: string;
  name: string;
  category: NodeCategory;
  icon: string;
  description: string;
  parameters: ParameterDefinition[];
  inputPorts: PortDefinition[];
  outputPorts: PortDefinition[];
}

enum NodeCategory {
  ACTION = 'action',      // Click, Type, Wait
  LOGIC = 'logic',        // If, Loop, Switch
  DATA = 'data',          // Variable, Extract, Transform
  FLOW = 'flow',          // Start, End, Subworkflow
  INTEGRATION = 'integration' // API, Database, File
}

3. Properties Panel

Le panneau de propriétés permet de configurer les paramètres de chaque node.

Responsabilités

  • Configuration des paramètres de nodes
  • Validation en temps réel
  • Target selector integration
  • Variable management
  • Help et documentation contextuelle

Composants

interface PropertyEditor {
  parameter: ParameterDefinition;
  value: any;
  onChange: (value: any) => void;
  onValidate: (value: any) => ValidationResult;
}

// Types d'éditeurs
class TextEditor extends PropertyEditor {}
class NumberEditor extends PropertyEditor {}
class BooleanEditor extends PropertyEditor {}
class SelectEditor extends PropertyEditor {}
class TargetEditor extends PropertyEditor {} // Sélection interactive
class VariableEditor extends PropertyEditor {}
class ExpressionEditor extends PropertyEditor {} // Conditions

4. Target Selector

Le sélecteur de cible permet de sélectionner interactivement des éléments UI à l'écran.

Workflow

class TargetSelector:
    async def start_selection(self) -> TargetInfo:
        """Démarre le processus de sélection interactive"""
        # 1. Capturer l'écran
        screenshot = await self.capture_screen()
        
        # 2. Entrer en mode sélection
        self.enter_selection_mode(screenshot)
        
        # 3. Attendre la sélection utilisateur
        element = await self.wait_for_user_selection()
        
        # 4. Extraire les propriétés
        target_info = await self.extract_element_properties(element)
        
        return target_info
    
    async def extract_element_properties(self, element) -> TargetInfo:
        """Extrait toutes les propriétés d'un élément"""
        return TargetInfo(
            text=element.text_content,
            tag_name=element.tag_name,
            class_name=element.class_name,
            position=self.get_element_position(element),
            size=self.get_element_size(element),
            embedding=await self.generate_clip_embedding(element)
        )

5. Execution Engine

Le moteur d'exécution convertit les workflows visuels en WorkflowGraph et les exécute.

Conversion Pipeline

class VisualToGraphConverter:
    def convert(self, visual_workflow: VisualWorkflow) -> WorkflowGraph:
        """Convertit un workflow visuel en WorkflowGraph"""
        # 1. Valider la structure
        self.validate_structure(visual_workflow)
        
        # 2. Convertir les nodes
        nodes = [self.convert_node(node) for node in visual_workflow.nodes]
        
        # 3. Convertir les edges
        edges = [self.convert_edge(edge) for edge in visual_workflow.edges]
        
        # 4. Créer le graph
        return WorkflowGraph(nodes, edges)
    
    def convert_node(self, visual_node: VisualNode) -> WorkflowNode:
        """Convertit un node visuel en WorkflowNode"""
        if visual_node.type == 'click':
            return ClickNode(
                target=visual_node.parameters['target'],
                timeout=visual_node.parameters.get('timeout', 5000),
                retries=visual_node.parameters.get('retries', 3)
            )
        elif visual_node.type == 'type':
            return TypeNode(
                target=visual_node.parameters['target'],
                text=visual_node.parameters['text'],
                clear_first=visual_node.parameters.get('clear_first', False)
            )
        # ... autres types

Modèles de Données

1. Visual Workflow

@dataclass
class VisualWorkflow:
    """Représentation complète d'un workflow visuel"""
    id: str
    name: str
    description: Optional[str]
    version: str
    created_at: datetime
    updated_at: datetime
    created_by: str
    
    # Structure visuelle
    nodes: List[VisualNode]
    edges: List[VisualEdge]
    
    # Configuration
    variables: List[Variable]
    settings: WorkflowSettings
    
    # Métadonnées
    tags: List[str]
    category: Optional[str]
    is_template: bool = False

2. Visual Node

@dataclass
class VisualNode:
    """Représentation d'un node dans le canvas"""
    id: str
    type: str  # 'click', 'type', 'wait', 'if', 'loop', etc.
    
    # Position visuelle
    position: Position
    size: Size
    
    # Configuration
    parameters: Dict[str, Any]
    
    # Connexions
    input_ports: List[Port]
    output_ports: List[Port]
    
    # État visuel
    selected: bool = False
    highlighted: bool = False
    status: Optional[NodeStatus] = None  # pendant l'exécution
    
    # Métadonnées
    label: Optional[str] = None
    description: Optional[str] = None
    color: Optional[str] = None

class NodeStatus(Enum):
    """États possibles d'un node pendant l'exécution"""
    IDLE = 'idle'
    RUNNING = 'running'
    SUCCESS = 'success'
    FAILED = 'failed'
    SKIPPED = 'skipped'

3. Visual Edge

@dataclass
class VisualEdge:
    """Représentation d'une connexion entre nodes"""
    id: str
    source: str  # node ID
    target: str  # node ID
    source_port: str
    target_port: str
    
    # Condition (pour les branches)
    condition: Optional[EdgeCondition] = None
    
    # Style visuel
    style: Optional[EdgeStyle] = None
    
    # État
    selected: bool = False
    animated: bool = False  # pendant l'exécution

@dataclass
class EdgeCondition:
    """Condition pour l'exécution d'un edge"""
    type: str  # 'always', 'success', 'failure', 'expression'
    expression: Optional[str] = None  # pour type 'expression'

4. Parameter Definitions

@dataclass
class ParameterDefinition:
    """Définition d'un paramètre de node"""
    name: str
    type: ParameterType
    required: bool
    default_value: Optional[Any] = None
    
    # Validation
    validation: Optional[List[ValidationRule]] = None
    
    # UI
    label: str = ""
    description: Optional[str] = None
    placeholder: Optional[str] = None
    
    # Comportement spécial
    is_target: bool = False  # Active le target selector
    is_variable: bool = False  # Permet ${variable}
    is_expression: bool = False  # Permet les expressions

class ParameterType(Enum):
    """Types de paramètres supportés"""
    STRING = 'string'
    NUMBER = 'number'
    BOOLEAN = 'boolean'
    SELECT = 'select'
    TARGET = 'target'
    VARIABLE = 'variable'
    EXPRESSION = 'expression'
    FILE = 'file'

5. Templates

@dataclass
class WorkflowTemplate:
    """Template de workflow pré-construit"""
    id: str
    name: str
    description: str
    category: str
    
    # Template data
    workflow: VisualWorkflow
    
    # Paramètres configurables
    parameters: List[TemplateParameter]
    
    # Métadonnées
    tags: List[str]
    difficulty: str  # 'beginner', 'intermediate', 'advanced'
    estimated_time: int  # minutes
    
    # Usage
    usage_count: int = 0
    rating: float = 0.0

@dataclass
class TemplateParameter:
    """Paramètre configurable d'un template"""
    name: str
    type: ParameterType
    description: str
    default_value: Optional[Any] = None
    
    # Mapping vers les nodes
    node_id: str
    parameter_name: str

Correctness Properties

Une propriété est une caractéristique ou un comportement qui doit être vrai pour toutes les exécutions valides d'un système - essentiellement, une déclaration formelle sur ce que le système doit faire. Les propriétés servent de pont entre les spécifications lisibles par l'humain et les garanties de correction vérifiables par machine.

Propriétés Structurelles

Propriété 1: Suppression de Node Cascade

Pour tout workflow et tout node, quand un node est supprimé, tous les edges connectés à ce node doivent également être supprimés automatiquement.

Valide: Exigences 1.5, 2.4

Propriété 2: Validation de Connexion

Pour tout edge créé entre deux nodes, la connexion doit être validée selon les règles de compatibilité des ports (type de sortie compatible avec type d'entrée).

Valide: Exigences 2.2

Propriété 3: Détection de Cycles

Pour tout workflow, si on ajoute un edge qui créerait un cycle (sauf pour les boucles explicites), le système doit détecter et rejeter cette connexion.

Valide: Exigences 12.4

Propriété 4: Nodes Déconnectés

Pour tout workflow, tous les nodes (sauf Start et End) doivent avoir au moins un edge entrant et un edge sortant, sinon un avertissement doit être affiché.

Valide: Exigences 12.3

Propriétés de Sérialisation

Propriété 5: Round-trip de Sérialisation

Pour tout workflow visuel, sérialiser puis désérialiser doit produire un workflow équivalent (même nodes, edges, paramètres, variables).

Valide: Exigences 5.1, 5.2

Propriété 6: Génération d'ID Unique

Pour tout nouveau workflow sans ID, la sauvegarde doit générer un ID unique qui n'existe pas déjà dans le système.

Valide: Exigences 5.4

Propriété 7: Validation de Champs Requis

Pour tout workflow lors de la sérialisation, tous les champs requis (id, name, nodes, edges) doivent être présents, sinon la sérialisation doit échouer.

Valide: Exigences 5.3

Propriétés de Configuration

Propriété 8: Validation de Paramètres Requis

Pour tout node, tous les paramètres marqués comme requis doivent avoir une valeur non-nulle, sinon le node doit afficher un indicateur d'avertissement.

Valide: Exigences 12.2

Propriété 9: Valeurs par Défaut

Pour tout nouveau node créé, tous les paramètres avec une valeur par défaut définie doivent être pré-remplis avec cette valeur.

Valide: Exigences 3.5

Propriété 10: Validation de Variables

Pour tout paramètre contenant une référence de variable ${nom}, la variable doit exister dans la liste des variables du workflow, sinon une erreur de validation doit être affichée.

Valide: Exigences 10.3

Propriété 11: Unicité des Noms de Variables

Pour tout workflow, tous les noms de variables doivent être uniques (pas de doublons).

Valide: Exigences 10.2

Propriétés d'Exécution

Propriété 12: Conversion Valide

Pour tout workflow visuel valide, la conversion en WorkflowGraph doit produire un graphe exécutable par ExecutionLoop sans erreur.

Valide: Exigences 6.1, 18.1

Propriété 13: Synchronisation d'État

Pour tout node pendant l'exécution, l'état visuel (status) doit refléter l'état d'exécution réel (idle → running → success/failed).

Valide: Exigences 6.3

Propriété 14: Substitution de Variables

Pour tout paramètre contenant ${variable} lors de l'exécution, la référence doit être remplacée par la valeur runtime de la variable.

Valide: Exigences 10.4

Propriété 15: Exécution de Conditions

Pour tout node Condition, l'exécution doit suivre le edge "true" si la condition est vraie, et le edge "false" si la condition est fausse.

Valide: Exigences 8.3

Propriété 16: Exécution de Boucles

Pour tout node Loop, le corps de la boucle doit être exécuté le nombre de fois spécifié par les paramètres (count pour repeat, condition pour while, items pour for-each).

Valide: Exigences 9.3

Propriétés d'Interaction

Propriété 17: Annuler/Refaire Cohérence

Pour toute séquence d'actions, effectuer une action puis l'annuler doit restaurer l'état précédent, et refaire doit restaurer l'état après l'action.

Valide: Exigences 13.2, 13.3

Propriété 18: Pile d'Annulation

Pour toute action effectuée, elle doit être ajoutée à la pile d'annulation, et on doit pouvoir annuler au moins 50 actions.

Valide: Exigences 13.1, 13.4

Propriété 19: Invalidation de Refaire

Pour toute nouvelle action effectuée après une annulation, la pile de refaire doit être vidée.

Valide: Exigences 13.5

Propriété 20: Extraction de Propriétés de Cible

Pour tout élément sélectionné via le target selector, les propriétés extraites doivent inclure au minimum: text, position, size, et embedding.

Valide: Exigences 4.5

Propriétés de Templates

Propriété 21: Chargement de Template

Pour tout template sélectionné, le chargement doit créer un workflow avec tous les nodes et edges du template, et tous les paramètres doivent être modifiables.

Valide: Exigences 11.2, 11.3

Propriété 22: Sauvegarde comme Template

Pour tout workflow créé par un utilisateur, il doit être possible de le sauvegarder comme template personnalisé avec des paramètres configurables.

Valide: Exigences 11.5

Propriétés d'Export/Import

Propriété 23: Complétude d'Export

Pour tout workflow exporté, le fichier JSON/YAML doit contenir tous les nodes, edges, variables, et métadonnées nécessaires pour reconstruire le workflow.

Valide: Exigences 15.1, 15.3

Propriété 24: Validation d'Import

Pour tout fichier importé, le système doit valider le format et rejeter les fichiers invalides avec un message d'erreur clair.

Valide: Exigences 15.2

Propriété 25: Migration de Version

Pour tout workflow d'une version antérieure, le système doit détecter l'incompatibilité et offrir une migration automatique vers la version actuelle.

Valide: Exigences 15.4

Propriétés d'Intégration

Propriété 26: Compatibilité de Format

Pour tout workflow sauvegardé par le Visual Builder, il doit être dans le même format que les workflows créés programmatiquement et vice-versa.

Valide: Exigences 18.4

Propriété 27: Intégration Self-Healing

Pour tout workflow utilisant self-healing, les nodes doivent être configurés avec les stratégies de récupération appropriées lors de la conversion.

Valide: Exigences 18.2

Propriété 28: Intégration Analytics

Pour tout workflow exécuté, les métriques doivent être automatiquement collectées et envoyées au système Analytics.

Valide: Exigences 18.3

Propriété 29: Rétrocompatibilité

Pour tout workflow existant créé programmatiquement (suivant le format standard), le Visual Builder doit pouvoir le charger, le visualiser et l'éditer.

Valide: Exigences 18.5

Propriétés de Recherche et Filtrage

Propriété 30: Filtrage de Palette

Pour toute requête de recherche dans la palette, seuls les types de nodes dont le nom ou la description contient la requête doivent être affichés.

Valide: Exigences 7.2

Propriété 31: Extensibilité de Palette

Pour tout nouveau type de node ajouté au système, il doit automatiquement apparaître dans la palette dans la catégorie appropriée.

Valide: Exigences 7.5

Propriétés de Zoom et Navigation

Propriété 32: Zoom Centré

Pour tout événement de zoom (molette de souris), le zoom doit être centré sur la position du curseur, pas sur le centre du canvas.

Valide: Exigences 14.1

Propriété 33: Fit-to-Screen

Pour tout workflow, la fonction "ajuster à l'écran" doit calculer le zoom et la position pour que tous les nodes soient visibles dans le viewport.

Valide: Exigences 14.3


Gestion des Erreurs

Stratégies de Gestion d'Erreurs

1. Erreurs de Validation

  • Détection: Validation en temps réel pendant l'édition
  • Affichage: Indicateurs visuels sur les nodes/edges problématiques
  • Blocage: Empêcher l'exécution si des erreurs critiques existent
  • Récupération: Suggestions de correction automatique quand possible

2. Erreurs d'Exécution

  • Capture: Intercepter toutes les exceptions pendant l'exécution
  • Affichage: Mettre en surbrillance le node en échec avec message d'erreur
  • Logging: Enregistrer tous les détails pour debugging
  • Récupération: Intégration avec Self-Healing pour tentatives automatiques

3. Erreurs de Sérialisation

  • Validation: Vérifier le schéma JSON avant sauvegarde/chargement
  • Affichage: Messages d'erreur clairs avec détails du problème
  • Récupération: Tentative de migration automatique pour anciennes versions
  • Backup: Sauvegarder l'état précédent avant toute modification

4. Erreurs Réseau

  • Retry: Réessayer automatiquement les requêtes échouées (3 tentatives)
  • Timeout: Timeouts configurables pour toutes les opérations réseau
  • Offline: Mode dégradé avec sauvegarde locale si serveur inaccessible
  • Feedback: Indicateurs de statut de connexion en temps réel

Codes d'Erreur

class ErrorCode(Enum):
    # Validation
    MISSING_REQUIRED_PARAMETER = 1001
    INVALID_PARAMETER_TYPE = 1002
    INVALID_VARIABLE_REFERENCE = 1003
    CIRCULAR_DEPENDENCY = 1004
    DISCONNECTED_NODE = 1005
    
    # Sérialisation
    INVALID_JSON_FORMAT = 2001
    MISSING_REQUIRED_FIELD = 2002
    VERSION_INCOMPATIBLE = 2003
    
    # Exécution
    CONVERSION_FAILED = 3001
    EXECUTION_FAILED = 3002
    TARGET_NOT_FOUND = 3003
    
    # Réseau
    CONNECTION_FAILED = 4001
    TIMEOUT = 4002
    SERVER_ERROR = 4003

Stratégie de Test

1. Tests Unitaires

Les tests unitaires vérifient le comportement de composants individuels.

Frontend Components

describe('Canvas Component', () => {
  test('should create node on drop', () => {
    const canvas = render(<Canvas nodes={[]} edges={[]} />);
    const nodeType = { type: 'click', name: 'Click' };
    
    fireEvent.drop(canvas, { dataTransfer: { getData: () => nodeType } });
    
    expect(canvas.nodes).toHaveLength(1);
    expect(canvas.nodes[0].type).toBe('click');
  });
  
  test('should select node on click', () => {
    const onSelect = jest.fn();
    const nodes = [createMockNode('click')];
    const canvas = render(<Canvas nodes={nodes} onNodeSelect={onSelect} />);
    
    fireEvent.click(canvas.getByTestId('node-click-1'));
    
    expect(onSelect).toHaveBeenCalledWith('click-1');
  });
});

describe('Properties Panel', () => {
  test('should display required parameter warning', () => {
    const node = createMockNode('click', { target: '' });
    const panel = render(<PropertiesPanel node={node} />);
    
    expect(panel.getByText('Target is required')).toBeInTheDocument();
  });
  
  test('should validate parameter on change', () => {
    const node = createMockNode('wait', { duration: -1 });
    const panel = render(<PropertiesPanel node={node} />);
    
    expect(panel.getByText('Duration must be positive')).toBeInTheDocument();
  });
});

Backend API

class TestWorkflowAPI(unittest.TestCase):
    def test_create_workflow(self):
        """Test création d'un nouveau workflow"""
        response = self.client.post('/api/workflows', json={
            'name': 'Test Workflow',
            'description': 'Test description'
        })
        
        self.assertEqual(response.status_code, 201)
        data = response.get_json()
        self.assertIn('id', data)
        self.assertEqual(data['name'], 'Test Workflow')
    
    def test_serialize_workflow(self):
        """Test sérialisation complète"""
        workflow = create_test_workflow_with_nodes(5)
        serialized = serialize_workflow(workflow)
        
        self.assertIn('nodes', serialized)
        self.assertIn('edges', serialized)
        self.assertEqual(len(serialized['nodes']), 5)
    
    def test_validate_required_fields(self):
        """Test validation des champs requis"""
        invalid_workflow = {'name': 'Test'}  # manque 'nodes' et 'edges'
        
        with self.assertRaises(ValidationError):
            validate_workflow(invalid_workflow)

2. Tests d'Intégration

Les tests d'intégration vérifient l'interaction entre composants.

class TestVisualToGraphConversion(unittest.TestCase):
    def test_simple_workflow_conversion(self):
        """Test conversion d'un workflow simple"""
        visual_workflow = {
            'nodes': [
                {'id': '1', 'type': 'start', 'position': {'x': 0, 'y': 0}},
                {'id': '2', 'type': 'click', 'position': {'x': 100, 'y': 0},
                 'parameters': {'target': 'Button "Login"', 'timeout': 5000}},
                {'id': '3', 'type': 'end', 'position': {'x': 200, 'y': 0}}
            ],
            'edges': [
                {'id': 'e1', 'source': '1', 'target': '2'},
                {'id': 'e2', 'source': '2', 'target': '3'}
            ]
        }
        
        converter = VisualToGraphConverter()
        workflow_graph = converter.convert(visual_workflow)
        
        self.assertEqual(len(workflow_graph.nodes), 3)
        self.assertEqual(workflow_graph.nodes[1].type, 'click')
        self.assertEqual(workflow_graph.nodes[1].parameters['target'], 'Button "Login"')
    
    def test_workflow_with_conditions(self):
        """Test conversion avec conditions"""
        visual_workflow = create_workflow_with_condition()
        converter = VisualToGraphConverter()
        workflow_graph = converter.convert(visual_workflow)
        
        # Vérifier que les branches true/false sont correctes
        condition_node = workflow_graph.get_node_by_type('condition')
        self.assertEqual(len(condition_node.output_edges), 2)
        
        true_edge = [e for e in condition_node.output_edges if e.condition.type == 'true'][0]
        false_edge = [e for e in condition_node.output_edges if e.condition.type == 'false'][0]
        
        self.assertIsNotNone(true_edge)
        self.assertIsNotNone(false_edge)

3. Tests Property-Based

Les tests property-based vérifient que les propriétés universelles sont respectées pour tous les inputs.

Framework: Hypothesis (Python) ou fast-check (TypeScript)

from hypothesis import given, strategies as st
from hypothesis.stateful import RuleBasedStateMachine, rule, invariant

class TestWorkflowProperties(unittest.TestCase):
    
    @given(st.lists(st.text(min_size=1), min_size=1, max_size=20))
    def test_property_5_serialization_roundtrip(self, node_names):
        """
        Feature: visual-workflow-builder, Property 5: Round-trip de Sérialisation
        Pour tout workflow, sérialiser puis désérialiser doit produire un workflow équivalent
        """
        # Créer un workflow avec des nodes aléatoires
        workflow = create_workflow_with_node_names(node_names)
        
        # Sérialiser
        serialized = serialize_workflow(workflow)
        
        # Désérialiser
        deserialized = deserialize_workflow(serialized)
        
        # Vérifier l'équivalence
        self.assertEqual(len(workflow.nodes), len(deserialized.nodes))
        self.assertEqual(len(workflow.edges), len(deserialized.edges))
        for i, node in enumerate(workflow.nodes):
            self.assertEqual(node.type, deserialized.nodes[i].type)
            self.assertEqual(node.parameters, deserialized.nodes[i].parameters)
    
    @given(st.integers(min_value=1, max_value=10))
    def test_property_1_node_deletion_cascade(self, num_nodes):
        """
        Feature: visual-workflow-builder, Property 1: Suppression de Node Cascade
        Pour tout workflow, supprimer un node doit supprimer tous ses edges
        """
        # Créer un workflow linéaire avec num_nodes nodes
        workflow = create_linear_workflow(num_nodes)
        initial_edge_count = len(workflow.edges)
        
        # Supprimer un node au milieu
        middle_node = workflow.nodes[num_nodes // 2]
        edges_connected_to_node = [e for e in workflow.edges 
                                   if e.source == middle_node.id or e.target == middle_node.id]
        
        workflow.delete_node(middle_node.id)
        
        # Vérifier que les edges connectés sont supprimés
        self.assertEqual(len(workflow.edges), initial_edge_count - len(edges_connected_to_node))
        for edge in edges_connected_to_node:
            self.assertNotIn(edge, workflow.edges)
    
    @given(st.lists(st.text(min_size=1, max_size=20), min_size=1, max_size=10))
    def test_property_11_variable_name_uniqueness(self, variable_names):
        """
        Feature: visual-workflow-builder, Property 11: Unicité des Noms de Variables
        Pour tout workflow, tous les noms de variables doivent être uniques
        """
        workflow = VisualWorkflow(id='test', name='Test', nodes=[], edges=[], variables=[])
        
        # Essayer d'ajouter des variables
        for name in variable_names:
            try:
                workflow.add_variable(name, 'string', '')
            except ValueError:
                # Nom dupliqué, c'est attendu
                pass
        
        # Vérifier l'unicité
        variable_names_in_workflow = [v.name for v in workflow.variables]
        self.assertEqual(len(variable_names_in_workflow), len(set(variable_names_in_workflow)))
@given(st.integers(min_value=2, max_value=50))
def test_property_18_undo_redo_stack_capacity(self, num_actions):
    """
    Feature: visual-workflow-builder, Property 18: Pile d'Annulation
    Pour toute action, on doit pouvoir annuler au moins 50 actions
    """
    workflow = VisualWorkflow(id='test', name='Test', nodes=[], edges=[], variables=[])
    undo_manager = UndoManager(workflow)
    
    # Effectuer num_actions actions
    for i in range(num_actions):
        node = VisualNode(id=f'node-{i}', type='click', position={'x': i*100, 'y': 0}, 
                        parameters={}, input_ports=[], output_ports=[])
        undo_manager.execute(AddNodeAction(node))
    
    # Vérifier qu'on peut annuler au moins min(50, num_actions) actions
    max_undo = min(50, num_actions)
    for i in range(max_undo):
        self.assertTrue(undo_manager.can_undo())
        undo_manager.undo()
    
    self.assertEqual(len(workflow.nodes), num_actions - max_undo)

@given(st.text(min_size=1, max_size=50))
def test_property_10_variable_reference_validation(self, variable_name):
    """
    Feature: visual-workflow-builder, Property 10: Validation de Variables
    Pour tout paramètre avec ${variable}, la variable doit exister
    """
    workflow = VisualWorkflow(id='test', name='Test', nodes=[], edges=[], variables=[])
    
    # Créer un node avec référence de variable
    node = VisualNode(
        id='node-1', 
        type='type',
        position={'x': 0, 'y': 0},
        parameters={'text': f'${{{variable_name}}}'},
        input_ports=[],
        output_ports=[]
    )
    workflow.nodes.append(node)
    
    # Valider
    validation_result = validate_workflow(workflow)
    
    # Si la variable n'existe pas, doit avoir une erreur
    if variable_name not in [v.name for v in workflow.variables]:
        self.assertFalse(validation_result.is_valid)
        self.assertTrue(any('variable' in err.lower() for err in validation_result.errors))
    else:
        self.assertTrue(validation_result.is_valid)

class WorkflowStateMachine(RuleBasedStateMachine): """ Machine à états pour tester les propriétés de manière stateful """ def init(self): super().init() self.workflow = VisualWorkflow(id='test', name='Test', nodes=[], edges=[], variables=[]) self.undo_manager = UndoManager(self.workflow)

@rule()
def add_node(self):
    """Ajouter un node aléatoire"""
    node_id = f'node-{len(self.workflow.nodes)}'
    node = VisualNode(id=node_id, type='click', position={'x': 0, 'y': 0},
                     parameters={}, input_ports=[], output_ports=[])
    self.undo_manager.execute(AddNodeAction(node))

@rule()
def delete_node(self):
    """Supprimer un node aléatoire"""
    if self.workflow.nodes:
        node = random.choice(self.workflow.nodes)
        self.undo_manager.execute(DeleteNodeAction(node.id))

@rule()
def undo(self):
    """Annuler la dernière action"""
    if self.undo_manager.can_undo():
        self.undo_manager.undo()

@rule()
def redo(self):
    """Refaire la dernière action annulée"""
    if self.undo_manager.can_redo():
        self.undo_manager.redo()

@invariant()
def nodes_have_unique_ids(self):
    """Invariant: tous les nodes ont des IDs uniques"""
    node_ids = [n.id for n in self.workflow.nodes]
    assert len(node_ids) == len(set(node_ids))

@invariant()
def edges_reference_existing_nodes(self):
    """Invariant: tous les edges référencent des nodes existants"""
    node_ids = {n.id for n in self.workflow.nodes}
    for edge in self.workflow.edges:
        assert edge.source in node_ids
        assert edge.target in node_ids

TestWorkflowStateMachine = WorkflowStateMachine.TestCase


### 4. Tests End-to-End

Les tests end-to-end vérifient le système complet du frontend au backend.

```python
class TestEndToEndWorkflow(unittest.TestCase):
    def test_complete_workflow_creation_and_execution(self):
        """Test du cycle complet: création → sauvegarde → exécution"""
        # 1. Créer un workflow via l'API
        response = self.client.post('/api/workflows', json={
            'name': 'Login Workflow',
            'description': 'Test login automation'
        })
        workflow_id = response.get_json()['id']
        
        # 2. Ajouter des nodes
        nodes = [
            {'id': '1', 'type': 'start', 'position': {'x': 0, 'y': 0}},
            {'id': '2', 'type': 'navigate', 'position': {'x': 100, 'y': 0},
             'parameters': {'url': 'https://example.com/login'}},
            {'id': '3', 'type': 'type', 'position': {'x': 200, 'y': 0},
             'parameters': {'target': 'input[name="username"]', 'text': 'testuser'}},
            {'id': '4', 'type': 'type', 'position': {'x': 300, 'y': 0},
             'parameters': {'target': 'input[name="password"]', 'text': 'testpass'}},
            {'id': '5', 'type': 'click', 'position': {'x': 400, 'y': 0},
             'parameters': {'target': 'button[type="submit"]'}},
            {'id': '6', 'type': 'end', 'position': {'x': 500, 'y': 0}}
        ]
        
        edges = [
            {'id': 'e1', 'source': '1', 'target': '2'},
            {'id': 'e2', 'source': '2', 'target': '3'},
            {'id': 'e3', 'source': '3', 'target': '4'},
            {'id': 'e4', 'source': '4', 'target': '5'},
            {'id': 'e5', 'source': '5', 'target': '6'}
        ]
        
        # 3. Mettre à jour le workflow
        response = self.client.put(f'/api/workflows/{workflow_id}', json={
            'nodes': nodes,
            'edges': edges
        })
        self.assertEqual(response.status_code, 200)
        
        # 4. Valider le workflow
        response = self.client.post(f'/api/workflows/{workflow_id}/validate')
        validation = response.get_json()
        self.assertTrue(validation['is_valid'])
        
        # 5. Exécuter le workflow
        response = self.client.post(f'/api/workflows/{workflow_id}/execute', json={
            'mode': 'test'
        })
        execution_id = response.get_json()['execution_id']
        
        # 6. Attendre la completion
        self.wait_for_execution_completion(execution_id, timeout=30)
        
        # 7. Vérifier les résultats
        response = self.client.get(f'/api/executions/{execution_id}')
        execution = response.get_json()
        
        self.assertEqual(execution['status'], 'completed')
        self.assertTrue(execution['success'])
        self.assertEqual(len(execution['node_results']), 6)

5. Tests de Performance

class TestPerformance(unittest.TestCase):
    def test_large_workflow_serialization(self):
        """Test performance de sérialisation avec 100 nodes"""
        workflow = create_workflow_with_nodes(100)
        
        start_time = time.time()
        serialized = serialize_workflow(workflow)
        end_time = time.time()
        
        # Doit être < 1 seconde
        self.assertLess(end_time - start_time, 1.0)
        self.assertEqual(len(serialized['nodes']), 100)
    
    def test_workflow_validation_performance(self):
        """Test performance de validation avec workflow complexe"""
        workflow = create_complex_workflow_with_conditions_and_loops(50)
        
        start_time = time.time()
        validation_result = validate_workflow(workflow)
        end_time = time.time()
        
        # Doit être < 100ms
        self.assertLess(end_time - start_time, 0.1)
        self.assertTrue(validation_result.is_valid)

API Design

REST Endpoints

Workflows

GET    /api/workflows              # Liste tous les workflows
POST   /api/workflows              # Crée un nouveau workflow
GET    /api/workflows/:id          # Récupère un workflow spécifique
PUT    /api/workflows/:id          # Met à jour un workflow
DELETE /api/workflows/:id          # Supprime un workflow
POST   /api/workflows/:id/validate # Valide un workflow
POST   /api/workflows/:id/execute  # Exécute un workflow
GET    /api/workflows/:id/export   # Exporte un workflow (JSON/YAML)
POST   /api/workflows/import       # Importe un workflow

Templates

GET    /api/templates              # Liste tous les templates
POST   /api/templates              # Crée un nouveau template
GET    /api/templates/:id          # Récupère un template spécifique
POST   /api/templates/:id/instantiate  # Crée un workflow depuis un template

Node Types

GET    /api/node-types             # Liste tous les types de nodes disponibles
GET    /api/node-types/:type       # Récupère la définition d'un type spécifique

Executions

GET    /api/executions/:id         # Récupère le statut d'une exécution
POST   /api/executions/:id/cancel  # Annule une exécution en cours

WebSocket Events

Client → Server

// S'abonner aux mises à jour d'exécution
{
  type: 'subscribe_execution',
  execution_id: string
}

// Se désabonner
{
  type: 'unsubscribe_execution',
  execution_id: string
}

Server → Client

// Statut d'exécution
{
  type: 'execution_status',
  execution_id: string,
  status: 'running' | 'completed' | 'failed',
  progress: number,  // 0-100
  current_node_id?: string
}

// Statut d'un node
{
  type: 'node_status',
  execution_id: string,
  node_id: string,
  status: 'running' | 'success' | 'failed' | 'skipped',
  duration?: number,
  error?: string
}

// Résultat final
{
  type: 'execution_complete',
  execution_id: string,
  success: boolean,
  duration: number,
  node_results: NodeResult[]
}

Considérations de Sécurité

1. Validation d'Entrée

  • Sanitization: Tous les inputs utilisateur doivent être nettoyés pour prévenir les injections
  • Schema Validation: Utiliser JSON Schema pour valider tous les workflows
  • Size Limits: Limiter la taille des workflows (max 1000 nodes, max 10MB)
  • Rate Limiting: Limiter les requêtes API (100 req/min par utilisateur)

2. Exécution Sécurisée

  • Sandboxing: Exécuter les workflows dans un environnement isolé
  • Permissions: Contrôle d'accès basé sur les rôles (RBAC)
  • Audit Trail: Logger toutes les actions utilisateur avec timestamps
  • Resource Limits: Limiter CPU (80%), mémoire (2GB), durée (30min)

3. Stockage Sécurisé

  • Encryption: Chiffrer les workflows sensibles au repos (AES-256)
  • Access Control: Permissions granulaires (owner, editor, viewer)
  • Backup: Sauvegardes automatiques quotidiennes avec rétention 30 jours
  • Versioning: Historique complet des modifications avec rollback

4. Communication Sécurisée

  • HTTPS: Toutes les communications via TLS 1.3
  • Authentication: JWT tokens avec expiration (1h) et refresh (7j)
  • CORS: Configuration stricte des origines autorisées
  • WebSocket: Authentification et chiffrement des messages

Plan de Déploiement

Phase 1: MVP (4-6 semaines)

Backend

  • API REST de base (CRUD workflows)
  • Conversion Visual → WorkflowGraph
  • Exécution simple via ExecutionLoop
  • Templates de base (Login, Form Fill)
  • Validation structurelle

Frontend

  • Canvas avec drag & drop basique
  • Palette avec nodes essentiels (Click, Type, Wait, Navigate, If, Loop)
  • Properties panel simple
  • Target selector basique
  • Sauvegarde/Chargement

Tests

  • Tests unitaires critiques (>70% coverage)
  • Tests d'intégration de base
  • Validation manuelle des workflows

Phase 2: Fonctionnalités Avancées (4-6 semaines)

Backend

  • WebSocket pour temps réel
  • Templates avancés (Data Extraction, API Integration)
  • Export/Import (JSON, YAML)
  • Validation avancée (cycles, variables)
  • Migration de versions

Frontend

  • Undo/Redo complet
  • Zoom/Pan optimisé avec virtualisation
  • Variables management UI
  • Validation temps réel avec indicateurs
  • Raccourcis clavier
  • Minimap et fit-to-screen

Tests

  • Tests de performance (100 nodes @ 60fps)
  • Property-based tests (Hypothesis)
  • Tests end-to-end automatisés
  • Tests de charge (10 utilisateurs simultanés)

Phase 3: Production (2-4 semaines)

Optimisations

  • Performance tuning (profiling, optimisations)
  • Caching intelligent (Redis pour templates)
  • Monitoring et métriques (Prometheus, Grafana)
  • Documentation utilisateur complète

Sécurité

  • Audit de sécurité complet
  • Penetration testing
  • Hardening (rate limiting, input validation)
  • Backup et disaster recovery

Déploiement

  • CI/CD pipeline (GitHub Actions)
  • Monitoring production (alertes, logs)
  • Rollback strategy (blue-green deployment)
  • Formation utilisateurs

Métriques de Succès

1. Métriques Techniques

Performance

  • Rendu Canvas: 60fps avec 100+ nodes ✓
  • Latence API: < 200ms pour opérations CRUD ✓
  • Temps de Conversion: Visual → Graph < 100ms ✓
  • Temps de Chargement: < 2s pour workflows de 50 nodes ✓
  • Uptime: > 99.9% ✓

Qualité

  • Test Coverage: > 80% pour code critique ✓
  • Bug Rate: < 5 bugs critiques par release ✓
  • Property Tests: 100% des propriétés testées ✓
  • Validation Rate: > 95% des workflows valides avant exécution ✓

2. Métriques Utilisateur

Adoption

  • Time to First Workflow: < 5 minutes pour un utilisateur novice ✓
  • Workflow Creation Speed: 50% plus rapide qu'écrire du code ✓
  • Error Rate: < 5% de workflows invalides créés ✓
  • Completion Rate: > 90% des workflows créés sont exécutés ✓

Satisfaction

  • User Satisfaction: > 4.5/5 dans les surveys ✓
  • NPS Score: > 50 ✓
  • Feature Requests: Priorisation basée sur votes utilisateurs ✓
  • Support Tickets: < 10% liés au Visual Builder ✓

3. Métriques Business

Productivité

  • Workflows Created: 3x plus de workflows créés vs méthode manuelle ✓
  • Time Saved: 60% de réduction du temps de développement ✓
  • Reusability: 40% des workflows utilisent des templates ✓
  • Collaboration: 30% des workflows sont partagés entre utilisateurs ✓

Adoption

  • Active Users: 80% des utilisateurs créent au moins 1 workflow ✓
  • Retention: 70% des utilisateurs reviennent après 1 semaine ✓
  • Power Users: 20% des utilisateurs créent 5+ workflows ✓
  • Template Usage: 50% des nouveaux workflows partent d'un template ✓

Conclusion

Le Visual Workflow Builder représente une évolution majeure de RPA Vision V3, transformant un outil technique en une solution accessible à tous. L'architecture proposée garantit:

  • Simplicité d'usage sans compromis sur la puissance
  • Performance adaptée aux workflows complexes (100+ nodes @ 60fps)
  • Intégration parfaite avec l'écosystème existant (ExecutionLoop, Analytics, Self-Healing)
  • Extensibilité pour les futures fonctionnalités (collaboration, AI-assisted design)
  • Qualité grâce à une stratégie de test complète (33 propriétés testées)

Cette solution positionnera RPA Vision V3 comme le leader du marché RPA no-code/low-code, démocratisant l'automatisation pour tous les utilisateurs, des débutants aux experts.

Prochaines Étapes

  1. Validation du Design: Review avec les stakeholders
  2. Création du Plan d'Implémentation: Tasks détaillées avec estimations
  3. Setup de l'Environnement: Configuration du projet frontend/backend
  4. Développement Phase 1: MVP en 4-6 semaines
  5. Tests et Itération: Feedback utilisateurs et améliorations