mcp-state-machine-test-framework 1.2.7 → 1.3.2

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 (3) hide show
  1. package/bin/init.js +75 -53
  2. package/index.js +26 -22
  3. package/package.json +1 -1
package/bin/init.js CHANGED
@@ -1,53 +1,75 @@
1
- #!/usr/bin/env node
2
- import fs from "fs/promises";
3
- import path from "path";
4
- import { execSync } from "child_process";
5
-
6
- async function init() {
7
- console.log("🚀 Iniciando instalación universal del State Machine Framework...");
8
- const root = process.cwd();
9
-
10
- // 1. Asegurar package.json
11
- try {
12
- await fs.access(path.join(root, "package.json"));
13
- } catch (e) {
14
- console.log("📦 No se encontró package.json. Inicializando proyecto Node...");
15
- execSync("npm init -y", { stdio: "inherit" });
16
- }
17
-
18
- // 2. Crear carpetas
19
- const dirs = ['maps', 'suites', 'test_cases', 'reports', 'data'];
20
- for (const dir of dirs) {
21
- await fs.mkdir(path.join(root, dir), { recursive: true });
22
- }
23
-
24
- // 3. Crear mcp_config.json
25
- const mcpConfig = {
26
- mcpServers: {
27
- "mcp-sms": {
28
- "command": "npx",
29
- "args": ["-y", "mcp-state-machine-test-framework"]
30
- }
31
- }
32
- };
33
- await fs.writeFile(path.join(root, "mcp_config.json"), JSON.stringify(mcpConfig, null, 2));
34
- console.log("🔌 mcp_config.json creado.");
35
-
36
- // 4. Crear Templates
37
- const templates = {
38
- 'maps/template_map.json': { nodos: { HOME: { transiciones: { IR_A_LOGIN: { destino: "LOGIN", accion: "mcp:wdio-mcp/click_element { \"selector\": \"~Login\" }" } } }, LOGIN: { transiciones: { VOLVER: { destino: "HOME", accion: "mcp:wdio-mcp/click_element { \"selector\": \"~Back\" }" } } } } },
39
- 'test_cases/template_case.json': { name: "Template Case", steps: [ { name: "Ir a Login", action: "IR_A_LOGIN" } ] },
40
- 'suites/template_suite.json': { name: "Template Suite", state_map: "template_map.json", tests: ["template_case"] }
41
- };
42
-
43
- for (const [relPath, content] of Object.entries(templates)) {
44
- await fs.writeFile(path.join(root, relPath), JSON.stringify(content, null, 2));
45
- }
46
-
47
- console.log("✅ ¡Instalación completada con éxito! Ya puedes usar las herramientas MCP.");
48
- }
49
-
50
- init().catch(err => {
51
- console.error("❌ Error durante la instalación:", err.message);
52
- process.exit(1);
53
- });
1
+ #!/usr/bin/env node
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ import { execSync } from "child_process";
5
+
6
+ async function init() {
7
+ console.log("🚀 Iniciando instalación universal del State Machine Framework...");
8
+ const root = process.cwd();
9
+
10
+ // 1. Asegurar package.json
11
+ try {
12
+ await fs.access(path.join(root, "package.json"));
13
+ } catch (e) {
14
+ console.log("📦 No se encontró package.json. Inicializando proyecto Node...");
15
+ execSync("npm init -y", { stdio: "inherit" });
16
+ }
17
+
18
+ // 2. Crear carpetas
19
+ const dirs = ['maps', 'suites', 'test_cases', 'reports', 'data'];
20
+ for (const dir of dirs) {
21
+ await fs.mkdir(path.join(root, dir), { recursive: true });
22
+ }
23
+
24
+ // 3. Instalar localmente para evitar problemas de npx en Windows
25
+ console.log("📦 Instalando dependencias locales...");
26
+ execSync("npm install mcp-state-machine-test-framework", { stdio: "inherit" });
27
+
28
+ // 4. Crear mcp_config.json con ruta local estable
29
+ const mcpConfig = {
30
+ mcpServers: {
31
+ "mcp-sms": {
32
+ "command": "node",
33
+ "args": ["node_modules/mcp-state-machine-test-framework/index.js"]
34
+ }
35
+ }
36
+ };
37
+ await fs.writeFile(path.join(root, "mcp_config.json"), JSON.stringify(mcpConfig, null, 2));
38
+ console.log("🔌 mcp_config.json creado.");
39
+
40
+ // 4. Crear Templates
41
+ const templates = {
42
+ 'maps/template_map.json': {
43
+ nodos: {
44
+ HOME: {
45
+ fingerprint: { selectors: ["~Login_Button"] },
46
+ transiciones: { IR_A_LOGIN: { destino: "LOGIN", accion: "mcp:wdio-mcp/click_element {\"selector\":\"~Login_Button\"}" } }
47
+ },
48
+ LOGIN: {
49
+ fingerprint: { selectors: ["~Back_Button", "id:login_title"] },
50
+ transiciones: { VOLVER: { destino: "HOME", accion: "mcp:wdio-mcp/click_element {\"selector\":\"~Back_Button\"}" } }
51
+ }
52
+ }
53
+ },
54
+ 'test_cases/template_case.json': {
55
+ name: "TC_Template",
56
+ steps: [
57
+ { name: "Navegar a Login", action: "transicion:IR_A_LOGIN", assert: "sh:echo 'Validación de UI exitosa'" }
58
+ ]
59
+ },
60
+ 'suites/template_suite.json': { name: "Suite_Template", state_map: "template_map.json", tests: ["TC_Template"] }
61
+ };
62
+
63
+ for (const [relPath, content] of Object.entries(templates)) {
64
+ await fs.writeFile(path.join(root, relPath), JSON.stringify(content, null, 2));
65
+ }
66
+
67
+ console.log("\n✅ ¡Instalación completada con éxito!");
68
+ console.log("👉 PRÓXIMO PASO: Dile al agente:");
69
+ console.log(" 'Agente, inicia el diseño del mapa para MiApp'");
70
+ }
71
+
72
+ init().catch(err => {
73
+ console.error("❌ Error durante la instalación:", err.message);
74
+ process.exit(1);
75
+ });
package/index.js CHANGED
@@ -68,7 +68,7 @@ export class MaquinaDeEstados {
68
68
  async ejecutarSuite(suiteName) {
69
69
  const suite = this.suites.get(suiteName);
70
70
  if (!suite) throw new Error(`Suite '${suiteName}' no encontrada.`);
71
-
71
+
72
72
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
73
73
  const reportDir = path.join(REPORTS_ROOT, `exec_${suiteName}_${timestamp}`);
74
74
  await fs.mkdir(reportDir, { recursive: true });
@@ -83,13 +83,13 @@ export class MaquinaDeEstados {
83
83
  const executeAction = async (act) => {
84
84
  const subRes = (act === action) ? actionRes : { action: act, status: "passed" };
85
85
  if (act !== action) targetRes.push(subRes);
86
-
86
+
87
87
  if (act.startsWith("transicion:")) {
88
88
  const transName = act.replace("transicion:", "").trim();
89
89
  const mapPath = path.join(__dirname, 'maps', suite.state_map);
90
90
  const mapData = JSON.parse(await fs.readFile(mapPath, 'utf8'));
91
91
  const nodes = mapData.nodos || mapData;
92
-
92
+
93
93
  let foundAction, destNodeName;
94
94
  for (const [nodeName, node] of Object.entries(nodes)) {
95
95
  if (node.transiciones && node.transiciones[transName]) {
@@ -217,68 +217,72 @@ server.tool("init_project", "Instalación Inicial. Crea carpetas y archivos de p
217
217
  return { content: [{ type: "text", text: "✨ Entorno Inicializado. Usa 'sms_builder' para empezar el diseño." }] };
218
218
  });
219
219
 
220
- server.tool("sms_builder", "Diseño Guiado: Nodos -> Tests -> Suite.", {
220
+ server.tool("sms_builder", "ASISTENTE PROACTIVO de Diseño. GUÍA AL USUARIO paso a paso. Instrucciones: 1. Empieza SIEMPRE con action: 'start' { mapName }. 2. No preguntes qué hacer, sugiere el siguiente paso lógico (Nodo -> Huella -> Test).", {
221
221
  action: z.enum(["start", "add_node", "add_fingerprint", "done_node", "add_test", "add_step", "add_assert", "save_test", "assemble_suite"]),
222
222
  data: z.record(z.any())
223
223
  }, async ({ action, data }) => {
224
224
  const ctxPath = path.join(__dirname, '.sms_builder_context.json');
225
225
  let ctx = { step: "IDLE", nodes: {}, tests: [] };
226
- try { ctx = JSON.parse(fsSync.readFileSync(ctxPath, 'utf8')); } catch (e) {}
226
+ try { ctx = JSON.parse(fsSync.readFileSync(ctxPath, 'utf8')); } catch (e) { }
227
227
 
228
228
  switch (action) {
229
229
  case "start":
230
- ctx = { step: "DESIGN", mapName: data.mapName, nodes: {}, tests: [] };
230
+ ctx = { step: "ADD_NODE", mapName: data.mapName, nodes: {}, tests: [] };
231
231
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
232
- return { content: [{ type: "text", text: `🏗️ Diseñando Mapa: ${data.mapName}. Añade un nodo (action: 'add_node' { nodeName }).` }] };
233
-
232
+ return { content: [{ type: "text", text: `🏗️ DISEÑO INICIADO: ${data.mapName}\n\n👉 ACCIÓN REQUERIDA: 'add_node' { nodeName: "HOME" }.` }] };
233
+
234
234
  case "add_node":
235
235
  ctx.currentNode = { name: data.nodeName, transiciones: {}, fingerprint: null };
236
+ ctx.step = "ADD_FINGERPRINT";
236
237
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
237
- return { content: [{ type: "text", text: `📍 Nodo '${data.nodeName}'. MANDATORIO: Añade huella (action: 'add_fingerprint' { selector }).` }] };
238
-
238
+ return { content: [{ type: "text", text: `📍 NODO '${data.nodeName}' CREADO.\n\n👉 ACCIÓN OBLIGATORIA: 'add_fingerprint' { selector: "..." }.` }] };
239
+
239
240
  case "add_fingerprint":
240
241
  if (!ctx.currentNode.fingerprint) ctx.currentNode.fingerprint = { selectors: [] };
241
242
  ctx.currentNode.fingerprint.selectors.push(data.selector);
242
243
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
243
- return { content: [{ type: "text", text: `🧬 Vector añadido. ¿Otro o 'done_node'?` }] };
244
-
244
+ return { content: [{ type: "text", text: `🧬 VECTOR AÑADIDO.\n\n👉 OPCIONES: 1. 'add_fingerprint' (más vectores) | 2. 'done_node' (finalizar nodo).` }] };
245
+
245
246
  case "done_node":
246
247
  ctx.nodes[ctx.currentNode.name] = { fingerprint: ctx.currentNode.fingerprint, transiciones: ctx.currentNode.transiciones };
247
248
  await fs.writeFile(path.join(__dirname, 'maps', `${ctx.mapName}.json`), JSON.stringify({ nodos: ctx.nodes }, null, 2));
248
249
  delete ctx.currentNode;
250
+ ctx.step = "DECIDE_NEXT";
249
251
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
250
- return { content: [{ type: "text", text: `✅ Nodo guardado. ¿Otro ('add_node') o Tests ('add_test')?` }] };
252
+ return { content: [{ type: "text", text: `✅ NODO GUARDADO.\n\n👉 OPCIONES: 1. 'add_node' (otro nodo) | 2. 'add_test' (empezar pruebas).` }] };
251
253
 
252
254
  case "add_test":
253
255
  ctx.currentTest = { name: data.testName, steps: [] };
256
+ ctx.step = "ADD_STEP";
254
257
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
255
- return { content: [{ type: "text", text: `🧪 Test '${data.testName}'. Añade pasos (action: 'add_step' { name, action }).` }] };
256
-
258
+ return { content: [{ type: "text", text: `🧪 TEST '${data.testName}' EN PROCESO.\n\n👉 ACCIÓN REQUERIDA: 'add_step' { name, action }.` }] };
259
+
257
260
  case "add_step":
258
261
  ctx.currentStep = { name: data.name, action: data.action };
262
+ ctx.step = "DECIDE_STEP";
259
263
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
260
- return { content: [{ type: "text", text: `✅ Paso añadido. ¿Assert? (action: 'add_assert' { assert }) o siguiente ('add_step').` }] };
261
-
264
+ return { content: [{ type: "text", text: `✅ PASO AÑADIDO.\n\n👉 OPCIONES: 1. 'add_assert' (validación) | 2. 'add_step' (más pasos) | 3. 'save_test'.` }] };
265
+
262
266
  case "add_assert":
263
267
  ctx.currentStep.assert = data.assert;
264
268
  ctx.currentTest.steps.push(ctx.currentStep);
265
269
  delete ctx.currentStep;
266
270
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
267
- return { content: [{ type: "text", text: `🛡️ Assert añadido. ¿Siguiente paso ('add_step') o guardar ('save_test')?` }] };
268
-
271
+ return { content: [{ type: "text", text: `🛡️ ASSERT REGISTRADO.\n\n👉 OPCIONES: 1. 'add_step' | 2. 'save_test'.` }] };
272
+
269
273
  case "save_test":
270
274
  if (ctx.currentStep) ctx.currentTest.steps.push(ctx.currentStep);
271
275
  await fs.writeFile(path.join(__dirname, 'test_cases', `${ctx.currentTest.name}.json`), JSON.stringify(ctx.currentTest, null, 2));
272
276
  ctx.tests.push(ctx.currentTest.name);
273
277
  delete ctx.currentTest;
274
278
  fsSync.writeFileSync(ctxPath, JSON.stringify(ctx, null, 2));
275
- return { content: [{ type: "text", text: `✅ Test guardado. ¿Otro ('add_test') o Suite ('assemble_suite')?` }] };
276
-
279
+ return { content: [{ type: "text", text: `✅ TEST GUARDADO.\n\n👉 OPCIONES: 1. 'add_test' | 2. 'assemble_suite'.` }] };
280
+
277
281
  case "assemble_suite":
278
282
  const suite = { name: data.suiteName, state_map: `${ctx.mapName}.json`, tests: ctx.tests };
279
283
  await fs.writeFile(path.join(__dirname, 'suites', `${data.suiteName}.json`), JSON.stringify(suite, null, 2));
280
284
  fsSync.unlinkSync(ctxPath);
281
- return { content: [{ type: "text", text: `🎊 Suite '${data.suiteName}' lista. Corre con 'sms_executor'.` }] };
285
+ return { content: [{ type: "text", text: `🎊 DISEÑO FINALIZADO. Suite '${data.suiteName}' lista.\n\n👉 ACCIÓN FINAL: Usa 'sms_executor' { suiteName: '${data.suiteName}' }.` }] };
282
286
  }
283
287
  });
284
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-state-machine-test-framework",
3
- "version": "1.2.7",
3
+ "version": "1.3.2",
4
4
  "description": "High-fidelity State Machine MCP Server for autonomous E2E testing orchestration.",
5
5
  "main": "index.js",
6
6
  "type": "module",