mcp-lab-agent 2.1.10 → 2.2.0
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 +125 -10
- package/dist/index.js.map +1 -1
- 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
|
@@ -1542,16 +1542,19 @@ async function handleAutoCommand() {
|
|
|
1542
1542
|
[Tentativa ${attempt}/${maxRetries}] Gerando teste...`);
|
|
1543
1543
|
const { provider, apiKey, baseUrl, model } = llm;
|
|
1544
1544
|
const memoryHints = memory.learnings?.filter((l) => l.success).slice(-10).map((l) => l.fix).join("\n") || "";
|
|
1545
|
+
const packageInfo = structure.packageJson || {};
|
|
1546
|
+
const isESM = packageInfo.type === "module";
|
|
1545
1547
|
const systemPrompt = `Voc\xEA \xE9 um engenheiro de QA especializado em ${fw}. Gere APENAS o c\xF3digo do spec, sem explica\xE7\xF5es.
|
|
1546
1548
|
${memoryHints ? `
|
|
1547
1549
|
Aprendizados anteriores (use como refer\xEAncia):
|
|
1548
1550
|
${memoryHints.slice(0, 1e3)}` : ""}
|
|
1551
|
+
${isESM ? "\nIMPORTANTE: Use sintaxe ESM (import/export), N\xC3O use require()." : ""}
|
|
1549
1552
|
Retorne SOMENTE o c\xF3digo, sem markdown.`;
|
|
1550
1553
|
const userPrompt = `Contexto:
|
|
1551
1554
|
${contextLines}
|
|
1552
1555
|
|
|
1553
1556
|
Gere teste para: ${cleanRequest}
|
|
1554
|
-
Framework: ${fw}`;
|
|
1557
|
+
Framework: ${fw}${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
|
|
1555
1558
|
try {
|
|
1556
1559
|
let specContent = "";
|
|
1557
1560
|
if (provider === "gemini") {
|
|
@@ -1565,6 +1568,9 @@ Framework: ${fw}`;
|
|
|
1565
1568
|
})
|
|
1566
1569
|
});
|
|
1567
1570
|
const data = await res.json();
|
|
1571
|
+
if (data.error) {
|
|
1572
|
+
throw new Error(`Gemini API Error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
1573
|
+
}
|
|
1568
1574
|
specContent = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1569
1575
|
} else {
|
|
1570
1576
|
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
@@ -1578,10 +1584,20 @@ Framework: ${fw}`;
|
|
|
1578
1584
|
})
|
|
1579
1585
|
});
|
|
1580
1586
|
const data = await res.json();
|
|
1587
|
+
if (data.error) {
|
|
1588
|
+
throw new Error(`API Error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
1589
|
+
}
|
|
1581
1590
|
specContent = data.choices?.[0]?.message?.content || "";
|
|
1582
1591
|
}
|
|
1592
|
+
console.log(`[DEBUG] Resposta do LLM recebida: ${specContent.length} caracteres`);
|
|
1593
|
+
if (!specContent || specContent.trim().length === 0) {
|
|
1594
|
+
throw new Error("LLM retornou conte\xFAdo vazio. Verifique sua API key e conex\xE3o.");
|
|
1595
|
+
}
|
|
1583
1596
|
specContent = specContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
1584
1597
|
testContent = specContent;
|
|
1598
|
+
if (!testContent || testContent.trim().length === 0) {
|
|
1599
|
+
throw new Error("Ap\xF3s parsing, o c\xF3digo ficou vazio. Resposta do LLM pode estar em formato inesperado.");
|
|
1600
|
+
}
|
|
1585
1601
|
if (!testFilePath) {
|
|
1586
1602
|
const fileName = cleanRequest.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 30);
|
|
1587
1603
|
const { ext, baseDir } = getExtensionAndBaseDir(fw, structure);
|
|
@@ -1590,11 +1606,16 @@ Framework: ${fw}`;
|
|
|
1590
1606
|
if (!fs5.existsSync(baseDir)) fs5.mkdirSync(baseDir, { recursive: true });
|
|
1591
1607
|
}
|
|
1592
1608
|
fs5.writeFileSync(testFilePath, testContent, "utf8");
|
|
1593
|
-
|
|
1609
|
+
const fileSize = fs5.statSync(testFilePath).size;
|
|
1610
|
+
if (fileSize === 0) {
|
|
1611
|
+
throw new Error("Arquivo gravado mas est\xE1 vazio. Problema na escrita do arquivo.");
|
|
1612
|
+
}
|
|
1613
|
+
console.log(`\u2705 Teste gravado: ${testFilePath} (${fileSize} bytes)`);
|
|
1594
1614
|
console.log(`
|
|
1595
1615
|
[Tentativa ${attempt}/${maxRetries}] Executando teste...`);
|
|
1616
|
+
const runArg = fw === "playwright" ? path5.relative(PROJECT_ROOT5, testFilePath).replace(/\\/g, "/") : testFilePath;
|
|
1596
1617
|
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",
|
|
1618
|
+
const child = spawn("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", runArg], {
|
|
1598
1619
|
cwd: PROJECT_ROOT5,
|
|
1599
1620
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1600
1621
|
shell: process.platform === "win32"
|
|
@@ -1641,8 +1662,59 @@ ${runResult.output.slice(0, 800)}
|
|
|
1641
1662
|
console.log(`\u26A0\uFE0F Flaky detectado (${flakyAnalysis.confidence.toFixed(2)}): ${flakyAnalysis.patterns.map((p) => p.pattern).join(", ")}`);
|
|
1642
1663
|
}
|
|
1643
1664
|
console.log(`
|
|
1644
|
-
[Tentativa ${attempt}/${maxRetries}] Aplicando corre\xE7\xE3o
|
|
1645
|
-
|
|
1665
|
+
[Tentativa ${attempt}/${maxRetries}] Aplicando corre\xE7\xE3o...`);
|
|
1666
|
+
try {
|
|
1667
|
+
const fixPrompt = `Voc\xEA \xE9 um engenheiro de QA. O teste falhou com o seguinte erro:
|
|
1668
|
+
|
|
1669
|
+
${runResult.output.slice(0, 1e3)}
|
|
1670
|
+
|
|
1671
|
+
C\xF3digo atual do teste:
|
|
1672
|
+
${testContent}
|
|
1673
|
+
|
|
1674
|
+
Analise o erro e corrija o teste. Considere:
|
|
1675
|
+
- Seletores podem estar errados (verifique se os elementos existem)
|
|
1676
|
+
- Pode precisar de waits (waitForSelector, waitForLoadState)
|
|
1677
|
+
- Rotas podem estar erradas (/buscar vs /busca)
|
|
1678
|
+
- Elementos podem ter nomes diferentes
|
|
1679
|
+
|
|
1680
|
+
Retorne APENAS o c\xF3digo corrigido, sem explica\xE7\xF5es.${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
|
|
1681
|
+
let fixedContent = "";
|
|
1682
|
+
if (provider === "gemini") {
|
|
1683
|
+
const url = `${baseUrl}/models/${model}:generateContent?key=${apiKey}`;
|
|
1684
|
+
const res = await fetch(url, {
|
|
1685
|
+
method: "POST",
|
|
1686
|
+
headers: { "Content-Type": "application/json" },
|
|
1687
|
+
body: JSON.stringify({
|
|
1688
|
+
contents: [{ parts: [{ text: fixPrompt }] }],
|
|
1689
|
+
generationConfig: { temperature: 0.3, maxOutputTokens: 4096 }
|
|
1690
|
+
})
|
|
1691
|
+
});
|
|
1692
|
+
const data = await res.json();
|
|
1693
|
+
fixedContent = data.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
1694
|
+
} else {
|
|
1695
|
+
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
1696
|
+
method: "POST",
|
|
1697
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1698
|
+
body: JSON.stringify({
|
|
1699
|
+
model,
|
|
1700
|
+
messages: [{ role: "user", content: fixPrompt }],
|
|
1701
|
+
temperature: 0.3,
|
|
1702
|
+
max_tokens: 4096
|
|
1703
|
+
})
|
|
1704
|
+
});
|
|
1705
|
+
const data = await res.json();
|
|
1706
|
+
fixedContent = data.choices?.[0]?.message?.content || "";
|
|
1707
|
+
}
|
|
1708
|
+
fixedContent = fixedContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
1709
|
+
if (fixedContent && fixedContent.length > 50) {
|
|
1710
|
+
testContent = fixedContent;
|
|
1711
|
+
console.log(`\u2705 Corre\xE7\xE3o gerada pelo LLM.`);
|
|
1712
|
+
} else {
|
|
1713
|
+
console.log(`\u26A0\uFE0F Corre\xE7\xE3o vazia, tentando novamente...`);
|
|
1714
|
+
}
|
|
1715
|
+
} catch (fixErr) {
|
|
1716
|
+
console.log(`\u26A0\uFE0F Erro ao gerar corre\xE7\xE3o: ${fixErr.message}. Tentando novamente...`);
|
|
1717
|
+
}
|
|
1646
1718
|
} catch (err) {
|
|
1647
1719
|
console.error(`
|
|
1648
1720
|
\u274C Erro na tentativa ${attempt}: ${err.message}
|
|
@@ -2394,8 +2466,18 @@ Framework alvo: ${fw}${referenceBlock}`;
|
|
|
2394
2466
|
}
|
|
2395
2467
|
specContent = specContent.replace(/^```(?:js|javascript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
2396
2468
|
const fileName = request.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 40);
|
|
2469
|
+
if (!specContent) {
|
|
2470
|
+
return {
|
|
2471
|
+
content: [{ type: "text", text: "Erro: LLM retornou conte\xFAdo vazio. Verifique API key (GROQ_API_KEY, GEMINI_API_KEY) e tente novamente." }],
|
|
2472
|
+
structuredContent: { ok: false, error: "Empty LLM response" }
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
const textWithCode = `Spec gerado (${specContent.length} chars). Use write_test para gravar com name="${fileName}" e content abaixo:
|
|
2476
|
+
|
|
2477
|
+
--- C\xF3digo (passe em content para write_test) ---
|
|
2478
|
+
${specContent}`;
|
|
2397
2479
|
return {
|
|
2398
|
-
content: [{ type: "text", text:
|
|
2480
|
+
content: [{ type: "text", text: textWithCode }],
|
|
2399
2481
|
structuredContent: {
|
|
2400
2482
|
ok: true,
|
|
2401
2483
|
specContent,
|
|
@@ -2477,6 +2559,12 @@ server.registerTool(
|
|
|
2477
2559
|
structuredContent: { ok: false, error: "No test framework" }
|
|
2478
2560
|
};
|
|
2479
2561
|
}
|
|
2562
|
+
if (!content || !String(content).trim()) {
|
|
2563
|
+
return {
|
|
2564
|
+
content: [{ type: "text", text: "Erro: content n\xE3o pode ser vazio. Chame generate_tests primeiro e passe o specContent retornado em content." }],
|
|
2565
|
+
structuredContent: { ok: false, error: "Empty content" }
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2480
2568
|
const { ext, baseDir } = getExtensionAndBaseDir2(fw, structure);
|
|
2481
2569
|
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
2570
|
const fileName = ext.startsWith("_") ? `${safeName}${ext}` : `${safeName}${ext}`;
|
|
@@ -4367,17 +4455,20 @@ server.registerTool(
|
|
|
4367
4455
|
learnings.push({ attempt, action: "generate_tests", result: "gerando..." });
|
|
4368
4456
|
const { provider, apiKey, baseUrl, model } = llm;
|
|
4369
4457
|
const memoryHints = memory.learnings?.filter((l) => l.fix).slice(-10).map((l) => l.fix).join("\n") || "";
|
|
4458
|
+
const packageInfo = structure.packageJson || {};
|
|
4459
|
+
const isESM = packageInfo.type === "module";
|
|
4370
4460
|
const systemPrompt = `Voc\xEA \xE9 um engenheiro de QA especializado em ${fw}. Gere APENAS o c\xF3digo do spec, sem explica\xE7\xF5es.
|
|
4371
4461
|
${UNIVERSAL_TEST_PRACTICES}
|
|
4372
4462
|
|
|
4373
4463
|
${memoryHints ? `Aprendizados anteriores (use como refer\xEAncia):
|
|
4374
4464
|
${memoryHints.slice(0, 1e3)}` : ""}
|
|
4465
|
+
${isESM ? "\nIMPORTANTE: Use sintaxe ESM (import/export), N\xC3O use require()." : ""}
|
|
4375
4466
|
Retorne SOMENTE o c\xF3digo, sem markdown.`;
|
|
4376
4467
|
const userPrompt = `Contexto:
|
|
4377
4468
|
${contextLines}
|
|
4378
4469
|
|
|
4379
4470
|
Gere teste para: ${request}
|
|
4380
|
-
Framework: ${fw}`;
|
|
4471
|
+
Framework: ${fw}${isESM ? "\nUse import { test, expect } from '@playwright/test';" : ""}`;
|
|
4381
4472
|
try {
|
|
4382
4473
|
let specContent = "";
|
|
4383
4474
|
if (provider === "gemini") {
|
|
@@ -4404,10 +4495,23 @@ Framework: ${fw}`;
|
|
|
4404
4495
|
})
|
|
4405
4496
|
});
|
|
4406
4497
|
const data = await res.json();
|
|
4498
|
+
if (data.error) {
|
|
4499
|
+
learnings.push({ attempt, action: "llm_call", result: `\u274C API error: ${data.error.message}` });
|
|
4500
|
+
throw new Error(`API Error: ${data.error.message || JSON.stringify(data.error)}`);
|
|
4501
|
+
}
|
|
4407
4502
|
specContent = data.choices?.[0]?.message?.content || "";
|
|
4408
4503
|
}
|
|
4504
|
+
if (!specContent || specContent.trim().length === 0) {
|
|
4505
|
+
learnings.push({ attempt, action: "generate_test", result: "\u274C LLM retornou vazio" });
|
|
4506
|
+
throw new Error("LLM retornou conte\xFAdo vazio. Verifique sua API key e conex\xE3o.");
|
|
4507
|
+
}
|
|
4508
|
+
learnings.push({ attempt, action: "generate_test", result: `\u2705 recebido ${specContent.length} chars` });
|
|
4409
4509
|
specContent = specContent.replace(/^```(?:js|javascript|typescript)?\n?/i, "").replace(/\n?```\s*$/i, "").trim();
|
|
4410
4510
|
testContent = specContent;
|
|
4511
|
+
if (!testContent || testContent.trim().length === 0) {
|
|
4512
|
+
learnings.push({ attempt, action: "parse_code", result: "\u274C c\xF3digo vazio ap\xF3s parsing" });
|
|
4513
|
+
throw new Error("Ap\xF3s parsing, o c\xF3digo ficou vazio. Resposta do LLM pode estar em formato inesperado.");
|
|
4514
|
+
}
|
|
4411
4515
|
if (!testFilePath) {
|
|
4412
4516
|
const fileName = request.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").slice(0, 30);
|
|
4413
4517
|
const { ext, baseDir } = getExtensionAndBaseDir2(fw, structure);
|
|
@@ -4416,10 +4520,16 @@ Framework: ${fw}`;
|
|
|
4416
4520
|
if (!fs6.existsSync(baseDir)) fs6.mkdirSync(baseDir, { recursive: true });
|
|
4417
4521
|
}
|
|
4418
4522
|
fs6.writeFileSync(testFilePath, testContent, "utf8");
|
|
4419
|
-
|
|
4523
|
+
const writtenFileSize = fs6.statSync(testFilePath).size;
|
|
4524
|
+
if (writtenFileSize === 0) {
|
|
4525
|
+
learnings.push({ attempt, action: "write_test", result: "\u274C arquivo vazio ap\xF3s gravar" });
|
|
4526
|
+
throw new Error("Arquivo gravado mas est\xE1 vazio. Problema na escrita do arquivo.");
|
|
4527
|
+
}
|
|
4528
|
+
learnings.push({ attempt, action: "write_test", result: `gravado: ${testFilePath} (${writtenFileSize} bytes)` });
|
|
4420
4529
|
learnings.push({ attempt, action: "run_tests", result: "executando..." });
|
|
4530
|
+
const runArg = fw === "playwright" ? path6.relative(PROJECT_ROOT6, testFilePath).replace(/\\/g, "/") : testFilePath;
|
|
4421
4531
|
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",
|
|
4532
|
+
const child = spawn2("npx", [fw === "cypress" ? "cypress" : fw === "playwright" ? "playwright" : fw, fw === "cypress" ? "run" : fw === "playwright" ? "test" : "run", runArg], {
|
|
4423
4533
|
cwd: PROJECT_ROOT6,
|
|
4424
4534
|
stdio: ["inherit", "pipe", "pipe"],
|
|
4425
4535
|
shell: process.platform === "win32"
|
|
@@ -4477,9 +4587,14 @@ ${runResult.output.slice(0, 500)}${learnedAppendix2}` }],
|
|
|
4477
4587
|
}
|
|
4478
4588
|
learnings.push({ attempt, action: "apply_fix", result: "aplicando corre\xE7\xE3o..." });
|
|
4479
4589
|
const fixedCode = explainResult.structuredContent.sugestaoCorrecao;
|
|
4590
|
+
if (!fixedCode || fixedCode.trim().length === 0) {
|
|
4591
|
+
learnings.push({ attempt, action: "apply_fix", result: "\u274C corre\xE7\xE3o vazia" });
|
|
4592
|
+
continue;
|
|
4593
|
+
}
|
|
4480
4594
|
testContent = fixedCode;
|
|
4481
4595
|
fs6.writeFileSync(testFilePath, testContent, "utf8");
|
|
4482
|
-
|
|
4596
|
+
const fixedFileSize = fs6.statSync(testFilePath).size;
|
|
4597
|
+
learnings.push({ attempt, action: "apply_fix", result: `corre\xE7\xE3o aplicada (${fixedFileSize} bytes)` });
|
|
4483
4598
|
if (flakyAnalysis.isLikelyFlaky) {
|
|
4484
4599
|
const inferredPattern = inferFailurePattern(runResult.output, fw);
|
|
4485
4600
|
const learningType = inferredPattern?.learningType || (flakyAnalysis.patterns[0]?.pattern === "selector" ? "selector_fix" : "timing_fix");
|