mcp-state-machine-test-framework 1.0.5 → 1.0.8
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.
- package/README.md +7 -4
- package/index.js +93 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,10 +27,13 @@ Or run it directly via MCP in your configuration:
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
## 🏁 Basic Workflow
|
|
30
|
-
1. **Initialize
|
|
31
|
-
2. **
|
|
32
|
-
3. **
|
|
33
|
-
4. **
|
|
30
|
+
1. **Initialize Project**: Run `init_project` to setup folders and templates.
|
|
31
|
+
2. **Guided Design**: Tell the agent to "Create a new node". The agent will ask for the mandatory fields (Map, Node Name, Transitions) before executing.
|
|
32
|
+
3. **Automatic Visualization**: Every map change regenerates a Mermaid diagram in `/maps`.
|
|
33
|
+
4. **Execution**: Run `execute_suite` to launch the automated flow.
|
|
34
|
+
|
|
35
|
+
## 🤖 AI Agent Interaction (Guided Design)
|
|
36
|
+
This framework enforces a "Strict Contract" via MCP tools. Agents are instructed NOT to edit JSON files directly, but to use the management tools (`upsert_node`, `save_test_case`, `save_suite`) which provide validation and structure.
|
|
34
37
|
|
|
35
38
|
## 📚 Technical Documentation
|
|
36
39
|
- [🧠 Technical Protocol & History](./SMS_Protocol.md)
|
package/index.js
CHANGED
|
@@ -326,16 +326,20 @@ export class MaquinaDeEstados {
|
|
|
326
326
|
|
|
327
327
|
const maquina = new MaquinaDeEstados();
|
|
328
328
|
const server = new McpServer({ name: "demo-state-machine", version: "11.1.0" });
|
|
329
|
-
server.tool("execute_suite", "Ejecución de Suite Completa", {
|
|
329
|
+
server.tool("execute_suite", "Ejecución de Suite Completa", {
|
|
330
|
+
name: z.string().describe("Nombre de la suite a ejecutar (ej: 'Suite_Login'). Buscará el archivo en /suites.")
|
|
331
|
+
}, async ({ name }) => {
|
|
330
332
|
await maquina.cargar();
|
|
331
333
|
const dir = await maquina.ejecutarSuite(name);
|
|
332
334
|
return { content: [{ type: "text", text: `Reporte: ${dir}` }] };
|
|
333
335
|
});
|
|
334
336
|
|
|
335
|
-
server.tool("upsert_node", "Añadir o actualizar un nodo en el mapa de estados", {
|
|
336
|
-
mapName: z.string(),
|
|
337
|
-
nodeName: z.string(),
|
|
338
|
-
nodeData: z.
|
|
337
|
+
server.tool("upsert_node", "Añadir o actualizar un nodo en el mapa de estados.", {
|
|
338
|
+
mapName: z.string().describe("Nombre del archivo del mapa (ej: 'home_map.json'). El servidor lo buscará en la carpeta /maps."),
|
|
339
|
+
nodeName: z.string().describe("Identificador único del nodo (ej: 'LOGIN_PAGE', 'HOME')."),
|
|
340
|
+
nodeData: z.object({
|
|
341
|
+
transiciones: z.any().optional().describe("Objeto que define las salidas del nodo. Ej: { 'IR_A_LOGIN': { 'destino': 'LOGIN_PAGE', 'accion': 'mcp:wdio-mcp/click_element ...' } }")
|
|
342
|
+
}).passthrough().describe("Datos completos del nodo, incluyendo transiciones y metadatos.")
|
|
339
343
|
}, async ({ mapName, nodeName, nodeData }) => {
|
|
340
344
|
const mapPath = path.join(__dirname, 'maps', mapName);
|
|
341
345
|
let map = { nodos: {} };
|
|
@@ -401,9 +405,12 @@ server.tool("inspect_framework", "Inspeccionar integridad y listar entidades del
|
|
|
401
405
|
return { content: [{ type: "text", text: JSON.stringify(entities, null, 2) }] };
|
|
402
406
|
});
|
|
403
407
|
|
|
404
|
-
server.tool("save_test_case", "Crear o actualizar un caso de prueba", {
|
|
405
|
-
name: z.string(),
|
|
406
|
-
steps: z.array(z.
|
|
408
|
+
server.tool("save_test_case", "Crear o actualizar un caso de prueba.", {
|
|
409
|
+
name: z.string().describe("Nombre identificador del test (ej: 'TC_Login_Exitoso')."),
|
|
410
|
+
steps: z.array(z.object({
|
|
411
|
+
name: z.string().optional().describe("Descripción amigable del paso (ej: 'Ingresar Usuario')."),
|
|
412
|
+
action: z.string().optional().describe("Acción a realizar. Puede ser un nombre de transición del mapa o un comando mcp:wdio-mcp/...")
|
|
413
|
+
}).passthrough()).describe("Lista ordenada de pasos lógicos a ejecutar.")
|
|
407
414
|
}, async ({ name, steps }) => {
|
|
408
415
|
const fileName = name.endsWith('.json') ? name : `${name}.json`;
|
|
409
416
|
const filePath = path.join(__dirname, 'test_cases', fileName);
|
|
@@ -413,12 +420,12 @@ server.tool("save_test_case", "Crear o actualizar un caso de prueba", {
|
|
|
413
420
|
return { content: [{ type: "text", text: `Caso de prueba '${name}' guardado correctamente.` }] };
|
|
414
421
|
});
|
|
415
422
|
|
|
416
|
-
server.tool("save_suite", "Crear o actualizar una suite de pruebas", {
|
|
417
|
-
name: z.string(),
|
|
418
|
-
state_map: z.string(),
|
|
419
|
-
tests: z.array(z.string()),
|
|
420
|
-
beforeSuite: z.
|
|
421
|
-
afterSuite: z.
|
|
423
|
+
server.tool("save_suite", "Crear o actualizar una suite de pruebas.", {
|
|
424
|
+
name: z.string().describe("Nombre de la suite (ej: 'Suite_E2E_Sanity')."),
|
|
425
|
+
state_map: z.string().describe("Archivo del mapa de estados a usar (ej: 'mob_perfecto_map.json')."),
|
|
426
|
+
tests: z.array(z.string()).describe("Lista de nombres de casos de prueba a incluir en la suite."),
|
|
427
|
+
beforeSuite: z.array(z.string()).optional().default([]).describe("Acciones globales antes de la suite (ej: iniciar sesión)."),
|
|
428
|
+
afterSuite: z.array(z.string()).optional().default([]).describe("Acciones globales tras la suite (ej: cerrar sesión).")
|
|
422
429
|
}, async ({ name, state_map, tests, beforeSuite, afterSuite }) => {
|
|
423
430
|
const fileName = name.endsWith('.json') ? name : `${name}.json`;
|
|
424
431
|
const filePath = path.join(__dirname, 'suites', fileName);
|
|
@@ -428,5 +435,76 @@ server.tool("save_suite", "Crear o actualizar una suite de pruebas", {
|
|
|
428
435
|
return { content: [{ type: "text", text: `Suite '${name}' guardada correctamente.` }] };
|
|
429
436
|
});
|
|
430
437
|
|
|
431
|
-
|
|
438
|
+
server.tool("init_project", "Inicializa el proyecto con carpetas y archivos de plantilla", {
|
|
439
|
+
force: z.optional(z.boolean()).default(false)
|
|
440
|
+
}, async ({ force }) => {
|
|
441
|
+
await ensureDirectories();
|
|
442
|
+
|
|
443
|
+
const templates = {
|
|
444
|
+
'maps/template_map.json': {
|
|
445
|
+
nodos: {
|
|
446
|
+
HOME: {
|
|
447
|
+
transiciones: {
|
|
448
|
+
IR_A_LOGIN: { destino: "LOGIN", accion: "mcp:wdio-mcp/click_element { \"selector\": \"~Login\" }" }
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
LOGIN: {
|
|
452
|
+
transiciones: {
|
|
453
|
+
VOLVER: { destino: "HOME", accion: "mcp:wdio-mcp/click_element { \"selector\": \"~Back\" }" }
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
'test_cases/template_case.json': {
|
|
459
|
+
name: "Template Case",
|
|
460
|
+
steps: [
|
|
461
|
+
{ name: "Ir a Login", action: "IR_A_LOGIN" },
|
|
462
|
+
{ name: "Verificar Pantalla", action: "mcp:wdio-mcp/get_screenshot {}" }
|
|
463
|
+
]
|
|
464
|
+
},
|
|
465
|
+
'suites/template_suite.json': {
|
|
466
|
+
name: "Template Suite",
|
|
467
|
+
state_map: "template_map.json",
|
|
468
|
+
tests: ["template_case"],
|
|
469
|
+
beforeSuite: ["mcp:wdio-mcp/start_session { \"platform\": \"android\", \"provider\": \"perfecto\" }"],
|
|
470
|
+
afterSuite: ["mcp:wdio-mcp/close_session {}"]
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
for (const [relPath, content] of Object.entries(templates)) {
|
|
475
|
+
const fullPath = path.join(__dirname, relPath);
|
|
476
|
+
try {
|
|
477
|
+
if (!force) {
|
|
478
|
+
try {
|
|
479
|
+
await fs.access(fullPath);
|
|
480
|
+
continue; // Skip if exists
|
|
481
|
+
} catch (e) {}
|
|
482
|
+
}
|
|
483
|
+
await fs.writeFile(fullPath, JSON.stringify(content, null, 2));
|
|
484
|
+
} catch (e) {
|
|
485
|
+
process.stderr.write(`[WARN] Error creando template ${relPath}: ${e.message}\n`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return { content: [{ type: "text", text: "✅ Proyecto inicializado. Se han creado las carpetas y los archivos de plantilla (templates) para guiar al agente." }] };
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
async function ensureDirectories() {
|
|
493
|
+
const dirs = ['maps', 'suites', 'test_cases', 'reports', 'data'];
|
|
494
|
+
for (const dir of dirs) {
|
|
495
|
+
const dirPath = path.join(__dirname, dir);
|
|
496
|
+
try {
|
|
497
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
498
|
+
} catch (e) {
|
|
499
|
+
process.stderr.write(`[WARN] Error creando directorio ${dir}: ${e.message}\n`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function main() {
|
|
505
|
+
await ensureDirectories();
|
|
506
|
+
await maquina.cargar();
|
|
507
|
+
const transport = new StdioServerTransport();
|
|
508
|
+
await server.connect(transport);
|
|
509
|
+
}
|
|
432
510
|
main().catch(console.error);
|