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,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "playwright-agentes-ia-siesa",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Kit Playwright + Agentes IA para automatización E2E — Siesa",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "npx playwright test",
|
|
7
|
+
"test:headed": "npx playwright test --headed",
|
|
8
|
+
"test:seed": "npx playwright test tests/seed-example.spec.ts --headed",
|
|
9
|
+
"recorder": "npx playwright test tools/locator-recorder.spec.ts --headed --timeout 3600000",
|
|
10
|
+
"test:proposed": "npx playwright test proposed/ --config playwright.config.ts",
|
|
11
|
+
"report": "npx playwright show-report",
|
|
12
|
+
"report:allure": "npx allure generate allure-results -o allure-report --clean && npx allure open allure-report",
|
|
13
|
+
"report:monocart": "npx monocart show-report monocart-report/index.html"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["playwright", "e2e", "siesa", "qa", "agentes-ia"],
|
|
16
|
+
"author": "Equipo QA I&D — Procesos y Calidad de Software, SIESA",
|
|
17
|
+
"license": "UNLICENSED",
|
|
18
|
+
"private": true,
|
|
19
|
+
"type": "commonjs",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@playwright/test": "^1.60.0",
|
|
22
|
+
"allure-commandline": "^2.42.0",
|
|
23
|
+
"allure-playwright": "^3.9.0",
|
|
24
|
+
"dotenv": "^16.4.5",
|
|
25
|
+
"monocart-reporter": "^2.11.2"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuracion Playwright — Kit Agentes IA Siesa
|
|
3
|
+
* Reporters: list + HTML + Allure + Monocart
|
|
4
|
+
*/
|
|
5
|
+
import { defineConfig } from '@playwright/test';
|
|
6
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
7
|
+
|
|
8
|
+
dotenvConfig(); // carga .env personal del QA (gitignored); usa defaults si no existe
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
globalSetup: './global-setup.ts',
|
|
12
|
+
testDir: '.',
|
|
13
|
+
timeout: 120_000,
|
|
14
|
+
expect: { timeout: 10_000 },
|
|
15
|
+
fullyParallel: false,
|
|
16
|
+
// R-CI-03: Blazor Server (SignalR) requiere reintentos ante fallos transitorios.
|
|
17
|
+
retries: process.env.CI ? 2 : 1,
|
|
18
|
+
workers: 1,
|
|
19
|
+
reporter: [
|
|
20
|
+
['list'],
|
|
21
|
+
['html', { open: 'never', outputFolder: 'playwright-report' }],
|
|
22
|
+
// R-CI-07: junit.xml para GitHub Actions / Jenkins (solo en CI).
|
|
23
|
+
...(process.env.CI ? [['junit', { outputFile: 'test-results/junit.xml' }] as const] : []),
|
|
24
|
+
['allure-playwright', {
|
|
25
|
+
outputFolder: 'allure-results',
|
|
26
|
+
detail: true,
|
|
27
|
+
suiteTitle: process.env.SUITE_TITLE || 'E2E — Pipeline Agentes IA',
|
|
28
|
+
}],
|
|
29
|
+
['monocart-reporter', {
|
|
30
|
+
name: process.env.SUITE_TITLE || 'E2E — Pipeline Agentes IA',
|
|
31
|
+
outputFile: 'monocart-report/index.html',
|
|
32
|
+
}],
|
|
33
|
+
],
|
|
34
|
+
use: {
|
|
35
|
+
baseURL: process.env.BASE_URL || 'https://erp-qa-beta.siesaerp.com',
|
|
36
|
+
// CI corre en runners Linux sin display → headless obligatorio.
|
|
37
|
+
headless: !!process.env.CI,
|
|
38
|
+
viewport: null,
|
|
39
|
+
// Timeouts parametrizables por entorno (CI lento puede subirlos sin tocar código).
|
|
40
|
+
actionTimeout: Number(process.env.ACTION_TIMEOUT ?? 30_000),
|
|
41
|
+
navigationTimeout: Number(process.env.NAVIGATION_TIMEOUT ?? 30_000),
|
|
42
|
+
// En CI solo capturar evidencia ante fallo para no saturar disco.
|
|
43
|
+
screenshot: process.env.CI ? 'only-on-failure' : 'on',
|
|
44
|
+
// En CI, trace solo en el primer reintento (archivos más pequeños que retain-on-failure).
|
|
45
|
+
trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure',
|
|
46
|
+
video: process.env.CI ? 'off' : 'on',
|
|
47
|
+
},
|
|
48
|
+
projects: [
|
|
49
|
+
{
|
|
50
|
+
name: 'chromium',
|
|
51
|
+
use: {
|
|
52
|
+
browserName: 'chromium',
|
|
53
|
+
launchOptions: {
|
|
54
|
+
slowMo: 150,
|
|
55
|
+
args: ['--force-device-scale-factor=1', '--high-dpi-support=1', '--start-maximized'],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgileTest Playwright Reporter
|
|
3
|
+
*
|
|
4
|
+
* Sincroniza resultados de Playwright con una Ejecución de Pruebas en AgileTest.
|
|
5
|
+
* Se desactiva silenciosamente si las env vars no están definidas.
|
|
6
|
+
*
|
|
7
|
+
* Env vars requeridas:
|
|
8
|
+
* AGILETEST_CLIENT_ID, AGILETEST_CLIENT_SECRET
|
|
9
|
+
* AGILETEST_EXECUTION_ID — ID interno de la ejecución (ej: 115230)
|
|
10
|
+
* AGILETEST_TC_ID — ID interno del test case (ej: 1531352)
|
|
11
|
+
* AGILETEST_PROJECT_ID — ID del proyecto Jira (default: 10032)
|
|
12
|
+
* AGILETEST_TOTAL_STEPS — Total de pasos del TC (default: 17)
|
|
13
|
+
* Si se ejecutan menos, el TC queda "In Progress"
|
|
14
|
+
*/
|
|
15
|
+
import type {
|
|
16
|
+
Reporter,
|
|
17
|
+
TestCase,
|
|
18
|
+
TestResult,
|
|
19
|
+
FullConfig,
|
|
20
|
+
Suite,
|
|
21
|
+
FullResult,
|
|
22
|
+
} from '@playwright/test/reporter';
|
|
23
|
+
|
|
24
|
+
const AGILETEST_BASE = 'https://jira.agiletest.app';
|
|
25
|
+
|
|
26
|
+
/** Status IDs para proyecto HCM (10032) */
|
|
27
|
+
const STATUS_IDS = {
|
|
28
|
+
todo: 15016,
|
|
29
|
+
pass: 15017,
|
|
30
|
+
fail: 15018,
|
|
31
|
+
blocked: 15019,
|
|
32
|
+
inProgress: 15020,
|
|
33
|
+
skipped: 15021,
|
|
34
|
+
retest: 15022,
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
interface StepResult {
|
|
38
|
+
paso: string;
|
|
39
|
+
title: string;
|
|
40
|
+
status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
|
|
41
|
+
duration: number;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface AgileTestConfig {
|
|
46
|
+
clientId: string;
|
|
47
|
+
clientSecret: string;
|
|
48
|
+
executionId: string;
|
|
49
|
+
tcId: string;
|
|
50
|
+
projectId: string;
|
|
51
|
+
totalSteps: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readConfig(): AgileTestConfig | null {
|
|
55
|
+
const clientId = process.env.AGILETEST_CLIENT_ID;
|
|
56
|
+
const clientSecret = process.env.AGILETEST_CLIENT_SECRET;
|
|
57
|
+
const executionId = process.env.AGILETEST_EXECUTION_ID;
|
|
58
|
+
const tcId = process.env.AGILETEST_TC_ID;
|
|
59
|
+
|
|
60
|
+
if (!clientId || !clientSecret || !executionId || !tcId) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
clientId,
|
|
66
|
+
clientSecret,
|
|
67
|
+
executionId,
|
|
68
|
+
tcId,
|
|
69
|
+
projectId: process.env.AGILETEST_PROJECT_ID || '10032',
|
|
70
|
+
totalSteps: parseInt(process.env.AGILETEST_TOTAL_STEPS || '17', 10),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function getToken(clientId: string, clientSecret: string): Promise<string> {
|
|
75
|
+
const res = await fetch(`${AGILETEST_BASE}/api/apikeys/authenticate`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({ clientId, clientSecret }),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
throw new Error(`AgileTest auth failed: ${res.status} ${await res.text()}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// La respuesta es el token como string con comillas
|
|
86
|
+
const raw = await res.text();
|
|
87
|
+
return raw.replace(/^"|"$/g, '');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class AgileTestReporter implements Reporter {
|
|
91
|
+
private results: StepResult[] = [];
|
|
92
|
+
private config: AgileTestConfig | null = null;
|
|
93
|
+
private enabled = false;
|
|
94
|
+
|
|
95
|
+
onBegin(_config: FullConfig, _suite: Suite): void {
|
|
96
|
+
this.config = readConfig();
|
|
97
|
+
this.enabled = this.config !== null;
|
|
98
|
+
|
|
99
|
+
if (!this.enabled) {
|
|
100
|
+
console.log('[AgileTest] Reporter desactivado — env vars no definidas');
|
|
101
|
+
} else {
|
|
102
|
+
console.log('[AgileTest] Reporter activo — sincronizará con ejecución', this.config!.executionId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onTestEnd(test: TestCase, result: TestResult): void {
|
|
107
|
+
if (!this.enabled) return;
|
|
108
|
+
|
|
109
|
+
const match = test.title.match(/Paso\s+(\d+|manual)/i);
|
|
110
|
+
const paso = match ? match[1] : '?';
|
|
111
|
+
|
|
112
|
+
this.results.push({
|
|
113
|
+
paso,
|
|
114
|
+
title: test.title,
|
|
115
|
+
status: result.status,
|
|
116
|
+
duration: result.duration,
|
|
117
|
+
error: result.errors?.[0]?.message?.substring(0, 300),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async onEnd(_result: FullResult): Promise<void> {
|
|
122
|
+
if (!this.enabled || this.results.length === 0) return;
|
|
123
|
+
|
|
124
|
+
const config = this.config!;
|
|
125
|
+
|
|
126
|
+
// Determinar si la ejecución fue completa o parcial
|
|
127
|
+
const executedSteps = this.results.filter(r => r.paso !== '?').length;
|
|
128
|
+
const isPartial = executedSteps < config.totalSteps;
|
|
129
|
+
|
|
130
|
+
const hasFailed = this.results.some(
|
|
131
|
+
r => r.status === 'failed' || r.status === 'timedOut' || r.status === 'interrupted',
|
|
132
|
+
);
|
|
133
|
+
const hasSkipped = this.results.some(r => r.status === 'skipped');
|
|
134
|
+
|
|
135
|
+
let globalStatusId: number;
|
|
136
|
+
let statusLabel: string;
|
|
137
|
+
|
|
138
|
+
if (hasFailed) {
|
|
139
|
+
globalStatusId = STATUS_IDS.fail;
|
|
140
|
+
statusLabel = 'FAIL';
|
|
141
|
+
} else if (isPartial) {
|
|
142
|
+
globalStatusId = STATUS_IDS.inProgress;
|
|
143
|
+
statusLabel = `IN PROGRESS (${executedSteps}/${config.totalSteps})`;
|
|
144
|
+
} else if (hasSkipped) {
|
|
145
|
+
globalStatusId = STATUS_IDS.skipped;
|
|
146
|
+
statusLabel = 'SKIPPED';
|
|
147
|
+
} else {
|
|
148
|
+
globalStatusId = STATUS_IDS.pass;
|
|
149
|
+
statusLabel = 'PASS';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
// 1. Autenticar
|
|
154
|
+
console.log('[AgileTest] Autenticando...');
|
|
155
|
+
const token = await getToken(config.clientId, config.clientSecret);
|
|
156
|
+
|
|
157
|
+
const headers = {
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
Authorization: `JWT ${token}`,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// 2. PUT resultado del TC en la ejecución
|
|
163
|
+
console.log(`[AgileTest] Actualizando TC ${config.tcId} → ${statusLabel}`);
|
|
164
|
+
const putUrl = `${AGILETEST_BASE}/ds/test-executions/${config.executionId}/test-case/${config.tcId}`;
|
|
165
|
+
const putRes = await fetch(putUrl, {
|
|
166
|
+
method: 'PUT',
|
|
167
|
+
headers,
|
|
168
|
+
body: JSON.stringify({
|
|
169
|
+
testExecutionItem: { result: globalStatusId },
|
|
170
|
+
projectId: config.projectId,
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (!putRes.ok) {
|
|
175
|
+
console.error(`[AgileTest] PUT falló: ${putRes.status} ${await putRes.text()}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const putData = await putRes.json();
|
|
180
|
+
const itemId = putData.id;
|
|
181
|
+
console.log(`[AgileTest] Resultado actualizado → ${statusLabel} (itemId: ${itemId})`);
|
|
182
|
+
|
|
183
|
+
// 3. POST comentario con tabla detallada vía test-execution-items
|
|
184
|
+
const comment = this.buildComment(statusLabel, isPartial, config.totalSteps);
|
|
185
|
+
const commentUrl = `${AGILETEST_BASE}/ds/test-execution-items/${itemId}/comments`;
|
|
186
|
+
const commentRes = await fetch(commentUrl, {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers,
|
|
189
|
+
body: JSON.stringify({ text: comment, projectId: config.projectId }),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (commentRes.ok) {
|
|
193
|
+
console.log('[AgileTest] Comentario agregado con detalle por paso');
|
|
194
|
+
} else {
|
|
195
|
+
console.warn(`[AgileTest] Comentario falló: ${commentRes.status} (no crítico)`);
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.error('[AgileTest] Error sincronizando resultados:', err);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private buildComment(globalStatus: string, isPartial: boolean, totalSteps: number): string {
|
|
203
|
+
const sorted = [...this.results].sort((a, b) => {
|
|
204
|
+
const na = a.paso === 'manual' ? 999 : a.paso === '?' ? 1000 : Number(a.paso);
|
|
205
|
+
const nb = b.paso === 'manual' ? 999 : b.paso === '?' ? 1000 : Number(b.paso);
|
|
206
|
+
return na - nb;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const totalDuration = this.results.reduce((sum, r) => sum + r.duration, 0);
|
|
210
|
+
const passed = this.results.filter(r => r.status === 'passed').length;
|
|
211
|
+
const failed = this.results.filter(
|
|
212
|
+
r => r.status === 'failed' || r.status === 'timedOut' || r.status === 'interrupted',
|
|
213
|
+
).length;
|
|
214
|
+
const skipped = this.results.filter(r => r.status === 'skipped').length;
|
|
215
|
+
const pending = totalSteps - this.results.filter(r => r.paso !== '?').length;
|
|
216
|
+
|
|
217
|
+
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
218
|
+
|
|
219
|
+
const lines = [
|
|
220
|
+
`*Playwright Automated Run — ${timestamp}*`,
|
|
221
|
+
`*Resultado: ${globalStatus}* | ${passed} passed, ${failed} failed, ${skipped} skipped${pending > 0 ? `, ${pending} pending` : ''} | ${this.formatDuration(totalDuration)}`,
|
|
222
|
+
'',
|
|
223
|
+
'||Paso||Nombre||Status||Duración||',
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
for (const r of sorted) {
|
|
227
|
+
const icon = r.status === 'passed' ? '(/)' : r.status === 'skipped' ? '(i)' : '(x)';
|
|
228
|
+
const statusText = r.status === 'passed' ? 'PASS' : r.status === 'skipped' ? 'SKIP' : 'FAIL';
|
|
229
|
+
lines.push(`|${r.paso}|${r.title}|${icon} ${statusText}|${this.formatDuration(r.duration)}|`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (isPartial) {
|
|
233
|
+
lines.push(`|—|_Pasos ${this.results.length + 1}-${totalSteps} pendientes_|(//) TO DO|—|`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (failed > 0) {
|
|
237
|
+
lines.push('', '*Errores:*');
|
|
238
|
+
for (const r of sorted.filter(s => s.error)) {
|
|
239
|
+
lines.push(`* Paso ${r.paso}: {noformat}${r.error}{noformat}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return lines.join('\n');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private formatDuration(ms: number): string {
|
|
247
|
+
if (ms < 1000) return `${ms}ms`;
|
|
248
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export default AgileTestReporter;
|
|
File without changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Architecture Brief — SIESA ERP (Blazor Server + DevExpress + Radzen)
|
|
2
|
+
|
|
3
|
+
> Brief lleno para la plataforma SIESA ERP. Los patrones aquí documentados son
|
|
4
|
+
> **transferibles entre módulos** porque todos comparten el mismo stack (SDK UI Kit).
|
|
5
|
+
> Lo que el agente aprende en un módulo le sirve en todos.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Stack y Comportamiento
|
|
10
|
+
|
|
11
|
+
- **Framework UI:** Blazor Server con SignalR (conexión persistente)
|
|
12
|
+
- **Componentes:** DevExpress BlazorGrid + Radzen Dialogs + SDK UI Kit
|
|
13
|
+
- **Pre-carga requerida:** SÍ — navegar a la URL base (sin parámetros `dat=`) para inicializar
|
|
14
|
+
SignalR, esperar `networkidle` + 3s, y luego navegar a la URL real.
|
|
15
|
+
- **Cultura/Idioma:** ES con tildes. Algunos dropdowns (Género, Estado civil) pueden aparecer
|
|
16
|
+
en EN según config del servidor → **intentar ES primero, fallback EN**.
|
|
17
|
+
- **Auth:** MasterCrud + SSO (ERP) · Email+Password (Portal de Empleo).
|
|
18
|
+
|
|
19
|
+
## Patrones de Interacción
|
|
20
|
+
|
|
21
|
+
### Campos de texto (SDKCharField)
|
|
22
|
+
- **Selector:** `[data-automation-id="SDKCharField_{CAMPO}"] input.dxbl-text-edit-input`
|
|
23
|
+
- **Interacción:** click → `fill("")` → `pressSequentially(valor, { delay: 30 })`
|
|
24
|
+
|
|
25
|
+
### Campos numéricos (SDKNumericField)
|
|
26
|
+
- **Selector:** `[data-automation-id="NumericField_{CAMPO}"] input.dxbl-text-edit-input`
|
|
27
|
+
- **Nota:** DevExpress formatea con decimales — assertion con regex `/^valor(\.00)?$/`.
|
|
28
|
+
|
|
29
|
+
### Dropdowns simples (SDKSelectField — Radzen)
|
|
30
|
+
- **Selector:** `[data-automation-id="SDKSelectField_{CAMPO}"] span.rz-dropdown-label`
|
|
31
|
+
- **Interacción:** click → esperar opciones → click en option por texto (ES, fallback EN).
|
|
32
|
+
|
|
33
|
+
### Entity fields (SDKEntityField — dropdown con búsqueda)
|
|
34
|
+
- **Input:** `[data-automation-id="SDKEntityField_{CAMPO}"] input`
|
|
35
|
+
- **Primera opción:** `[data-automation-id="SDKEntityField_{CAMPO}_1"]`
|
|
36
|
+
- **Lupa:** `[data-automation-id="SDKEntityField_{CAMPO}_SearchBtn"]`
|
|
37
|
+
- **Crear:** `[data-automation-id="SDKEntityField_{CAMPO}_CreateBtn"]`
|
|
38
|
+
- **Dropdown:** click input → esperar lista → click `_1`
|
|
39
|
+
- **Búsqueda:** click lupa → submit → dblclick en celda del grid
|
|
40
|
+
- **Crear inline:** click crear → llenar Id + Name + Status Activo
|
|
41
|
+
|
|
42
|
+
### Textareas (SDKTextField) — descripción SIEMPRE es textarea
|
|
43
|
+
- **Selector:** `[data-automation-id="SDKTextField_{CAMPO}"] textarea`
|
|
44
|
+
- **Interacción:** click → Ctrl+A → `fill("")` → `fill(valor)`
|
|
45
|
+
|
|
46
|
+
### Campos de fecha (Radzen DatePicker)
|
|
47
|
+
- **Interacción:** `click({ clickCount: 3 })` → `pressSequentially(MM/DD/YYYY)` → Tab
|
|
48
|
+
|
|
49
|
+
### Botones toolbar (TopBarButton)
|
|
50
|
+
- **Editar:** `[data-automation-id="TopBarButton_Action.Edit"]`.last()
|
|
51
|
+
- **Guardar:** `[data-automation-id="TopBarButton_Action.Save"]`.last()
|
|
52
|
+
- **Cancelar:** `[data-automation-id="TopBarButton_Action.Cancel"]`.last()
|
|
53
|
+
|
|
54
|
+
### Dialogs y Popups
|
|
55
|
+
- **Wrapper:** `.rz-dialog-wrapper` (puede interceptar clicks)
|
|
56
|
+
- **Cerrar:** JS click en `a.rz-dialog-titlebar-close`
|
|
57
|
+
- **Grid DevExpress:** `td` con `role="gridcell"`, dblclick para seleccionar
|
|
58
|
+
|
|
59
|
+
## Problemas Conocidos
|
|
60
|
+
|
|
61
|
+
- [x] **Pre-load SignalR:** navegar a URL base sin `dat=` primero, luego URL real.
|
|
62
|
+
- [x] **Modal reconexión Blazor:** `#components-reconnect-modal` intercepta clicks → esperar hidden o force click.
|
|
63
|
+
- [x] **Loader SDK:** `.sdk-loader` bloquea interacción → esperar `state: hidden` (timeout 10s).
|
|
64
|
+
- [x] **Toolbar duplicada:** sidebar oculto duplica botones → usar `.last()`.
|
|
65
|
+
- [x] **Cultura dual:** dropdowns pueden mostrar ES o EN → intentar ES, fallback EN.
|
|
66
|
+
- [x] **Celda de filtro DevExpress:** tras `.fill()` presionar `Enter` para activar el binding.
|
|
67
|
+
- [x] **Listados dual-mode:** primera visita = formulario de búsqueda; después = grid.
|
|
68
|
+
|
|
69
|
+
## Helpers disponibles (`helpers/blazor-e2e-helpers.ts`)
|
|
70
|
+
|
|
71
|
+
`login` · `fillSDKText` · `fillSDKTextArea` · `fillFechaRadzen` · `clickToolbar` ·
|
|
72
|
+
`closeOverlays` · `clickActivo` · `waitForSave` · `selectEntityPopup` · `searchInGrid` ·
|
|
73
|
+
`navigateTo` · `waitForGrid` · `getFechas` · `createObsCollector`
|
|
74
|
+
|
|
75
|
+
## Aplicaciones registradas
|
|
76
|
+
|
|
77
|
+
| Aplicación | Stack | URL base | Auth |
|
|
78
|
+
|-----------|-------|----------|------|
|
|
79
|
+
| Portal de Empleo | Blazor + DevExpress + Radzen | beta-aws.siesadev.com/BLPortalLogin | Email+Password |
|
|
80
|
+
| ERP HCM | Blazor + DevExpress + SDK UI Kit | beta-aws.siesadev.com | MasterCrud + SSO |
|
|
81
|
+
| Encuestas | Blazor + DevExpress + SDK UI Kit | beta-aws.siesadev.com | SSO |
|
|
82
|
+
| Financiero | React 18 + siesa-ui-kit + .NET 8 | qa.finance.siesa.dev/app | SSO |
|
|
83
|
+
|
|
84
|
+
> **Patrones transferibles:** entity field, SDKSelectField, SDKCharField, TopBarButton,
|
|
85
|
+
> pre-load SignalR, modal reconexión, loader SDK, toolbar duplicada, cultura dual.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Architecture Brief — SIESA React (Vite + TanStack + siesa-ui-kit)
|
|
2
|
+
|
|
3
|
+
> Brief para las apps SIESA construidas con el **stack corporativo React** (la arquitectura
|
|
4
|
+
> objetivo de los microservicios nuevos). Los patrones aquí son **transferibles entre módulos**
|
|
5
|
+
> porque todos comparten siesa-ui-kit + MasterCrud + LookupField.
|
|
6
|
+
>
|
|
7
|
+
> ⚠️ Este es el stack **distinto al ERP Blazor**. Si la app objetivo es Blazor Server / DevExpress /
|
|
8
|
+
> Radzen, usa `architecture-brief-siesa-erp.md`, NO este.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Stack y Comportamiento
|
|
13
|
+
|
|
14
|
+
- **Framework UI:** React (componentes `.tsx`, hooks) · Vite 7+ · TanStack Router (file-based)
|
|
15
|
+
- **Componentes:** siesa-ui-kit (librería propia) + Radix UI + shadcn/ui. Orquestador de masters: `MasterCrud`.
|
|
16
|
+
- **Estado:** Zustand (cliente) + TanStack Query (servidor). Los datos llegan async → esperar fin de fetch.
|
|
17
|
+
- **Pre-carga requerida:** NO. No hay SignalR ni conexión persistente (a diferencia de Blazor).
|
|
18
|
+
Esperar a que TanStack Query resuelva (spinner/skeleton oculto) en vez de `networkidle` fijo.
|
|
19
|
+
- **Cultura/Idioma:** ES por defecto; i18n por `react-i18next`/locale. Confirmar en el brief del módulo.
|
|
20
|
+
- **Auth:** SSO corporativo (JWT con TenantId). `Company-ID` en headers para operaciones multi-compañía.
|
|
21
|
+
- **Microfrontends:** Single-SPA (default). La app se monta en `#single-spa-application`.
|
|
22
|
+
|
|
23
|
+
## Ambiente
|
|
24
|
+
|
|
25
|
+
- **URL base:** {URL — ej: qa.finance.siesa.dev/app}
|
|
26
|
+
- **Usuario de prueba:** {referencia a env var}
|
|
27
|
+
- **Password:** {referencia a env var, NUNCA literal en el repo}
|
|
28
|
+
|
|
29
|
+
## Estrategia de selectores (orden de preferencia)
|
|
30
|
+
|
|
31
|
+
> En React, prioriza selectores **semánticos y estables** sobre CSS. Si el componente no expone
|
|
32
|
+
> rol/label accesible, pide `data-testid` al equipo y documéntalo aquí; no uses clases de Tailwind
|
|
33
|
+
> como selector (cambian con el estilo).
|
|
34
|
+
|
|
35
|
+
1. `getByRole('button', { name: 'Guardar' })` / `getByRole('textbox', { name: 'Código' })`
|
|
36
|
+
2. `getByLabel('Nombre')` (campos asociados a `<label>`)
|
|
37
|
+
3. `getByTestId('...')` (`data-testid` acordado con el equipo)
|
|
38
|
+
4. CSS solo como último recurso, documentado abajo.
|
|
39
|
+
|
|
40
|
+
## Patrones de Interacción (completar con lo confirmado por módulo/dev)
|
|
41
|
+
|
|
42
|
+
### Campos de texto / número (React Hook Form)
|
|
43
|
+
- **Selector:** `getByLabel('{CAMPO}')` o `getByRole('textbox', { name: '{CAMPO}' })`
|
|
44
|
+
- **Interacción:** `click` → `fill('')` → `fill(valor)`. La validación Zod aparece on-blur/submit.
|
|
45
|
+
- **Nota:** assertion sobre el valor con `toHaveValue`; para errores, `getByText` del mensaje Zod.
|
|
46
|
+
|
|
47
|
+
### Dropdowns / Select (siesa-ui-kit / Radix Select)
|
|
48
|
+
- **Selector trigger:** `getByRole('combobox', { name: '{CAMPO}' })`
|
|
49
|
+
- **Interacción:** click trigger → `getByRole('option', { name: '{VALOR}' })` → click.
|
|
50
|
+
|
|
51
|
+
### LookupField (selección de registros maestros — patrón R-LF-001)
|
|
52
|
+
- **Input:** `getByRole('combobox', { name: '{CAMPO}' })` o `getByTestId('lookup-{campo}')`
|
|
53
|
+
- **Interacción:** escribir término → esperar lista (TanStack Query) → click opción.
|
|
54
|
+
- **Dependientes (cascada):** seleccionar el padre primero; el hijo se habilita por `dependencies`.
|
|
55
|
+
|
|
56
|
+
### MasterCrud (orquestador de masters)
|
|
57
|
+
- **Crear:** botón `getByRole('button', { name: 'Nuevo' })` → llenar form → `Guardar`.
|
|
58
|
+
- **Editar:** seleccionar fila de la grilla → `Editar` → modificar → `Guardar`.
|
|
59
|
+
- **Navegación:** modal / sidebar / page según prop `navigationType` (confirmar en brief del módulo).
|
|
60
|
+
|
|
61
|
+
### Formularios (React Hook Form + Zod)
|
|
62
|
+
- **Submit:** `getByRole('button', { name: 'Guardar' })`.
|
|
63
|
+
- **Validación:** los errores Zod se renderizan como texto cerca del campo → `getByText(/mensaje/)`.
|
|
64
|
+
|
|
65
|
+
### Botones / Toolbar
|
|
66
|
+
- **Guardar / Cancelar / Eliminar:** `getByRole('button', { name: '{ACCIÓN}' })`.
|
|
67
|
+
|
|
68
|
+
### Dialogs y Popups (Radix Dialog)
|
|
69
|
+
- **Wrapper:** `getByRole('dialog')` · **Cerrar:** `getByRole('button', { name: /cerrar|close/i })`.
|
|
70
|
+
|
|
71
|
+
## Estados de carga y espera (clave en React)
|
|
72
|
+
|
|
73
|
+
- **No** uses esperas fijas. Espera a que el spinner/skeleton de TanStack Query desaparezca:
|
|
74
|
+
`await expect(page.getByRole('progressbar')).toBeHidden()` o el `data-testid` del loader del kit.
|
|
75
|
+
- Tras guardar, espera el toast de éxito o la invalidación de query (la grilla se refresca sola).
|
|
76
|
+
|
|
77
|
+
## Problemas Conocidos (marcar los que apliquen por módulo)
|
|
78
|
+
|
|
79
|
+
- [ ] **Carga async TanStack Query:** esperar fin de fetch antes de interactuar/aseverar.
|
|
80
|
+
- [ ] **Toast de confirmación:** aparece y se auto-oculta → capturarlo rápido o assert por rol `status`.
|
|
81
|
+
- [ ] **Single-SPA mount:** la app vive bajo `#single-spa-application`; esperar montaje.
|
|
82
|
+
- [ ] **Dark mode (class-based):** no afecta selectores semánticos; sí selectores por color.
|
|
83
|
+
- [ ] **Otro:** {describir}
|
|
84
|
+
|
|
85
|
+
## Datos de Prueba de referencia
|
|
86
|
+
|
|
87
|
+
| Campo | Valor | Patrón | Notas |
|
|
88
|
+
|-------|-------|--------|-------|
|
|
89
|
+
| {campo} | {valor} | texto/select/lookup/fecha | {nota} |
|
|
90
|
+
|
|
91
|
+
## Helpers disponibles (`helpers/react-e2e-helpers.ts`)
|
|
92
|
+
|
|
93
|
+
`createObsCollector` · `login` · `waitForQuery` · `fillField` · `selectOption` · `selectLookup` ·
|
|
94
|
+
`setToggle` · `submitForm` · `clickAction` · `expectToast` · `expectFieldError` · `navigateTo` ·
|
|
95
|
+
`waitForGrid` · `getFechas` · `step` · `generateRunId`
|
|
96
|
+
|
|
97
|
+
> ⚠️ **Scaffold:** los selectores concretos de siesa-ui-kit no están documentados en el repo,
|
|
98
|
+
> así que varios helpers traen suposiciones marcadas con `TODO[brief]` (rol del LookupField,
|
|
99
|
+
> loader, ruta de login SSO). Cada helper acepta `byTestId`/overrides — confírmalos contra la
|
|
100
|
+
> app real (vía dev o grabador) antes de promover specs a `tests/`.
|
|
101
|
+
|
|
102
|
+
## Aplicaciones registradas (React)
|
|
103
|
+
|
|
104
|
+
| Aplicación | Stack | URL base | Auth |
|
|
105
|
+
|-----------|-------|----------|------|
|
|
106
|
+
| Financiero | React 18 + siesa-ui-kit + .NET 8 | qa.finance.siesa.dev/app | SSO |
|
|
107
|
+
|
|
108
|
+
> **Retroalimentación del Healer:** cada patrón/quirk nuevo que descubra el Healer se agrega aquí
|
|
109
|
+
> con prefijo `[HEALER]` (validar que se repita en ≥2 corridas antes de marcarlo estable).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Architecture Brief — {NOMBRE_APLICACIÓN}
|
|
2
|
+
|
|
3
|
+
> **Qué es:** el insumo que le da al Agente Ejecutor E2E el conocimiento del stack, los
|
|
4
|
+
> selectores y los patrones de interacción de la app objetivo. Con brief, el agente genera
|
|
5
|
+
> specs correctos al primer intento (~85-90% autonomía); sin él, descubre por prueba y error.
|
|
6
|
+
>
|
|
7
|
+
> **Cómo llenarlo:** (1) un dev lo completa en ~5 min, o (2) QA usa el grabador
|
|
8
|
+
> (`tools/locator-recorder.spec.ts`) en una sesión exploratoria y lo llena.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Stack y Comportamiento
|
|
13
|
+
|
|
14
|
+
- **Framework UI:** {ej: Blazor Server con SignalR / React 18 / Angular}
|
|
15
|
+
- **Componentes:** {ej: DevExpress BlazorGrid + Radzen Dialogs + SDK UI Kit}
|
|
16
|
+
- **Pre-carga requerida:** {SI/NO — si SI, describir el patrón. Ej: "navegar a URL base sin parámetros para inicializar SignalR, luego navegar a la URL real"}
|
|
17
|
+
- **Cultura/Idioma:** {ej: "ES con tildes; algunos dropdowns aparecen en EN según config del servidor — intentar ES primero"}
|
|
18
|
+
- **Auth:** {ej: Email+Password / SSO corporativo / MasterCrud}
|
|
19
|
+
|
|
20
|
+
## Ambiente
|
|
21
|
+
|
|
22
|
+
- **URL base:** {URL}
|
|
23
|
+
- **Usuario de prueba:** {usuario o referencia a env var}
|
|
24
|
+
- **Password:** {referencia a env var, NUNCA literal en el repo}
|
|
25
|
+
|
|
26
|
+
## Patrones de Interacción (completar solo los que apliquen)
|
|
27
|
+
|
|
28
|
+
### Campos de texto
|
|
29
|
+
- **Selector:** `{ej: [data-automation-id="SDKCharField_{CAMPO}"] input.dxbl-text-edit-input}`
|
|
30
|
+
- **Interacción:** {ej: click → fill("") → pressSequentially(valor, { delay: 30 })}
|
|
31
|
+
|
|
32
|
+
### Campos numéricos
|
|
33
|
+
- **Selector:** `{...}`
|
|
34
|
+
- **Nota:** {ej: DevExpress formatea con decimales — "70" se muestra como "70.00"}
|
|
35
|
+
|
|
36
|
+
### Dropdowns simples
|
|
37
|
+
- **Selector:** `{ej: [data-automation-id="SDKSelectField_{CAMPO}"] span.rz-dropdown-label}`
|
|
38
|
+
- **Interacción:** {click → esperar opciones → click en option por texto}
|
|
39
|
+
|
|
40
|
+
### Entity fields (dropdown con búsqueda/lupa)
|
|
41
|
+
- **Input:** `{...}` · **Primera opción:** `{..._1}` · **Lupa:** `{..._SearchBtn}` · **Crear:** `{..._CreateBtn}`
|
|
42
|
+
- **Interacción:** {dropdown: click input → esperar lista → click `_1`; búsqueda: click lupa → submit → dblclick celda}
|
|
43
|
+
|
|
44
|
+
### Campos de fecha
|
|
45
|
+
- **Selector:** `{...}` · **Interacción:** {ej: click({clickCount:3}) → pressSequentially(MM/DD/YYYY) → Tab}
|
|
46
|
+
|
|
47
|
+
### Botones toolbar
|
|
48
|
+
- **Editar / Guardar / Cancelar:** `{...}`
|
|
49
|
+
|
|
50
|
+
### Dialogs y Popups
|
|
51
|
+
- **Wrapper / cerrar / grid:** `{...}`
|
|
52
|
+
|
|
53
|
+
## Problemas Conocidos (marcar los que apliquen)
|
|
54
|
+
|
|
55
|
+
- [ ] **Pre-load:** {descripción}
|
|
56
|
+
- [ ] **Modal de reconexión:** {selector + cómo manejarlo}
|
|
57
|
+
- [ ] **Loader bloqueante:** {selector + esperar hidden}
|
|
58
|
+
- [ ] **Toolbar duplicada:** {usar `.last()`}
|
|
59
|
+
- [ ] **Cultura dual ES/EN:** {intentar ES, fallback EN}
|
|
60
|
+
- [ ] **Otro:** {describir}
|
|
61
|
+
|
|
62
|
+
## Datos de Prueba de referencia
|
|
63
|
+
|
|
64
|
+
| Campo | Valor | Patrón | Notas |
|
|
65
|
+
|-------|-------|--------|-------|
|
|
66
|
+
| {campo} | {valor} | texto/dropdown/entity/fecha | {nota} |
|
|
67
|
+
|
|
68
|
+
> **Retroalimentación del Healer:** cada vez que el Healer descubra un patrón/quirk nuevo,
|
|
69
|
+
> agregarlo aquí con prefijo `[HEALER]` para que los próximos módulos se beneficien.
|