mcp-lab-agent 2.1.10 → 2.3.1
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 +103 -23
- package/dist/index.js +209 -10
- package/dist/index.js.map +1 -1
- package/learning-hub/src/server.js +6 -6
- package/package.json +3 -2
- package/slack-bot/check-config.js +11 -3
- package/slack-bot/src/config.js +22 -5
package/README.md
CHANGED
|
@@ -4,48 +4,127 @@
|
|
|
4
4
|
[](https://nodejs.org)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**PT-BR** | [English](#english)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Português (PT-BR)
|
|
12
|
+
|
|
13
|
+
**Sistema de QA autônomo com IA.** Reduz tempo de debug de testes, elimina flaky e mantém seletores estáveis — com um sistema de aprendizado que melhora a cada correção.
|
|
14
|
+
|
|
15
|
+
> **TL;DR para recrutadores:** QA autônomo que explica *por que* os testes falharam em linguagem clara e aplica correções automaticamente. Testes que se autocorrigem e aprendem a cada fix. Integra com IDE (Cursor) e Slack. Feito para QA Engineers, SDETs e roles de Automação/IA.
|
|
16
|
+
|
|
17
|
+
### Por que isso importa
|
|
18
|
+
|
|
19
|
+
| Problema real | Impacto no mercado | O que o mcp-lab-agent faz |
|
|
20
|
+
|---------------|--------------------|---------------------------|
|
|
21
|
+
| **Testes flaky** | Times gastam 5–10h/semana. Microsoft: ~25% das falhas em CI são flaky; Slack tinha 56% antes de remediar. | Detecta padrões flaky, sugere correções, retry automático com fixes |
|
|
22
|
+
| **"Por que falhou?"** | QAs e devs perdem horas lendo stack traces e logs. "Teste falhou" genérico não ajuda. | **Causa + correção em 30 segundos.** Diagnóstico em linguagem clara: o que aconteceu, por que e como corrigir |
|
|
23
|
+
| **Seletores quebrados** | Refactors de UI quebram testes. Seletores frágeis (classes CSS, XPath longo) exigem manutenção manual. | Auto-fix de seletores, sugere `data-testid`, aplica correções e tenta de novo |
|
|
24
|
+
|
|
25
|
+
### O WOW: Testes que se autocorrigem e aprendem
|
|
26
|
+
|
|
27
|
+
**Quando um teste falha, você recebe a causa e a correção em 30 segundos. Sem cavar em stack traces.**
|
|
28
|
+
|
|
29
|
+
Cada correção bem-sucedida é salva e reutilizada. Na próxima falha similar, o agente aplica o padrão aprendido automaticamente. **A taxa de sucesso na primeira tentativa melhora ao longo do tempo** — mensurável via `mcp-lab-agent stats`.
|
|
8
30
|
|
|
9
31
|
```bash
|
|
10
32
|
npx mcp-lab-agent auto "login flow" --max-retries 5
|
|
11
33
|
```
|
|
12
34
|
|
|
13
|
-
|
|
35
|
+
*Um comando. Análise completa. Autocorreção. Aprendizado.*
|
|
14
36
|
|
|
15
|
-
|
|
37
|
+
### Principais resultados
|
|
16
38
|
|
|
17
|
-
**
|
|
39
|
+
- **Reduz tempo de debug** — "Por que falhou?" em linguagem clara, não stack traces
|
|
40
|
+
- **Corta manutenção de flaky** — Detecção, diagnóstico e sugestões de correção
|
|
41
|
+
- **Escala QA sem escalar headcount** — Agente no IDE + Slack bot; funciona com Cypress, Playwright, Appium, Jest e 11+ frameworks
|
|
42
|
+
- **Pronto para enterprise** — Socket Mode (sem URL pública), Ollama (offline), Learning Hub para times
|
|
18
43
|
|
|
19
|
-
|
|
44
|
+
### Como funciona
|
|
20
45
|
|
|
21
|
-
|
|
46
|
+
**🤖 Agente no IDE (Cursor, Cline, Windsurf)** — Pergunte no chat: *"Gere teste para login"*, *"Por que o teste falhou?"*, *"Roda o teste X"*. O agente detecta o projeto, executa testes, analisa falhas, aplica correções e aprende.
|
|
22
47
|
|
|
23
|
-
|
|
48
|
+
**💬 Slack Bot** — Mencione o bot em qualquer canal — ele executa testes e posta o relatório. Funciona em ambiente corporativo (Socket Mode, sem ngrok). QA no fluxo da conversa.
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Para quem
|
|
50
|
+
### Para quem é
|
|
30
51
|
|
|
31
52
|
| Perfil | Benefício |
|
|
32
53
|
|--------|-----------|
|
|
33
|
-
| **QAs e SDETs** | Geração assistida de testes, análise de falhas com sugestões de correção, detecção de
|
|
34
|
-
| **Desenvolvedores** | "Por que falhou?", análise de arquivos
|
|
54
|
+
| **QAs e SDETs** | Geração assistida de testes, análise de falhas com sugestões de correção, detecção de flaky |
|
|
55
|
+
| **Desenvolvedores** | "Por que falhou?" em segundos, análise de arquivos/métodos, integração direta no IDE |
|
|
35
56
|
| **Tech leads** | Visão de risco por área, métricas de estabilidade, relatórios para decisão |
|
|
36
|
-
| **
|
|
37
|
-
|
|
38
|
-
---
|
|
57
|
+
| **Times** | Learning Hub, Slack bot para QA no chat, CI/CD, Ollama (offline) |
|
|
39
58
|
|
|
40
|
-
|
|
59
|
+
### Como é diferente
|
|
41
60
|
|
|
42
61
|
| Outras ferramentas | mcp-lab-agent |
|
|
43
62
|
|--------------------|---------------|
|
|
44
|
-
| Só executam testes | Executa, analisa causa
|
|
45
|
-
|
|
|
46
|
-
| Sem
|
|
47
|
-
|
|
|
48
|
-
|
|
63
|
+
| Só executam testes | Executa, analisa causa, sugere fix, aplica correção |
|
|
64
|
+
| "Teste falhou" genérico | Linguagem clara: "Login falha 30% das vezes (timing). Adicione waitForDisplayed." |
|
|
65
|
+
| Sem memória entre execuções | Learning system: cada fix melhora as próximas gerações |
|
|
66
|
+
| Uma ferramenta por tarefa | End-to-end: gera, executa, analisa, reporta, aprende |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
<a name="english"></a>
|
|
71
|
+
|
|
72
|
+
## English
|
|
73
|
+
|
|
74
|
+
**AI-powered autonomous QA system.** Reduces test debugging time, eliminates flaky tests, and keeps selectors stable — with a learning system that gets smarter with every fix.
|
|
75
|
+
|
|
76
|
+
> **TL;DR for recruiters:** Autonomous QA that explains *why* tests fail in plain language and applies fixes automatically. Self-healing tests that learn from each fix. Integrates with IDE (Cursor) and Slack. Built for QA Engineers, SDETs, and AI/Automation roles.
|
|
77
|
+
|
|
78
|
+
### Why this matters
|
|
79
|
+
|
|
80
|
+
| Real problem | Industry impact | What mcp-lab-agent does |
|
|
81
|
+
|--------------|-----------------|-------------------------|
|
|
82
|
+
| **Flaky tests** | Teams spend 5–10h/week. Microsoft: ~25% of CI failures are flaky; Slack had 56% before remediation. | Detects flaky patterns, suggests fixes, auto-retries with corrections |
|
|
83
|
+
| **"Why did it fail?"** | QAs and devs lose hours reading stack traces and logs. Generic "test failed" doesn't help. | **Cause + fix in 30 seconds.** Plain-language diagnosis: what happened, why, and how to fix |
|
|
84
|
+
| **Broken selectors** | UI refactors break tests. Fragile selectors (CSS classes, long XPath) require manual maintenance. | Auto-fix selectors, suggests `data-testid`, applies corrections and retries |
|
|
85
|
+
|
|
86
|
+
### The WOW: Self-healing tests that learn
|
|
87
|
+
|
|
88
|
+
**When a test fails, you get the cause and fix in 30 seconds. No more digging through stack traces.**
|
|
89
|
+
|
|
90
|
+
Each successful fix is saved and reused. The next time a similar failure happens, the agent applies the learned pattern automatically. **First-attempt success rate improves over time** — measurable via `mcp-lab-agent stats`.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx mcp-lab-agent auto "login flow" --max-retries 5
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
*One command. Full analysis. Self-correction. Learning.*
|
|
97
|
+
|
|
98
|
+
### Key outcomes
|
|
99
|
+
|
|
100
|
+
- **Reduce debugging time** — "Why did it fail?" in plain language, not stack traces
|
|
101
|
+
- **Cut flaky test maintenance** — Detection, diagnosis, and suggested fixes
|
|
102
|
+
- **Scale QA without scaling headcount** — IDE agent + Slack bot; works with Cypress, Playwright, Appium, Jest, and 11+ frameworks
|
|
103
|
+
- **Enterprise-ready** — Socket Mode (no public URL), Ollama (offline), Learning Hub for teams
|
|
104
|
+
|
|
105
|
+
### How it works
|
|
106
|
+
|
|
107
|
+
**🤖 IDE Agent (Cursor, Cline, Windsurf)** — Ask in chat: *"Generate a test for login"*, *"Why did the test fail?"*, *"Run test X"*. The agent detects your project, runs tests, analyzes failures, applies fixes, and learns.
|
|
108
|
+
|
|
109
|
+
**💬 Slack Bot** — Mention the bot in any channel — it runs tests and posts the report. Works in corporate environments (Socket Mode, no ngrok). QA in the flow of conversation.
|
|
110
|
+
|
|
111
|
+
### Who it's for
|
|
112
|
+
|
|
113
|
+
| Role | Benefit |
|
|
114
|
+
|------|---------|
|
|
115
|
+
| **QAs & SDETs** | Assisted test generation, failure analysis with fix suggestions, flaky detection |
|
|
116
|
+
| **Developers** | "Why did it fail?" in seconds, file/method analysis, direct IDE integration |
|
|
117
|
+
| **Tech leads** | Risk visibility by area, stability metrics, decision-ready reports |
|
|
118
|
+
| **Teams** | Learning Hub, Slack bot for QA in chat, CI/CD integration, Ollama (offline) |
|
|
119
|
+
|
|
120
|
+
### How it's different
|
|
121
|
+
|
|
122
|
+
| Other tools | mcp-lab-agent |
|
|
123
|
+
|-------------|---------------|
|
|
124
|
+
| Run tests only | Run, analyze cause, suggest fix, apply correction |
|
|
125
|
+
| Generic "test failed" | Plain-language: "Login fails 30% of the time (timing). Add waitForDisplayed." |
|
|
126
|
+
| No memory between runs | Learning system: each fix improves future generations |
|
|
127
|
+
| One tool per task | End-to-end: generate, run, analyze, report, learn |
|
|
49
128
|
|
|
50
129
|
---
|
|
51
130
|
|
|
@@ -299,6 +378,7 @@ npm install playwright
|
|
|
299
378
|
- [CHANGELOG.md](CHANGELOG.md) — Histórico de versões
|
|
300
379
|
- [slack-bot/README.md](slack-bot/README.md) — Slack Bot
|
|
301
380
|
- [learning-hub/README.md](learning-hub/README.md) — Learning Hub
|
|
381
|
+
- [docs/PORTFOLIO_COPY_PT-BR.md](docs/PORTFOLIO_COPY_PT-BR.md) — Copy em PT-BR para portfólio (Vercel)
|
|
302
382
|
|
|
303
383
|
---
|
|
304
384
|
|
package/dist/index.js
CHANGED
|
@@ -1506,15 +1506,99 @@ function runTestsOnce(cmd, args, cwd, env = process.env) {
|
|
|
1506
1506
|
});
|
|
1507
1507
|
});
|
|
1508
1508
|
}
|
|
1509
|
+
async function handleMultipleAutoTests(testRequests, maxRetries) {
|
|
1510
|
+
console.log(`\u{1F4CB} Testes a executar:`);
|
|
1511
|
+
testRequests.forEach((req, i) => console.log(` ${i + 1}. ${req}`));
|
|
1512
|
+
console.log("");
|
|
1513
|
+
const results = [];
|
|
1514
|
+
const startTime = Date.now();
|
|
1515
|
+
const promises = testRequests.map(async (testRequest, index) => {
|
|
1516
|
+
const testNum = index + 1;
|
|
1517
|
+
const prefix = `[Teste ${testNum}/${testRequests.length}]`;
|
|
1518
|
+
console.log(`${prefix} \u{1F680} Iniciando: "${testRequest}"
|
|
1519
|
+
`);
|
|
1520
|
+
try {
|
|
1521
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
1522
|
+
const args = ["auto", testRequest, "--max-retries", maxRetries.toString()];
|
|
1523
|
+
return new Promise((resolve) => {
|
|
1524
|
+
const child = spawn3("mcp-lab-agent", args, {
|
|
1525
|
+
cwd: process.cwd(),
|
|
1526
|
+
stdio: "pipe",
|
|
1527
|
+
shell: process.platform === "win32"
|
|
1528
|
+
});
|
|
1529
|
+
let output = "";
|
|
1530
|
+
if (child.stdout) child.stdout.on("data", (d) => {
|
|
1531
|
+
const text = d.toString();
|
|
1532
|
+
output += text;
|
|
1533
|
+
process.stdout.write(text.split("\n").map((line) => line ? `${prefix} ${line}` : "").join("\n"));
|
|
1534
|
+
});
|
|
1535
|
+
if (child.stderr) child.stderr.on("data", (d) => {
|
|
1536
|
+
output += d.toString();
|
|
1537
|
+
});
|
|
1538
|
+
child.on("close", (code) => {
|
|
1539
|
+
const success = code === 0;
|
|
1540
|
+
const duration = Math.round((Date.now() - startTime) / 1e3);
|
|
1541
|
+
resolve({
|
|
1542
|
+
testRequest,
|
|
1543
|
+
success,
|
|
1544
|
+
exitCode: code,
|
|
1545
|
+
output,
|
|
1546
|
+
duration
|
|
1547
|
+
});
|
|
1548
|
+
});
|
|
1549
|
+
});
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
return {
|
|
1552
|
+
testRequest,
|
|
1553
|
+
success: false,
|
|
1554
|
+
exitCode: 1,
|
|
1555
|
+
error: err.message,
|
|
1556
|
+
duration: 0
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
const allResults = await Promise.all(promises);
|
|
1561
|
+
const totalDuration = Math.round((Date.now() - startTime) / 1e3);
|
|
1562
|
+
const passed = allResults.filter((r) => r.success).length;
|
|
1563
|
+
const failed = allResults.length - passed;
|
|
1564
|
+
console.log("\n" + "=".repeat(60));
|
|
1565
|
+
console.log("\u{1F4CA} RESUMO DOS TESTES");
|
|
1566
|
+
console.log("=".repeat(60) + "\n");
|
|
1567
|
+
allResults.forEach((result, i) => {
|
|
1568
|
+
const icon = result.success ? "\u2705" : "\u274C";
|
|
1569
|
+
const status = result.success ? "PASSOU" : "FALHOU";
|
|
1570
|
+
console.log(`${icon} ${i + 1}. ${result.testRequest} - ${status}`);
|
|
1571
|
+
});
|
|
1572
|
+
console.log("");
|
|
1573
|
+
console.log(`Total: ${allResults.length} testes`);
|
|
1574
|
+
console.log(`\u2705 Passou: ${passed}`);
|
|
1575
|
+
console.log(`\u274C Falhou: ${failed}`);
|
|
1576
|
+
console.log(`\u23F1\uFE0F Tempo total: ${totalDuration}s`);
|
|
1577
|
+
console.log("");
|
|
1578
|
+
if (failed > 0) {
|
|
1579
|
+
process.exit(1);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1509
1582
|
async function handleAutoCommand() {
|
|
1510
1583
|
const request = process.argv.slice(3).join(" ");
|
|
1511
1584
|
if (!request) {
|
|
1512
1585
|
console.error("\u274C Uso: mcp-lab-agent auto <descri\xE7\xE3o do teste> [--max-retries N]");
|
|
1586
|
+
console.error(" Exemplos:");
|
|
1587
|
+
console.error(' mcp-lab-agent auto "login"');
|
|
1588
|
+
console.error(' mcp-lab-agent auto "login, cadastro, buscar" --max-retries 3');
|
|
1513
1589
|
process.exit(1);
|
|
1514
1590
|
}
|
|
1515
1591
|
const maxRetriesIdx = process.argv.indexOf("--max-retries");
|
|
1516
1592
|
const maxRetries = maxRetriesIdx !== -1 && process.argv[maxRetriesIdx + 1] ? parseInt(process.argv[maxRetriesIdx + 1], 10) : 3;
|
|
1517
1593
|
const cleanRequest = request.replace(/--max-retries\s+\d+/g, "").trim();
|
|
1594
|
+
const testRequests = cleanRequest.split(",").map((r) => r.trim()).filter(Boolean);
|
|
1595
|
+
if (testRequests.length > 1) {
|
|
1596
|
+
console.log(`
|
|
1597
|
+
\u{1F916} Modo aut\xF4nomo iniciado: ${testRequests.length} testes em paralelo
|
|
1598
|
+
`);
|
|
1599
|
+
await handleMultipleAutoTests(testRequests, maxRetries);
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1518
1602
|
console.log(`
|
|
1519
1603
|
\u{1F916} Modo aut\xF4nomo iniciado: "${cleanRequest}"
|
|
1520
1604
|
`);
|
|
@@ -1542,16 +1626,19 @@ async function handleAutoCommand() {
|
|
|
1542
1626
|
[Tentativa ${attempt}/${maxRetries}] Gerando teste...`);
|
|
1543
1627
|
const { provider, apiKey, baseUrl, model } = llm;
|
|
1544
1628
|
const memoryHints = memory.learnings?.filter((l) => l.success).slice(-10).map((l) => l.fix).join("\n") || "";
|
|
1629
|
+
const packageInfo = structure.packageJson || {};
|
|
1630
|
+
const isESM = packageInfo.type === "module";
|
|
1545
1631
|
const systemPrompt = `Voc\xEA \xE9 um engenheiro de QA especializado em ${fw}. Gere APENAS o c\xF3digo do spec, sem explica\xE7\xF5es.
|
|
1546
1632
|
${memoryHints ? `
|
|
1547
1633
|
Aprendizados anteriores (use como refer\xEAncia):
|
|
1548
1634
|
${memoryHints.slice(0, 1e3)}` : ""}
|
|
1635
|
+
${isESM ? "\nIMPORTANTE: Use sintaxe ESM (import/export), N\xC3O use require()." : ""}
|
|
1549
1636
|
Retorne SOMENTE o c\xF3digo, sem markdown.`;
|
|
1550
1637
|
const userPrompt = `Contexto:
|
|
1551
1638
|
${contextLines}
|
|
1552
1639
|
|
|
1553
1640
|
Gere teste para: ${cleanRequest}
|
|
1554
|
-
Framework: ${fw}`;
|
|
1641
|
+
Framework: ${fw}${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
|
|
1555
1642
|
try {
|
|
1556
1643
|
let specContent = "";
|
|
1557
1644
|
if (provider === "gemini") {
|
|
@@ -1565,6 +1652,9 @@ Framework: ${fw}`;
|
|
|
1565
1652
|
})
|
|
1566
1653
|
});
|
|
1567
1654
|
const data = await res.json();
|
|
1655
|
+
if (data.error) {
|
|
1656
|
+
throw new Error(`Gemini API Error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
1657
|
+
}
|
|
1568
1658
|
specContent = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1569
1659
|
} else {
|
|
1570
1660
|
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
@@ -1578,10 +1668,20 @@ Framework: ${fw}`;
|
|
|
1578
1668
|
})
|
|
1579
1669
|
});
|
|
1580
1670
|
const data = await res.json();
|
|
1671
|
+
if (data.error) {
|
|
1672
|
+
throw new Error(`API Error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
1673
|
+
}
|
|
1581
1674
|
specContent = data.choices?.[0]?.message?.content || "";
|
|
1582
1675
|
}
|
|
1676
|
+
console.log(`[DEBUG] Resposta do LLM recebida: ${specContent.length} caracteres`);
|
|
1677
|
+
if (!specContent || specContent.trim().length === 0) {
|
|
1678
|
+
throw new Error("LLM retornou conte\xFAdo vazio. Verifique sua API key e conex\xE3o.");
|
|
1679
|
+
}
|
|
1583
1680
|
specContent = specContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
1584
1681
|
testContent = specContent;
|
|
1682
|
+
if (!testContent || testContent.trim().length === 0) {
|
|
1683
|
+
throw new Error("Ap\xF3s parsing, o c\xF3digo ficou vazio. Resposta do LLM pode estar em formato inesperado.");
|
|
1684
|
+
}
|
|
1585
1685
|
if (!testFilePath) {
|
|
1586
1686
|
const fileName = cleanRequest.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 30);
|
|
1587
1687
|
const { ext, baseDir } = getExtensionAndBaseDir(fw, structure);
|
|
@@ -1590,11 +1690,16 @@ Framework: ${fw}`;
|
|
|
1590
1690
|
if (!fs5.existsSync(baseDir)) fs5.mkdirSync(baseDir, { recursive: true });
|
|
1591
1691
|
}
|
|
1592
1692
|
fs5.writeFileSync(testFilePath, testContent, "utf8");
|
|
1593
|
-
|
|
1693
|
+
const fileSize = fs5.statSync(testFilePath).size;
|
|
1694
|
+
if (fileSize === 0) {
|
|
1695
|
+
throw new Error("Arquivo gravado mas est\xE1 vazio. Problema na escrita do arquivo.");
|
|
1696
|
+
}
|
|
1697
|
+
console.log(`\u2705 Teste gravado: ${testFilePath} (${fileSize} bytes)`);
|
|
1594
1698
|
console.log(`
|
|
1595
1699
|
[Tentativa ${attempt}/${maxRetries}] Executando teste...`);
|
|
1700
|
+
const runArg = fw === "playwright" ? path5.relative(PROJECT_ROOT5, testFilePath).replace(/\\/g, "/") : testFilePath;
|
|
1596
1701
|
const runResult = await new Promise((resolve) => {
|
|
1597
|
-
const child = spawn("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run",
|
|
1702
|
+
const child = spawn("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", runArg], {
|
|
1598
1703
|
cwd: PROJECT_ROOT5,
|
|
1599
1704
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1600
1705
|
shell: process.platform === "win32"
|
|
@@ -1641,8 +1746,59 @@ ${runResult.output.slice(0, 800)}
|
|
|
1641
1746
|
console.log(`\u26A0\uFE0F Flaky detectado (${flakyAnalysis.confidence.toFixed(2)}): ${flakyAnalysis.patterns.map((p) => p.pattern).join(", ")}`);
|
|
1642
1747
|
}
|
|
1643
1748
|
console.log(`
|
|
1644
|
-
[Tentativa ${attempt}/${maxRetries}] Aplicando corre\xE7\xE3o
|
|
1645
|
-
|
|
1749
|
+
[Tentativa ${attempt}/${maxRetries}] Aplicando corre\xE7\xE3o...`);
|
|
1750
|
+
try {
|
|
1751
|
+
const fixPrompt = `Voc\xEA \xE9 um engenheiro de QA. O teste falhou com o seguinte erro:
|
|
1752
|
+
|
|
1753
|
+
${runResult.output.slice(0, 1e3)}
|
|
1754
|
+
|
|
1755
|
+
C\xF3digo atual do teste:
|
|
1756
|
+
${testContent}
|
|
1757
|
+
|
|
1758
|
+
Analise o erro e corrija o teste. Considere:
|
|
1759
|
+
- Seletores podem estar errados (verifique se os elementos existem)
|
|
1760
|
+
- Pode precisar de waits (waitForSelector, waitForLoadState)
|
|
1761
|
+
- Rotas podem estar erradas (/buscar vs /busca)
|
|
1762
|
+
- Elementos podem ter nomes diferentes
|
|
1763
|
+
|
|
1764
|
+
Retorne APENAS o c\xF3digo corrigido, sem explica\xE7\xF5es.${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
|
|
1765
|
+
let fixedContent = "";
|
|
1766
|
+
if (provider === "gemini") {
|
|
1767
|
+
const url = `${baseUrl}/models/${model}:generateContent?key=${apiKey}`;
|
|
1768
|
+
const res = await fetch(url, {
|
|
1769
|
+
method: "POST",
|
|
1770
|
+
headers: { "Content-Type": "application/json" },
|
|
1771
|
+
body: JSON.stringify({
|
|
1772
|
+
contents: [{ parts: [{ text: fixPrompt }] }],
|
|
1773
|
+
generationConfig: { temperature: 0.3, maxOutputTokens: 4096 }
|
|
1774
|
+
})
|
|
1775
|
+
});
|
|
1776
|
+
const data = await res.json();
|
|
1777
|
+
fixedContent = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1778
|
+
} else {
|
|
1779
|
+
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
1780
|
+
method: "POST",
|
|
1781
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1782
|
+
body: JSON.stringify({
|
|
1783
|
+
model,
|
|
1784
|
+
messages: [{ role: "user", content: fixPrompt }],
|
|
1785
|
+
temperature: 0.3,
|
|
1786
|
+
max_tokens: 4096
|
|
1787
|
+
})
|
|
1788
|
+
});
|
|
1789
|
+
const data = await res.json();
|
|
1790
|
+
fixedContent = data.choices?.[0]?.message?.content || "";
|
|
1791
|
+
}
|
|
1792
|
+
fixedContent = fixedContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
1793
|
+
if (fixedContent && fixedContent.length > 50) {
|
|
1794
|
+
testContent = fixedContent;
|
|
1795
|
+
console.log(`\u2705 Corre\xE7\xE3o gerada pelo LLM.`);
|
|
1796
|
+
} else {
|
|
1797
|
+
console.log(`\u26A0\uFE0F Corre\xE7\xE3o vazia, tentando novamente...`);
|
|
1798
|
+
}
|
|
1799
|
+
} catch (fixErr) {
|
|
1800
|
+
console.log(`\u26A0\uFE0F Erro ao gerar corre\xE7\xE3o: ${fixErr.message}. Tentando novamente...`);
|
|
1801
|
+
}
|
|
1646
1802
|
} catch (err) {
|
|
1647
1803
|
console.error(`
|
|
1648
1804
|
\u274C Erro na tentativa ${attempt}: ${err.message}
|
|
@@ -2394,8 +2550,18 @@ Framework alvo: ${fw}${referenceBlock}`;
|
|
|
2394
2550
|
}
|
|
2395
2551
|
specContent = specContent.replace(/^```(?:js|javascript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
2396
2552
|
const fileName = request.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 40);
|
|
2553
|
+
if (!specContent) {
|
|
2554
|
+
return {
|
|
2555
|
+
content: [{ type: "text", text: "Erro: LLM retornou conte\xFAdo vazio. Verifique API key (GROQ_API_KEY, GEMINI_API_KEY) e tente novamente." }],
|
|
2556
|
+
structuredContent: { ok: false, error: "Empty LLM response" }
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
const textWithCode = `Spec gerado (${specContent.length} chars). Use write_test para gravar com name="${fileName}" e content abaixo:
|
|
2560
|
+
|
|
2561
|
+
--- C\xF3digo (passe em content para write_test) ---
|
|
2562
|
+
${specContent}`;
|
|
2397
2563
|
return {
|
|
2398
|
-
content: [{ type: "text", text:
|
|
2564
|
+
content: [{ type: "text", text: textWithCode }],
|
|
2399
2565
|
structuredContent: {
|
|
2400
2566
|
ok: true,
|
|
2401
2567
|
specContent,
|
|
@@ -2477,6 +2643,12 @@ server.registerTool(
|
|
|
2477
2643
|
structuredContent: { ok: false, error: "No test framework" }
|
|
2478
2644
|
};
|
|
2479
2645
|
}
|
|
2646
|
+
if (!content || !String(content).trim()) {
|
|
2647
|
+
return {
|
|
2648
|
+
content: [{ type: "text", text: "Erro: content n\xE3o pode ser vazio. Chame generate_tests primeiro e passe o specContent retornado em content." }],
|
|
2649
|
+
structuredContent: { ok: false, error: "Empty content" }
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2480
2652
|
const { ext, baseDir } = getExtensionAndBaseDir2(fw, structure);
|
|
2481
2653
|
const safeName = name.replace(/[^a-z0-9-_]/gi, "-").replace(/-+/g, "-").replace(/_+/g, "_").replace(/\.(cy|spec|test|robot|feature|py)\.?(js|ts|py)?$/i, "").replace(/^[-_]+|[-_]+$/g, "");
|
|
2482
2654
|
const fileName = ext.startsWith("_") ? `${safeName}${ext}` : `${safeName}${ext}`;
|
|
@@ -4367,17 +4539,20 @@ server.registerTool(
|
|
|
4367
4539
|
learnings.push({ attempt, action: "generate_tests", result: "gerando..." });
|
|
4368
4540
|
const { provider, apiKey, baseUrl, model } = llm;
|
|
4369
4541
|
const memoryHints = memory.learnings?.filter((l) => l.fix).slice(-10).map((l) => l.fix).join("\n") || "";
|
|
4542
|
+
const packageInfo = structure.packageJson || {};
|
|
4543
|
+
const isESM = packageInfo.type === "module";
|
|
4370
4544
|
const systemPrompt = `Voc\xEA \xE9 um engenheiro de QA especializado em ${fw}. Gere APENAS o c\xF3digo do spec, sem explica\xE7\xF5es.
|
|
4371
4545
|
${UNIVERSAL_TEST_PRACTICES}
|
|
4372
4546
|
|
|
4373
4547
|
${memoryHints ? `Aprendizados anteriores (use como refer\xEAncia):
|
|
4374
4548
|
${memoryHints.slice(0, 1e3)}` : ""}
|
|
4549
|
+
${isESM ? "\nIMPORTANTE: Use sintaxe ESM (import/export), N\xC3O use require()." : ""}
|
|
4375
4550
|
Retorne SOMENTE o c\xF3digo, sem markdown.`;
|
|
4376
4551
|
const userPrompt = `Contexto:
|
|
4377
4552
|
${contextLines}
|
|
4378
4553
|
|
|
4379
4554
|
Gere teste para: ${request}
|
|
4380
|
-
Framework: ${fw}`;
|
|
4555
|
+
Framework: ${fw}${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
|
|
4381
4556
|
try {
|
|
4382
4557
|
let specContent = "";
|
|
4383
4558
|
if (provider === "gemini") {
|
|
@@ -4404,10 +4579,23 @@ Framework: ${fw}`;
|
|
|
4404
4579
|
})
|
|
4405
4580
|
});
|
|
4406
4581
|
const data = await res.json();
|
|
4582
|
+
if (data.error) {
|
|
4583
|
+
learnings.push({ attempt, action: "llm_call", result: `\u274C API error: ${data.error.message}` });
|
|
4584
|
+
throw new Error(`API Error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
4585
|
+
}
|
|
4407
4586
|
specContent = data.choices?.[0]?.message?.content || "";
|
|
4408
4587
|
}
|
|
4588
|
+
if (!specContent || specContent.trim().length === 0) {
|
|
4589
|
+
learnings.push({ attempt, action: "generate_test", result: "\u274C LLM retornou vazio" });
|
|
4590
|
+
throw new Error("LLM retornou conte\xFAdo vazio. Verifique sua API key e conex\xE3o.");
|
|
4591
|
+
}
|
|
4592
|
+
learnings.push({ attempt, action: "generate_test", result: `\u2705 recebido ${specContent.length} chars` });
|
|
4409
4593
|
specContent = specContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4410
4594
|
testContent = specContent;
|
|
4595
|
+
if (!testContent || testContent.trim().length === 0) {
|
|
4596
|
+
learnings.push({ attempt, action: "parse_code", result: "\u274C c\xF3digo vazio ap\xF3s parsing" });
|
|
4597
|
+
throw new Error("Ap\xF3s parsing, o c\xF3digo ficou vazio. Resposta do LLM pode estar em formato inesperado.");
|
|
4598
|
+
}
|
|
4411
4599
|
if (!testFilePath) {
|
|
4412
4600
|
const fileName = request.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 30);
|
|
4413
4601
|
const { ext, baseDir } = getExtensionAndBaseDir2(fw, structure);
|
|
@@ -4416,10 +4604,16 @@ Framework: ${fw}`;
|
|
|
4416
4604
|
if (!fs6.existsSync(baseDir)) fs6.mkdirSync(baseDir, { recursive: true });
|
|
4417
4605
|
}
|
|
4418
4606
|
fs6.writeFileSync(testFilePath, testContent, "utf8");
|
|
4419
|
-
|
|
4607
|
+
const writtenFileSize = fs6.statSync(testFilePath).size;
|
|
4608
|
+
if (writtenFileSize === 0) {
|
|
4609
|
+
learnings.push({ attempt, action: "write_test", result: "\u274C arquivo vazio ap\xF3s gravar" });
|
|
4610
|
+
throw new Error("Arquivo gravado mas est\xE1 vazio. Problema na escrita do arquivo.");
|
|
4611
|
+
}
|
|
4612
|
+
learnings.push({ attempt, action: "write_test", result: `gravado: ${testFilePath} (${writtenFileSize} bytes)` });
|
|
4420
4613
|
learnings.push({ attempt, action: "run_tests", result: "executando..." });
|
|
4614
|
+
const runArg = fw === "playwright" ? path6.relative(PROJECT_ROOT6, testFilePath).replace(/\\/g, "/") : testFilePath;
|
|
4421
4615
|
const runResult = await new Promise((resolve) => {
|
|
4422
|
-
const child = spawn2("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run",
|
|
4616
|
+
const child = spawn2("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", runArg], {
|
|
4423
4617
|
cwd: PROJECT_ROOT6,
|
|
4424
4618
|
stdio: ["inherit", "pipe", "pipe"],
|
|
4425
4619
|
shell: process.platform === "win32"
|
|
@@ -4477,9 +4671,14 @@ ${runResult.output.slice(0, 500)}${learnedAppendix2}` }],
|
|
|
4477
4671
|
}
|
|
4478
4672
|
learnings.push({ attempt, action: "apply_fix", result: "aplicando corre\xE7\xE3o..." });
|
|
4479
4673
|
const fixedCode = explainResult.structuredContent.sugestaoCorrecao;
|
|
4674
|
+
if (!fixedCode || fixedCode.trim().length === 0) {
|
|
4675
|
+
learnings.push({ attempt, action: "apply_fix", result: "\u274C corre\xE7\xE3o vazia" });
|
|
4676
|
+
continue;
|
|
4677
|
+
}
|
|
4480
4678
|
testContent = fixedCode;
|
|
4481
4679
|
fs6.writeFileSync(testFilePath, testContent, "utf8");
|
|
4482
|
-
|
|
4680
|
+
const fixedFileSize = fs6.statSync(testFilePath).size;
|
|
4681
|
+
learnings.push({ attempt, action: "apply_fix", result: `corre\xE7\xE3o aplicada (${fixedFileSize} bytes)` });
|
|
4483
4682
|
if (flakyAnalysis.isLikelyFlaky) {
|
|
4484
4683
|
const inferredPattern = inferFailurePattern(runResult.output, fw);
|
|
4485
4684
|
const learningType = inferredPattern?.learningType || (flakyAnalysis.patterns[0]?.pattern === "selector" ? "selector_fix" : "timing_fix");
|