socialseed-e2e 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. socialseed_e2e/__init__.py +184 -20
  2. socialseed_e2e/__version__.py +2 -2
  3. socialseed_e2e/cli.py +353 -190
  4. socialseed_e2e/core/base_page.py +368 -49
  5. socialseed_e2e/core/config_loader.py +15 -3
  6. socialseed_e2e/core/headers.py +11 -4
  7. socialseed_e2e/core/loaders.py +6 -4
  8. socialseed_e2e/core/test_orchestrator.py +2 -0
  9. socialseed_e2e/core/test_runner.py +487 -0
  10. socialseed_e2e/templates/agent_docs/AGENT_GUIDE.md.template +412 -0
  11. socialseed_e2e/templates/agent_docs/EXAMPLE_TEST.md.template +152 -0
  12. socialseed_e2e/templates/agent_docs/FRAMEWORK_CONTEXT.md.template +55 -0
  13. socialseed_e2e/templates/agent_docs/WORKFLOW_GENERATION.md.template +182 -0
  14. socialseed_e2e/templates/data_schema.py.template +111 -70
  15. socialseed_e2e/templates/e2e.conf.template +19 -0
  16. socialseed_e2e/templates/service_page.py.template +82 -27
  17. socialseed_e2e/templates/test_module.py.template +21 -7
  18. socialseed_e2e/templates/verify_installation.py +192 -0
  19. socialseed_e2e/utils/__init__.py +29 -0
  20. socialseed_e2e/utils/ai_generator.py +463 -0
  21. socialseed_e2e/utils/pydantic_helpers.py +392 -0
  22. socialseed_e2e/utils/state_management.py +312 -0
  23. {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/METADATA +64 -27
  24. socialseed_e2e-0.1.2.dist-info/RECORD +38 -0
  25. socialseed_e2e-0.1.0.dist-info/RECORD +0 -29
  26. {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/WHEEL +0 -0
  27. {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/entry_points.txt +0 -0
  28. {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/licenses/LICENSE +0 -0
  29. {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/top_level.txt +0 -0
socialseed_e2e/cli.py CHANGED
@@ -5,7 +5,6 @@ This module provides the command-line interface for the E2E testing framework,
5
5
  enabling developers and AI agents to create, manage, and run API tests.
6
6
  """
7
7
 
8
- import os
9
8
  import subprocess
10
9
  import sys
11
10
  from pathlib import Path
@@ -15,7 +14,6 @@ import click
15
14
  from rich.console import Console
16
15
  from rich.panel import Panel
17
16
  from rich.table import Table
18
- from rich.text import Text
19
17
 
20
18
  from socialseed_e2e import __version__
21
19
  from socialseed_e2e.core.config_loader import ApiConfigLoader, ConfigError
@@ -25,33 +23,33 @@ console = Console()
25
23
 
26
24
 
27
25
  @click.group()
28
- @click.version_option(version=__version__, prog_name="socialseed-e2e")
26
+ @click.version_option(version=str(__version__), prog_name="socialseed-e2e")
29
27
  def cli():
30
- """socialseed-e2e: Framework E2E para APIs REST.
28
+ """socialseed-e2e: E2E Framework for REST APIs.
31
29
 
32
- Un framework agnóstico de servicios para testing End-to-End de APIs REST,
33
- diseñado para desarrolladores y agentes de IA.
30
+ A service-agnostic framework for End-to-End testing of REST APIs,
31
+ designed for developers and AI agents.
34
32
  """
35
33
  pass
36
34
 
37
35
 
38
36
  @cli.command()
39
37
  @click.argument("directory", default=".", required=False)
40
- @click.option("--force", is_flag=True, help="Sobrescribir archivos existentes")
38
+ @click.option("--force", is_flag=True, help="Overwrite existing files")
41
39
  def init(directory: str, force: bool):
42
- """Inicializa un nuevo proyecto E2E.
40
+ """Initialize a new E2E project.
43
41
 
44
- Crea la estructura de directorios y archivos de configuración inicial.
42
+ Creates the initial directory structure and configuration files.
45
43
 
46
44
  Args:
47
- directory: Directorio donde crear el proyecto (default: directorio actual)
48
- force: Si es True, sobrescribe archivos existentes
45
+ directory: Directory to create the project (default: current directory)
46
+ force: If True, overwrites existing files
49
47
  """
50
48
  target_path = Path(directory).resolve()
51
49
 
52
- console.print(f"\n🌱 [bold green]Inicializando proyecto E2E en:[/bold green] {target_path}\n")
50
+ console.print(f"\n🌱 [bold green]Initializing E2E project at:[/bold green] {target_path}\n")
53
51
 
54
- # Crear estructura de directorios
52
+ # Create directory structure
55
53
  dirs_to_create = [
56
54
  target_path / "services",
57
55
  target_path / "tests",
@@ -67,11 +65,13 @@ def init(directory: str, force: bool):
67
65
  if dir_path.parent == target_path
68
66
  else str(dir_path.relative_to(target_path))
69
67
  )
70
- console.print(f" [green]✓[/green] Creado: {dir_path.relative_to(target_path)}")
68
+ console.print(f" [green]✓[/green] Created: {dir_path.relative_to(target_path)}")
71
69
  else:
72
- console.print(f" [yellow]⚠[/yellow] Ya existe: {dir_path.relative_to(target_path)}")
70
+ console.print(
71
+ f" [yellow]⚠[/yellow] Already exists: {dir_path.relative_to(target_path)}"
72
+ )
73
73
 
74
- # Crear archivo de configuración
74
+ # Create configuration file
75
75
  config_path = target_path / "e2e.conf"
76
76
  if not config_path.exists() or force:
77
77
  engine = TemplateEngine()
@@ -87,11 +87,11 @@ def init(directory: str, force: bool):
87
87
  str(config_path),
88
88
  overwrite=force,
89
89
  )
90
- console.print(f" [green]✓[/green] Creado: e2e.conf")
90
+ console.print(" [green]✓[/green] Created: e2e.conf")
91
91
  else:
92
- console.print(f" [yellow]⚠[/yellow] Ya existe: e2e.conf (usa --force para sobrescribir)")
92
+ console.print(" [yellow]⚠[/yellow] Already exists: e2e.conf (use --force to overwrite)")
93
93
 
94
- # Crear .gitignore
94
+ # Create .gitignore
95
95
  gitignore_path = target_path / ".gitignore"
96
96
  if not gitignore_path.exists() or force:
97
97
  gitignore_content = """# Python
@@ -121,70 +121,207 @@ test-results/
121
121
  htmlcov/
122
122
  """
123
123
  gitignore_path.write_text(gitignore_content)
124
- console.print(f" [green]✓[/green] Creado: .gitignore")
124
+ console.print(" [green]✓[/green] Created: .gitignore")
125
+ else:
126
+ console.print(" [yellow]⚠[/yellow] Already exists: .gitignore")
127
+
128
+ # Create requirements.txt
129
+ requirements_path = target_path / "requirements.txt"
130
+ if not requirements_path.exists() or force:
131
+ requirements_content = """pydantic>=2.0.0
132
+ email-validator>=2.0.0
133
+ """
134
+ requirements_path.write_text(requirements_content)
135
+ console.print(" [green]✓[/green] Created: requirements.txt")
125
136
  else:
126
- console.print(f" [yellow]⚠[/yellow] Ya existe: .gitignore")
137
+ console.print(" [yellow]⚠[/yellow] Already exists: requirements.txt")
138
+
139
+ # Show success message
140
+ console.print("\n[bold green]✅ Project initialized successfully![/bold green]\n")
141
+
142
+ # Create .agent folder for AI documentation
143
+ agent_docs_path = target_path / ".agent"
144
+ if not agent_docs_path.exists() or force:
145
+ if not agent_docs_path.exists():
146
+ agent_docs_path.mkdir()
147
+
148
+ # Instantiate engine if it doesn't exist
149
+ engine = TemplateEngine()
150
+
151
+ # List of documentation templates for the agent
152
+ agent_templates = [
153
+ ("agent_docs/FRAMEWORK_CONTEXT.md.template", "FRAMEWORK_CONTEXT.md"),
154
+ ("agent_docs/WORKFLOW_GENERATION.md.template", "WORKFLOW_GENERATION.md"),
155
+ ("agent_docs/EXAMPLE_TEST.md.template", "EXAMPLE_TEST.md"),
156
+ ("agent_docs/AGENT_GUIDE.md.template", "AGENT_GUIDE.md"),
157
+ ]
158
+
159
+ for template_name, output_name in agent_templates:
160
+ engine.render_to_file(
161
+ template_name,
162
+ {}, # No variables to replace in these MD
163
+ str(agent_docs_path / output_name),
164
+ overwrite=force,
165
+ )
166
+
167
+ console.print(" [green]✓[/green] Created: .agent/ (AI Documentation)")
168
+
169
+ # Copy verification script
170
+ verify_script_path = target_path / "verify_installation.py"
171
+ if not verify_script_path.exists() or force:
172
+ try:
173
+ import shutil
174
+
175
+ from socialseed_e2e.templates import __file__ as templates_init
127
176
 
128
- # Mostrar mensaje de éxito
129
- console.print(f"\n[bold green]✅ Proyecto inicializado correctamente![/bold green]\n")
177
+ templates_dir = Path(templates_init).parent
178
+ source_script = templates_dir / "verify_installation.py"
179
+ if source_script.exists():
180
+ shutil.copy(str(source_script), str(verify_script_path))
181
+ console.print(" [green]✓[/green] Created: verify_installation.py")
182
+ except Exception:
183
+ # If it fails, it's not critical
184
+ pass
130
185
 
131
186
  console.print(
132
187
  Panel(
133
- "[bold]Próximos pasos:[/bold]\n\n"
134
- "1. Edita [cyan]e2e.conf[/cyan] para configurar tu API\n"
135
- "2. Ejecuta: [cyan]e2e new-service <nombre>[/cyan]\n"
136
- "3. Ejecuta: [cyan]e2e new-test <nombre> --service <svc>[/cyan]\n"
137
- "4. Ejecuta: [cyan]e2e run[/cyan] para correr tests",
138
- title="🚀 Empezar",
188
+ "[bold]Next steps:[/bold]\n\n"
189
+ "1. Edit [cyan]e2e.conf[/cyan] to configure your API\n"
190
+ '2. Ask your AI Agent: [italic]"Read the AGENT_GUIDE.md and '
191
+ 'generate tests for my API"[/italic]\n'
192
+ "3. Or do it manually: [cyan]e2e new-service <name>[/cyan]",
193
+ title="🚀 Getting Started",
139
194
  border_style="green",
140
195
  )
141
196
  )
142
197
 
198
+ console.print(
199
+ Panel(
200
+ "[bold]⚠️ Important for AI Agents:[/bold]\n\n"
201
+ "• Use [cyan]absolute imports[/cyan] (not relative) in tests\n"
202
+ "• Remember to use [cyan]model_dump(by_alias=True)[/cyan] to serialize DTOs\n"
203
+ "• Review [cyan]AGENT_GUIDE.md[/cyan] for correct patterns and conventions",
204
+ title="🤖 Development Guide",
205
+ border_style="yellow",
206
+ )
207
+ )
208
+
209
+ # 1. Auto-install dependencies (if requirements.txt was created or force=True)
210
+ console.print("\n📦 Installing dependencies...")
211
+ try:
212
+ result = subprocess.run(
213
+ [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
214
+ cwd=str(target_path),
215
+ capture_output=True,
216
+ text=True,
217
+ timeout=120,
218
+ )
219
+ if result.returncode == 0:
220
+ console.print(" [green]✓[/green] Dependencies installed")
221
+ else:
222
+ console.print(" [yellow]⚠ Warning:[/yellow] Some dependencies could not be installed")
223
+ if result.stderr:
224
+ console.print(f" [dim]{result.stderr[:200]}...[/dim]")
225
+ except subprocess.TimeoutExpired:
226
+ console.print(" [yellow]⚠ Warning:[/yellow] Installation took too long")
227
+ except Exception as e:
228
+ console.print(f" [yellow]⚠ Warning:[/yellow] Could not install dependencies: {e}")
229
+
230
+ # 2. Run verification (always)
231
+ console.print("\n🔍 Verifying installation...")
232
+ all_checks_passed = False
233
+ try:
234
+ # Try to import and run verification
235
+ import importlib.util
236
+
237
+ verify_script_path = target_path / "verify_installation.py"
238
+ if verify_script_path.exists():
239
+ spec = importlib.util.spec_from_file_location(
240
+ "verify_installation", str(verify_script_path)
241
+ )
242
+ verify_module = None
243
+ if spec and spec.loader:
244
+ verify_module = importlib.util.module_from_spec(spec)
245
+ spec.loader.exec_module(verify_module)
246
+
247
+ # Run the verification function if it exists
248
+ if verify_module and hasattr(verify_module, "run_verification"):
249
+ all_checks_passed = verify_module.run_verification(str(target_path))
250
+ else:
251
+ # Fallback: run via subprocess
252
+ result = subprocess.run(
253
+ [sys.executable, str(verify_script_path), str(target_path)],
254
+ cwd=str(target_path),
255
+ capture_output=True,
256
+ text=True,
257
+ timeout=30,
258
+ )
259
+ all_checks_passed = result.returncode == 0
260
+ if result.stdout:
261
+ console.print(result.stdout)
262
+ else:
263
+ console.print(" [yellow]⚠[/yellow] Verification script not found")
264
+ except Exception as e:
265
+ console.print(f" [yellow]⚠[/yellow] Could not run verification: {e}")
266
+ all_checks_passed = True # Consider successful if verification couldn't run
267
+
268
+ # 3. Final success panel (if all checks pass)
269
+ if all_checks_passed:
270
+ console.print(
271
+ Panel(
272
+ "[bold green]✅ ALL READY![/bold green] Your project is configured and verified.\n\n"
273
+ "🤖 You can ask your AI Agent to read [cyan].agent/AGENT_GUIDE.md[/cyan]\n"
274
+ "🚀 And generate E2E tests automatically",
275
+ title="🎉 Success",
276
+ border_style="green",
277
+ )
278
+ )
279
+
143
280
 
144
281
  @cli.command()
145
282
  @click.argument("name")
146
- @click.option("--base-url", default="http://localhost:8080", help="URL base del servicio")
147
- @click.option("--health-endpoint", default="/health", help="Endpoint de health check")
283
+ @click.option("--base-url", default="http://localhost:8080", help="Service base URL")
284
+ @click.option("--health-endpoint", default="/health", help="Health check endpoint")
148
285
  def new_service(name: str, base_url: str, health_endpoint: str):
149
- """Crea un nuevo servicio con scaffolding.
286
+ """Create a new service with scaffolding.
150
287
 
151
288
  Args:
152
- name: Nombre del servicio (ej: users-api)
153
- base_url: URL base del servicio
154
- health_endpoint: Endpoint para health checks
289
+ name: Service name (e.g.: users-api)
290
+ base_url: Service base URL
291
+ health_endpoint: Health check endpoint
155
292
  """
156
- console.print(f"\n🔧 [bold blue]Creando servicio:[/bold blue] {name}\n")
293
+ console.print(f"\n🔧 [bold blue]Creating service:[/bold blue] {name}\n")
157
294
 
158
- # Verificar que estamos en un proyecto E2E
295
+ # Verify we are in an E2E project
159
296
  if not _is_e2e_project():
160
- console.print("[red]❌ Error:[/red] No se encontró e2e.conf. ¿Estás en un proyecto E2E?")
161
- console.print(" Ejecuta: [cyan]e2e init[/cyan] primero")
297
+ console.print("[red]❌ Error:[/red] e2e.conf not found. Are you in an E2E project?")
298
+ console.print(" Run: [cyan]e2e init[/cyan] first")
162
299
  sys.exit(1)
163
300
 
164
- # Crear estructura del servicio
301
+ # Create service structure
165
302
  service_path = Path("services") / name
166
303
  modules_path = service_path / "modules"
167
304
 
168
305
  try:
169
306
  service_path.mkdir(parents=True)
170
307
  modules_path.mkdir()
171
- console.print(f" [green]✓[/green] Creado: services/{name}/")
172
- console.print(f" [green]✓[/green] Creado: services/{name}/modules/")
308
+ console.print(f" [green]✓[/green] Created: services/{name}/")
309
+ console.print(f" [green]✓[/green] Created: services/{name}/modules/")
173
310
  except FileExistsError:
174
- console.print(f" [yellow]⚠[/yellow] El servicio '{name}' ya existe")
175
- if not click.confirm("¿Deseas continuar y sobrescribir archivos?"):
311
+ console.print(f" [yellow]⚠[/yellow] Service '{name}' already exists")
312
+ if not click.confirm("Do you want to continue and overwrite files?"):
176
313
  return
177
314
 
178
- # Crear __init__.py
179
- _create_file(service_path / "__init__.py", f'"""Servicio {name}."""\n')
180
- _create_file(modules_path / "__init__.py", f'"""Módulos de test para {name}."""\n')
181
- console.print(f" [green]✓[/green] Creado: services/{name}/__init__.py")
182
- console.print(f" [green]✓[/green] Creado: services/{name}/modules/__init__.py")
315
+ # Create __init__.py
316
+ _create_file(service_path / "__init__.py", f'"""Service {name}."""\n')
317
+ _create_file(modules_path / "__init__.py", f'"""Test modules for {name}."""\n')
318
+ console.print(f" [green]✓[/green] Created: services/{name}/__init__.py")
319
+ console.print(f" [green]✓[/green] Created: services/{name}/modules/__init__.py")
183
320
 
184
- # Inicializar TemplateEngine
321
+ # Initialize TemplateEngine
185
322
  engine = TemplateEngine()
186
323
 
187
- # Variables para los templates
324
+ # Variables for templates
188
325
  class_name = _to_class_name(name)
189
326
  snake_case_name = to_snake_case(name)
190
327
  template_vars = {
@@ -194,42 +331,45 @@ def new_service(name: str, base_url: str, health_endpoint: str):
194
331
  "endpoint_prefix": "entities",
195
332
  }
196
333
 
197
- # Crear página del servicio
334
+ # Create service page
198
335
  engine.render_to_file(
199
336
  "service_page.py.template",
200
337
  template_vars,
201
338
  str(service_path / f"{snake_case_name}_page.py"),
202
339
  overwrite=False,
203
340
  )
204
- console.print(f" [green]✓[/green] Creado: services/{name}/{snake_case_name}_page.py")
341
+ console.print(f" [green]✓[/green] Created: services/{name}/{snake_case_name}_page.py")
205
342
 
206
- # Crear archivo de configuración
343
+ # Create configuration file
207
344
  engine.render_to_file(
208
- "config.py.template", template_vars, str(service_path / "config.py"), overwrite=False
345
+ "config.py.template",
346
+ template_vars,
347
+ str(service_path / "config.py"),
348
+ overwrite=False,
209
349
  )
210
- console.print(f" [green]✓[/green] Creado: services/{name}/config.py")
350
+ console.print(f" [green]✓[/green] Created: services/{name}/config.py")
211
351
 
212
- # Crear data_schema.py
352
+ # Create data_schema.py
213
353
  engine.render_to_file(
214
354
  "data_schema.py.template",
215
355
  template_vars,
216
356
  str(service_path / "data_schema.py"),
217
357
  overwrite=False,
218
358
  )
219
- console.print(f" [green]✓[/green] Creado: services/{name}/data_schema.py")
359
+ console.print(f" [green]✓[/green] Created: services/{name}/data_schema.py")
220
360
 
221
- # Actualizar e2e.conf
361
+ # Update e2e.conf
222
362
  _update_e2e_conf(name, base_url, health_endpoint)
223
363
 
224
- console.print(f"\n[bold green]✅ Servicio '{name}' creado correctamente![/bold green]\n")
364
+ console.print(f"\n[bold green]✅ Service '{name}' created successfully![/bold green]\n")
225
365
 
226
366
  console.print(
227
367
  Panel(
228
- f"[bold]Próximos pasos:[/bold]\n\n"
229
- f"1. Edita [cyan]services/{name}/data_schema.py[/cyan] para definir tus DTOs\n"
230
- f"2. Ejecuta: [cyan]e2e new-test <nombre> --service {name}[/cyan]\n"
231
- f"3. Ejecuta: [cyan]e2e run --service {name}[/cyan]",
232
- title="🚀 Continuar",
368
+ f"[bold]Next steps:[/bold]\n\n"
369
+ f"1. Edit [cyan]services/{name}/data_schema.py[/cyan] to define your DTOs\n"
370
+ f"2. Run: [cyan]e2e new-test <name> --service {name}[/cyan]\n"
371
+ f"3. Run: [cyan]e2e run --service {name}[/cyan]",
372
+ title="🚀 Continue",
233
373
  border_style="blue",
234
374
  )
235
375
  )
@@ -237,36 +377,36 @@ def new_service(name: str, base_url: str, health_endpoint: str):
237
377
 
238
378
  @cli.command()
239
379
  @click.argument("name")
240
- @click.option("--service", "-s", required=True, help="Nombre del servicio")
241
- @click.option("--description", "-d", default="", help="Descripción del test")
380
+ @click.option("--service", "-s", required=True, help="Service name")
381
+ @click.option("--description", "-d", default="", help="Test description")
242
382
  def new_test(name: str, service: str, description: str):
243
- """Crea un nuevo módulo de test.
383
+ """Create a new test module.
244
384
 
245
385
  Args:
246
- name: Nombre del test (ej: login, create-user)
247
- service: Servicio al que pertenece el test
248
- description: Descripción opcional del test
386
+ name: Test name (e.g.: login, create-user)
387
+ service: Service to which the test belongs
388
+ description: Optional test description
249
389
  """
250
- console.print(f"\n📝 [bold cyan]Creando test:[/bold cyan] {name}\n")
390
+ console.print(f"\n📝 [bold cyan]Creating test:[/bold cyan] {name}\n")
251
391
 
252
- # Verificar que estamos en un proyecto E2E
392
+ # Verify we are in an E2E project
253
393
  if not _is_e2e_project():
254
- console.print("[red]❌ Error:[/red] No se encontró e2e.conf. ¿Estás en un proyecto E2E?")
394
+ console.print("[red]❌ Error:[/red] e2e.conf not found. Are you in an E2E project?")
255
395
  sys.exit(1)
256
396
 
257
- # Verificar que el servicio existe
397
+ # Verify that the service exists
258
398
  service_path = Path("services") / service
259
399
  modules_path = service_path / "modules"
260
400
 
261
401
  if not service_path.exists():
262
- console.print(f"[red]❌ Error:[/red] El servicio '{service}' no existe.")
263
- console.print(f" Crea el servicio primero: [cyan]e2e new-service {service}[/cyan]")
402
+ console.print(f"[red]❌ Error:[/red] Service '{service}' does not exist.")
403
+ console.print(f" Create the service first: [cyan]e2e new-service {service}[/cyan]")
264
404
  sys.exit(1)
265
405
 
266
406
  if not modules_path.exists():
267
407
  modules_path.mkdir(parents=True)
268
408
 
269
- # Encontrar siguiente número disponible
409
+ # Find next available number
270
410
  existing_tests = sorted(modules_path.glob("[0-9][0-9]_*.py"))
271
411
  if existing_tests:
272
412
  last_num = int(existing_tests[-1].name[:2])
@@ -277,16 +417,16 @@ def new_test(name: str, service: str, description: str):
277
417
  test_filename = f"{next_num:02d}_{name}_flow.py"
278
418
  test_path = modules_path / test_filename
279
419
 
280
- # Verificar si ya existe
420
+ # Check if it already exists
281
421
  if test_path.exists():
282
- console.print(f"[yellow]⚠[/yellow] El test '{name}' ya existe.")
283
- if not click.confirm("¿Deseas sobrescribirlo?"):
422
+ console.print(f"[yellow]⚠[/yellow] Test '{name}' already exists.")
423
+ if not click.confirm("Do you want to overwrite it?"):
284
424
  return
285
425
 
286
- # Inicializar TemplateEngine
426
+ # Initialize TemplateEngine
287
427
  engine = TemplateEngine()
288
428
 
289
- # Variables para el template
429
+ # Variables for template
290
430
  class_name = _to_class_name(service)
291
431
  snake_case_name = to_snake_case(service)
292
432
  test_description = description or f"Test flow for {name}"
@@ -299,169 +439,189 @@ def new_test(name: str, service: str, description: str):
299
439
  "test_description": test_description,
300
440
  }
301
441
 
302
- # Crear test usando template
442
+ # Create test using template
303
443
  engine.render_to_file("test_module.py.template", template_vars, str(test_path), overwrite=False)
304
- console.print(f" [green]✓[/green] Creado: services/{service}/modules/{test_filename}")
444
+ console.print(f" [green]✓[/green] Created: services/{service}/modules/{test_filename}")
305
445
 
306
- console.print(f"\n[bold green]✅ Test '{name}' creado correctamente![/bold green]\n")
446
+ console.print(f"\n[bold green]✅ Test '{name}' created successfully![/bold green]\n")
307
447
 
308
448
  console.print(
309
449
  Panel(
310
- f"[bold]Próximos pasos:[/bold]\n\n"
311
- f"1. Edita [cyan]services/{service}/modules/{test_filename}[/cyan]\n"
312
- f"2. Implementa la lógica del test\n"
313
- f"3. Ejecuta: [cyan]e2e run --service {service}[/cyan]",
314
- title="🚀 Implementar",
450
+ f"[bold]Next steps:[/bold]\n\n"
451
+ f"1. Edit [cyan]services/{service}/modules/{test_filename}[/cyan]\n"
452
+ f"2. Implement the test logic\n"
453
+ f"3. Run: [cyan]e2e run --service {service}[/cyan]",
454
+ title="🚀 Implement",
315
455
  border_style="cyan",
316
456
  )
317
457
  )
318
458
 
319
459
 
320
460
  @cli.command()
321
- @click.option("--service", "-s", help="Filtrar por servicio específico")
322
- @click.option("--module", "-m", help="Filtrar por módulo específico")
323
- @click.option("--verbose", "-v", is_flag=True, help="Modo verbose")
461
+ @click.option("--service", "-s", help="Filter by specific service")
462
+ @click.option("--module", "-m", help="Filter by specific module")
463
+ @click.option("--config", "-c", help="Path to configuration file (e2e.conf)")
464
+ @click.option("--verbose", "-v", is_flag=True, help="Verbose mode")
324
465
  @click.option(
325
- "--output", "-o", type=click.Choice(["text", "json"]), default="text", help="Formato de salida"
466
+ "--output",
467
+ "-o",
468
+ type=click.Choice(["text", "json"]),
469
+ default="text",
470
+ help="Output format",
326
471
  )
327
- def run(service: Optional[str], module: Optional[str], verbose: bool, output: str):
328
- """Ejecuta los tests E2E.
472
+ def run(
473
+ service: Optional[str],
474
+ module: Optional[str],
475
+ config: Optional[str],
476
+ verbose: bool,
477
+ output: str,
478
+ ):
479
+ """Execute E2E tests.
329
480
 
330
- Descubre y ejecuta automáticamente todos los tests disponibles.
481
+ Discovers and automatically executes all available tests.
331
482
 
332
483
  Args:
333
- service: Si se especifica, solo ejecuta tests de este servicio
334
- module: Si se especifica, solo ejecuta este módulo de test
335
- verbose: Si es True, muestra información detallada
336
- output: Formato de salida (text o json)
484
+ service: If specified, only run tests for this service
485
+ module: If specified, only run this test module
486
+ config: Path to the e2e.conf file
487
+ verbose: If True, shows detailed information
488
+ output: Output format (text or json)
337
489
  """
338
- from .core.test_orchestrator import TestOrchestrator
490
+ # from .core.test_orchestrator import TestOrchestrator
339
491
 
340
492
  console.print(f"\n🚀 [bold green]socialseed-e2e v{__version__}[/bold green]")
341
493
  console.print("═" * 50)
342
494
  console.print()
343
495
 
344
- # Verificar configuración
496
+ # Verify configuration
345
497
  try:
346
498
  loader = ApiConfigLoader()
347
- config = loader.load()
348
- console.print(f"📋 [cyan]Configuración:[/cyan] {loader._config_path}")
349
- console.print(f"🌍 [cyan]Environment:[/cyan] {config.environment}")
499
+ app_config = loader.load(config) # Pass the config path if provided
500
+ console.print(f"📋 [cyan]Configuration:[/cyan] {loader._config_path}")
501
+ console.print(f"🌍 [cyan]Environment:[/cyan] {app_config.environment}")
350
502
  console.print()
351
503
  except ConfigError as e:
352
- console.print(f"[red]❌ Error de configuración:[/red] {e}")
353
- console.print(" Ejecuta: [cyan]e2e init[/cyan] para crear un proyecto")
504
+ console.print(f"[red]❌ Configuration error:[/red] {e}")
505
+ console.print(" Run: [cyan]e2e init[/cyan] to create a project")
354
506
  sys.exit(1)
507
+ except Exception as e:
508
+ console.print(f"[red]❌ Unexpected error:[/red] {e}")
509
+ sys.exit(1)
510
+
511
+ # Import test runner
512
+ from .core.test_runner import print_summary, run_all_tests
355
513
 
356
- # TODO: Implementar ejecución real de tests
357
- # Por ahora, mostramos información de descubrimiento
514
+ # Determine services path
515
+ # If a config file was explicitly provided, we prioritize the 'services' folder next to it.
516
+ services_path = Path("services")
517
+ if loader._config_path:
518
+ alt_path = loader._config_path.parent / "services"
519
+ if alt_path.exists():
520
+ services_path = alt_path
521
+ elif not services_path.exists():
522
+ # Fallback if neither works
523
+ pass
358
524
 
359
525
  if service:
360
- console.print(f"🔍 [yellow]Filtrando por servicio:[/yellow] {service}")
526
+ console.print(f"🔍 [yellow]Filtering by service:[/yellow] {service}")
361
527
  if module:
362
- console.print(f"🔍 [yellow]Filtrando por módulo:[/yellow] {module}")
528
+ console.print(f"🔍 [yellow]Filtering by module:[/yellow] {module}")
363
529
  if verbose:
364
- console.print(f"📢 [yellow]Modo verbose activado[/yellow]")
530
+ console.print("📢 [yellow]Verbose mode activated[/yellow]")
365
531
 
366
532
  console.print()
367
- console.print("[yellow]⚠ Nota:[/yellow] La ejecución de tests aún no está implementada")
368
- console.print(" Este es un placeholder para la versión 0.1.0")
369
- console.print()
370
533
 
371
- # Mostrar tabla de servicios encontrados
372
- services_path = Path("services")
373
- if services_path.exists():
374
- services = [
375
- d.name for d in services_path.iterdir() if d.is_dir() and not d.name.startswith("__")
376
- ]
534
+ # Execute tests
535
+ try:
536
+ results = run_all_tests(
537
+ services_path=services_path,
538
+ specific_service=service,
539
+ specific_module=module,
540
+ verbose=verbose,
541
+ )
377
542
 
378
- if services:
379
- table = Table(title="Servicios Encontrados")
380
- table.add_column("Servicio", style="cyan")
381
- table.add_column("Tests", style="green")
382
- table.add_column("Estado", style="yellow")
543
+ # Print summary
544
+ all_passed = print_summary(results)
383
545
 
384
- for svc in services:
385
- modules_path = services_path / svc / "modules"
386
- if modules_path.exists():
387
- test_count = len(list(modules_path.glob("[0-9][0-9]_*.py")))
388
- table.add_row(svc, str(test_count), "Ready" if test_count > 0 else "Empty")
389
- else:
390
- table.add_row(svc, "0", "No modules")
546
+ # Exit with appropriate code
547
+ sys.exit(0 if all_passed else 1)
391
548
 
392
- console.print(table)
393
- else:
394
- console.print("[yellow]⚠ No se encontraron servicios[/yellow]")
395
- console.print(" Crea uno con: [cyan]e2e new-service <nombre>[/cyan]")
396
- else:
397
- console.print("[red]❌ No se encontró el directorio 'services/'[/red]")
549
+ except Exception as e:
550
+ console.print(f"[red]❌ Error executing tests:[/red] {e}")
551
+ if verbose:
552
+ import traceback
398
553
 
399
- console.print()
400
- console.print("═" * 50)
401
- console.print("[bold]Para implementar la ejecución real, contribuye en:[/bold]")
402
- console.print("[cyan]https://github.com/daironpf/socialseed-e2e[/cyan]")
554
+ console.print(traceback.format_exc())
555
+ sys.exit(1)
403
556
 
404
557
 
405
558
  @cli.command()
406
559
  def doctor():
407
- """Verifica la instalación y dependencias.
560
+ """Verify installation and dependencies.
408
561
 
409
- Comprueba que todo esté correctamente configurado para usar el framework.
562
+ Checks that everything is properly configured to use the framework.
410
563
  """
411
564
  console.print("\n🏥 [bold green]socialseed-e2e Doctor[/bold green]\n")
412
565
 
413
566
  checks = []
414
567
 
415
- # Verificar Python
568
+ # Check Python
416
569
  python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
417
570
  checks.append(("Python", python_version, sys.version_info >= (3, 9)))
418
571
 
419
- # Verificar Playwright
572
+ # Check Playwright
420
573
  try:
421
574
  from importlib.metadata import version
422
575
 
423
576
  pw_version = version("playwright")
424
577
  checks.append(("Playwright", pw_version, True))
425
578
  except Exception:
426
- checks.append(("Playwright", "No instalado", False))
579
+ checks.append(("Playwright", "Not installed", False))
427
580
 
428
- # Verificar browsers de Playwright
581
+ # Check Playwright browsers
429
582
  try:
430
583
  result = subprocess.run(
431
- ["playwright", "install", "--help"], capture_output=True, text=True, timeout=5
584
+ ["playwright", "install", "--help"],
585
+ capture_output=True,
586
+ text=True,
587
+ timeout=5,
432
588
  )
433
589
  browsers_installed = result.returncode == 0
434
- checks.append(("Playwright CLI", "Disponible", browsers_installed))
590
+ checks.append(("Playwright CLI", "Available", browsers_installed))
435
591
  except (subprocess.TimeoutExpired, FileNotFoundError):
436
- checks.append(("Playwright CLI", "No disponible", False))
592
+ checks.append(("Playwright CLI", "Not available", False))
437
593
 
438
- # Verificar Pydantic
594
+ # Check Pydantic
439
595
  try:
440
596
  import pydantic
441
597
 
442
598
  checks.append(("Pydantic", pydantic.__version__, True))
443
599
  except ImportError:
444
- checks.append(("Pydantic", "No instalado", False))
600
+ checks.append(("Pydantic", "Not installed", False))
445
601
 
446
- # Verificar e2e.conf
602
+ # Check e2e.conf
447
603
  if _is_e2e_project():
448
- checks.append(("Configuración", "e2e.conf encontrado", True))
604
+ checks.append(("Configuration", "e2e.conf found", True))
449
605
  else:
450
- checks.append(("Configuración", "e2e.conf no encontrado", False))
606
+ checks.append(("Configuration", "e2e.conf not found", False))
451
607
 
452
- # Verificar estructura de directorios
608
+ # Check directory structure
453
609
  services_exists = Path("services").exists()
454
610
  tests_exists = Path("tests").exists()
455
611
  checks.append(
456
- ("Directorio services/", "OK" if services_exists else "No encontrado", services_exists)
612
+ (
613
+ "services/ directory",
614
+ "OK" if services_exists else "Not found",
615
+ services_exists,
616
+ )
457
617
  )
458
- checks.append(("Directorio tests/", "OK" if tests_exists else "No encontrado", tests_exists))
618
+ checks.append(("tests/ directory", "OK" if tests_exists else "Not found", tests_exists))
459
619
 
460
- # Mostrar resultados
461
- table = Table(title="Verificación del Sistema")
462
- table.add_column("Componente", style="cyan")
463
- table.add_column("Versión/Estado", style="white")
464
- table.add_column("Estado", style="bold")
620
+ # Show results
621
+ table = Table(title="System Verification")
622
+ table.add_column("Component", style="cyan")
623
+ table.add_column("Version/Status", style="white")
624
+ table.add_column("Status", style="bold")
465
625
 
466
626
  all_ok = True
467
627
  for name, value, ok in checks:
@@ -474,67 +634,70 @@ def doctor():
474
634
 
475
635
  console.print()
476
636
  if all_ok:
477
- console.print("[bold green]✅ Todo está configurado correctamente![/bold green]")
637
+ console.print("[bold green]✅ Everything is configured correctly![/bold green]")
478
638
  else:
479
- console.print("[bold yellow]⚠ Se encontraron algunos problemas[/bold yellow]")
639
+ console.print("[bold yellow]⚠ Some issues were found[/bold yellow]")
480
640
  console.print()
481
- console.print("[cyan]Soluciones sugeridas:[/cyan]")
641
+ console.print("[cyan]Suggested solutions:[/cyan]")
482
642
 
483
643
  if not any(name == "Playwright" and ok for name, _, ok in checks):
484
- console.print(" • Instala Playwright: [white]pip install playwright[/white]")
644
+ console.print(" • Install Playwright: [white]pip install playwright[/white]")
485
645
  if not any(name == "Playwright CLI" and ok for name, _, ok in checks):
486
- console.print(" • Instala browsers: [white]playwright install chromium[/white]")
646
+ console.print(" • Install browsers: [white]playwright install chromium[/white]")
487
647
  if not any(name == "Pydantic" and ok for name, _, ok in checks):
488
- console.print(" • Instala dependencias: [white]pip install socialseed-e2e[/white]")
648
+ console.print(" • Install dependencies: [white]pip install socialseed-e2e[/white]")
489
649
  if not _is_e2e_project():
490
- console.print(" • Inicializa proyecto: [white]e2e init[/white]")
650
+ console.print(" • Initialize project: [white]e2e init[/white]")
491
651
 
492
652
  console.print()
493
653
 
494
654
 
495
655
  @cli.command()
496
656
  def config():
497
- """Muestra y valida la configuración actual.
657
+ """Show and validate current configuration.
498
658
 
499
- Muestra la configuración cargada desde e2e.conf y valida su sintaxis.
659
+ Shows the configuration loaded from e2e.conf and validates its syntax.
500
660
  """
501
- console.print("\n⚙️ [bold blue]Configuración E2E[/bold blue]\n")
661
+ console.print("\n⚙️ [bold blue]E2E Configuration[/bold blue]\n")
502
662
 
503
663
  try:
504
664
  loader = ApiConfigLoader()
505
665
  config = loader.load()
506
666
 
507
- console.print(f"📋 [cyan]Configuración:[/cyan] {loader._config_path}")
667
+ console.print(f"📋 [cyan]Configuration:[/cyan] {loader._config_path}")
508
668
  console.print(f"🌍 [cyan]Environment:[/cyan] {config.environment}")
509
669
  console.print(f"[cyan]Timeout:[/cyan] {config.timeout}ms")
510
670
  console.print(f"[cyan]Verbose:[/cyan] {config.verbose}")
511
671
  console.print()
512
672
 
513
673
  if config.services:
514
- table = Table(title="Servicios Configurados")
515
- table.add_column("Nombre", style="cyan")
674
+ table = Table(title="Configured Services")
675
+ table.add_column("Name", style="cyan")
516
676
  table.add_column("Base URL", style="green")
517
677
  table.add_column("Health", style="yellow")
518
- table.add_column("Requerido", style="white")
678
+ table.add_column("Required", style="white")
519
679
 
520
680
  for name, svc in config.services.items():
521
681
  table.add_row(
522
- name, svc.base_url, svc.health_endpoint or "N/A", "✓" if svc.required else "✗"
682
+ name,
683
+ svc.base_url,
684
+ svc.health_endpoint or "N/A",
685
+ "✓" if svc.required else "✗",
523
686
  )
524
687
 
525
688
  console.print(table)
526
689
  else:
527
- console.print("[yellow]⚠ No hay servicios configurados[/yellow]")
528
- console.print(" Usa: [cyan]e2e new-service <nombre>[/cyan]")
690
+ console.print("[yellow]⚠ No services configured[/yellow]")
691
+ console.print(" Use: [cyan]e2e new-service <name>[/cyan]")
529
692
 
530
693
  console.print()
531
- console.print("[bold green]✅ Configuración válida[/bold green]")
694
+ console.print("[bold green]✅ Valid configuration[/bold green]")
532
695
 
533
696
  except ConfigError as e:
534
- console.print(f"[red]❌ Error de configuración:[/red] {e}")
697
+ console.print(f"[red]❌ Configuration error:[/red] {e}")
535
698
  sys.exit(1)
536
699
  except Exception as e:
537
- console.print(f"[red]❌ Error inesperado:[/red] {e}")
700
+ console.print(f"[red]❌ Unexpected error:[/red] {e}")
538
701
  sys.exit(1)
539
702
 
540
703
 
@@ -583,11 +746,11 @@ def _update_e2e_conf(service_name: str, base_url: str, health_endpoint: str) ->
583
746
 
584
747
  content = config_path.read_text()
585
748
 
586
- # Verificar si ya existe la sección de servicios
749
+ # Check if services section already exists
587
750
  if "services:" not in content:
588
751
  content += "\nservices:\n"
589
752
 
590
- # Agregar configuración del servicio
753
+ # Add service configuration
591
754
  service_config = f""" {service_name}:
592
755
  name: {service_name}-service
593
756
  base_url: {base_url}
@@ -599,7 +762,7 @@ def _update_e2e_conf(service_name: str, base_url: str, health_endpoint: str) ->
599
762
 
600
763
  content += service_config
601
764
  config_path.write_text(content)
602
- console.print(f" [green]✓[/green] Actualizado: e2e.conf")
765
+ console.print(" [green]✓[/green] Updated: e2e.conf")
603
766
 
604
767
 
605
768
  def main():