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 CHANGED
@@ -4,48 +4,127 @@
4
4
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18-green)](https://nodejs.org)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
6
 
7
- **Assistente de teste que aprende com falhas.** Reduz tempo de debug, elimina flaky e mantém seletores estáveis. Executa testes, analisa causas de falha, corrige automaticamente e aprende padrões que melhoram as próximas gerações. Integra ao Cursor, Cline, Windsurf ou Slack.
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
- **1 comando. Análise completa.**
35
+ *Um comando. Análise completa. Autocorreção. Aprendizado.*
14
36
 
15
- > Teste falhou? Em 30 segundos: o que aconteceu, por que e como corrigir. O mcp-lab-agent analisa causas, corrige e acumula conhecimento.
37
+ ### Principais resultados
16
38
 
17
- **Foco:** [Top 3 problemas de QA](docs/TOP3_QA_PROBLEMAS_E_ROADMAP.md)flaky, "por que falhou?", manutenção de seletores.
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
- ## O que é
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
- O **mcp-lab-agent** é um sistema de inteligência em qualidade de software não uma ferramenta de teste isolada. Ele entende o seu projeto, identifica frameworks (Cypress, Playwright, Jest, Appium, Robot, pytest e outros), gera testes com base em contexto e memória, executa, analisa falhas e aplica correções automaticamente. O valor central está no **learning**: cada correção bem-sucedida é salva e usada nas próximas gerações, aumentando a taxa de sucesso na primeira tentativa.
48
+ **💬 Slack Bot** Mencione o bot em qualquer canalele executa testes e posta o relatório. Funciona em ambiente corporativo (Socket Mode, sem ngrok). QA no fluxo da conversa.
24
49
 
25
- Com o **Learning Hub**, os aprendizados são centralizados e agregados entre projetos e — em deploy compartilhado — entre times e empresas, formando uma base de conhecimento em qualidade que escala além do repositório.
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 flakiness |
34
- | **Desenvolvedores** | "Por que falhou?", análise de arquivos e métodos, integração direta no IDE |
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
- | **Empresas** | Learning Hub centralizado, escala entre squads e organizações, CI/CD, Ollama (offline), Slack para QA via chat |
37
-
38
- ---
57
+ | **Times** | Learning Hub, Slack bot para QA no chat, CI/CD, Ollama (offline) |
39
58
 
40
- ## Comparação
59
+ ### Como é diferente
41
60
 
42
61
  | Outras ferramentas | mcp-lab-agent |
43
62
  |--------------------|---------------|
44
- | Só executam testes | Executa, analisa causa da falha e sugere correção |
45
- | Saída genérica "teste falhou" | Diagnóstico: "login falha 30% das vezes (timing)" |
46
- | Sem visão de risco | Identifica áreas sem testes e classifica risco (alto/médio/baixo) |
47
- | Sem memória entre execuções | Learning system: cada padrão de falha vira correção aplicada nas próximas gerações |
48
- | Uma ferramenta por tarefa | Sistema de inteligência: geração, execução, análise, relatórios, predição, learning |
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
- console.log(`\u2705 Teste gravado: ${testFilePath}`);
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", testFilePath], {
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 (simulada)...`);
1645
- console.log(`\u26A0\uFE0F Corre\xE7\xE3o autom\xE1tica ainda n\xE3o implementada nesta vers\xE3o CLI. Tentando novamente...`);
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: `Spec gerado (${specContent.length} chars). Use write_test para gravar.` }],
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
- learnings.push({ attempt, action: "write_test", result: `gravado: ${testFilePath}` });
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", testFilePath], {
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
- learnings.push({ attempt, action: "apply_fix", result: "corre\xE7\xE3o aplicada" });
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");