siesa-agents 2.1.72-qa.2 → 2.1.72-qa.21
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/bmad/bmm/config.yaml +33 -0
- package/claude/skills/sa-agent-sre-sentinel/SKILL.md +180 -0
- package/claude/skills/sa-aplicar/SKILL.md +268 -0
- package/claude/skills/sa-auditar-servicio/SKILL.md +255 -0
- package/claude/skills/sa-nueva-transversal/SKILL.md +317 -0
- package/claude/skills/sa-nuevo-ambiente/SKILL.md +147 -0
- package/claude/skills/sa-nuevo-servicio/SKILL.md +530 -0
- package/claude/skills/sa-onboard-db/SKILL.md +122 -0
- package/claude/skills/sa-qa-data-generator/SKILL.md +200 -51
- package/claude/skills/sa-quality-process/SKILL.md +6 -0
- package/claude/skills/sa-registrar-permisos/SKILL.md +233 -0
- package/package.json +1 -1
- package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_design_test.md +1037 -60
- package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_dor_gate.md +419 -379
- package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_e2e_executor.md +273 -0
- package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_playwright_impl.md +359 -355
- package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_test_plan.md +73 -111
- package/siesa-agents/bmm/workflows/3-solutioning/quality-process/workflow.md +1320 -997
- package/siesa-agents/resources/playwright-kit/.env.example +42 -0
- package/siesa-agents/resources/playwright-kit/README.md +228 -0
- package/siesa-agents/resources/playwright-kit/allure/categories.json +26 -0
- package/siesa-agents/resources/playwright-kit/global-setup.ts +34 -0
- package/siesa-agents/resources/playwright-kit/helpers/blazor-e2e-helpers.ts +417 -0
- package/siesa-agents/resources/playwright-kit/helpers/react-e2e-helpers.ts +297 -0
- package/siesa-agents/resources/playwright-kit/package-lock.json +850 -0
- package/siesa-agents/resources/playwright-kit/package.json +27 -0
- package/siesa-agents/resources/playwright-kit/playwright.config.ts +60 -0
- package/siesa-agents/resources/playwright-kit/proposed/.gitkeep +0 -0
- package/siesa-agents/resources/playwright-kit/reporters/agiletest-reporter.ts +252 -0
- package/siesa-agents/resources/playwright-kit/specs/.gitkeep +0 -0
- package/siesa-agents/resources/playwright-kit/specs/architecture-brief-siesa-erp.md +85 -0
- package/siesa-agents/resources/playwright-kit/specs/architecture-brief-siesa-react.md +109 -0
- package/siesa-agents/resources/playwright-kit/specs/architecture-brief-template.md +69 -0
- package/siesa-agents/resources/playwright-kit/tests/seed-example.spec.ts +95 -0
- package/siesa-agents/resources/playwright-kit/tools/locator-overlay.js +1306 -0
- package/siesa-agents/resources/playwright-kit/tools/locator-recorder.spec.ts +986 -0
- package/siesa-agents/scripts/__pycache__/bmad_to_agiletest.cpython-36.pyc +0 -0
- package/siesa-agents/scripts/bmad_to_agiletest.py +352 -337
- package/siesa-agents/scripts/converters/convert_csv_to_test_design.py +327 -0
- package/siesa-agents/scripts/converters/convert_excel_to_test_design.py +360 -0
- package/siesa-agents/scripts/converters/convert_md_to_test_design.py +359 -0
- package/siesa-agents/scripts/converters/convert_pdf_to_test_design.py +412 -0
- package/siesa-agents/scripts/converters/export_yaml_to_csv.py +149 -0
- package/siesa-agents/scripts/converters/export_yaml_to_excel.py +233 -0
- package/siesa-agents/scripts/converters/export_yaml_to_markdown.py +172 -0
- package/siesa-agents/scripts/converters/export_yaml_to_pdf.py +297 -0
- package/siesa-agents/scripts/merge_test_design.py +106 -106
- package/siesa-agents/scripts/playwright/yaml-to-playwright-spec.ts +203 -0
- package/siesa-agents/scripts/requirements.txt +7 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
convert_md_to_test_design.py — Convierte Markdown a test-design YAML
|
|
4
|
+
Lee MD con secciones ## (key-value en listas) + tabla pipe de pasos.
|
|
5
|
+
Uso: python convert_md_to_test_design.py <input.md> [output.yml]
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import yaml
|
|
14
|
+
except ImportError:
|
|
15
|
+
print("ERROR: pyyaml requerido. Instalar con: pip install pyyaml")
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# --- YAML formatting helpers ---
|
|
20
|
+
|
|
21
|
+
class LiteralStr(str):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def literal_representer(dumper, data):
|
|
26
|
+
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style=">")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
yaml.add_representer(LiteralStr, literal_representer)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def yaml_str_representer(dumper, data):
|
|
33
|
+
if any(c in data for c in (":", "{", "}", "[", "]", ",", "&", "*", "?", "|", ">", "!", "%", "@", "`")):
|
|
34
|
+
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"')
|
|
35
|
+
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
yaml.add_representer(str, yaml_str_representer)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# --- Parsing ---
|
|
42
|
+
|
|
43
|
+
def parse_pipe_list(value: str) -> list[str]:
|
|
44
|
+
if not value:
|
|
45
|
+
return []
|
|
46
|
+
return [v.strip() for v in value.split("|") if v.strip()]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def parse_sections(text: str) -> dict[str, list[str]]:
|
|
50
|
+
"""Divide el markdown en secciones por headers ##."""
|
|
51
|
+
sections = {}
|
|
52
|
+
current = "_title"
|
|
53
|
+
sections[current] = []
|
|
54
|
+
|
|
55
|
+
for line in text.split("\n"):
|
|
56
|
+
stripped = line.strip()
|
|
57
|
+
if stripped.startswith("## "):
|
|
58
|
+
current = stripped[3:].strip().lower()
|
|
59
|
+
# Normalizar nombres de seccion
|
|
60
|
+
current = current.replace(" de pruebas", "_pruebas").replace(" de prueba", "_prueba")
|
|
61
|
+
sections[current] = []
|
|
62
|
+
elif stripped.startswith("# "):
|
|
63
|
+
sections["_title"] = [stripped[2:].strip()]
|
|
64
|
+
else:
|
|
65
|
+
sections[current].append(line)
|
|
66
|
+
|
|
67
|
+
return sections
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def parse_kv_from_list(lines: list[str]) -> list[tuple[str, str]]:
|
|
71
|
+
"""Extrae key-value de lineas con formato '- **key**: value'."""
|
|
72
|
+
pairs = []
|
|
73
|
+
for line in lines:
|
|
74
|
+
match = re.match(r"^-\s+\*\*(.+?)\*\*:\s*(.*)$", line.strip())
|
|
75
|
+
if match:
|
|
76
|
+
pairs.append((match.group(1).strip(), match.group(2).strip()))
|
|
77
|
+
return pairs
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parse_table(lines: list[str]) -> list[dict]:
|
|
81
|
+
"""Parsea tabla Markdown pipe como lista de dicts."""
|
|
82
|
+
# Encontrar header y filas
|
|
83
|
+
table_lines = [l.strip() for l in lines if l.strip().startswith("|")]
|
|
84
|
+
if len(table_lines) < 3: # header + separator + al menos 1 fila
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
# Parsear header
|
|
88
|
+
raw_headers = [c.strip() for c in table_lines[0].split("|")]
|
|
89
|
+
raw_headers = [h for h in raw_headers if h] # quitar vacios
|
|
90
|
+
header_map = {
|
|
91
|
+
"paso": "paso", "nombre": "nombre", "accion": "accion",
|
|
92
|
+
"resultado esperado": "resultado", "datos": "datos",
|
|
93
|
+
"script": "script", "automatizado": "automatizado",
|
|
94
|
+
"bloqueado": "bloqueado", "nota": "nota",
|
|
95
|
+
}
|
|
96
|
+
headers = [header_map.get(h.lower(), h.lower()) for h in raw_headers]
|
|
97
|
+
|
|
98
|
+
pasos = []
|
|
99
|
+
for row_line in table_lines[2:]: # skip header + separator
|
|
100
|
+
# Splitear respetando escaped pipes (\|)
|
|
101
|
+
cells = re.split(r"(?<!\\)\|", row_line)
|
|
102
|
+
cells = [c.strip().replace("\\|", "|") for c in cells if c.strip() or cells.index(c) not in (0, len(cells)-1)]
|
|
103
|
+
# Filtrar celdas vacias del inicio/final del split
|
|
104
|
+
cells = [c.strip().replace("\\|", "|") for c in row_line.split("|")][1:-1]
|
|
105
|
+
|
|
106
|
+
if not cells or not cells[0].strip():
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
row_dict = {}
|
|
110
|
+
for i, val in enumerate(cells):
|
|
111
|
+
if i < len(headers):
|
|
112
|
+
row_dict[headers[i]] = val.strip().replace("\\|", "|")
|
|
113
|
+
|
|
114
|
+
paso_val = row_dict.get("paso", "").strip()
|
|
115
|
+
if paso_val.isdigit():
|
|
116
|
+
paso_key = int(paso_val)
|
|
117
|
+
else:
|
|
118
|
+
paso_key = paso_val
|
|
119
|
+
|
|
120
|
+
step = {"paso": paso_key}
|
|
121
|
+
step["nombre"] = row_dict.get("nombre", "")
|
|
122
|
+
step["accion"] = row_dict.get("accion", "")
|
|
123
|
+
step["resultado"] = row_dict.get("resultado", "")
|
|
124
|
+
|
|
125
|
+
datos = row_dict.get("datos", "")
|
|
126
|
+
if datos:
|
|
127
|
+
step["datos"] = datos
|
|
128
|
+
|
|
129
|
+
script = row_dict.get("script", "")
|
|
130
|
+
if script:
|
|
131
|
+
step["script"] = script
|
|
132
|
+
|
|
133
|
+
auto_str = row_dict.get("automatizado", "false").strip().lower()
|
|
134
|
+
step["automatizado"] = auto_str == "true"
|
|
135
|
+
|
|
136
|
+
bloqueado = row_dict.get("bloqueado", "")
|
|
137
|
+
if bloqueado:
|
|
138
|
+
step["bloqueado"] = bloqueado
|
|
139
|
+
|
|
140
|
+
nota = row_dict.get("nota", "")
|
|
141
|
+
if nota:
|
|
142
|
+
step["nota"] = nota
|
|
143
|
+
|
|
144
|
+
pasos.append(step)
|
|
145
|
+
|
|
146
|
+
return pasos
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# --- YAML builders ---
|
|
150
|
+
|
|
151
|
+
def get_kv(pairs: list[tuple[str, str]], key: str, default: str = "") -> str:
|
|
152
|
+
d = dict(pairs)
|
|
153
|
+
return d.get(key, default)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def build_metadata_section(pairs: list[tuple[str, str]]) -> dict:
|
|
157
|
+
d = dict(pairs)
|
|
158
|
+
result = {}
|
|
159
|
+
for field in ("modulo", "version", "autor", "fecha", "stack"):
|
|
160
|
+
if field in d:
|
|
161
|
+
result[field] = d[field]
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def build_requisito_section(pairs: list[tuple[str, str]]) -> dict:
|
|
166
|
+
d = dict(pairs)
|
|
167
|
+
result = {}
|
|
168
|
+
for field in ("id", "nombre"):
|
|
169
|
+
if field in d:
|
|
170
|
+
result[field] = d[field]
|
|
171
|
+
desc = d.get("descripcion", "")
|
|
172
|
+
if desc:
|
|
173
|
+
result["descripcion"] = LiteralStr(desc)
|
|
174
|
+
criterios = parse_pipe_list(d.get("criterios_aceptacion", ""))
|
|
175
|
+
if criterios:
|
|
176
|
+
result["criterios_aceptacion"] = criterios
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def build_plan_section(pairs: list[tuple[str, str]]) -> dict:
|
|
181
|
+
d = dict(pairs)
|
|
182
|
+
result = {}
|
|
183
|
+
for field in ("id", "nombre", "framework"):
|
|
184
|
+
if field in d:
|
|
185
|
+
result[field] = d[field]
|
|
186
|
+
objetivo = d.get("objetivo", "")
|
|
187
|
+
if objetivo:
|
|
188
|
+
result["objetivo"] = LiteralStr(objetivo)
|
|
189
|
+
|
|
190
|
+
precondiciones = parse_pipe_list(d.get("precondiciones", ""))
|
|
191
|
+
if precondiciones:
|
|
192
|
+
result["precondiciones"] = precondiciones
|
|
193
|
+
|
|
194
|
+
ambientes = []
|
|
195
|
+
i = 0
|
|
196
|
+
while True:
|
|
197
|
+
nombre = d.get(f"ambiente_{i}.nombre", "")
|
|
198
|
+
if not nombre:
|
|
199
|
+
break
|
|
200
|
+
amb = {"nombre": nombre}
|
|
201
|
+
url = d.get(f"ambiente_{i}.url", "")
|
|
202
|
+
if url:
|
|
203
|
+
amb["url"] = url
|
|
204
|
+
usuario = d.get(f"ambiente_{i}.usuario", "")
|
|
205
|
+
if usuario:
|
|
206
|
+
amb["usuario"] = usuario
|
|
207
|
+
amb["validado"] = True
|
|
208
|
+
ambientes.append(amb)
|
|
209
|
+
i += 1
|
|
210
|
+
if ambientes:
|
|
211
|
+
result["ambientes"] = ambientes
|
|
212
|
+
|
|
213
|
+
tiempos = {}
|
|
214
|
+
for key, val in pairs:
|
|
215
|
+
if key.startswith("tiempos."):
|
|
216
|
+
t_key = key.replace("tiempos.", "")
|
|
217
|
+
tiempos[t_key] = val
|
|
218
|
+
if tiempos:
|
|
219
|
+
result["tiempos_referencia"] = tiempos
|
|
220
|
+
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def build_caso_prueba_section(pairs: list[tuple[str, str]], pasos: list[dict]) -> dict:
|
|
225
|
+
d = dict(pairs)
|
|
226
|
+
result = {}
|
|
227
|
+
for field in ("id", "nombre", "prioridad", "tipo"):
|
|
228
|
+
if field in d:
|
|
229
|
+
result[field] = d[field]
|
|
230
|
+
scripts = parse_pipe_list(d.get("scripts", ""))
|
|
231
|
+
if scripts:
|
|
232
|
+
result["scripts"] = scripts
|
|
233
|
+
result["pasos"] = pasos
|
|
234
|
+
return result
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def build_ejecucion_section(pairs: list[tuple[str, str]]) -> dict:
|
|
238
|
+
d = dict(pairs)
|
|
239
|
+
result = {}
|
|
240
|
+
for field in ("id", "nombre"):
|
|
241
|
+
if field in d:
|
|
242
|
+
result[field] = d[field]
|
|
243
|
+
|
|
244
|
+
variables = []
|
|
245
|
+
comandos = {}
|
|
246
|
+
for key, val in pairs:
|
|
247
|
+
if key.startswith("variable."):
|
|
248
|
+
var_name = key.replace("variable.", "")
|
|
249
|
+
parts = [p.strip() for p in val.split("|")]
|
|
250
|
+
var = {"nombre": var_name}
|
|
251
|
+
if len(parts) >= 1:
|
|
252
|
+
var["descripcion"] = parts[0]
|
|
253
|
+
if len(parts) >= 2:
|
|
254
|
+
var["default"] = parts[1]
|
|
255
|
+
variables.append(var)
|
|
256
|
+
elif key.startswith("comando."):
|
|
257
|
+
cmd_name = key.replace("comando.", "")
|
|
258
|
+
comandos[cmd_name] = val
|
|
259
|
+
|
|
260
|
+
if variables:
|
|
261
|
+
result["variables_entorno"] = variables
|
|
262
|
+
if comandos:
|
|
263
|
+
result["comandos"] = comandos
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def build_integraciones_section(pairs: list[tuple[str, str]]) -> dict:
|
|
268
|
+
result = {}
|
|
269
|
+
for key, val in pairs:
|
|
270
|
+
parts = key.split(".", 1)
|
|
271
|
+
if len(parts) == 2:
|
|
272
|
+
integ_name, field = parts
|
|
273
|
+
if integ_name not in result:
|
|
274
|
+
result[integ_name] = {}
|
|
275
|
+
result[integ_name][field] = val
|
|
276
|
+
return result
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def main():
|
|
280
|
+
if len(sys.argv) < 2:
|
|
281
|
+
print("Uso: python convert_md_to_test_design.py <input.md> [output.yml]")
|
|
282
|
+
sys.exit(1)
|
|
283
|
+
|
|
284
|
+
input_path = Path(sys.argv[1])
|
|
285
|
+
if not input_path.exists():
|
|
286
|
+
print(f"ERROR: No se encontro {input_path}")
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
|
|
289
|
+
if len(sys.argv) >= 3:
|
|
290
|
+
output_path = Path(sys.argv[2])
|
|
291
|
+
else:
|
|
292
|
+
output_path = input_path.with_name(
|
|
293
|
+
input_path.stem.replace("test-cases-", "test-design-") + ".yml"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
with open(input_path, "r", encoding="utf-8") as f:
|
|
297
|
+
text = f.read()
|
|
298
|
+
|
|
299
|
+
sections = parse_sections(text)
|
|
300
|
+
|
|
301
|
+
# Parsear secciones key-value
|
|
302
|
+
meta_kv = parse_kv_from_list(sections.get("metadata", []))
|
|
303
|
+
req_kv = parse_kv_from_list(sections.get("requisito", []))
|
|
304
|
+
plan_kv = parse_kv_from_list(sections.get("plan_pruebas", []))
|
|
305
|
+
caso_kv = parse_kv_from_list(sections.get("caso_prueba", []))
|
|
306
|
+
ejec_kv = parse_kv_from_list(sections.get("ejecucion", []))
|
|
307
|
+
integ_kv = parse_kv_from_list(sections.get("integraciones", []))
|
|
308
|
+
|
|
309
|
+
# Parsear tabla de pasos
|
|
310
|
+
pasos = parse_table(sections.get("pasos", []))
|
|
311
|
+
|
|
312
|
+
# Construir YAML
|
|
313
|
+
autor = get_kv(meta_kv, "autor", "QA Team")
|
|
314
|
+
doc = {}
|
|
315
|
+
|
|
316
|
+
metadata = build_metadata_section(meta_kv)
|
|
317
|
+
if metadata:
|
|
318
|
+
doc["metadata"] = metadata
|
|
319
|
+
|
|
320
|
+
requisito = build_requisito_section(req_kv)
|
|
321
|
+
if requisito:
|
|
322
|
+
doc["requisito"] = requisito
|
|
323
|
+
|
|
324
|
+
plan = build_plan_section(plan_kv)
|
|
325
|
+
if plan:
|
|
326
|
+
doc["plan_pruebas"] = plan
|
|
327
|
+
|
|
328
|
+
caso = build_caso_prueba_section(caso_kv, pasos)
|
|
329
|
+
doc["caso_prueba"] = caso
|
|
330
|
+
|
|
331
|
+
ejecucion = build_ejecucion_section(ejec_kv)
|
|
332
|
+
if ejecucion:
|
|
333
|
+
doc["ejecucion"] = ejecucion
|
|
334
|
+
|
|
335
|
+
integraciones = build_integraciones_section(integ_kv)
|
|
336
|
+
if integraciones:
|
|
337
|
+
doc["integraciones"] = integraciones
|
|
338
|
+
|
|
339
|
+
header = (
|
|
340
|
+
f"# test-design generado desde {input_path.name}\n"
|
|
341
|
+
f"# Input para: AgileTest + Playwright + Agente de Datos\n"
|
|
342
|
+
f"# Autor: {autor}\n\n"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
346
|
+
f.write(header)
|
|
347
|
+
yaml.dump(
|
|
348
|
+
doc, f,
|
|
349
|
+
default_flow_style=False,
|
|
350
|
+
allow_unicode=True,
|
|
351
|
+
sort_keys=False,
|
|
352
|
+
width=120,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
print(f"OK: {input_path.name} -> {output_path.name} ({len(pasos)} pasos convertidos)")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if __name__ == "__main__":
|
|
359
|
+
main()
|