Files
rpa_vision_v3/tests/property/test_vwb_action_properties_12jan2026.py
Dom a27b74cf22 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>
2026-01-29 11:23:51 +01:00

779 lines
31 KiB
Python

#!/usr/bin/env python3
"""
Tests de propriétés pour VWBActionProperties - Détection et gestion des actions VWB
Auteur : Dom, Alice, Kiro - 12 janvier 2026
Ce module teste les propriétés universelles du composant VWBActionProperties,
en particulier la détection correcte et la gestion des états de chargement.
Feature: interface-proprietes-etapes-complete
Property 4: Détection correcte des actions VWB
Property 5: Gestion des états de chargement VWB
Validates: Requirements 2.1, 2.2, 2.4
"""
import pytest
import json
import subprocess
import tempfile
import os
from pathlib import Path
from typing import Dict, List, Any, Optional
from hypothesis import given, strategies as st, settings, assume, note
from hypothesis.stateful import RuleBasedStateMachine, Bundle, rule, initialize, invariant
# Configuration des tests de propriétés
PROPERTY_TEST_SETTINGS = settings(
max_examples=100,
deadline=30000, # 30 secondes par test
suppress_health_check=[],
)
# Stratégies de génération de données
@st.composite
def vwb_action_strategy(draw):
"""Génère des actions VWB valides"""
action_types = [
'click_anchor', 'type_text', 'type_secret', 'wait_for_anchor',
'extract_text', 'screenshot_evidence', 'scroll_to_anchor',
'focus_anchor', 'hotkey', 'navigate_to_url'
]
action_type = draw(st.sampled_from(action_types))
return {
'id': action_type,
'name': draw(st.text(min_size=5, max_size=50)),
'description': draw(st.text(min_size=10, max_size=200)),
'category': draw(st.sampled_from(['interaction', 'navigation', 'extraction', 'validation'])),
'parameters': draw(vwb_parameters_strategy()),
'examples': draw(st.lists(vwb_example_strategy(), max_size=3)),
'version': draw(st.text(min_size=3, max_size=10)),
'tags': draw(st.lists(st.text(min_size=3, max_size=20), max_size=5))
}
@st.composite
def vwb_parameters_strategy(draw):
"""Génère des paramètres d'action VWB"""
param_count = draw(st.integers(min_value=1, max_value=6))
parameters = {}
for i in range(param_count):
param_name = draw(st.sampled_from([
'target_anchor', 'text_content', 'confidence_threshold',
'timeout_seconds', 'retry_count', 'scroll_direction'
]))
param_type = draw(st.sampled_from(['string', 'number', 'boolean', 'VWBVisualAnchor']))
parameters[param_name] = {
'type': param_type,
'required': draw(st.booleans()),
'description': draw(st.text(min_size=10, max_size=100)),
'default': draw(get_default_value_strategy(param_type))
}
if param_type == 'number':
parameters[param_name]['min'] = draw(st.one_of(st.none(), st.integers(min_value=0, max_value=100)))
parameters[param_name]['max'] = draw(st.one_of(st.none(), st.integers(min_value=100, max_value=1000)))
return parameters
@st.composite
def vwb_example_strategy(draw):
"""Génère des exemples d'utilisation VWB"""
return {
'name': draw(st.text(min_size=5, max_size=30)),
'description': draw(st.text(min_size=10, max_size=100)),
'parameters': draw(st.dictionaries(
st.text(min_size=3, max_size=20),
st.one_of(st.text(), st.integers(), st.booleans()),
max_size=5
)),
'expectedResult': draw(st.one_of(st.none(), st.text(min_size=10, max_size=100)))
}
def get_default_value_strategy(param_type: str):
"""Retourne une stratégie pour les valeurs par défaut selon le type"""
if param_type == 'string':
return st.one_of(st.none(), st.text(max_size=50))
elif param_type == 'number':
return st.one_of(st.none(), st.integers(min_value=0, max_value=100))
elif param_type == 'boolean':
return st.one_of(st.none(), st.booleans())
elif param_type == 'VWBVisualAnchor':
return st.none() # Les ancres visuelles n'ont pas de valeur par défaut
else:
return st.none()
@st.composite
def loading_state_strategy(draw):
"""Génère des états de chargement"""
return {
'isLoading': draw(st.booleans()),
'hasError': draw(st.booleans()),
'errorMessage': draw(st.one_of(st.none(), st.text(min_size=10, max_size=100))),
'stepType': draw(st.one_of(st.none(), st.sampled_from([
'click_anchor', 'type_text', 'wait_for_anchor', 'extract_text'
])))
}
@st.composite
def variable_strategy(draw):
"""Génère des variables"""
return {
'id': draw(st.text(min_size=1, max_size=20)),
'name': draw(st.text(min_size=1, max_size=30, alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')))),
'value': draw(st.one_of(st.text(), st.integers(), st.booleans())),
'type': draw(st.sampled_from(['string', 'number', 'boolean']))
}
class VWBActionPropertiesTestHelper:
"""Helper pour tester le composant VWBActionProperties via Node.js"""
def __init__(self):
self.project_root = Path(__file__).parent.parent.parent
self.frontend_path = self.project_root / "visual_workflow_builder" / "frontend"
def create_test_script(self, action: Optional[Dict], loading_state: Dict, parameters: Dict, variables: List[Dict]) -> str:
"""Crée un script de test Node.js pour VWBActionProperties"""
test_script = f"""
const React = require('react');
// Configuration du test
const vwbAction = {json.dumps(action)};
const loadingState = {json.dumps(loading_state)};
const parameters = {json.dumps(parameters)};
const variables = {json.dumps(variables)};
// Simulation du composant VWBActionProperties
class VWBActionPropertiesSimulator {{
constructor(action, isLoading, error, stepType, parameters, variables) {{
this.action = action;
this.isLoading = isLoading;
this.error = error;
this.stepType = stepType;
this.parameters = parameters;
this.variables = variables;
this.validationResults = [];
this.parameterChanges = [];
}}
// Simulation de la détection d'action VWB
detectVWBAction() {{
const detection = {{
isVWBAction: false,
detectionMethods: {{}},
confidence: 0
}};
// Méthodes de détection
if (this.action) {{
detection.detectionMethods.hasAction = true;
detection.isVWBAction = true;
detection.confidence += 0.4;
}}
if (this.stepType) {{
const vwbPatterns = ['_anchor', '_text', '_secret', 'click_', 'type_', 'wait_', 'extract_'];
detection.detectionMethods.hasVWBPattern = vwbPatterns.some(pattern =>
this.stepType.includes(pattern)
);
if (detection.detectionMethods.hasVWBPattern) {{
detection.isVWBAction = true;
detection.confidence += 0.3;
}}
}}
if (this.parameters && Object.keys(this.parameters).length > 0) {{
const vwbParamNames = ['target_anchor', 'confidence_threshold', 'visual_anchor'];
detection.detectionMethods.hasVWBParams = Object.keys(this.parameters).some(param =>
vwbParamNames.some(vwbParam => param.includes(vwbParam))
);
if (detection.detectionMethods.hasVWBParams) {{
detection.isVWBAction = true;
detection.confidence += 0.2;
}}
}}
detection.detectionMethods.isLoadingState = this.isLoading;
detection.detectionMethods.hasError = Boolean(this.error);
return detection;
}}
// Simulation de la gestion des états de chargement
handleLoadingStates() {{
const stateHandling = {{
currentState: 'unknown',
canRender: false,
showsAppropriateUI: false,
providesUserFeedback: false,
hasRecoveryOptions: false
}};
if (this.isLoading) {{
stateHandling.currentState = 'loading';
stateHandling.canRender = true;
stateHandling.showsAppropriateUI = true;
stateHandling.providesUserFeedback = true;
}} else if (this.error) {{
stateHandling.currentState = 'error';
stateHandling.canRender = true;
stateHandling.showsAppropriateUI = true;
stateHandling.providesUserFeedback = true;
stateHandling.hasRecoveryOptions = true; // Bouton retry, suggestions alternatives
}} else if (!this.action) {{
stateHandling.currentState = 'not_found';
stateHandling.canRender = true;
stateHandling.showsAppropriateUI = true;
stateHandling.providesUserFeedback = true;
stateHandling.hasRecoveryOptions = true; // Actions alternatives, config manuelle
}} else {{
stateHandling.currentState = 'loaded';
stateHandling.canRender = true;
stateHandling.showsAppropriateUI = true;
stateHandling.providesUserFeedback = true;
}}
return stateHandling;
}}
// Simulation de la validation des paramètres
validateParameters() {{
const validation = {{
is_valid: true,
errors: [],
warnings: [],
suggestions: []
}};
if (!this.action) {{
// Pas de validation possible sans action
return validation;
}}
// Validation des paramètres requis
Object.entries(this.action.parameters || {{}}).forEach(([paramName, paramConfig]) => {{
const value = this.parameters[paramName];
if (paramConfig.required && (value === undefined || value === null || value === '')) {{
validation.is_valid = false;
validation.errors.push({{
parameter: paramName,
message: `Le paramètre "${{paramName}}" est requis`,
code: 'REQUIRED_PARAMETER',
severity: 'error'
}});
}}
// Validation par type
if (value !== undefined && value !== null && value !== '') {{
switch (paramConfig.type) {{
case 'number':
const numValue = Number(value);
if (isNaN(numValue)) {{
validation.is_valid = false;
validation.errors.push({{
parameter: paramName,
message: `"${{paramName}}" doit être un nombre`,
code: 'INVALID_TYPE',
severity: 'error'
}});
}} else {{
if (paramConfig.min !== undefined && numValue < paramConfig.min) {{
validation.is_valid = false;
validation.errors.push({{
parameter: paramName,
message: `"${{paramName}}" doit être >= ${{paramConfig.min}}`,
code: 'MIN_VALUE',
severity: 'error'
}});
}}
if (paramConfig.max !== undefined && numValue > paramConfig.max) {{
validation.is_valid = false;
validation.errors.push({{
parameter: paramName,
message: `"${{paramName}}" doit être <= ${{paramConfig.max}}`,
code: 'MAX_VALUE',
severity: 'error'
}});
}}
}}
break;
case 'VWBVisualAnchor':
if (typeof value !== 'object' || !value.anchor_id) {{
validation.warnings.push({{
parameter: paramName,
message: `"${{paramName}}" nécessite une sélection visuelle valide`,
impact: 'medium'
}});
}}
break;
}}
}}
}});
return validation;
}}
// Simulation du rendu des alternatives
getAlternativeActions() {{
const alternatives = [];
if (!this.action || this.error) {{
// Suggérer des alternatives basées sur le type d'étape
const stepTypeAlternatives = {{
'click_anchor': [
{{ name: 'click', description: 'Clic standard sur élément' }},
{{ name: 'type', description: 'Saisie de texte' }}
],
'type_text': [
{{ name: 'type', description: 'Saisie de texte standard' }},
{{ name: 'click', description: 'Clic pour focus puis saisie' }}
],
'wait_for_anchor': [
{{ name: 'wait', description: 'Attente temporelle' }},
{{ name: 'condition', description: 'Attente conditionnelle' }}
]
}};
const typeAlternatives = stepTypeAlternatives[this.stepType] || [
{{ name: 'click', description: 'Clic standard' }},
{{ name: 'type', description: 'Saisie standard' }}
];
alternatives.push(...typeAlternatives);
}}
return alternatives;
}}
}}
// Test des propriétés VWBActionProperties
function testVWBActionProperties() {{
const results = {{}};
try {{
const simulator = new VWBActionPropertiesSimulator(
vwbAction,
loadingState.isLoading,
loadingState.hasError ? new Error(loadingState.errorMessage || 'Test error') : null,
loadingState.stepType,
parameters,
variables
);
// 1. Test de détection d'action VWB (Property 4)
const detection = simulator.detectVWBAction();
results.vwbDetection = {{
isVWBAction: detection.isVWBAction,
detectionMethods: detection.detectionMethods,
confidence: detection.confidence,
methodCount: Object.values(detection.detectionMethods).filter(Boolean).length
}};
// 2. Test de gestion des états de chargement (Property 5)
const stateHandling = simulator.handleLoadingStates();
results.loadingStateHandling = {{
currentState: stateHandling.currentState,
canRender: stateHandling.canRender,
showsAppropriateUI: stateHandling.showsAppropriateUI,
providesUserFeedback: stateHandling.providesUserFeedback,
hasRecoveryOptions: stateHandling.hasRecoveryOptions
}};
// 3. Test de validation des paramètres
const validation = simulator.validateParameters();
results.parameterValidation = {{
is_valid: validation.is_valid,
errorCount: validation.errors.length,
warningCount: validation.warnings.length,
suggestionCount: validation.suggestions.length,
validationPossible: Boolean(vwbAction)
}};
// 4. Test des alternatives
const alternatives = simulator.getAlternativeActions();
results.alternatives = {{
count: alternatives.length,
hasAlternatives: alternatives.length > 0,
alternatives: alternatives
}};
// 5. Test de cohérence globale
results.consistency = {{
stateMatchesData: (
(loadingState.isLoading && stateHandling.currentState === 'loading') ||
(loadingState.hasError && stateHandling.currentState === 'error') ||
(!vwbAction && !loadingState.isLoading && !loadingState.hasError && stateHandling.currentState === 'not_found') ||
(vwbAction && !loadingState.isLoading && !loadingState.hasError && stateHandling.currentState === 'loaded')
),
detectionMatchesAction: (
(vwbAction && detection.isVWBAction) ||
(!vwbAction && loadingState.stepType && detection.isVWBAction) ||
(!vwbAction && !loadingState.stepType)
),
validationMatchesState: (
(!vwbAction && !validation.is_valid) ||
(vwbAction && validation !== null)
)
}};
results.success = true;
}} catch (error) {{
results.success = false;
results.error = error.message;
}}
return results;
}}
// Exécuter le test
const testResults = testVWBActionProperties();
console.log(JSON.stringify(testResults, null, 2));
"""
return test_script
def run_test_script(self, script_content: str) -> Dict[str, Any]:
"""Exécute un script de test Node.js et retourne les résultats"""
with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
f.write(script_content)
script_path = f.name
try:
# Exécuter le script dans le contexte du frontend
result = subprocess.run(
['node', script_path],
cwd=self.frontend_path,
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return {
'success': False,
'error': f'Invalid JSON output: {result.stdout}',
'stderr': result.stderr
}
else:
return {
'success': False,
'error': f'Script failed with code {result.returncode}',
'stdout': result.stdout,
'stderr': result.stderr
}
except subprocess.TimeoutExpired:
return {
'success': False,
'error': 'Test script timeout'
}
except Exception as e:
return {
'success': False,
'error': f'Execution error: {str(e)}'
}
finally:
# Nettoyer le fichier temporaire
try:
os.unlink(script_path)
except:
pass
class TestVWBActionPropertiesProperties:
"""Tests de propriétés pour VWBActionProperties"""
def setup_method(self):
"""Configuration avant chaque test"""
self.helper = VWBActionPropertiesTestHelper()
@given(
action=st.one_of(st.none(), vwb_action_strategy()),
loading_state=loading_state_strategy(),
parameters=st.dictionaries(st.text(min_size=3, max_size=20), st.one_of(st.text(), st.integers(), st.booleans()), max_size=5),
variables=st.lists(variable_strategy(), max_size=3)
)
@PROPERTY_TEST_SETTINGS
def test_property_4_vwb_action_detection(self, action, loading_state, parameters, variables):
"""
Property 4: Détection correcte des actions VWB
Pour toute étape identifiée comme action VWB par le StepTypeResolver,
le PropertiesPanel doit utiliser le composant VWBActionProperties.
"""
note(f"Testing VWB detection - Action: {action is not None}, Loading: {loading_state}")
note(f"Parameters: {len(parameters)}, Variables: {len(variables)}")
# Créer et exécuter le test
script = self.helper.create_test_script(action, loading_state, parameters, variables)
results = self.helper.run_test_script(script)
# Vérifications des propriétés
assert results.get('success', False), f"Test failed: {results.get('error', 'Unknown error')}"
# Property 4.1: Détection basée sur l'action
detection = results.get('vwbDetection', {})
if action is not None:
assert detection.get('isVWBAction', False), "Action VWB non détectée malgré la présence d'une action"
assert detection.get('confidence', 0) > 0, "Confiance de détection nulle avec action présente"
# Property 4.2: Détection basée sur le type d'étape
step_type = loading_state.get('stepType')
if step_type and any(pattern in step_type for pattern in ['_anchor', '_text', 'click_', 'type_']):
assert detection.get('isVWBAction', False), f"Type VWB non détecté: {step_type}"
# Property 4.3: Méthodes de détection multiples
detection_methods = detection.get('detectionMethods', {})
method_count = detection.get('methodCount', 0)
assert isinstance(detection_methods, dict), "Méthodes de détection invalides"
assert method_count >= 0, "Nombre de méthodes de détection invalide"
# Property 4.4: Cohérence de la détection
consistency = results.get('consistency', {})
assert consistency.get('detectionMatchesAction', False), "Détection incohérente avec l'action"
@given(
action=st.one_of(st.none(), vwb_action_strategy()),
loading_state=loading_state_strategy(),
parameters=st.dictionaries(st.text(min_size=3, max_size=20), st.one_of(st.text(), st.integers()), max_size=3),
variables=st.lists(variable_strategy(), max_size=2)
)
@PROPERTY_TEST_SETTINGS
def test_property_5_loading_state_management(self, action, loading_state, parameters, variables):
"""
Property 5: Gestion des états de chargement VWB
Pour toute action VWB en cours de chargement, le système doit afficher
un indicateur de chargement approprié.
"""
note(f"Testing loading states - Loading: {loading_state.get('isLoading')}, Error: {loading_state.get('hasError')}")
script = self.helper.create_test_script(action, loading_state, parameters, variables)
results = self.helper.run_test_script(script)
assert results.get('success', False), f"Test failed: {results.get('error')}"
# Property 5.1: Gestion de l'état de chargement
state_handling = results.get('loadingStateHandling', {})
assert state_handling.get('canRender', False), "Composant ne peut pas se rendre"
assert state_handling.get('showsAppropriateUI', False), "Interface utilisateur inappropriée"
assert state_handling.get('providesUserFeedback', False), "Pas de feedback utilisateur"
# Property 5.2: États spécifiques
current_state = state_handling.get('currentState', 'unknown')
if loading_state.get('isLoading', False):
assert current_state == 'loading', f"État incorrect pendant le chargement: {current_state}"
elif loading_state.get('hasError', False):
assert current_state == 'error', f"État incorrect en cas d'erreur: {current_state}"
assert state_handling.get('hasRecoveryOptions', False), "Options de récupération manquantes"
elif action is None:
assert current_state == 'not_found', f"État incorrect pour action manquante: {current_state}"
assert state_handling.get('hasRecoveryOptions', False), "Options de récupération manquantes"
else:
assert current_state == 'loaded', f"État incorrect pour action chargée: {current_state}"
# Property 5.3: Cohérence globale des états
consistency = results.get('consistency', {})
assert consistency.get('stateMatchesData', False), "État incohérent avec les données"
@given(
action=vwb_action_strategy(),
parameters=st.dictionaries(st.text(min_size=3, max_size=20), st.one_of(st.text(), st.integers(), st.booleans()), max_size=4),
variables=st.lists(variable_strategy(), max_size=3)
)
@PROPERTY_TEST_SETTINGS
def test_property_vwb_parameter_validation(self, action, parameters, variables):
"""
Test de validation des paramètres VWB
Pour toute action VWB avec paramètres, le système doit :
1. Valider les paramètres requis
2. Valider les types de paramètres
3. Fournir des messages d'erreur appropriés
"""
note(f"Testing parameter validation for action: {action['id']}")
note(f"Parameters: {list(parameters.keys())}")
# État normal (pas de chargement, pas d'erreur)
loading_state = {
'isLoading': False,
'hasError': False,
'errorMessage': None,
'stepType': action['id']
}
script = self.helper.create_test_script(action, loading_state, parameters, variables)
results = self.helper.run_test_script(script)
assert results.get('success', False), f"Test failed: {results.get('error')}"
# Vérifier la validation des paramètres
validation = results.get('parameterValidation', {})
assert validation.get('validationPossible', False), "Validation impossible avec action présente"
assert isinstance(validation.get('errorCount', -1), int), "Nombre d'erreurs invalide"
assert isinstance(validation.get('warningCount', -1), int), "Nombre d'avertissements invalide"
# Vérifier la cohérence de la validation
if validation.get('errorCount', 0) > 0:
assert not validation.get('is_valid', True), "Validation marquée valide malgré les erreurs"
@given(
loading_state=loading_state_strategy(),
parameters=st.dictionaries(st.text(min_size=3, max_size=15), st.text(max_size=50), max_size=3)
)
@PROPERTY_TEST_SETTINGS
def test_property_alternative_actions_suggestions(self, loading_state, parameters):
"""
Test des suggestions d'actions alternatives
Quand une action VWB n'est pas disponible, le système doit :
1. Suggérer des actions alternatives appropriées
2. Basées sur le type d'étape détecté
3. Permettre la configuration manuelle
"""
note(f"Testing alternatives for step type: {loading_state.get('stepType')}")
# Forcer l'absence d'action pour tester les alternatives
action = None
variables = []
script = self.helper.create_test_script(action, loading_state, parameters, variables)
results = self.helper.run_test_script(script)
assert results.get('success', False), f"Test failed: {results.get('error')}"
# Vérifier les alternatives
alternatives = results.get('alternatives', {})
assert alternatives.get('count', 0) > 0, "Aucune alternative suggérée"
assert alternatives.get('hasAlternatives', False), "Flag d'alternatives incorrect"
# Vérifier que les alternatives sont appropriées
alternative_list = alternatives.get('alternatives', [])
assert len(alternative_list) > 0, "Liste d'alternatives vide"
for alt in alternative_list:
assert 'name' in alt, "Alternative sans nom"
assert 'description' in alt, "Alternative sans description"
class VWBActionPropertiesStateMachine(RuleBasedStateMachine):
"""Machine à états pour tester les propriétés de VWBActionProperties"""
actions = Bundle('actions')
states = Bundle('states')
def __init__(self):
super().__init__()
self.helper = VWBActionPropertiesTestHelper()
self.test_results = []
self.current_actions = []
@initialize()
def setup(self):
"""Initialisation de la machine à états"""
pass
@rule(target=actions, action=vwb_action_strategy())
def add_action(self, action):
"""Ajoute une action VWB"""
self.current_actions.append(action)
return action
@rule(
action=st.one_of(st.none(), actions),
loading_state=loading_state_strategy(),
parameters=st.dictionaries(st.text(min_size=3, max_size=15), st.text(max_size=30), max_size=3)
)
def test_action_properties(self, action, loading_state, parameters):
"""Teste les propriétés avec une action"""
script = self.helper.create_test_script(action, loading_state, parameters, [])
results = self.helper.run_test_script(script)
self.test_results.append(results)
# Vérifications d'état
if results.get('success'):
detection = results.get('vwbDetection', {})
state_handling = results.get('loadingStateHandling', {})
assert state_handling.get('canRender', False), "Rendu impossible"
if action:
assert detection.get('isVWBAction', False), "Détection VWB échouée"
@invariant()
def all_tests_successful(self):
"""Invariant: tous les tests doivent réussir"""
for result in self.test_results:
if not result.get('success', False):
assert False, f"Test failed: {result.get('error', 'Unknown error')}"
# Configuration de la machine à états
TestVWBActionPropertiesStateMachine = VWBActionPropertiesStateMachine.TestCase
def test_vwb_action_properties_comprehensive():
"""Test complet des propriétés de VWBActionProperties"""
helper = VWBActionPropertiesTestHelper()
# Test de base avec action complète
basic_action = {
'id': 'click_anchor',
'name': 'Clic sur ancre visuelle',
'description': 'Clique sur un élément identifié visuellement',
'category': 'interaction',
'parameters': {
'target_anchor': {
'type': 'VWBVisualAnchor',
'required': True,
'description': 'Élément cible à cliquer'
},
'confidence_threshold': {
'type': 'number',
'required': False,
'description': 'Seuil de confiance',
'default': 0.8,
'min': 0.5,
'max': 1.0
}
},
'examples': [],
'version': '1.0.0',
'tags': ['interaction', 'click']
}
basic_loading_state = {
'isLoading': False,
'hasError': False,
'errorMessage': None,
'stepType': 'click_anchor'
}
basic_parameters = {
'target_anchor': None,
'confidence_threshold': 0.8
}
script = helper.create_test_script(basic_action, basic_loading_state, basic_parameters, [])
results = helper.run_test_script(script)
assert results.get('success', False), f"Basic test failed: {results.get('error')}"
assert results.get('vwbDetection', {}).get('isVWBAction', False), "VWB detection failed"
if __name__ == '__main__':
# Exécution directe pour tests rapides
test_vwb_action_properties_comprehensive()
print("✅ Tests de propriétés VWBActionProperties - Tous les tests passent")