mcp-state-machine-test-framework 1.2.8 → 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.
- package/bin/init.js +75 -69
- package/index.js +26 -22
- package/package.json +1 -1
package/bin/init.js
CHANGED
|
@@ -1,69 +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.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
fingerprint: { selectors: ["~
|
|
46
|
-
transiciones: {
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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: "
|
|
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: `🏗️
|
|
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: `📍
|
|
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: `🧬
|
|
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: `✅
|
|
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: `🧪
|
|
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: `✅
|
|
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: `🛡️
|
|
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: `✅
|
|
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
|
|
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
|
|