@silasfmartins/testhub 1.0.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.
Files changed (296) hide show
  1. package/.github/copilot-instructions.md +520 -0
  2. package/biome.json +37 -0
  3. package/dist/index.d.ts +45 -0
  4. package/dist/index.js +169 -0
  5. package/dist/scripts/consumer-postinstall.d.ts +15 -0
  6. package/dist/scripts/consumer-postinstall.js +785 -0
  7. package/dist/scripts/generate-docs.d.ts +16 -0
  8. package/dist/scripts/generate-docs.js +1363 -0
  9. package/dist/scripts/generate-index.d.ts +2 -0
  10. package/dist/scripts/generate-index.js +314 -0
  11. package/dist/scripts/init-api.d.ts +2 -0
  12. package/dist/scripts/init-api.js +525 -0
  13. package/dist/scripts/init-banco.d.ts +2 -0
  14. package/dist/scripts/init-banco.js +347 -0
  15. package/dist/scripts/init-frontend.d.ts +2 -0
  16. package/dist/scripts/init-frontend.js +627 -0
  17. package/dist/scripts/init-mobile.d.ts +2 -0
  18. package/dist/scripts/init-mobile.js +481 -0
  19. package/dist/scripts/init-scenarios.d.ts +2 -0
  20. package/dist/scripts/init-scenarios.js +846 -0
  21. package/dist/scripts/init-ssh.d.ts +2 -0
  22. package/dist/scripts/init-ssh.js +639 -0
  23. package/dist/scripts/package-versions.d.ts +57 -0
  24. package/dist/scripts/package-versions.js +768 -0
  25. package/dist/scripts/postinstall.d.ts +1 -0
  26. package/dist/scripts/postinstall.js +527 -0
  27. package/dist/scripts/robust-build.d.ts +7 -0
  28. package/dist/scripts/robust-build.js +88 -0
  29. package/dist/scripts/setup-local-packages.d.ts +31 -0
  30. package/dist/scripts/setup-local-packages.js +237 -0
  31. package/dist/scripts/smart-override.d.ts +2 -0
  32. package/dist/scripts/smart-override.js +1360 -0
  33. package/dist/scripts/sync-configs.d.ts +27 -0
  34. package/dist/scripts/sync-configs.js +248 -0
  35. package/dist/scripts/test-biome-parse.d.ts +5 -0
  36. package/dist/scripts/test-biome-parse.js +84 -0
  37. package/dist/scripts/ultracite-setup.d.ts +4 -0
  38. package/dist/scripts/ultracite-setup.js +310 -0
  39. package/dist/scripts/update-all-init-scripts.d.ts +2 -0
  40. package/dist/scripts/update-all-init-scripts.js +52 -0
  41. package/dist/scripts/update-biome-schema.d.ts +15 -0
  42. package/dist/scripts/update-biome-schema.js +124 -0
  43. package/dist/src/AutoCoreFacade.d.ts +145 -0
  44. package/dist/src/AutoCoreFacade.js +217 -0
  45. package/dist/src/api/ApiActions.d.ts +297 -0
  46. package/dist/src/api/ApiActions.js +1905 -0
  47. package/dist/src/api/Certificate.d.ts +60 -0
  48. package/dist/src/api/Certificate.js +79 -0
  49. package/dist/src/api/JsonResponse.d.ts +116 -0
  50. package/dist/src/api/JsonResponse.js +206 -0
  51. package/dist/src/appium/DeviceFarmViewer.d.ts +79 -0
  52. package/dist/src/appium/DeviceFarmViewer.js +1083 -0
  53. package/dist/src/appium/MobileActions.d.ts +347 -0
  54. package/dist/src/appium/MobileActions.js +1632 -0
  55. package/dist/src/appium/MobileConnection.d.ts +160 -0
  56. package/dist/src/appium/MobileConnection.js +772 -0
  57. package/dist/src/config/envLoader.d.ts +123 -0
  58. package/dist/src/config/envLoader.js +361 -0
  59. package/dist/src/config/jest-safe-setup.d.ts +19 -0
  60. package/dist/src/config/jest-safe-setup.js +369 -0
  61. package/dist/src/config/timeouts.d.ts +32 -0
  62. package/dist/src/config/timeouts.js +38 -0
  63. package/dist/src/desktop/DesktopActions.d.ts +46 -0
  64. package/dist/src/desktop/DesktopActions.js +398 -0
  65. package/dist/src/desktop/DesktopConnection.d.ts +32 -0
  66. package/dist/src/desktop/DesktopConnection.js +84 -0
  67. package/dist/src/domain/entities/TestExecution.d.ts +117 -0
  68. package/dist/src/domain/entities/TestExecution.js +150 -0
  69. package/dist/src/domain/entities/TestReport.d.ts +114 -0
  70. package/dist/src/domain/entities/TestReport.js +179 -0
  71. package/dist/src/domain/repositories/ITestRepository.d.ts +196 -0
  72. package/dist/src/domain/repositories/ITestRepository.js +14 -0
  73. package/dist/src/domain/schemas/ValidationSchemas.d.ts +159 -0
  74. package/dist/src/domain/schemas/ValidationSchemas.js +181 -0
  75. package/dist/src/functions/errors/BaseError.d.ts +78 -0
  76. package/dist/src/functions/errors/BaseError.js +245 -0
  77. package/dist/src/functions/errors/ConfigurationError.d.ts +16 -0
  78. package/dist/src/functions/errors/ConfigurationError.js +48 -0
  79. package/dist/src/functions/errors/ErrorCatalog.d.ts +148 -0
  80. package/dist/src/functions/errors/ErrorCatalog.js +157 -0
  81. package/dist/src/functions/errors/GlobalErrorHandler.d.ts +101 -0
  82. package/dist/src/functions/errors/GlobalErrorHandler.js +281 -0
  83. package/dist/src/functions/errors/IntegrationError.d.ts +17 -0
  84. package/dist/src/functions/errors/IntegrationError.js +51 -0
  85. package/dist/src/functions/errors/SecurityError.d.ts +14 -0
  86. package/dist/src/functions/errors/SecurityError.js +42 -0
  87. package/dist/src/functions/errors/SystemError.d.ts +12 -0
  88. package/dist/src/functions/errors/SystemError.js +36 -0
  89. package/dist/src/functions/errors/ValidationError.d.ts +14 -0
  90. package/dist/src/functions/errors/ValidationError.js +61 -0
  91. package/dist/src/functions/errors/index.d.ts +12 -0
  92. package/dist/src/functions/errors/index.js +13 -0
  93. package/dist/src/global-setup.d.ts +1 -0
  94. package/dist/src/global-setup.js +1037 -0
  95. package/dist/src/helpers/BancoActions.d.ts +188 -0
  96. package/dist/src/helpers/BancoActions.js +581 -0
  97. package/dist/src/helpers/EnviromentHelper.d.ts +17 -0
  98. package/dist/src/helpers/EnviromentHelper.js +66 -0
  99. package/dist/src/helpers/ParallelExecutionHelper.d.ts +183 -0
  100. package/dist/src/helpers/ParallelExecutionHelper.js +375 -0
  101. package/dist/src/helpers/SyncSignal.d.ts +15 -0
  102. package/dist/src/helpers/SyncSignal.js +44 -0
  103. package/dist/src/hubdocs/CategoryDetector.d.ts +83 -0
  104. package/dist/src/hubdocs/CategoryDetector.js +401 -0
  105. package/dist/src/hubdocs/DirectStatementInterceptor.d.ts +54 -0
  106. package/dist/src/hubdocs/DirectStatementInterceptor.js +243 -0
  107. package/dist/src/hubdocs/ExecutionTracker.d.ts +107 -0
  108. package/dist/src/hubdocs/ExecutionTracker.js +702 -0
  109. package/dist/src/hubdocs/HubDocs.d.ts +395 -0
  110. package/dist/src/hubdocs/HubDocs.js +3586 -0
  111. package/dist/src/hubdocs/StatementMethodFilter.d.ts +71 -0
  112. package/dist/src/hubdocs/StatementMethodFilter.js +618 -0
  113. package/dist/src/hubdocs/StatementTracker.d.ts +417 -0
  114. package/dist/src/hubdocs/StatementTracker.js +2419 -0
  115. package/dist/src/hubdocs/SwaggerGenerator.d.ts +59 -0
  116. package/dist/src/hubdocs/SwaggerGenerator.js +405 -0
  117. package/dist/src/hubdocs/index.d.ts +9 -0
  118. package/dist/src/hubdocs/index.js +9 -0
  119. package/dist/src/hubdocs/types.d.ts +114 -0
  120. package/dist/src/hubdocs/types.js +5 -0
  121. package/dist/src/infrastructure/DependencyContainer.d.ts +142 -0
  122. package/dist/src/infrastructure/DependencyContainer.js +250 -0
  123. package/dist/src/infrastructure/adapters/AppiumAdapter.d.ts +168 -0
  124. package/dist/src/infrastructure/adapters/AppiumAdapter.js +468 -0
  125. package/dist/src/infrastructure/adapters/OracleAdapter.d.ts +150 -0
  126. package/dist/src/infrastructure/adapters/OracleAdapter.js +388 -0
  127. package/dist/src/infrastructure/adapters/PlaywrightAdapter.d.ts +192 -0
  128. package/dist/src/infrastructure/adapters/PlaywrightAdapter.js +382 -0
  129. package/dist/src/infrastructure/adapters/SSHAdapter.d.ts +141 -0
  130. package/dist/src/infrastructure/adapters/SSHAdapter.js +428 -0
  131. package/dist/src/interfaces.d.ts +501 -0
  132. package/dist/src/interfaces.js +25 -0
  133. package/dist/src/internal/fakes/__fake-actions__.d.ts +17 -0
  134. package/dist/src/internal/fakes/__fake-actions__.js +21 -0
  135. package/dist/src/internal/fakes/__forbidden__.d.ts +10 -0
  136. package/dist/src/internal/fakes/__forbidden__.js +18 -0
  137. package/dist/src/internal/fakes/__honeypot__.d.ts +15 -0
  138. package/dist/src/internal/fakes/__honeypot__.js +24 -0
  139. package/dist/src/octane/OctaneReporter.d.ts +13 -0
  140. package/dist/src/octane/OctaneReporter.js +61 -0
  141. package/dist/src/playwright/CryptoActions.d.ts +20 -0
  142. package/dist/src/playwright/CryptoActions.js +75 -0
  143. package/dist/src/playwright/EnhancedWebActions.d.ts +7 -0
  144. package/dist/src/playwright/EnhancedWebActions.js +65 -0
  145. package/dist/src/playwright/WebActions.d.ts +1599 -0
  146. package/dist/src/playwright/WebActions.js +11788 -0
  147. package/dist/src/playwright/actions/ActionTimeline.d.ts +36 -0
  148. package/dist/src/playwright/actions/ActionTimeline.js +101 -0
  149. package/dist/src/playwright/actions/RecoveryQueue.d.ts +82 -0
  150. package/dist/src/playwright/actions/RecoveryQueue.js +130 -0
  151. package/dist/src/playwright/actions/SelectorCache.d.ts +53 -0
  152. package/dist/src/playwright/actions/SelectorCache.js +96 -0
  153. package/dist/src/playwright/actions/index.d.ts +13 -0
  154. package/dist/src/playwright/actions/index.js +14 -0
  155. package/dist/src/playwright/actions/types.d.ts +147 -0
  156. package/dist/src/playwright/actions/types.js +5 -0
  157. package/dist/src/playwright/fixtures.d.ts +112 -0
  158. package/dist/src/playwright/fixtures.js +718 -0
  159. package/dist/src/playwright/network-logs-reporter.d.ts +7 -0
  160. package/dist/src/playwright/network-logs-reporter.js +66 -0
  161. package/dist/src/playwright/registerRecoveryWrappers.d.ts +1 -0
  162. package/dist/src/playwright/registerRecoveryWrappers.js +54 -0
  163. package/dist/src/security/BuildSecurity.d.ts +12 -0
  164. package/dist/src/security/BuildSecurity.js +138 -0
  165. package/dist/src/security/EulaProtection.d.ts +70 -0
  166. package/dist/src/security/EulaProtection.js +155 -0
  167. package/dist/src/security/HoneypotManager.d.ts +46 -0
  168. package/dist/src/security/HoneypotManager.js +234 -0
  169. package/dist/src/security/KeysManager.d.ts +36 -0
  170. package/dist/src/security/KeysManager.js +158 -0
  171. package/dist/src/security/ProofOfWorkIntegration.d.ts +64 -0
  172. package/dist/src/security/ProofOfWorkIntegration.js +206 -0
  173. package/dist/src/security/SecurityValidation.d.ts +21 -0
  174. package/dist/src/security/SecurityValidation.js +163 -0
  175. package/dist/src/security/SourceMapProtection.d.ts +55 -0
  176. package/dist/src/security/SourceMapProtection.js +220 -0
  177. package/dist/src/security/protector.d.ts +1 -0
  178. package/dist/src/security/protector.js +97 -0
  179. package/dist/src/ssh/SSHActions.d.ts +262 -0
  180. package/dist/src/ssh/SSHActions.js +790 -0
  181. package/dist/src/ssh/SSHClient.d.ts +99 -0
  182. package/dist/src/ssh/SSHClient.js +409 -0
  183. package/dist/src/statements/BaseStatement.d.ts +38 -0
  184. package/dist/src/statements/BaseStatement.js +78 -0
  185. package/dist/src/testContext/AuthStateManager.d.ts +93 -0
  186. package/dist/src/testContext/AuthStateManager.js +256 -0
  187. package/dist/src/testContext/CoverageManager.d.ts +198 -0
  188. package/dist/src/testContext/CoverageManager.js +917 -0
  189. package/dist/src/testContext/TestAnnotations.d.ts +476 -0
  190. package/dist/src/testContext/TestAnnotations.js +2647 -0
  191. package/dist/src/testContext/TestContext.d.ts +138 -0
  192. package/dist/src/testContext/TestContext.js +369 -0
  193. package/dist/src/testContext/UnifiedHtmlGenerator.d.ts +7 -0
  194. package/dist/src/testContext/UnifiedHtmlGenerator.js +264 -0
  195. package/dist/src/testContext/UnifiedReportManager.d.ts +211 -0
  196. package/dist/src/testContext/UnifiedReportManager.js +1206 -0
  197. package/dist/src/testhub/DynamicConfigManager.d.ts +121 -0
  198. package/dist/src/testhub/DynamicConfigManager.js +320 -0
  199. package/dist/src/testhub/SystemsManager.d.ts +119 -0
  200. package/dist/src/testhub/SystemsManager.js +365 -0
  201. package/dist/src/testhub/TestHubClient.d.ts +335 -0
  202. package/dist/src/testhub/TestHubClient.js +1215 -0
  203. package/dist/src/testhub/TestHubReporter.d.ts +62 -0
  204. package/dist/src/testhub/TestHubReporter.js +576 -0
  205. package/dist/src/testhub/TestHubVars.d.ts +116 -0
  206. package/dist/src/testhub/TestHubVars.js +273 -0
  207. package/dist/src/utils/ActionInterceptor.d.ts +59 -0
  208. package/dist/src/utils/ActionInterceptor.js +741 -0
  209. package/dist/src/utils/ArtifactsCompressor.d.ts +43 -0
  210. package/dist/src/utils/ArtifactsCompressor.js +181 -0
  211. package/dist/src/utils/AutoLogsFinal.d.ts +47 -0
  212. package/dist/src/utils/AutoLogsFinal.js +148 -0
  213. package/dist/src/utils/CodeGenSession.d.ts +114 -0
  214. package/dist/src/utils/CodeGenSession.js +264 -0
  215. package/dist/src/utils/ConfigLogger.d.ts +133 -0
  216. package/dist/src/utils/ConfigLogger.js +611 -0
  217. package/dist/src/utils/CustomReporter.d.ts +22 -0
  218. package/dist/src/utils/CustomReporter.js +352 -0
  219. package/dist/src/utils/DataStore.d.ts +171 -0
  220. package/dist/src/utils/DataStore.js +484 -0
  221. package/dist/src/utils/DatabaseInterceptor.d.ts +19 -0
  222. package/dist/src/utils/DatabaseInterceptor.js +295 -0
  223. package/dist/src/utils/DateHelper.d.ts +16 -0
  224. package/dist/src/utils/DateHelper.js +120 -0
  225. package/dist/src/utils/DateValidator.d.ts +4 -0
  226. package/dist/src/utils/DateValidator.js +51 -0
  227. package/dist/src/utils/DocumentGenerator.d.ts +35 -0
  228. package/dist/src/utils/DocumentGenerator.js +129 -0
  229. package/dist/src/utils/EvidenceCapture.d.ts +90 -0
  230. package/dist/src/utils/EvidenceCapture.js +600 -0
  231. package/dist/src/utils/EvidenceReportGenerator.d.ts +70 -0
  232. package/dist/src/utils/EvidenceReportGenerator.js +799 -0
  233. package/dist/src/utils/FrameManagementUtil.d.ts +42 -0
  234. package/dist/src/utils/FrameManagementUtil.js +75 -0
  235. package/dist/src/utils/GlobalStatementsInterceptor.d.ts +1 -0
  236. package/dist/src/utils/GlobalStatementsInterceptor.js +1 -0
  237. package/dist/src/utils/HTMLTemplate.d.ts +1 -0
  238. package/dist/src/utils/HTMLTemplate.js +1034 -0
  239. package/dist/src/utils/InterceptacaoMagica.d.ts +23 -0
  240. package/dist/src/utils/InterceptacaoMagica.js +365 -0
  241. package/dist/src/utils/LogSanitizer.d.ts +35 -0
  242. package/dist/src/utils/LogSanitizer.js +110 -0
  243. package/dist/src/utils/Logger.d.ts +65 -0
  244. package/dist/src/utils/Logger.js +284 -0
  245. package/dist/src/utils/McpLocalClient.d.ts +141 -0
  246. package/dist/src/utils/McpLocalClient.js +871 -0
  247. package/dist/src/utils/PDFEvidenceGenerator.d.ts +20 -0
  248. package/dist/src/utils/PDFEvidenceGenerator.js +156 -0
  249. package/dist/src/utils/SpecFileAnalyzer.d.ts +35 -0
  250. package/dist/src/utils/SpecFileAnalyzer.js +209 -0
  251. package/dist/src/utils/StatementInterceptor.d.ts +18 -0
  252. package/dist/src/utils/StatementInterceptor.js +87 -0
  253. package/dist/src/utils/StatementLogger.d.ts +33 -0
  254. package/dist/src/utils/StatementLogger.js +113 -0
  255. package/dist/src/utils/StatementsInterceptor.d.ts +1 -0
  256. package/dist/src/utils/StatementsInterceptor.js +1 -0
  257. package/dist/src/utils/TeamsFlushHook.d.ts +17 -0
  258. package/dist/src/utils/TeamsFlushHook.js +168 -0
  259. package/dist/src/utils/TerminalLogCapture.d.ts +158 -0
  260. package/dist/src/utils/TerminalLogCapture.js +531 -0
  261. package/dist/src/utils/TestMethodLogger.d.ts +70 -0
  262. package/dist/src/utils/TestMethodLogger.js +95 -0
  263. package/dist/src/utils/UnifiedTeardown.d.ts +4 -0
  264. package/dist/src/utils/UnifiedTeardown.js +400 -0
  265. package/dist/src/utils/XPathCatalog.d.ts +152 -0
  266. package/dist/src/utils/XPathCatalog.js +350 -0
  267. package/dist/src/utils/generators.d.ts +90 -0
  268. package/dist/src/utils/generators.js +167 -0
  269. package/dist/src/utils/testRecovery/ResilientPlaywright.d.ts +152 -0
  270. package/dist/src/utils/testRecovery/ResilientPlaywright.js +715 -0
  271. package/dist/src/utils/testRecovery/TestRecoveryClient.d.ts +801 -0
  272. package/dist/src/utils/testRecovery/TestRecoveryClient.js +1415 -0
  273. package/dist/src/utils/testRecovery/autoFixCode.d.ts +65 -0
  274. package/dist/src/utils/testRecovery/autoFixCode.js +32 -0
  275. package/dist/vitest.config.d.ts +2 -0
  276. package/dist/vitest.config.js +59 -0
  277. package/dist/wdio.conf.d.ts +1 -0
  278. package/dist/wdio.conf.js +420 -0
  279. package/package.json +137 -0
  280. package/protect-loader.mjs +643 -0
  281. package/scripts/consumer-postinstall.ts +975 -0
  282. package/scripts/generate-index.ts +343 -0
  283. package/scripts/init-api.ts +613 -0
  284. package/scripts/init-banco.ts +437 -0
  285. package/scripts/init-frontend.ts +727 -0
  286. package/scripts/init-mobile.ts +558 -0
  287. package/scripts/init-scenarios.ts +925 -0
  288. package/scripts/init-ssh.ts +734 -0
  289. package/scripts/package-versions.ts +978 -0
  290. package/scripts/postinstall.ts +605 -0
  291. package/scripts/smart-override.ts +1675 -0
  292. package/scripts/sync-configs.ts +302 -0
  293. package/scripts/ultracite-setup.ts +370 -0
  294. package/src/types/globals.d.ts +48 -0
  295. package/tsconfig.json +29 -0
  296. package/types/autocore-sync-signal.d.ts +10 -0
@@ -0,0 +1,2419 @@
1
+ /**
2
+ * 🎯 INTERCEPTADOR AUTOMÁTICO DE STATEMENTS
3
+ * Sistema 100% automático - detecta tudo pelos logs existentes
4
+ * Integrado com ExecutionTracker e relatórios HTML
5
+ * @version 3.1.0 - PERSISTÊNCIA AUTOMÁTICA ANTI-PERDA
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { Logger } from '../utils/Logger.js';
10
+ /**
11
+ * 📊 RASTREADOR AUTOMÁTICO DE CTs
12
+ * ZERO CONFIGURAÇÃO - DETECÇÃO AUTOMÁTICA + INTEGRAÇÃO COM RELATÓRIOS
13
+ */
14
+ export class StatementTracker {
15
+ /**
16
+ * 🛑 Flag global para indicar que algum CT falhou com stopOnFailure ativo
17
+ * Quando true, todos os próximos CTs devem ser pulados
18
+ */
19
+ static hasFailedWithStopOnFailure = false;
20
+ /**
21
+ * 🔒 Controle de duplicação de logs - evita logs repetidos
22
+ */
23
+ static lastLoggedCT = null;
24
+ static lastLoggedRequest = null;
25
+ static lastRequestTimestamp = 0;
26
+ static ctLogStreams = new Map();
27
+ /**
28
+ * ✅ CORRIGIDO: Criar CT automaticamente se não existir - VERSÃO ROBUSTA
29
+ * Um CT é criado por test(), não por statement!
30
+ * 🔧 FIX: Verificar se já existe CT com o MESMO NOME, não apenas status='running'
31
+ * ✅ AGORA COM FALLBACKS ROBUSTOS PARA PROJETOS CONSUMIDORES
32
+ */
33
+ static ensureCTCreated() {
34
+ const testName = StatementTracker.getCurrentTestName();
35
+ const testData = StatementTracker.executions.get(testName);
36
+ // ✅ PROTEÇÃO CRÍTICA #1: Se já existe um CT "running", NÃO criar outro
37
+ // Isso evita criar CTs duplicados quando statements executam
38
+ if (testData && testData.cts.length > 0) {
39
+ const runningCT = testData.cts.find((ct) => ct.status === 'running');
40
+ if (runningCT) {
41
+ // CT running já existe, não fazer nada
42
+ return;
43
+ }
44
+ }
45
+ // Pegar nome do CT atual (test()) com fallbacks robustos
46
+ let ctName = StatementTracker.getCurrentCTName();
47
+ // 🆘 FALLBACK 1: Se não conseguiu pegar CT do TestContext, usar teste como CT
48
+ if (!ctName) {
49
+ ctName = testName;
50
+ }
51
+ // 🆘 FALLBACK 2: Se o ctName ainda for vago, criar um nome único
52
+ if (!ctName || ctName === 'Unknown Test') {
53
+ ctName = `CT_${Date.now()}`; // CT único baseado em timestamp
54
+ }
55
+ // 🆘 FALLBACK 3: Se ctName é igual ao testName, criar um CT específico
56
+ if (ctName === testName) {
57
+ ctName = `${testName}_CT_Principal`;
58
+ }
59
+ // ✅ PROTEÇÃO #2: Verificar se já existe CT com este NOME, não apenas status
60
+ // Um test() deve ter apenas 1 CT, mesmo que falhe no meio
61
+ if (testData && testData.cts.length > 0 && ctName) {
62
+ const existingCT = testData.cts.find((ct) => ct.name === ctName);
63
+ if (existingCT) {
64
+ // CT já existe, não criar outro!
65
+ return;
66
+ }
67
+ }
68
+ if (ctName) {
69
+ StatementTracker.startCT(ctName);
70
+ StatementTracker.ctAutoCreated = true;
71
+ }
72
+ else {
73
+ // 🚨 FORÇA CRIAÇÃO DE CT COMO ÚLTIMO RECURSO
74
+ const forcedCTName = `CT_FORCED_${Date.now()}`;
75
+ StatementTracker.startCT(forcedCTName);
76
+ StatementTracker.ctAutoCreated = true;
77
+ }
78
+ }
79
+ /**
80
+ * 🎯 MÉTODO AUXILIAR: Detectar CT via análise de contexto de execução
81
+ * Para projetos consumidores onde TestContext pode não estar disponível
82
+ */
83
+ static detectCTFromExecutionContext() {
84
+ try {
85
+ // Capturar stack trace atual
86
+ const error = new Error();
87
+ const stack = error.stack;
88
+ if (!stack)
89
+ return null;
90
+ // Padrões comuns de nomes de CT em projetos
91
+ const ctPatterns = [
92
+ // Padrões específicos
93
+ /CT\d+[_\s-].*?(?=\s|"|'|`|\n|$)/gi,
94
+ /test\(["`']([^"`']+)["`']/gi,
95
+ /describe\(["`'].*?["`'].*?test\(["`']([^"`']+)["`']/gi,
96
+ // Padrões de método que executam testes
97
+ /(?:executar|execute|run|test)([A-Z][a-zA-Z0-9_]+)/gi,
98
+ // Padrões SSH específicos
99
+ /(?:Consumo|Voz|Dados|SMS|Internet|Teste)[_\s-].*?(?=\s|"|'|`|\n|$)/gi,
100
+ ];
101
+ for (const pattern of ctPatterns) {
102
+ pattern.lastIndex = 0; // Reset regex
103
+ const matches = Array.from(stack.matchAll(pattern));
104
+ for (const match of matches) {
105
+ const potentialCT = match[1] || match[0];
106
+ if (potentialCT &&
107
+ potentialCT.length > 3 &&
108
+ potentialCT.length < 100) {
109
+ const cleanCT = potentialCT.trim().replace(/["`']/g, '');
110
+ return cleanCT;
111
+ }
112
+ }
113
+ }
114
+ // Fallback: procurar por qualquer string que pareça um nome de teste
115
+ const lines = stack.split('\n');
116
+ for (const line of lines) {
117
+ if (line.includes('.spec.ts') || line.includes('test')) {
118
+ // Extrair nome do arquivo como possível CT
119
+ const fileMatch = line.match(/([^/\\]+)\.spec\.ts/);
120
+ if (fileMatch) {
121
+ const fileName = fileMatch[1];
122
+ return fileName;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ catch (_error) {
128
+ // Silenciosamente ignorar erros
129
+ }
130
+ return null;
131
+ }
132
+ static getCTStreamKey(testName, ctName) {
133
+ const safeTest = testName || 'Unknown Test';
134
+ const safeCT = ctName || 'Unknown CT';
135
+ return `${safeTest}::${safeCT}`;
136
+ }
137
+ static startCTLogStream(testName, ctName) {
138
+ try {
139
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
140
+ if (!TerminalLogCapture ||
141
+ typeof TerminalLogCapture.addListener !== 'function') {
142
+ return;
143
+ }
144
+ const key = StatementTracker.getCTStreamKey(testName, ctName);
145
+ if (StatementTracker.ctLogStreams.has(key)) {
146
+ return;
147
+ }
148
+ const listenerId = `${key}-${Date.now()}-${Math.random()
149
+ .toString(36)
150
+ .slice(2)}`;
151
+ const buffer = [];
152
+ const listener = (log) => {
153
+ if (!log || !log.message) {
154
+ return;
155
+ }
156
+ const level = (log.level || 'LOG').toUpperCase();
157
+ buffer.push(`[${level}] ${log.message}`);
158
+ };
159
+ TerminalLogCapture.addListener(listenerId, listener);
160
+ StatementTracker.ctLogStreams.set(key, {
161
+ listenerId,
162
+ logs: buffer,
163
+ });
164
+ }
165
+ catch {
166
+ // Ignorar se captura em tempo real não estiver disponível
167
+ }
168
+ }
169
+ static stopCTLogStream(testName, ctName) {
170
+ const key = StatementTracker.getCTStreamKey(testName, ctName);
171
+ const streamInfo = StatementTracker.ctLogStreams.get(key);
172
+ if (!streamInfo) {
173
+ return null;
174
+ }
175
+ StatementTracker.ctLogStreams.delete(key);
176
+ try {
177
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
178
+ if (TerminalLogCapture &&
179
+ typeof TerminalLogCapture.removeListener === 'function') {
180
+ TerminalLogCapture.removeListener(streamInfo.listenerId);
181
+ }
182
+ }
183
+ catch {
184
+ // Ignorar erros ao remover listener
185
+ }
186
+ return streamInfo.logs.length > 0 ? [...streamInfo.logs] : null;
187
+ }
188
+ /**
189
+ * 🚫 PADRÕES DE LOGS INTERNOS QUE NÃO DEVEM SER ENVIADOS PARA O TESTHUB
190
+ * Estes são logs do próprio framework, não logs úteis do teste
191
+ */
192
+ static INTERNAL_LOG_PATTERNS = [
193
+ // Logs do próprio StatementTracker
194
+ /^\[CT-LOGS\]/,
195
+ /^📋 \[CT-LOGS\]/,
196
+ /^\[forceLoadFromFile\]/,
197
+ /^🔄 \[forceLoadFromFile\]/,
198
+ /^Carregando dados de \d+ arquivos/,
199
+ /^📁 Arquivos de logs encontrados:/,
200
+ // Logs aninhados (já processados)
201
+ /^\s*\[LOG\]/,
202
+ /^\s{2,}\[LOG\]/,
203
+ // Logs de debug internos
204
+ /^🔍 Analisando \d+ candidatos/,
205
+ /^❌ Erro ao ler/,
206
+ /^❌ Erro ao listar/,
207
+ // Logs de sistema do Playwright
208
+ /^\s*\d+\s+tests\\/,
209
+ /^\s*\d+\s+C:\\/,
210
+ /^ℹ️\s+\d+\s+C:\\/,
211
+ /^ℹ️\s+\d+\s+tests\\/,
212
+ // Logs de registro de teste (duplicados)
213
+ /^🏷️\s+Teste registrado:/,
214
+ // Logs do dotenv
215
+ /^\[dotenv@/,
216
+ // Logs de stop-on-failure (contexto, não conteúdo)
217
+ /^🛑 \[STOP-ON-FAILURE\] ATIVADO/,
218
+ // Logs de AutoIntercept
219
+ /^🔄 \[AutoIntercept\]/,
220
+ ];
221
+ /**
222
+ * 🔍 Verifica se um log é interno do framework e deve ser filtrado
223
+ */
224
+ static isInternalLog(message) {
225
+ if (!message || typeof message !== 'string')
226
+ return true;
227
+ const trimmed = message.trim();
228
+ // Filtrar logs vazios
229
+ if (trimmed.length === 0)
230
+ return true;
231
+ // Filtrar logs que começam com [LOG] (já aninhados)
232
+ if (trimmed.startsWith('[LOG]'))
233
+ return true;
234
+ // Verificar contra padrões internos
235
+ for (const pattern of StatementTracker.INTERNAL_LOG_PATTERNS) {
236
+ if (pattern.test(trimmed))
237
+ return true;
238
+ }
239
+ return false;
240
+ }
241
+ static hydrateCTLogs(ct, testName, ctNameOverride) {
242
+ const ctName = ctNameOverride || ct.name || 'Unknown CT';
243
+ // 🔧 ROBUSTO: Estratégia que funciona para single E multi-worker
244
+ const currentGlobalIndex = StatementTracker.getCurrentGlobalLogIndex();
245
+ const startIndex = Math.max(0, ct.logStartIndex ?? 0);
246
+ // 🆕 Converter timestamps para comparação
247
+ const ctStartTime = ct.startTime;
248
+ const ctEndTime = ct.endTime || Date.now();
249
+ // ========================================
250
+ // 🎯 ESTRATÉGIA UNIFICADA (UNION STRATEGY)
251
+ // Combina logs por Nome do CT E por Timestamp para garantir completude
252
+ // ========================================
253
+ let allCandidates = [];
254
+ // 1️⃣ COLETAR TODOS OS LOGS DISPONÍVEIS (Memória + Arquivos)
255
+ try {
256
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
257
+ // Tentar pegar logs combinados (memória + arquivo compartilhado)
258
+ if (TerminalLogCapture && typeof TerminalLogCapture.getAllLogsCombined === 'function') {
259
+ allCandidates = TerminalLogCapture.getAllLogsCombined();
260
+ }
261
+ // Fallback: pegar logs da memória local
262
+ else if (TerminalLogCapture && typeof TerminalLogCapture.getAllLogs === 'function') {
263
+ allCandidates = TerminalLogCapture.getAllLogs();
264
+ }
265
+ }
266
+ catch {
267
+ // Ignorar erros de acesso ao TerminalLogCapture
268
+ }
269
+ // 2️⃣ SE NÃO TEM LOGS, TENTAR LER DIRETAMENTE DOS ARQUIVOS (Fallback Multi-worker)
270
+ if (allCandidates.length === 0) {
271
+ try {
272
+ const dir = path.resolve(process.cwd(), '.rbqa');
273
+ if (fs.existsSync(dir)) {
274
+ const files = fs.readdirSync(dir).filter(f => f.startsWith('captured-logs-') && f.endsWith('.json'));
275
+ for (const file of files) {
276
+ try {
277
+ const content = fs.readFileSync(path.join(dir, file), 'utf8');
278
+ const data = JSON.parse(content);
279
+ if (Array.isArray(data.logs)) {
280
+ allCandidates = allCandidates.concat(data.logs);
281
+ }
282
+ }
283
+ catch {
284
+ // Silenciosamente ignorar erros de leitura
285
+ }
286
+ }
287
+ }
288
+ }
289
+ catch {
290
+ // Silenciosamente ignorar erros de listagem
291
+ }
292
+ }
293
+ // 3️⃣ FILTRAR E UNIFICAR LOGS
294
+ if (allCandidates.length > 0) {
295
+ const filteredLogs = allCandidates.filter((log) => {
296
+ // 🚫 PRIMEIRO: Filtrar logs internos do framework
297
+ if (log.message && StatementTracker.isInternalLog(log.message)) {
298
+ return false;
299
+ }
300
+ // 🚫 SEGUNDO: Filtrar logs que pertencem a OUTROS CTs (não este)
301
+ if (log.ctName && log.ctName !== ctName) {
302
+ // Se o log tem ctName definido e é de outro CT, excluir
303
+ return false;
304
+ }
305
+ // A. Match por CT Name (Alta Precisão)
306
+ if (log.ctName === ctName)
307
+ return true;
308
+ // B. Match por mensagem contendo o nome do CT
309
+ if (log.message && typeof log.message === 'string' && log.message.includes(ctName)) {
310
+ // Mas excluir se for log de processamento de outro CT
311
+ if (log.message.includes('[CT-LOGS]'))
312
+ return false;
313
+ return true;
314
+ }
315
+ // C. Match por Timestamp (com margem reduzida para evitar contaminar)
316
+ if (log.timestamp) {
317
+ const logTime = new Date(log.timestamp).getTime();
318
+ // 🔧 REDUZIDO: Margem de 1000ms (1s) para evitar contaminar logs de outros CTs
319
+ const margin = 1000;
320
+ const inRange = logTime >= (ctStartTime - margin) && logTime <= (ctEndTime + margin);
321
+ return inRange;
322
+ }
323
+ return false;
324
+ });
325
+ if (filteredLogs.length > 0) {
326
+ // Deduplicar logs (usando índice ou timestamp+mensagem)
327
+ const uniqueLogsMap = new Map();
328
+ for (const log of filteredLogs) {
329
+ const key = log.index !== undefined ? `idx_${log.index}` : `${log.timestamp}_${log.message}`;
330
+ if (!uniqueLogsMap.has(key)) {
331
+ uniqueLogsMap.set(key, log);
332
+ }
333
+ }
334
+ const uniqueLogs = Array.from(uniqueLogsMap.values());
335
+ // Ordenar por índice ou timestamp
336
+ uniqueLogs.sort((a, b) => {
337
+ if (a.index !== undefined && b.index !== undefined)
338
+ return a.index - b.index;
339
+ return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
340
+ });
341
+ // 🎯 LIMPAR MENSAGENS: Remover prefixo [LOG] se já existir
342
+ ct.logs = uniqueLogs.map((log) => {
343
+ let message = log.message || '';
344
+ // Remover prefixos [LOG] aninhados
345
+ message = message.replace(/^\s*\[LOG\]\s*/g, '');
346
+ return message.trim();
347
+ }).filter((msg) => msg.length > 0); // Remover mensagens vazias
348
+ ct.logEndIndex = currentGlobalIndex;
349
+ return;
350
+ }
351
+ }
352
+ // 4️⃣ FALLBACK SINTÉTICO: Se ainda não achou nada
353
+ ct.logs = StatementTracker.buildFallbackLogs(ctName, testName, ct.duration, ct.status, ct.error, ct);
354
+ ct.logEndIndex = currentGlobalIndex || startIndex;
355
+ }
356
+ /**
357
+ * ✅ CORRIGIDO: Pegar nome do CT atual (test()) - VERSÃO ROBUSTA COM MÚLTIPLOS FALLBACKS
358
+ * ES MODULES PURO + COMPATIBILIDADE TOTAL COM PROJETOS CONSUMIDORES
359
+ */
360
+ static getCurrentCTName() {
361
+ try {
362
+ // 🎯 MÉTODO 1: TestContext global (mais confiável)
363
+ const TestContext = globalThis.__TEST_CONTEXT__;
364
+ if (TestContext) {
365
+ const testInfo = TestContext?.getSafeTestInfo?.();
366
+ if (testInfo?.title) {
367
+ return testInfo.title;
368
+ }
369
+ }
370
+ // 🎯 MÉTODO 2: Usar CT setado manualmente (se disponível)
371
+ if (StatementTracker.currentCTName) {
372
+ return StatementTracker.currentCTName;
373
+ }
374
+ // 🎯 MÉTODO 4: Análise de stack trace (último recurso)
375
+ const stackTrace = new Error().stack;
376
+ if (stackTrace) {
377
+ // Procurar por padrões de CT/test em métodos chamadores
378
+ const ctPatterns = [
379
+ /CT\d+[_\s-]?[^"\s)]+/i,
380
+ /test[_\s-]?[^"\s)]+/i,
381
+ /caso[_\s-]?de[_\s-]?teste[_\s-]?[^"\s)]+/i,
382
+ ];
383
+ for (const pattern of ctPatterns) {
384
+ const match = stackTrace.match(pattern);
385
+ if (match) {
386
+ const ctName = match[0].trim();
387
+ return ctName;
388
+ }
389
+ }
390
+ }
391
+ // 🎯 MÉTODO 4: Análise de stack trace (último recurso)
392
+ const stackCT = StatementTracker.detectCTFromExecutionContext();
393
+ if (stackCT) {
394
+ return stackCT;
395
+ }
396
+ }
397
+ catch (_error) {
398
+ // Silenciosamente ignorar
399
+ }
400
+ // 🎯 MÉTODO 5: Fallback final - usar nome do teste como CT
401
+ const currentTest = StatementTracker.getCurrentTestName();
402
+ if (currentTest && currentTest !== 'Unknown Test') {
403
+ return currentTest;
404
+ }
405
+ return null;
406
+ }
407
+ /**
408
+ * ✅ CORRIGIDO: Registrar início de um Statement (método)
409
+ * 🔧 FIX: Buscar CT pelo NOME, não pelo último índice
410
+ * 🆕 AGORA COM CRIAÇÃO AUTOMÁTICA DE CT SE NÃO EXISTIR
411
+ */
412
+ static startStatement(className, methodName) {
413
+ const testName = StatementTracker.getCurrentTestName();
414
+ // 🔧 CORREÇÃO: Pegar nome do CT atual PRIMEIRO
415
+ const ctName = StatementTracker.getCurrentCTName();
416
+ // 🆕 CRÍTICO: Garantir que existe estrutura de teste
417
+ if (!StatementTracker.executions.has(testName)) {
418
+ StatementTracker.executions.set(testName, {
419
+ testName,
420
+ cts: [],
421
+ totalDuration: 0,
422
+ totalCTs: 0,
423
+ passedCTs: 0,
424
+ failedCTs: 0,
425
+ });
426
+ }
427
+ const testData = StatementTracker.executions.get(testName);
428
+ // 🔧 CORREÇÃO CRÍTICA: Buscar CT pelo nome do test() atual
429
+ let currentCT = testData.cts.find((ct) => ct.name === ctName);
430
+ // ✅ SE CT NÃO EXISTE, CRIAR AUTOMATICAMENTE
431
+ if (!currentCT && ctName) {
432
+ StatementTracker.startCT(ctName);
433
+ // Buscar novamente após criação
434
+ currentCT = testData.cts.find((ct) => ct.name === ctName);
435
+ }
436
+ // Se ainda não encontrou CT, erro crítico
437
+ if (!currentCT) {
438
+ Logger.error(`❌ [StatementTracker] FALHA CRÍTICA: Não foi possível criar/encontrar CT "${ctName}" para statement ${className}.${methodName}`);
439
+ return Date.now();
440
+ }
441
+ // ✅ Criar e registrar statement no CT correto
442
+ const statement = {
443
+ name: `${className}.${methodName}`,
444
+ className,
445
+ methodName,
446
+ startTime: Date.now(),
447
+ status: 'running',
448
+ };
449
+ currentCT.statements.push(statement);
450
+ return statement.startTime;
451
+ }
452
+ /**
453
+ * ✅ CORRIGIDO: Registrar fim de um Statement
454
+ * 🔧 FIX: Buscar CT pelo NOME, não pelo último índice
455
+ */
456
+ static finishStatement(timestamp, success, error) {
457
+ const testName = StatementTracker.getCurrentTestName();
458
+ const testData = StatementTracker.executions.get(testName);
459
+ if (!testData || testData.cts.length === 0) {
460
+ return;
461
+ }
462
+ // 🔧 CORREÇÃO: Buscar CT pelo nome do test() atual
463
+ const ctName = StatementTracker.getCurrentCTName();
464
+ const currentCT = testData.cts.find((ct) => ct.name === ctName);
465
+ if (!currentCT || currentCT.statements.length === 0) {
466
+ return;
467
+ }
468
+ const lastStatement = currentCT.statements.at(-1);
469
+ if (lastStatement && lastStatement.status === 'running') {
470
+ lastStatement.endTime = Date.now();
471
+ lastStatement.duration = lastStatement.endTime - lastStatement.startTime;
472
+ lastStatement.status = success ? 'passed' : 'failed';
473
+ if (error)
474
+ lastStatement.error = error;
475
+ // 💾 AUTO-SAVE: Salvar dados após cada Statement para evitar perda de dados
476
+ StatementTracker.saveDataToFile();
477
+ }
478
+ }
479
+ static waitForDataReady() {
480
+ return Promise.resolve();
481
+ }
482
+ /**
483
+ * ✅ NOVO: Finalizar e persistir todos os dados
484
+ * Salva ct-execution-data.json com dados completos
485
+ */
486
+ static async finalize() {
487
+ try {
488
+ // ✅ Finalizar CTs que ainda estão "running" antes de persistir os dados
489
+ for (const [testName, data] of StatementTracker.executions.entries()) {
490
+ const hasRunningCts = Array.isArray(data.cts)
491
+ ? data.cts.some((ct) => ct.status === 'running')
492
+ : false;
493
+ if (hasRunningCts) {
494
+ Logger.warning(`⚠️ [StatementTracker] Finalizando CTs pendentes do teste "${testName}" antes do teardown`);
495
+ StatementTracker.finalizeRunningCTsAsFailed(testName, 'Execução finalizada - CT não foi concluído automaticamente');
496
+ }
497
+ // Garantir que as métricas estejam atualizadas para cada teste
498
+ StatementTracker.recomputeTestStats(data);
499
+ }
500
+ // Salvar dados finais em arquivo
501
+ StatementTracker.saveDataToFile();
502
+ // ✅ NOVO: Copiar também para pasta docs/ para acesso dos relatórios HTML
503
+ const sourceFilePath = StatementTracker.getDataFilePath();
504
+ const docsPath = path.resolve(process.cwd(), 'docs', 'ct-execution-data.json');
505
+ if (fs.existsSync(sourceFilePath)) {
506
+ const docsDir = path.dirname(docsPath);
507
+ if (!fs.existsSync(docsDir)) {
508
+ fs.mkdirSync(docsDir, { recursive: true });
509
+ }
510
+ fs.copyFileSync(sourceFilePath, docsPath);
511
+ }
512
+ const stats = StatementTracker.getStats();
513
+ }
514
+ catch (error) {
515
+ Logger.warning(`⚠️ [StatementTracker] Erro ao finalizar: ${error}`);
516
+ }
517
+ return Promise.resolve();
518
+ }
519
+ // 🔗 SINGLETON GLOBAL COM PERSISTÊNCIA AUTOMÁTICA
520
+ static get executions() {
521
+ if (globalThis.__SIMPLE_STATEMENT_EXECUTIONS__) {
522
+ const size = globalThis.__SIMPLE_STATEMENT_EXECUTIONS__.size;
523
+ if (size > 0) {
524
+ }
525
+ }
526
+ else {
527
+ ;
528
+ globalThis.__SIMPLE_STATEMENT_EXECUTIONS__ = new Map();
529
+ // 🆕 RECUPERAR DADOS AUTOMATICAMENTE
530
+ StatementTracker.loadDataFromFile();
531
+ }
532
+ return globalThis.__SIMPLE_STATEMENT_EXECUTIONS__;
533
+ }
534
+ static get currentTest() {
535
+ if (!globalThis.__SIMPLE_STATEMENT_CURRENT_TEST__) {
536
+ ;
537
+ globalThis.__SIMPLE_STATEMENT_CURRENT_TEST__ = 'Unknown Test';
538
+ }
539
+ return globalThis.__SIMPLE_STATEMENT_CURRENT_TEST__;
540
+ }
541
+ static set currentTest(value) {
542
+ ;
543
+ globalThis.__SIMPLE_STATEMENT_CURRENT_TEST__ = value;
544
+ }
545
+ static get ctCounter() {
546
+ if (!globalThis.__SIMPLE_STATEMENT_CT_COUNTER__) {
547
+ ;
548
+ globalThis.__SIMPLE_STATEMENT_CT_COUNTER__ = 0;
549
+ }
550
+ return globalThis.__SIMPLE_STATEMENT_CT_COUNTER__;
551
+ }
552
+ static set ctCounter(value) {
553
+ ;
554
+ globalThis.__SIMPLE_STATEMENT_CT_COUNTER__ = value;
555
+ }
556
+ static get currentCTName() {
557
+ return globalThis.__SIMPLE_STATEMENT_CURRENT_CT_NAME__ || null;
558
+ }
559
+ static set currentCTName(value) {
560
+ ;
561
+ globalThis.__SIMPLE_STATEMENT_CURRENT_CT_NAME__ = value;
562
+ }
563
+ static get ctAutoCreated() {
564
+ return globalThis.__SIMPLE_STATEMENT_CT_AUTO_CREATED__;
565
+ }
566
+ static set ctAutoCreated(value) {
567
+ ;
568
+ globalThis.__SIMPLE_STATEMENT_CT_AUTO_CREATED__ = value;
569
+ }
570
+ /**
571
+ * 🆕 Define o sistema atual (chamado por TestAnnotations.setSystem)
572
+ */
573
+ static setCurrentSystem(systemName) {
574
+ try {
575
+ // ✅ SEMPRE armazenar no globalThis para próximos CTs
576
+ ;
577
+ globalThis.__SIMPLE_STATEMENT_CURRENT_SYSTEM__ = systemName;
578
+ const testName = StatementTracker.getCurrentTestName();
579
+ const testData = StatementTracker.executions.get(testName);
580
+ if (testData) {
581
+ // ✅ CORRIGIDO: Pegar o CT mais recente em execução (último iniciado)
582
+ // Isso resolve o problema de múltiplos CTs simultâneos
583
+ const runningCT = testData.cts
584
+ .filter((ct) => ct.status === 'running')
585
+ .sort((a, b) => b.startTime - a.startTime)[0]; // Mais recente primeiro
586
+ if (runningCT) {
587
+ runningCT.systemName = systemName;
588
+ }
589
+ else {
590
+ Logger.warning(`⚠️ [setCurrentSystem] Nenhum CT em execução - sistema "${systemName}" armazenado para próximo CT`);
591
+ }
592
+ }
593
+ else {
594
+ Logger.warning(`⚠️ [setCurrentSystem] Teste não encontrado - sistema "${systemName}" armazenado para próximo CT`);
595
+ }
596
+ }
597
+ catch (error) {
598
+ Logger.warning(`⚠️ [StatementTracker] Erro ao aplicar sistema no CT atual: ${String(error)}`);
599
+ globalThis.__SIMPLE_STATEMENT_CURRENT_SYSTEM__ = systemName;
600
+ }
601
+ }
602
+ /**
603
+ * 🆕 Obtém o sistema atual
604
+ */
605
+ static getCurrentSystem() {
606
+ return globalThis.__SIMPLE_STATEMENT_CURRENT_SYSTEM__;
607
+ }
608
+ /**
609
+ * � Obtém referência segura dos logs capturados globalmente
610
+ */
611
+ /**
612
+ * 🆕 Obtém o sistema do CT em execução (público para uso externo)
613
+ * Retorna o sistema configurado via setSystem() para o CT atual
614
+ * @returns {string | null} Nome do sistema ou null se não configurado
615
+ */
616
+ static getCurrentCTSystem() {
617
+ try {
618
+ const testName = StatementTracker.getCurrentTestName();
619
+ if (!testName) {
620
+ return null;
621
+ }
622
+ const testData = StatementTracker.executions.get(testName);
623
+ if (!testData) {
624
+ return null;
625
+ }
626
+ // ✅ CORRIGIDO: Buscar CT mais recente em execução (último iniciado)
627
+ const runningCT = testData.cts
628
+ .filter((ct) => ct.status === 'running')
629
+ .sort((a, b) => b.startTime - a.startTime)[0]; // Mais recente primeiro
630
+ if (runningCT &&
631
+ runningCT.systemName &&
632
+ runningCT.systemName !== 'Unknown') {
633
+ return runningCT.systemName;
634
+ }
635
+ return null;
636
+ }
637
+ catch (error) {
638
+ Logger.warning(`⚠️ [StatementTracker.getCurrentCTSystem] Erro ao obter sistema do CT: ${String(error)}`);
639
+ return null;
640
+ }
641
+ }
642
+ /**
643
+ * 📝 Obtém logs globais normalizados (com índice + nível)
644
+ * 🔧 ATUALIZADO: Trabalha com índices globais para permitir fatiamento preciso por CT
645
+ */
646
+ static getGlobalLogs() {
647
+ const fromCapture = StatementTracker.gatherLogsFromTerminalCapture();
648
+ if (fromCapture.length > 0) {
649
+ return fromCapture;
650
+ }
651
+ const fromSharedFile = StatementTracker.gatherLogsFromSharedFile();
652
+ if (fromSharedFile.length > 0) {
653
+ return fromSharedFile;
654
+ }
655
+ return StatementTracker.gatherLegacyLogs();
656
+ }
657
+ static gatherLogsFromTerminalCapture() {
658
+ try {
659
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
660
+ if (TerminalLogCapture &&
661
+ typeof TerminalLogCapture.getAllLogs === 'function') {
662
+ const capturedLogs = TerminalLogCapture.getAllLogs();
663
+ if (Array.isArray(capturedLogs) && capturedLogs.length > 0) {
664
+ return StatementTracker.normalizeLogs(capturedLogs);
665
+ }
666
+ }
667
+ }
668
+ catch {
669
+ // Ignorar e tentar próximo fallback
670
+ }
671
+ return [];
672
+ }
673
+ static gatherLogsFromSharedFile() {
674
+ const sharedLogsPath = path.resolve(process.cwd(), '.rbqa', 'captured-logs.json');
675
+ if (!fs.existsSync(sharedLogsPath)) {
676
+ return [];
677
+ }
678
+ try {
679
+ const data = JSON.parse(fs.readFileSync(sharedLogsPath, 'utf8'));
680
+ if (Array.isArray(data.logs) && data.logs.length > 0) {
681
+ // Ordenar por índice para garantir consistência
682
+ const sortedLogs = [...data.logs].sort((a, b) => (a.index || 0) - (b.index || 0));
683
+ return StatementTracker.normalizeLogs(sortedLogs);
684
+ }
685
+ }
686
+ catch {
687
+ // Arquivo pode estar em uso por outro worker - ignorar
688
+ }
689
+ return [];
690
+ }
691
+ static gatherLegacyLogs() {
692
+ const aggregated = [];
693
+ const directLogs = globalThis.logsCapturados;
694
+ if (Array.isArray(directLogs) && directLogs.length > 0) {
695
+ aggregated.push(...StatementTracker.normalizeLogs(directLogs));
696
+ }
697
+ const fallbackLogs = globalThis.__AUTOCORE_LOGS_CAPTURADOS__;
698
+ if (Array.isArray(fallbackLogs) && fallbackLogs.length > 0) {
699
+ aggregated.push(...StatementTracker.normalizeLogs(fallbackLogs));
700
+ }
701
+ return aggregated.sort((a, b) => a.index - b.index);
702
+ }
703
+ static normalizeLogs(rawLogs) {
704
+ const normalized = [];
705
+ for (const rawLog of rawLogs) {
706
+ const previousIndex = normalized.length > 0 ? normalized[normalized.length - 1].index : 0;
707
+ const fallbackIndex = previousIndex + 1;
708
+ const normalizedEntry = StatementTracker.normalizeSingleLog(rawLog, fallbackIndex);
709
+ if (normalizedEntry) {
710
+ normalized.push(normalizedEntry);
711
+ }
712
+ }
713
+ return normalized.sort((a, b) => a.index - b.index);
714
+ }
715
+ static normalizeSingleLog(rawLog, fallbackIndex) {
716
+ if (rawLog === null || typeof rawLog === 'undefined') {
717
+ return null;
718
+ }
719
+ const indexCandidate = typeof rawLog?.index === 'number'
720
+ ? rawLog.index
721
+ : typeof rawLog?.globalIndex === 'number'
722
+ ? rawLog.globalIndex
723
+ : fallbackIndex;
724
+ const rawLevel = typeof rawLog?.level === 'string'
725
+ ? rawLog.level
726
+ : typeof rawLog?.type === 'string'
727
+ ? rawLog.type
728
+ : typeof rawLog?.severity === 'string'
729
+ ? rawLog.severity
730
+ : 'LOG';
731
+ let message;
732
+ if (typeof rawLog?.message === 'string') {
733
+ message = rawLog.message;
734
+ }
735
+ else if (typeof rawLog === 'string') {
736
+ message = rawLog;
737
+ }
738
+ else if (rawLog?.message !== undefined) {
739
+ try {
740
+ message = JSON.stringify(rawLog.message);
741
+ }
742
+ catch {
743
+ message = String(rawLog.message);
744
+ }
745
+ }
746
+ else {
747
+ try {
748
+ message = JSON.stringify(rawLog);
749
+ }
750
+ catch {
751
+ message = String(rawLog);
752
+ }
753
+ }
754
+ return {
755
+ index: indexCandidate,
756
+ level: rawLevel.toUpperCase(),
757
+ message,
758
+ };
759
+ }
760
+ static formatLogEntry(log) {
761
+ return `[${log.level}] ${log.message}`;
762
+ }
763
+ static captureLogsBetweenIndices(startIndex, endIndex, cachedLogs) {
764
+ const logs = cachedLogs ?? StatementTracker.getGlobalLogs();
765
+ if (logs.length === 0) {
766
+ return [];
767
+ }
768
+ const filtered = logs.filter((log) => {
769
+ const afterStart = log.index > startIndex;
770
+ const beforeEnd = typeof endIndex === 'number' ? log.index <= endIndex : true;
771
+ return afterStart && beforeEnd;
772
+ });
773
+ return filtered.map((log) => StatementTracker.formatLogEntry(log));
774
+ }
775
+ static getLogsFromTestScope(testName) {
776
+ try {
777
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
778
+ if (TerminalLogCapture &&
779
+ typeof TerminalLogCapture.getLogsForTest === 'function') {
780
+ const candidateName = testName || StatementTracker.currentTest || 'Unknown Test';
781
+ const testLogs = TerminalLogCapture.getLogsForTest(candidateName);
782
+ if (Array.isArray(testLogs) && testLogs.length > 0) {
783
+ return testLogs.map((log) => `[${log.level?.toUpperCase() || 'LOG'}] ${log.message}`);
784
+ }
785
+ }
786
+ }
787
+ catch {
788
+ // Ignorar fallbacks silenciosamente
789
+ }
790
+ return [];
791
+ }
792
+ static buildFallbackLogs(ctName, testName, duration, status, error, ct) {
793
+ const fallbackLogs = [];
794
+ // 🆕 ESTRATÉGIA 1: Buscar logs REAIS do terminal pelo TIMESTAMP do CT
795
+ if (ct && ct.startTime && ct.endTime) {
796
+ try {
797
+ // Tentar buscar do arquivo compartilhado (funciona em single E multi-worker)
798
+ const sharedLogsPath = path.resolve(process.cwd(), '.rbqa', 'captured-logs.json');
799
+ if (fs.existsSync(sharedLogsPath)) {
800
+ const data = JSON.parse(fs.readFileSync(sharedLogsPath, 'utf8'));
801
+ if (Array.isArray(data.logs) && data.logs.length > 0) {
802
+ // Filtrar logs pelo timestamp do CT (com margem de 500ms)
803
+ const ctStart = ct.startTime - 500;
804
+ const ctEnd = ct.endTime + 500;
805
+ const logsInRange = data.logs.filter((log) => {
806
+ if (!log.timestamp)
807
+ return false;
808
+ // 🚫 Filtrar logs internos do framework
809
+ if (log.message && StatementTracker.isInternalLog(log.message))
810
+ return false;
811
+ const logTime = new Date(log.timestamp).getTime();
812
+ return logTime >= ctStart && logTime <= ctEnd;
813
+ });
814
+ if (logsInRange.length > 0) {
815
+ for (const log of logsInRange) {
816
+ // ✅ CORRIGIDO: Apenas a mensagem, sem prefixo [LOG]
817
+ const message = (log.message || '').replace(/^\s*\[LOG\]\s*/g, '').trim();
818
+ if (message)
819
+ fallbackLogs.push(message);
820
+ }
821
+ return fallbackLogs;
822
+ }
823
+ }
824
+ }
825
+ }
826
+ catch {
827
+ // Ignorar erros de leitura
828
+ }
829
+ // 🆕 ESTRATÉGIA 2: Buscar da memória local (TerminalLogCapture)
830
+ try {
831
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
832
+ if (TerminalLogCapture) {
833
+ // Tentar getAllLogsCombined primeiro
834
+ const getAllLogs = TerminalLogCapture.getAllLogsCombined || TerminalLogCapture.getAllLogs;
835
+ if (typeof getAllLogs === 'function') {
836
+ const allLogs = getAllLogs.call(TerminalLogCapture);
837
+ if (Array.isArray(allLogs) && allLogs.length > 0) {
838
+ const ctStart = ct.startTime - 500;
839
+ const ctEnd = ct.endTime + 500;
840
+ const logsInRange = allLogs.filter((log) => {
841
+ if (!log.timestamp)
842
+ return false;
843
+ // 🚫 Filtrar logs internos do framework
844
+ if (log.message && StatementTracker.isInternalLog(log.message))
845
+ return false;
846
+ const logTime = new Date(log.timestamp).getTime();
847
+ return logTime >= ctStart && logTime <= ctEnd;
848
+ });
849
+ if (logsInRange.length > 0) {
850
+ for (const log of logsInRange) {
851
+ // ✅ CORRIGIDO: Apenas a mensagem, sem prefixo [LOG]
852
+ const message = (log.message || '').replace(/^\s*\[LOG\]\s*/g, '').trim();
853
+ if (message)
854
+ fallbackLogs.push(message);
855
+ }
856
+ return fallbackLogs;
857
+ }
858
+ }
859
+ }
860
+ }
861
+ }
862
+ catch {
863
+ // Ignorar
864
+ }
865
+ }
866
+ // 🆕 ESTRATÉGIA 3: Logs sintéticos (último recurso)
867
+ // ✅ CORRIGIDO: Logs sem prefixo [LOG] para evitar duplicação
868
+ fallbackLogs.push(`🚀 [CT-START] === INICIANDO NOVO CT ===`);
869
+ fallbackLogs.push(` 🎯 CT Name: "${ctName}"`);
870
+ fallbackLogs.push(` 📋 Test Name: "${testName}"`);
871
+ fallbackLogs.push(`🚀 [CT-START] === CT CRIADO ===`);
872
+ // Se temos referência ao CT, incluir logs dos statements e actions
873
+ if (ct) {
874
+ // Adicionar logs dos statements executados
875
+ if (ct.statements && ct.statements.length > 0) {
876
+ fallbackLogs.push(`📝 === STATEMENTS EXECUTADOS ===`);
877
+ for (const stmt of ct.statements) {
878
+ const stmtStatus = stmt.status === 'passed' ? '✅' : stmt.status === 'failed' ? '❌' : '⏳';
879
+ fallbackLogs.push(`${stmtStatus} ${stmt.className}.${stmt.methodName} (${stmt.duration || 0}ms)`);
880
+ if (stmt.error) {
881
+ fallbackLogs.push(` ❌ Erro: ${stmt.error}`);
882
+ }
883
+ }
884
+ }
885
+ // Adicionar logs das actions executadas
886
+ if (ct.executedActions && ct.executedActions.length > 0) {
887
+ fallbackLogs.push(`🎬 === ACTIONS EXECUTADAS ===`);
888
+ for (const action of ct.executedActions) {
889
+ const actionStatus = action.success ? '✅' : '❌';
890
+ fallbackLogs.push(`${actionStatus} [${action.type}] ${action.description} (${action.duration || 0}ms)`);
891
+ if (!action.success && action.details) {
892
+ fallbackLogs.push(` ❌ Detalhes: ${JSON.stringify(action.details)}`);
893
+ }
894
+ }
895
+ }
896
+ // Adicionar logs das actions (formato antigo)
897
+ if (ct.actions && ct.actions.length > 0 && (!ct.executedActions || ct.executedActions.length === 0)) {
898
+ fallbackLogs.push(`🎬 === ACTIONS ===`);
899
+ for (const action of ct.actions) {
900
+ fallbackLogs.push(`▸ ${action}`);
901
+ }
902
+ }
903
+ }
904
+ // Status final
905
+ fallbackLogs.push(`📊 CT "${ctName}" executado com status: ${status || 'unknown'}`);
906
+ fallbackLogs.push(`⏱️ Duração: ${duration ?? 0}ms`);
907
+ if (error) {
908
+ fallbackLogs.push(`❌ Erro: ${error}`);
909
+ }
910
+ fallbackLogs.push(`🏁 [CT-END] === CT FINALIZADO ===`);
911
+ return fallbackLogs;
912
+ }
913
+ static getCurrentGlobalLogIndex() {
914
+ try {
915
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
916
+ if (TerminalLogCapture) {
917
+ if (typeof TerminalLogCapture.getCurrentGlobalIndex === 'function') {
918
+ return TerminalLogCapture.getCurrentGlobalIndex();
919
+ }
920
+ if (typeof TerminalLogCapture.getSharedGlobalIndex === 'function') {
921
+ const sharedIndex = TerminalLogCapture.getSharedGlobalIndex();
922
+ if (typeof sharedIndex === 'number') {
923
+ return sharedIndex;
924
+ }
925
+ }
926
+ if (typeof TerminalLogCapture.getAllLogs === 'function') {
927
+ const allLogs = TerminalLogCapture.getAllLogs();
928
+ if (Array.isArray(allLogs) && allLogs.length > 0) {
929
+ const lastLog = allLogs[allLogs.length - 1];
930
+ if (lastLog && typeof lastLog.index === 'number') {
931
+ return lastLog.index;
932
+ }
933
+ return allLogs.length;
934
+ }
935
+ }
936
+ }
937
+ }
938
+ catch {
939
+ // Ignorar e usar fallback de arquivo
940
+ }
941
+ try {
942
+ const sharedLogsPath = path.resolve(process.cwd(), '.rbqa', 'captured-logs.json');
943
+ if (fs.existsSync(sharedLogsPath)) {
944
+ const data = JSON.parse(fs.readFileSync(sharedLogsPath, 'utf8'));
945
+ if (typeof data.globalIndex === 'number') {
946
+ return data.globalIndex;
947
+ }
948
+ if (Array.isArray(data.logs) && data.logs.length > 0) {
949
+ const sorted = [...data.logs].sort((a, b) => (a.index || 0) - (b.index || 0));
950
+ const lastEntry = sorted[sorted.length - 1];
951
+ if (lastEntry && typeof lastEntry.index === 'number') {
952
+ return lastEntry.index;
953
+ }
954
+ return sorted.length;
955
+ }
956
+ }
957
+ }
958
+ catch {
959
+ // Ignorar
960
+ }
961
+ return 0;
962
+ }
963
+ /**
964
+ * 📊 Recalcula estatísticas derivadas com base nos CTs registrados
965
+ */
966
+ static recomputeTestStats(data) {
967
+ const cts = Array.isArray(data.cts) ? data.cts : [];
968
+ data.totalCTs = cts.length;
969
+ data.passedCTs = cts.filter((ct) => ct.status === 'passed').length;
970
+ data.failedCTs = cts.filter((ct) => ct.status === 'failed').length;
971
+ let totalDuration = 0;
972
+ for (const ct of cts) {
973
+ if (typeof ct.duration === 'number') {
974
+ totalDuration += ct.duration;
975
+ }
976
+ else if (ct.endTime) {
977
+ totalDuration += ct.endTime - ct.startTime;
978
+ }
979
+ }
980
+ data.totalDuration = totalDuration;
981
+ }
982
+ /**
983
+ * ��🔄 Obtém caminho do arquivo de persistência
984
+ */
985
+ static getDataFilePath() {
986
+ // 🆕 Usar arquivo separado por worker para evitar conflitos de escrita (ENOENT)
987
+ const workerIndex = process.env.TEST_WORKER_INDEX;
988
+ if (workerIndex !== undefined) {
989
+ return path.resolve(process.cwd(), '.rbqa', `ct-execution-data-${workerIndex}.json`);
990
+ }
991
+ return path.resolve(process.cwd(), '.rbqa', 'ct-execution-data.json');
992
+ }
993
+ /**
994
+ * ⏱️ Formatar duração em formato legível
995
+ */
996
+ static formatDuration(milliseconds) {
997
+ if (!milliseconds || milliseconds === 0)
998
+ return '0ms';
999
+ const seconds = Math.floor(milliseconds / 1000);
1000
+ const minutes = Math.floor(seconds / 60);
1001
+ const hours = Math.floor(minutes / 60);
1002
+ if (hours > 0) {
1003
+ const remainingMinutes = minutes % 60;
1004
+ const remainingSeconds = seconds % 60;
1005
+ return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;
1006
+ }
1007
+ if (minutes > 0) {
1008
+ const remainingSeconds = seconds % 60;
1009
+ return `${minutes}m ${remainingSeconds}s`;
1010
+ }
1011
+ if (seconds > 0) {
1012
+ const remainingMs = milliseconds % 1000;
1013
+ return `${seconds}s ${remainingMs}ms`;
1014
+ }
1015
+ return `${milliseconds}ms`;
1016
+ }
1017
+ /**
1018
+ * 💾 Salva dados automaticamente no arquivo
1019
+ * 🛡️ CORRIGIDO: Mescla com dados existentes antes de salvar (multi-worker safe)
1020
+ */
1021
+ static saveDataToFile() {
1022
+ try {
1023
+ const filePath = StatementTracker.getDataFilePath();
1024
+ const dir = path.dirname(filePath);
1025
+ // Garantir que diretório existe
1026
+ if (!fs.existsSync(dir)) {
1027
+ fs.mkdirSync(dir, { recursive: true });
1028
+ }
1029
+ // 🔄 PASSO 1: Carregar dados existentes do arquivo
1030
+ let existingData = { tests: [] };
1031
+ if (fs.existsSync(filePath)) {
1032
+ try {
1033
+ const fileContent = fs.readFileSync(filePath, 'utf8');
1034
+ if (fileContent && fileContent.trim().length > 0) {
1035
+ existingData = JSON.parse(fileContent);
1036
+ }
1037
+ }
1038
+ catch (parseError) {
1039
+ Logger.warning(`⚠️ [SAVE] Erro ao ler arquivo existente, será sobrescrito: ${parseError}`);
1040
+ }
1041
+ }
1042
+ // 🔄 PASSO 2: Criar mapa de CTs existentes por testName
1043
+ const existingTestsMap = new Map();
1044
+ const existingCTsByTest = new Map();
1045
+ if (existingData.tests && Array.isArray(existingData.tests)) {
1046
+ for (const testData of existingData.tests) {
1047
+ existingTestsMap.set(testData.testName, testData);
1048
+ const ctMap = new Map();
1049
+ for (const ct of testData.cts || []) {
1050
+ ctMap.set(ct.name, ct);
1051
+ }
1052
+ existingCTsByTest.set(testData.testName, ctMap);
1053
+ }
1054
+ }
1055
+ const executionsSize = StatementTracker.executions.size;
1056
+ // 🔄 PASSO 3: Mesclar dados da memória com dados do arquivo
1057
+ const mergedTests = new Map();
1058
+ // Processar testes da memória
1059
+ for (const [testName, data] of StatementTracker.executions.entries()) {
1060
+ StatementTracker.recomputeTestStats(data);
1061
+ const existingTest = existingTestsMap.get(testName);
1062
+ const existingCTMap = existingCTsByTest.get(testName) || new Map();
1063
+ // Mesclar CTs
1064
+ const mergedCTMap = new Map(existingCTMap);
1065
+ for (const ct of data.cts) {
1066
+ // ✅ CORREÇÃO: Só hidratar logs se o CT estiver FINALIZADO (não running)
1067
+ // Isso evita que logs incompletos/sintéticos sejam gerados durante o startCT
1068
+ if (ct.status !== 'running' && (!ct.logs || ct.logs.length === 0)) {
1069
+ StatementTracker.hydrateCTLogs(ct, testName);
1070
+ }
1071
+ // ✅ PROTEÇÃO CRÍTICA: NÃO recalcular status de CTs já finalizados
1072
+ // Se o CT já tem status passed/failed/skipped, foi explicitamente finalizado pelo CustomReporter
1073
+ // Apenas processar CTs com status 'running'
1074
+ let computedStatus = ct.status;
1075
+ let computedError = ct.error;
1076
+ if (ct.status === 'running') {
1077
+ // Só aqui fazemos análise de statements para determinar status
1078
+ const statements = Array.isArray(ct.statements) ? ct.statements : [];
1079
+ const hasFailedStatement = statements.some((stmt) => stmt.status === 'failed');
1080
+ const hasRunningStatement = statements.some((stmt) => stmt.status === 'running');
1081
+ const hasAnyStatement = statements.length > 0;
1082
+ const ctHasExplicitEnd = typeof ct.endTime === 'number';
1083
+ if (hasFailedStatement) {
1084
+ computedStatus = 'failed';
1085
+ computedError =
1086
+ computedError ||
1087
+ statements.find((stmt) => stmt.status === 'failed')?.error ||
1088
+ 'Statement falhou';
1089
+ }
1090
+ else if (!hasRunningStatement && hasAnyStatement) {
1091
+ computedStatus = 'passed';
1092
+ computedError = undefined;
1093
+ }
1094
+ else if (ctHasExplicitEnd) {
1095
+ computedStatus = 'failed';
1096
+ computedError =
1097
+ computedError ||
1098
+ (hasAnyStatement
1099
+ ? 'CT finalizado com estado indefinido'
1100
+ : 'CT finalizado sem statements executadas');
1101
+ }
1102
+ }
1103
+ // ✅ PROTEÇÃO: Só calcular endTime/duration se CT ainda está running
1104
+ // CTs finalizados já têm esses valores corretos do CustomReporter
1105
+ let finalEndTime = ct.endTime;
1106
+ let finalDuration = ct.duration;
1107
+ if (ct.status === 'running') {
1108
+ // Só para CTs running, tentar inferir endTime/duration dos statements
1109
+ const statements = Array.isArray(ct.statements) ? ct.statements : [];
1110
+ const statementEndTimes = statements
1111
+ .map((stmt) => stmt.endTime)
1112
+ .filter((value) => typeof value === 'number');
1113
+ const latestStatementEnd = statementEndTimes.length > 0
1114
+ ? Math.max(...statementEndTimes)
1115
+ : undefined;
1116
+ if (computedStatus !== 'running') {
1117
+ if (!finalEndTime && latestStatementEnd) {
1118
+ finalEndTime = latestStatementEnd;
1119
+ }
1120
+ if (!finalEndTime &&
1121
+ typeof ct.duration === 'number' &&
1122
+ ct.duration > 0) {
1123
+ finalEndTime = ct.startTime + ct.duration;
1124
+ }
1125
+ if (!finalEndTime) {
1126
+ finalEndTime = ct.startTime;
1127
+ }
1128
+ if (typeof finalDuration !== 'number') {
1129
+ finalDuration =
1130
+ finalEndTime !== undefined
1131
+ ? Math.max(0, finalEndTime - ct.startTime)
1132
+ : 0;
1133
+ }
1134
+ }
1135
+ else if (typeof finalDuration !== 'number') {
1136
+ finalDuration = 0;
1137
+ }
1138
+ ct.status = computedStatus;
1139
+ ct.error = computedStatus === 'passed' ? undefined : computedError;
1140
+ ct.endTime = finalEndTime;
1141
+ ct.duration = finalDuration;
1142
+ }
1143
+ // Se não é running, manter valores originais (já finalizados)
1144
+ const durationFormatted = StatementTracker.formatDuration(ct.duration);
1145
+ const ctData = {
1146
+ name: ct.name,
1147
+ systemName: ct.systemName,
1148
+ status: ct.status,
1149
+ startTime: ct.startTime,
1150
+ startTimeFormatted: new Date(ct.startTime).toISOString(),
1151
+ endTime: ct.endTime,
1152
+ endTimeFormatted: ct.endTime
1153
+ ? new Date(ct.endTime).toISOString()
1154
+ : undefined,
1155
+ duration: ct.duration,
1156
+ durationFormatted,
1157
+ error: ct.error,
1158
+ actions: ct.actions || [],
1159
+ executedActions: ct.executedActions || [],
1160
+ statements: ct.statements || [],
1161
+ logs: ct.logs || [],
1162
+ };
1163
+ mergedCTMap.set(ct.name, ctData);
1164
+ }
1165
+ const mergedCTs = Array.from(mergedCTMap.values());
1166
+ // ❌ REMOVIDO: Log de debug que listava todos os CTs
1167
+ // Calcular estatísticas do teste mesclado
1168
+ const testStartTime = mergedCTs.length > 0
1169
+ ? Math.min(...mergedCTs.map((ct) => ct.startTime))
1170
+ : Date.now();
1171
+ const testEndTime = mergedCTs.length > 0 && mergedCTs.some((ct) => ct.endTime)
1172
+ ? Math.max(...mergedCTs
1173
+ .filter((ct) => ct.endTime)
1174
+ .map((ct) => ct.endTime))
1175
+ : undefined;
1176
+ // ✅ CORREÇÃO CRÍTICA: CTs "running" devem ser contados como "failed"
1177
+ const passedCTs = mergedCTs.filter((ct) => ct.status === 'passed').length;
1178
+ const failedCTs = mergedCTs.filter((ct) => ct.status === 'failed' || ct.status === 'running').length;
1179
+ const skippedCTs = mergedCTs.filter((ct) => ct.status === 'skipped').length;
1180
+ const totalDuration = mergedCTs.reduce((sum, ct) => sum + (ct.duration || 0), 0);
1181
+ mergedTests.set(testName, {
1182
+ testName,
1183
+ startTime: testStartTime,
1184
+ startTimeFormatted: new Date(testStartTime).toISOString(),
1185
+ endTime: testEndTime,
1186
+ endTimeFormatted: testEndTime
1187
+ ? new Date(testEndTime).toISOString()
1188
+ : undefined,
1189
+ totalCTs: mergedCTs.length,
1190
+ passedCTs,
1191
+ failedCTs,
1192
+ skippedCTs,
1193
+ duration: totalDuration,
1194
+ durationFormatted: StatementTracker.formatDuration(totalDuration),
1195
+ cts: mergedCTs,
1196
+ });
1197
+ }
1198
+ // Adicionar testes que estão apenas no arquivo (não na memória)
1199
+ for (const [testName, testData] of existingTestsMap.entries()) {
1200
+ if (!mergedTests.has(testName)) {
1201
+ Logger.info(` 📂 Preservando "${testName}" do arquivo (não está na memória)`);
1202
+ mergedTests.set(testName, testData);
1203
+ }
1204
+ }
1205
+ // 🆕 Calcular estatísticas globais
1206
+ const allCTs = Array.from(mergedTests.values()).flatMap((t) => t.cts || []);
1207
+ // ✅ CORREÇÃO CRÍTICA: CTs "running" devem ser contados como "failed"
1208
+ const stats = {
1209
+ totalCTs: allCTs.length,
1210
+ passedCTs: allCTs.filter((ct) => ct.status === 'passed').length,
1211
+ failedCTs: allCTs.filter((ct) => ct.status === 'failed' || ct.status === 'running').length,
1212
+ skippedCTs: allCTs.filter((ct) => ct.status === 'skipped').length,
1213
+ totalDuration: allCTs.reduce((sum, ct) => sum + (ct.duration || 0), 0),
1214
+ };
1215
+ const exportData = {
1216
+ timestamp: new Date().toISOString(),
1217
+ totalCTs: stats.totalCTs,
1218
+ passedCTs: stats.passedCTs,
1219
+ failedCTs: stats.failedCTs,
1220
+ skippedCTs: stats.skippedCTs,
1221
+ totalDuration: stats.totalDuration,
1222
+ totalDurationFormatted: StatementTracker.formatDuration(stats.totalDuration),
1223
+ // 🛑 PERSISTIR FLAG DE STOP-ON-FAILURE para compartilhar entre processos/workers
1224
+ stopOnFailureTriggered: StatementTracker.hasFailedWithStopOnFailure,
1225
+ tests: Array.from(mergedTests.values()),
1226
+ };
1227
+ // 🔄 PASSO 4: Salvar com proteção atômica (temp file + rename)
1228
+ const tempFilePath = `${filePath}.tmp`;
1229
+ fs.writeFileSync(tempFilePath, JSON.stringify(exportData, null, 2), 'utf8');
1230
+ // Move atômico (renomear substitui o arquivo antigo)
1231
+ // ✅ CORREÇÃO: Verificar se arquivo existe antes de deletar
1232
+ try {
1233
+ if (fs.existsSync(filePath)) {
1234
+ fs.unlinkSync(filePath);
1235
+ }
1236
+ fs.renameSync(tempFilePath, filePath);
1237
+ }
1238
+ catch (renameError) {
1239
+ // Se rename falhar, tentar salvar direto (fallback)
1240
+ Logger.warning(`⚠️ [SAVE] Rename falhou, salvando direto: ${renameError}`);
1241
+ fs.copyFileSync(tempFilePath, filePath);
1242
+ fs.unlinkSync(tempFilePath);
1243
+ }
1244
+ }
1245
+ catch (error) {
1246
+ Logger.error('❌ [AUTO-SAVE] Erro ao salvar dados:', error);
1247
+ }
1248
+ }
1249
+ /**
1250
+ * 🔄 FORÇA CARREGAMENTO DOS DADOS DO ARQUIVO
1251
+ * Usado pelo CustomReporter que roda em processo separado dos testes
1252
+ * Garante que os CTs criados pelos testes estejam disponíveis no Reporter
1253
+ * 🆕 AGORA SUPORTA MÚLTIPLOS ARQUIVOS DE WORKERS
1254
+ */
1255
+ static forceLoadFromFile() {
1256
+ const dir = path.resolve(process.cwd(), '.rbqa');
1257
+ if (!fs.existsSync(dir)) {
1258
+ return;
1259
+ }
1260
+ try {
1261
+ // Encontrar todos os arquivos de dados (principal + workers)
1262
+ const files = fs.readdirSync(dir).filter(f => f.startsWith('ct-execution-data') && f.endsWith('.json'));
1263
+ if (files.length === 0) {
1264
+ Logger.warning('⚠️ [forceLoadFromFile] Nenhum arquivo de dados encontrado');
1265
+ return;
1266
+ }
1267
+ for (const file of files) {
1268
+ const filePath = path.join(dir, file);
1269
+ try {
1270
+ const fileContent = fs.readFileSync(filePath, 'utf8');
1271
+ if (!fileContent || fileContent.trim().length === 0)
1272
+ continue;
1273
+ const data = JSON.parse(fileContent);
1274
+ // 🛑 CARREGAR FLAG DE STOP-ON-FAILURE
1275
+ if (data.stopOnFailureTriggered === true) {
1276
+ StatementTracker.hasFailedWithStopOnFailure = true;
1277
+ }
1278
+ if (!data.tests || !Array.isArray(data.tests))
1279
+ continue;
1280
+ for (const testData of data.tests) {
1281
+ const existingData = StatementTracker.executions.get(testData.testName);
1282
+ if (existingData) {
1283
+ // 🔄 MESCLAR CTs
1284
+ const existingCTNames = new Set(existingData.cts.map(ct => ct.name));
1285
+ const newCTs = (testData.cts || []).filter((ct) => !existingCTNames.has(ct.name));
1286
+ if (newCTs.length > 0) {
1287
+ existingData.cts.push(...newCTs);
1288
+ existingData.totalCTs = existingData.cts.length;
1289
+ // Recalcular stats
1290
+ existingData.passedCTs = existingData.cts.filter(ct => ct.status === 'passed').length;
1291
+ existingData.failedCTs = existingData.cts.filter(ct => ct.status === 'failed').length;
1292
+ }
1293
+ }
1294
+ else {
1295
+ const executionData = {
1296
+ testName: testData.testName,
1297
+ cts: testData.cts || [],
1298
+ totalDuration: testData.duration || 0,
1299
+ totalCTs: testData.totalCTs || 0,
1300
+ passedCTs: testData.passedCTs || 0,
1301
+ failedCTs: testData.failedCTs || 0,
1302
+ };
1303
+ StatementTracker.executions.set(testData.testName, executionData);
1304
+ }
1305
+ }
1306
+ }
1307
+ catch (err) {
1308
+ Logger.warning(`⚠️ [forceLoadFromFile] Erro ao ler ${file}: ${err}`);
1309
+ }
1310
+ }
1311
+ }
1312
+ catch (error) {
1313
+ Logger.error('❌ [forceLoadFromFile] Erro geral:', error);
1314
+ }
1315
+ }
1316
+ /**
1317
+ * 📂 Carrega dados do arquivo de persistência
1318
+ */
1319
+ static loadDataFromFile() {
1320
+ try {
1321
+ const filePath = StatementTracker.getDataFilePath();
1322
+ if (!fs.existsSync(filePath)) {
1323
+ return;
1324
+ }
1325
+ const fileContent = fs.readFileSync(filePath, 'utf8');
1326
+ // ✅ PROTEÇÃO: Verificar se arquivo não está vazio ou corrompido
1327
+ if (!fileContent || fileContent.trim().length === 0) {
1328
+ Logger.warning('⚠️ [LOAD] Arquivo vazio, ignorando');
1329
+ return;
1330
+ }
1331
+ // ✅ PROTEÇÃO: Verificar se é JSON válido
1332
+ let data;
1333
+ try {
1334
+ data = JSON.parse(fileContent);
1335
+ }
1336
+ catch (parseError) {
1337
+ Logger.error('❌ [LOAD] Erro ao fazer parse do JSON, arquivo pode estar corrompido');
1338
+ Logger.error(`📄 Conteúdo: ${fileContent.substring(0, 200)}...`);
1339
+ return;
1340
+ }
1341
+ if (data.tests && Array.isArray(data.tests)) {
1342
+ for (const testData of data.tests) {
1343
+ // 🔄 MESCLAR dados em vez de sobrescrever
1344
+ const existingData = StatementTracker.executions.get(testData.testName);
1345
+ if (existingData) {
1346
+ // ✅ JÁ EXISTE - Mesclar CTs
1347
+ Logger.info(` 🔄 [LOAD] Mesclando dados para "${testData.testName}"`);
1348
+ Logger.info(` 📊 Existente: ${existingData.cts.length} CTs`);
1349
+ Logger.info(` 📊 Arquivo: ${(testData.cts || []).length} CTs`);
1350
+ // Mesclar CTs por nome (evitar duplicatas)
1351
+ const existingCTNames = new Set(existingData.cts.map((ct) => ct.name));
1352
+ const newCTs = (testData.cts || []).filter((ct) => !existingCTNames.has(ct.name));
1353
+ existingData.cts.push(...newCTs);
1354
+ existingData.totalCTs = existingData.cts.length;
1355
+ existingData.passedCTs = existingData.cts.filter((ct) => ct.status === 'passed').length;
1356
+ existingData.failedCTs = existingData.cts.filter((ct) => ct.status === 'failed').length;
1357
+ Logger.info(` ✅ [LOAD] Mesclado: ${existingData.cts.length} CTs total`);
1358
+ }
1359
+ else {
1360
+ // ✅ NÃO EXISTE - Criar novo
1361
+ const executionData = {
1362
+ testName: testData.testName,
1363
+ cts: testData.cts || [],
1364
+ totalDuration: testData.duration || 0,
1365
+ totalCTs: testData.totalCTs || 0,
1366
+ passedCTs: testData.passedCTs || 0,
1367
+ failedCTs: testData.failedCTs || 0,
1368
+ };
1369
+ StatementTracker.executions.set(testData.testName, executionData);
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+ catch (error) {
1375
+ Logger.error('❌ [LOAD] Erro ao carregar dados:', error);
1376
+ }
1377
+ }
1378
+ /**
1379
+ * 🚫 LISTA DE MÉTODOS QUE NÃO SÃO CTs
1380
+ * Métodos de validação, assertions e utilitários não devem ser tratados como CTs
1381
+ * ✅ CORRIGIDO: Incluir métodos do BancoActions que são AÇÕES, não CTs
1382
+ * 🎯 NOVO: Lista de métodos que são apenas ações internas, não CTs completos
1383
+ *
1384
+ * ⚠️ IMPORTANTE: Métodos como validateCep, processOrder são CTs REAIS!
1385
+ * Apenas métodos auxiliares como assert, log, setup devem estar aqui.
1386
+ */
1387
+ static NON_CT_METHODS = [
1388
+ // ⚠️ REMOVIDO 'validar' e 'validate' - estes PODEM ser CTs reais
1389
+ 'assert',
1390
+ 'check',
1391
+ 'verify',
1392
+ 'expect',
1393
+ 'should',
1394
+ 'wait',
1395
+ 'sleep',
1396
+ 'delay',
1397
+ 'log',
1398
+ 'print',
1399
+ 'debug',
1400
+ 'setup',
1401
+ 'teardown',
1402
+ 'before',
1403
+ 'after',
1404
+ 'init',
1405
+ 'cleanup',
1406
+ 'helper',
1407
+ 'util',
1408
+ 'constructor',
1409
+ 'toString',
1410
+ 'valueOf',
1411
+ 'hasOwnProperty',
1412
+ ];
1413
+ /**
1414
+ * 🎯 LISTA DE MÉTODOS QUE SÃO APENAS AÇÕES INTERNAS (NÃO CTs)
1415
+ * Estes métodos são chamados DENTRO de um CT real, mas não são CTs por si só
1416
+ */
1417
+ static INTERNAL_ACTION_METHODS = [
1418
+ // ✅ BANCO: Métodos do BancoActions que são AÇÕES internas
1419
+ 'select',
1420
+ 'executequery',
1421
+ 'executarquery',
1422
+ 'execute',
1423
+ 'executar',
1424
+ 'insert',
1425
+ 'update',
1426
+ 'delete',
1427
+ 'commit',
1428
+ 'rollback',
1429
+ 'connect',
1430
+ 'disconnect',
1431
+ 'close',
1432
+ // ✅ API: Métodos de API que são ações internas
1433
+ 'get',
1434
+ 'post',
1435
+ 'put',
1436
+ 'patch',
1437
+ 'delete',
1438
+ 'request',
1439
+ // ✅ UI: Métodos de UI que são ações internas
1440
+ 'click',
1441
+ 'type',
1442
+ 'fill',
1443
+ 'select',
1444
+ 'hover',
1445
+ 'scroll',
1446
+ 'waitfor',
1447
+ 'screenshot',
1448
+ // ✅ MOBILE: Métodos de Mobile que são ações internas
1449
+ 'tap',
1450
+ 'swipe',
1451
+ 'scroll',
1452
+ 'pinch',
1453
+ 'zoom',
1454
+ 'rotate',
1455
+ // ✅ SSH: Métodos de SSH que são ações internas
1456
+ 'execute',
1457
+ 'upload',
1458
+ 'download',
1459
+ 'connect',
1460
+ 'disconnect',
1461
+ ];
1462
+ /**
1463
+ * 🔍 VERIFICA SE MÉTODO É UM CT OU AÇÃO INTERNA
1464
+ * ✅ NOVA LÓGICA: CT real vs Ação interna
1465
+ *
1466
+ * Um CT REAL é um método da classe Statement que:
1467
+ * 1. Não está na lista NON_CT_METHODS (utilitários)
1468
+ * 2. Não está na lista INTERNAL_ACTION_METHODS (ações como select, execute, click, etc.)
1469
+ * 3. Não é método privado (_method)
1470
+ * 4. É o método PRINCIPAL da classe (como validateCep, processOrder, etc.)
1471
+ */
1472
+ static isValidCTMethod(methodName) {
1473
+ const lowerMethodName = methodName.toLowerCase();
1474
+ // 1️⃣ Ignorar métodos utilitários e de sistema (comparação EXATA)
1475
+ if (StatementTracker.NON_CT_METHODS.includes(lowerMethodName)) {
1476
+ return false;
1477
+ }
1478
+ // 2️⃣ NOVO: Ignorar métodos que são apenas ações internas (comparação EXATA)
1479
+ if (StatementTracker.INTERNAL_ACTION_METHODS.includes(lowerMethodName)) {
1480
+ return false;
1481
+ }
1482
+ // 3️⃣ Ignorar métodos privados (começam com _)
1483
+ if (methodName.startsWith('_')) {
1484
+ return false;
1485
+ }
1486
+ // 4️⃣ NOVO: Ignorar getters/setters (começam com get/set)
1487
+ if (methodName.startsWith('get') || methodName.startsWith('set')) {
1488
+ return false;
1489
+ }
1490
+ // 5️⃣ NOVO: Ignorar apenas métodos que são claramente utilitários (começam com prefixo)
1491
+ const utilityPrefixes = [
1492
+ 'assert',
1493
+ 'check',
1494
+ 'verify',
1495
+ 'log',
1496
+ 'debug',
1497
+ 'setup',
1498
+ 'teardown',
1499
+ ];
1500
+ for (const prefix of utilityPrefixes) {
1501
+ if (lowerMethodName.startsWith(prefix)) {
1502
+ return false;
1503
+ }
1504
+ }
1505
+ // ✅ MÉTODO VÁLIDO: É um CT real!
1506
+ return true;
1507
+ }
1508
+ /**
1509
+ * 🎯 INTERCEPTAR CLASSE AUTOMATICAMENTE
1510
+ * ✅ CORRIGIDO: Apenas interceptar classes Statement, não Actions
1511
+ */
1512
+ static interceptStatement(StatementClass) {
1513
+ const className = StatementClass.name;
1514
+ // 🚫 FILTRO CRÍTICO: Não interceptar classes que são Actions (BancoActions, WebActions, etc.)
1515
+ if (className.endsWith('Actions') ||
1516
+ className.endsWith('Helper') ||
1517
+ className.endsWith('Manager')) {
1518
+ return StatementClass; // Retornar classe original sem interceptação
1519
+ }
1520
+ // 🚫 FILTRO: Não interceptar classes específicas do sistema
1521
+ const systemClasses = [
1522
+ 'TestContext',
1523
+ 'Logger',
1524
+ 'AutoDocs',
1525
+ 'UnifiedReportManager',
1526
+ 'EnviromentHelper',
1527
+ ];
1528
+ if (systemClasses.includes(className)) {
1529
+ return StatementClass;
1530
+ }
1531
+ return new Proxy(StatementClass, {
1532
+ construct(target) {
1533
+ const instance = new target();
1534
+ const prototype = target.prototype;
1535
+ for (const methodName of Object.getOwnPropertyNames(prototype)) {
1536
+ if (methodName !== 'constructor' &&
1537
+ typeof instance[methodName] === 'function') {
1538
+ const originalMethod = instance[methodName];
1539
+ // ✅ FILTRAR: Só interceptar métodos que são CTs de verdade
1540
+ if (!StatementTracker.isValidCTMethod(methodName)) {
1541
+ continue;
1542
+ }
1543
+ ;
1544
+ instance[methodName] = async function (...methodArgs) {
1545
+ const statementName = `${className}.${methodName}`;
1546
+ // ✅ NOVO: Cada método Statement = 1 CT individual
1547
+ const ctName = `CT - ${statementName}`;
1548
+ // Iniciar CT específico para este método
1549
+ StatementTracker.startCT(ctName);
1550
+ // Registrar statement dentro do CT
1551
+ StatementTracker.startStatement(className, methodName);
1552
+ const startTime = Date.now();
1553
+ try {
1554
+ const result = await originalMethod.apply(this, methodArgs);
1555
+ const endTime = Date.now();
1556
+ // Finalizar statement como sucesso
1557
+ StatementTracker.finishStatement(endTime, true);
1558
+ // Finalizar CT como sucesso
1559
+ StatementTracker.endCT(ctName, true);
1560
+ return result;
1561
+ }
1562
+ catch (error) {
1563
+ const endTime = Date.now();
1564
+ const errorMessage = error instanceof Error ? error.message : String(error);
1565
+ // Finalizar statement como falha
1566
+ StatementTracker.finishStatement(endTime, false, errorMessage);
1567
+ // Finalizar CT como falha
1568
+ StatementTracker.endCT(ctName, false, errorMessage);
1569
+ throw error;
1570
+ }
1571
+ };
1572
+ }
1573
+ }
1574
+ return instance;
1575
+ },
1576
+ });
1577
+ }
1578
+ /**
1579
+ * 🚀 INICIAR CT (CASO DE TESTE) AUTOMATICAMENTE
1580
+ */
1581
+ static startCT(ctName) {
1582
+ // 🛑 CARREGAR FLAG DO ARQUIVO PRIMEIRO (para compartilhar entre processos/workers)
1583
+ StatementTracker.loadStopOnFailureFlagFromFile();
1584
+ // 🛑 VERIFICAR SE JÁ HOUVE FALHA COM STOP-ON-FAILURE
1585
+ if (StatementTracker.hasAnyTestFailedWithStopOnFailure()) {
1586
+ const errorMsg = `🛑 [STOP-ON-FAILURE] CT "${ctName}" pulado - um CT anterior falhou.`;
1587
+ Logger.error(`\n${errorMsg}`);
1588
+ throw new Error(errorMsg);
1589
+ }
1590
+ const testName = StatementTracker.getCurrentTestName();
1591
+ if (!StatementTracker.executions.has(testName)) {
1592
+ StatementTracker.executions.set(testName, {
1593
+ testName,
1594
+ cts: [],
1595
+ totalDuration: 0,
1596
+ totalCTs: 0,
1597
+ passedCTs: 0,
1598
+ failedCTs: 0,
1599
+ });
1600
+ }
1601
+ const testData = StatementTracker.executions.get(testName);
1602
+ // 🔍 VERIFICAÇÃO DE DUPLICATA ANTES DE CRIAR
1603
+ const existingCT = testData.cts.find((ct) => ct.name === ctName);
1604
+ if (existingCT) {
1605
+ // CT já existe, não criar duplicata
1606
+ return;
1607
+ }
1608
+ StatementTracker.ctCounter++;
1609
+ StatementTracker.currentCTName = ctName;
1610
+ // 🆕 Definir CT atual no TerminalLogCapture para filtragem precisa
1611
+ try {
1612
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
1613
+ if (TerminalLogCapture && typeof TerminalLogCapture.setCurrentCT === 'function') {
1614
+ TerminalLogCapture.setCurrentCT(ctName);
1615
+ }
1616
+ }
1617
+ catch {
1618
+ // Ignorar se TerminalLogCapture não estiver disponível
1619
+ }
1620
+ // 🧹 NOVO: Limpar cache de contextos do ApiActions para novo CT
1621
+ try {
1622
+ import('../api/ApiActions.js')
1623
+ .then(({ ApiActions }) => {
1624
+ return ApiActions.clearContextCache();
1625
+ })
1626
+ .catch(() => {
1627
+ // Silenciosamente ignorar se ApiActions não estiver disponível
1628
+ });
1629
+ }
1630
+ catch {
1631
+ // Silenciosamente ignorar
1632
+ }
1633
+ // 🔧 Capturar sistema configurado do globalThis (definido por TestAnnotations.setSystem)
1634
+ const capturedSystem = StatementTracker.getCurrentSystem();
1635
+ const logStartIndex = StatementTracker.getCurrentGlobalLogIndex();
1636
+ const ct = {
1637
+ name: ctName,
1638
+ startTime: Date.now(),
1639
+ status: 'running',
1640
+ systemName: 'Unknown',
1641
+ statements: [], // ✅ CORRETO: Array para armazenar statements
1642
+ actions: [],
1643
+ executedActions: [],
1644
+ logs: [], // Logs serão associados no final do CT
1645
+ logStartIndex, // 📍 Marca onde este CT começa nos logs globais (ANTES dos logs de CT-START)
1646
+ };
1647
+ testData.cts.push(ct);
1648
+ testData.totalCTs++;
1649
+ StatementTracker.startCTLogStream(testName, ctName);
1650
+ globalThis.__SIMPLE_STATEMENT_CURRENT_SYSTEM__ = undefined;
1651
+ // 🔒 PROTEÇÃO ANTI-DUPLICAÇÃO: Só logar se é um CT diferente do último
1652
+ // ✅ IMPORTANTE: Estes logs são impressos DEPOIS de capturar logStartIndex,
1653
+ // mas serão incluídos na captura pois logStartIndex foi capturado ANTES
1654
+ if (StatementTracker.lastLoggedCT !== ctName) {
1655
+ StatementTracker.lastLoggedCT = ctName;
1656
+ Logger.info('\n🚀 [CT-START] === INICIANDO NOVO CT ===');
1657
+ Logger.info(` 🎯 CT Name: "${ctName}"`);
1658
+ Logger.info(` 📋 Test Name: "${testName}"`);
1659
+ Logger.info(` 🔢 CT #${testData.totalCTs}`);
1660
+ Logger.info(` 📊 Total de CTs no teste: ${testData.cts.length}`);
1661
+ Logger.info('🚀 [CT-START] === CT CRIADO ===\n');
1662
+ }
1663
+ // 💾 Auto-save após iniciar CT
1664
+ StatementTracker.saveDataToFile();
1665
+ }
1666
+ /**
1667
+ * 🏁 FINALIZAR CT (CASO DE TESTE) AUTOMATICAMENTE
1668
+ */
1669
+ static endCT(ctName, success, errorMessage) {
1670
+ const testName = StatementTracker.getCurrentTestName();
1671
+ const testData = StatementTracker.executions.get(testName);
1672
+ if (!testData) {
1673
+ Logger.warning(`⚠️ [CT] Teste "${testName}" não encontrado ao finalizar CT: ${ctName}`);
1674
+ return;
1675
+ }
1676
+ const ct = testData.cts.find((c) => c.name === ctName && c.status === 'running');
1677
+ if (!ct) {
1678
+ Logger.warning(`⚠️ [CT] CT "${ctName}" não encontrado ou já finalizado`);
1679
+ Logger.info(` 📋 CTs disponíveis: ${testData.cts.map((c) => `${c.name}(${c.status})`).join(', ')}`);
1680
+ return;
1681
+ }
1682
+ // ✅ SALVAR ENDTIME E STATUS
1683
+ ct.endTime = Date.now();
1684
+ ct.duration = ct.endTime - ct.startTime;
1685
+ ct.status = success ? 'passed' : 'failed';
1686
+ if (errorMessage)
1687
+ ct.error = errorMessage;
1688
+ StatementTracker.hydrateCTLogs(ct, testName, ctName);
1689
+ testData.totalDuration += ct.duration;
1690
+ if (success)
1691
+ testData.passedCTs++;
1692
+ else
1693
+ testData.failedCTs++;
1694
+ StatementTracker.currentCTName = null;
1695
+ // 🆕 Limpar CT atual no TerminalLogCapture
1696
+ try {
1697
+ const TerminalLogCapture = globalThis.TerminalLogCapture;
1698
+ if (TerminalLogCapture && typeof TerminalLogCapture.clearCurrentCT === 'function') {
1699
+ TerminalLogCapture.clearCurrentCT();
1700
+ }
1701
+ }
1702
+ catch {
1703
+ // Ignorar se TerminalLogCapture não estiver disponível
1704
+ }
1705
+ // 💾 CRÍTICO: Auto-save após finalizar CT
1706
+ StatementTracker.saveDataToFile();
1707
+ // 🛑 STOP ON FAILURE: Se o CT falhou e stopOnFailure está ativo, marcar flag e lançar erro
1708
+ if (!success) {
1709
+ const stopOnFailure = StatementTracker.isStopOnFailureEnabled();
1710
+ if (stopOnFailure) {
1711
+ // Marcar flag global para impedir próximos CTs
1712
+ StatementTracker.markFailedWithStopOnFailure();
1713
+ const errorMsg = `🛑 [STOP-ON-FAILURE] CT "${ctName}" falhou. Execução interrompida.`;
1714
+ Logger.error(`\n${errorMsg}`);
1715
+ // Lançar erro para interromper execução dos próximos CTs
1716
+ throw new Error(errorMsg);
1717
+ }
1718
+ }
1719
+ }
1720
+ /**
1721
+ * 🎯 REGISTRAR AÇÃO EXECUTADA DURANTE STATEMENT (DENTRO DO CT)
1722
+ * @param type Tipo da ação (API, SSH, DB, UI, MOBILE, OTHER)
1723
+ * @param description Descrição da ação executada
1724
+ * @param success Se a ação foi bem-sucedida
1725
+ * @param duration Duração em milissegundos
1726
+ * @param details Detalhes específicos da ação (método, url, statusCode, command, query, selector, etc.)
1727
+ */
1728
+ static recordAction(type, description, success = true, duration, details) {
1729
+ const testName = StatementTracker.getCurrentTestName();
1730
+ const testData = StatementTracker.executions.get(testName);
1731
+ if (!testData)
1732
+ return;
1733
+ const currentCT = testData.cts.find((c) => c.status === 'running');
1734
+ if (!currentCT)
1735
+ return;
1736
+ // 🎯 CORRIGIDO: Pegar o statement mais recente em execução (último iniciado)
1737
+ // Isso resolve o problema de múltiplos statements simultâneos
1738
+ const activeStatement = currentCT.statements
1739
+ .filter((s) => s.status === 'running')
1740
+ .sort((a, b) => b.startTime - a.startTime)[0]; // Mais recente primeiro
1741
+ const action = {
1742
+ type,
1743
+ description,
1744
+ timestamp: new Date().toISOString(),
1745
+ duration,
1746
+ success,
1747
+ details,
1748
+ };
1749
+ // Registrar no Statement ativo (prioridade)
1750
+ if (activeStatement) {
1751
+ if (!activeStatement.executedActions)
1752
+ activeStatement.executedActions = [];
1753
+ activeStatement.executedActions.push(action);
1754
+ }
1755
+ else {
1756
+ // Fallback: Registrar no CT se não houver Statement ativo
1757
+ if (!currentCT.executedActions)
1758
+ currentCT.executedActions = [];
1759
+ currentCT.executedActions.push(action);
1760
+ }
1761
+ }
1762
+ /**
1763
+ * 📊 OBTER DADOS FORMATADOS PARA INTEGRAÇÃO COM RELATÓRIOS HTML
1764
+ */
1765
+ static getReportData() {
1766
+ const reportData = {
1767
+ cts: [],
1768
+ totalCTs: 0,
1769
+ passedCTs: 0,
1770
+ failedCTs: 0,
1771
+ totalDuration: 0,
1772
+ };
1773
+ for (const testData of StatementTracker.executions.values()) {
1774
+ for (let i = 0; i < testData.cts.length; i++) {
1775
+ const ct = testData.cts[i];
1776
+ const ctNumber = i + 1;
1777
+ reportData.cts.push({
1778
+ ctId: `${testData.testName}_CT${ctNumber.toString().padStart(3, '0')}`,
1779
+ ctName: ct.name,
1780
+ ctOrder: ctNumber,
1781
+ className: ct.name.split('.')[0] || 'Unknown',
1782
+ methodName: ct.name.split('.')[1] || 'unknown',
1783
+ testName: testData.testName,
1784
+ parentTest: testData.testName,
1785
+ status: ct.status,
1786
+ duration: ct.duration || 0,
1787
+ timestamp: new Date(ct.startTime).toISOString(),
1788
+ error: ct.error,
1789
+ success: ct.status === 'passed',
1790
+ executedActions: ct.executedActions || [],
1791
+ statusIcon: ct.status === 'passed'
1792
+ ? '✅'
1793
+ : ct.status === 'failed'
1794
+ ? '❌'
1795
+ : '⏳',
1796
+ });
1797
+ }
1798
+ reportData.totalCTs += testData.totalCTs;
1799
+ reportData.passedCTs += testData.passedCTs;
1800
+ reportData.failedCTs += testData.failedCTs;
1801
+ reportData.totalDuration += testData.totalDuration;
1802
+ }
1803
+ return reportData.cts;
1804
+ }
1805
+ /**
1806
+ * 📊 OBTER RELATÓRIO COMPLETO
1807
+ */
1808
+ static getFullReport() {
1809
+ return Array.from(StatementTracker.executions.values());
1810
+ }
1811
+ /**
1812
+ * 🔢 OBTER TOTAL DE CTs
1813
+ */
1814
+ static getTotalCTs() {
1815
+ let total = 0;
1816
+ const executionsSize = StatementTracker.executions.size;
1817
+ Logger.info(`🔍 [StatementTracker] getTotalCTs() - Executions map size: ${executionsSize}`);
1818
+ // 🔍 DEBUGGING ULTRA-DETALHADO
1819
+ Logger.info(`🔍 [ULTRA-DEBUG] Global instance check: ${globalThis.__SIMPLE_STATEMENT_TRACKER__ === StatementTracker}`);
1820
+ Logger.info(`🔍 [ULTRA-DEBUG] Executions Map keys: ${Array.from(StatementTracker.executions.keys())}`);
1821
+ for (const [testName, testData] of StatementTracker.executions.entries()) {
1822
+ Logger.info(`🔍 [ULTRA-DEBUG] Test "${testName}":`);
1823
+ Logger.info(`🔍 [ULTRA-DEBUG] - totalCTs property: ${testData.totalCTs}`);
1824
+ Logger.info(`🔍 [ULTRA-DEBUG] - cts.length: ${testData.cts.length}`);
1825
+ Logger.info(`🔍 [ULTRA-DEBUG] - CTs: ${testData.cts.map((ct) => `${ct.name}(${ct.status})`).join(', ')}`);
1826
+ total += testData.totalCTs;
1827
+ }
1828
+ Logger.info(`🔍 [StatementTracker] Total calculado: ${total}`);
1829
+ return total;
1830
+ }
1831
+ /**
1832
+ * 📈 OBTER ESTATÍSTICAS
1833
+ */
1834
+ static getStats() {
1835
+ let totalCTs = 0;
1836
+ let passedCTs = 0;
1837
+ let failedCTs = 0;
1838
+ let totalDuration = 0;
1839
+ for (const testData of StatementTracker.executions.values()) {
1840
+ StatementTracker.recomputeTestStats(testData);
1841
+ totalCTs += testData.totalCTs;
1842
+ passedCTs += testData.passedCTs;
1843
+ failedCTs += testData.failedCTs;
1844
+ totalDuration += testData.totalDuration;
1845
+ }
1846
+ return { totalCTs, passedCTs, failedCTs, totalDuration };
1847
+ }
1848
+ /**
1849
+ * 🔍 DETECTAR NOME DO TESTE AUTOMATICAMENTE - VERSÃO ROBUSTA COM MÚLTIPLOS FALLBACKS
1850
+ * CORRIGIDO: Pega o CN (test.describe) com compatibilidade total para projetos consumidores
1851
+ * ✅ ACEITA qualquer nome no test.describe, com ou sem prefixo "CN"
1852
+ * ES MODULES PURO + COMPATIBILIDADE UNIVERSAL
1853
+ */
1854
+ static getCurrentTestName() {
1855
+ // 🆕 PRIORIDADE MÁXIMA: testName definido globalmente (usado em recursão de finalizarCT)
1856
+ const globalTestName = globalThis
1857
+ .__SIMPLE_STATEMENT_CURRENT_TEST_NAME__;
1858
+ if (globalTestName && globalTestName !== 'Unknown Test') {
1859
+ return globalTestName;
1860
+ }
1861
+ try {
1862
+ // 1️⃣ MÉTODO PREFERIDO: TestContext (método mais confiável)
1863
+ const TestContext = globalThis.__TEST_CONTEXT__;
1864
+ if (TestContext) {
1865
+ const testInfo = TestContext?.getSafeTestInfo?.();
1866
+ if (testInfo) {
1867
+ // ✅ CRÍTICO: titlePath[0] = FILENAME, titlePath[1] = CN (test.describe), titlePath[2] = CT (test())
1868
+ // Exemplos:
1869
+ // titlePath = ["CN0001_Orquestrador.spec.ts", "CN001_Orquestrador FTTH", "CT001_Token"] → "CN001_Orquestrador FTTH"
1870
+ // titlePath = ["SSH.spec.ts", "Teste SSH via Gateway - SMP", "CT01 - Consumo de Voz"] → "Teste SSH via Gateway - SMP"
1871
+ if (testInfo.titlePath && testInfo.titlePath.length > 1) {
1872
+ const cnName = testInfo.titlePath[1];
1873
+ return cnName;
1874
+ }
1875
+ // Fallback para title se titlePath não existir
1876
+ if (testInfo.title) {
1877
+ return testInfo.title;
1878
+ }
1879
+ }
1880
+ }
1881
+ }
1882
+ catch (error) {
1883
+ Logger.warning(`⚠️ [getCurrentTestName] Erro no TestContext: ${error}`);
1884
+ // Ignorar erro e tentar próximo método
1885
+ }
1886
+ try {
1887
+ // 2️⃣ MÉTODO DIRETO: Playwright test.info() (para projetos consumidores)
1888
+ if (typeof globalThis !== 'undefined' && globalThis.test) {
1889
+ const testInfo = globalThis.test.info?.();
1890
+ if (testInfo) {
1891
+ // ✅ CRÍTICO: titlePath[0] = FILENAME, titlePath[1] = CN (test.describe)
1892
+ if (testInfo.titlePath && testInfo.titlePath.length > 1) {
1893
+ const cnName = testInfo.titlePath[1];
1894
+ return cnName;
1895
+ }
1896
+ // Fallback para title
1897
+ if (testInfo.title) {
1898
+ return testInfo.title;
1899
+ }
1900
+ }
1901
+ }
1902
+ }
1903
+ catch (error) {
1904
+ Logger.warning(`⚠️ [getCurrentTestName] Erro no Playwright direto: ${error}`);
1905
+ // Ignorar erro
1906
+ }
1907
+ // 3️⃣ MÉTODO MANUAL: Nome setado via setTestName()
1908
+ const currentTestName = StatementTracker.currentTest;
1909
+ if (currentTestName && currentTestName !== 'Unknown Test') {
1910
+ return currentTestName;
1911
+ }
1912
+ // 4️⃣ MÉTODO STACK TRACE: Analisar call stack para detectar teste
1913
+ try {
1914
+ const stackTrace = new Error().stack;
1915
+ if (stackTrace) {
1916
+ // Procurar por padrões de nome de teste em arquivos .spec.ts
1917
+ const testFilePattern = /([^/\\]+)\.spec\.ts/i;
1918
+ const testFileMatch = stackTrace.match(testFilePattern);
1919
+ if (testFileMatch) {
1920
+ const testFileName = testFileMatch[1];
1921
+ return testFileName;
1922
+ }
1923
+ }
1924
+ }
1925
+ catch (error) {
1926
+ Logger.warning(`⚠️ [getCurrentTestName] Erro na análise de stack: ${error}`);
1927
+ }
1928
+ // 5️⃣ ÚLTIMO RECURSO: Unknown Test
1929
+ Logger.warning(`⚠️ [getCurrentTestName] TODOS OS MÉTODOS FALHARAM - usando "Unknown Test"`);
1930
+ return 'Unknown Test';
1931
+ }
1932
+ /**
1933
+ * 🏷️ DEFINIR NOME DO TESTE MANUALMENTE
1934
+ * Use este método para garantir que o nome correto do teste seja usado
1935
+ */
1936
+ static setTestName(testName) {
1937
+ if (testName && testName !== 'Unknown Test') {
1938
+ StatementTracker.currentTest = testName;
1939
+ // 💾 Criar entrada para o teste se não existir
1940
+ if (!StatementTracker.executions.has(testName)) {
1941
+ StatementTracker.executions.set(testName, {
1942
+ testName,
1943
+ cts: [],
1944
+ totalDuration: 0,
1945
+ totalCTs: 0,
1946
+ passedCTs: 0,
1947
+ failedCTs: 0,
1948
+ });
1949
+ StatementTracker.saveDataToFile();
1950
+ }
1951
+ }
1952
+ else {
1953
+ Logger.warning(`⚠️ [StatementTracker] Tentativa de definir nome de teste inválido: "${testName}"`);
1954
+ }
1955
+ }
1956
+ /**
1957
+ * 🔍 OBTER NOME DO TESTE ATUAL (PÚBLICO)
1958
+ * Retorna o nome do teste atual sendo executado
1959
+ */
1960
+ static getCurrentTest() {
1961
+ return StatementTracker.getCurrentTestName();
1962
+ }
1963
+ /**
1964
+ * ❌ FINALIZAR APENAS CTs "RUNNING" COMO "FAILED"
1965
+ * ✅ PRESERVA CTs que já passaram (status='passed')
1966
+ * ❌ Marca apenas CTs em execução (status='running') como failed
1967
+ * 📊 Preserva duração, logs e ações de todos os CTs
1968
+ */
1969
+ /**
1970
+ * ✅ NOVO: Finaliza CTs "running" como PASSED quando teste PASSOU
1971
+ */
1972
+ static finalizeRunningCTsAsPassed(testName, ctName) {
1973
+ const targetTestName = testName || StatementTracker.getCurrentTestName();
1974
+ const testData = StatementTracker.executions.get(targetTestName);
1975
+ if (!testData) {
1976
+ Logger.warning(`⚠️ [StatementTracker] Nenhum dado encontrado para teste: ${targetTestName}`);
1977
+ return;
1978
+ }
1979
+ let finalizados = 0;
1980
+ let preservados = 0;
1981
+ for (let i = 0; i < testData.cts.length; i++) {
1982
+ const ct = testData.cts[i];
1983
+ if (ctName && ct.name !== ctName) {
1984
+ continue;
1985
+ }
1986
+ if (ct.status === 'running') {
1987
+ // ✅ CORREÇÃO: Analisar statements para determinar status do CT
1988
+ ct.endTime = Date.now();
1989
+ ct.duration = ct.endTime - ct.startTime;
1990
+ // ✅ PROTEÇÃO: Verificar se statements existe e é array
1991
+ const statements = Array.isArray(ct.statements) ? ct.statements : [];
1992
+ // Verificar se alguma statement falhou
1993
+ const hasFailedStatement = statements.some((stmt) => stmt.status === 'failed');
1994
+ if (hasFailedStatement) {
1995
+ // Se alguma statement falhou, CT falhou (mesmo que teste tenha passado)
1996
+ ct.status = 'failed';
1997
+ ct.error =
1998
+ ct.statements.find((stmt) => stmt.error)?.error ||
1999
+ 'Statement falhou';
2000
+ testData.failedCTs++;
2001
+ Logger.info(` ❌ CT${i + 1} "${ct.name}": RUNNING → FAILED (${ct.duration}ms) - Statement falhou`);
2002
+ }
2003
+ else {
2004
+ // ✅ Teste passou, CT passou (assume sucesso se não falhou explicitamente)
2005
+ ct.status = 'passed';
2006
+ testData.passedCTs++;
2007
+ Logger.info(` ✅ CT${i + 1} "${ct.name}": RUNNING → PASSED (${ct.duration}ms) - Teste OK`);
2008
+ }
2009
+ testData.totalDuration += ct.duration;
2010
+ finalizados++;
2011
+ }
2012
+ else if (ct.status === 'passed') {
2013
+ // ✅ Preservar CT que já passou
2014
+ preservados++;
2015
+ Logger.info(` ✅ CT${i + 1} "${ct.name}": PASSED → PRESERVADO (${ct.duration || 0}ms)`);
2016
+ }
2017
+ else if (ct.status === 'failed') {
2018
+ // ❌ CT que já tinha falhado antes
2019
+ preservados++;
2020
+ Logger.info(` ❌ CT${i + 1} "${ct.name}": FAILED → PRESERVADO (${ct.duration || 0}ms)`);
2021
+ }
2022
+ else if (ct.status === 'skipped') {
2023
+ preservados++;
2024
+ Logger.info(` ⏭️ CT${i + 1} "${ct.name}": SKIPPED → PRESERVADO (${ct.duration || 0}ms)`);
2025
+ }
2026
+ }
2027
+ if (finalizados > 0) {
2028
+ StatementTracker.saveDataToFile();
2029
+ }
2030
+ }
2031
+ /**
2032
+ * ❌ Finaliza CTs "running" como FAILED quando teste FALHOU
2033
+ */
2034
+ static finalizeRunningCTsAsFailed(testName, reason, ctName) {
2035
+ const targetTestName = testName || StatementTracker.getCurrentTestName();
2036
+ const testData = StatementTracker.executions.get(targetTestName);
2037
+ if (!testData) {
2038
+ Logger.warning(`⚠️ [StatementTracker] Nenhum dado encontrado para teste: ${targetTestName}`);
2039
+ return;
2040
+ }
2041
+ let finalizados = 0;
2042
+ let preservados = 0;
2043
+ for (let i = 0; i < testData.cts.length; i++) {
2044
+ const ct = testData.cts[i];
2045
+ if (ctName && ct.name !== ctName) {
2046
+ continue;
2047
+ }
2048
+ if (ct.status === 'running') {
2049
+ // ✅ CORREÇÃO: Analisar statements para determinar status do CT
2050
+ ct.endTime = Date.now();
2051
+ ct.duration = ct.endTime - ct.startTime;
2052
+ // ✅ PROTEÇÃO: Verificar se statements existe e é array
2053
+ const statements = Array.isArray(ct.statements) ? ct.statements : [];
2054
+ // Verificar se alguma statement falhou
2055
+ const hasFailedStatement = statements.some((stmt) => stmt.status === 'failed');
2056
+ const hasRunningStatement = statements.some((stmt) => stmt.status === 'running');
2057
+ if (hasFailedStatement) {
2058
+ // Se alguma statement falhou, CT falhou
2059
+ ct.status = 'failed';
2060
+ ct.error =
2061
+ ct.statements.find((stmt) => stmt.error)?.error ||
2062
+ reason ||
2063
+ 'Statement falhou';
2064
+ testData.failedCTs++;
2065
+ Logger.info(` ❌ CT${i + 1} "${ct.name}": RUNNING → FAILED (${ct.duration}ms) - Statement falhou`);
2066
+ }
2067
+ else if (hasRunningStatement) {
2068
+ // Se tem statement ainda rodando, marcar como failed (não finalizou corretamente)
2069
+ ct.status = 'failed';
2070
+ ct.error = 'Teste interrompido - statements não finalizadas';
2071
+ testData.failedCTs++;
2072
+ Logger.info(` ❌ CT${i + 1} "${ct.name}": RUNNING → FAILED (${ct.duration}ms) - Interrompido`);
2073
+ }
2074
+ else if (statements.length > 0 &&
2075
+ statements.every((stmt) => stmt.status === 'passed')) {
2076
+ // Se todas as statements passaram, CT passou!
2077
+ ct.status = 'passed';
2078
+ testData.passedCTs++;
2079
+ Logger.info(` ✅ CT${i + 1} "${ct.name}": RUNNING → PASSED (${ct.duration}ms) - Todas statements OK`);
2080
+ }
2081
+ else {
2082
+ // Sem statements ou status desconhecido, marcar como failed
2083
+ ct.status = 'failed';
2084
+ ct.error = reason || 'CT sem statements executadas';
2085
+ testData.failedCTs++;
2086
+ Logger.info(` ❌ CT${i + 1} "${ct.name}": RUNNING → FAILED (${ct.duration}ms) - Sem statements`);
2087
+ }
2088
+ testData.totalDuration += ct.duration;
2089
+ finalizados++;
2090
+ }
2091
+ else if (ct.status === 'passed') {
2092
+ // ✅ Preservar CT que já passou
2093
+ preservados++;
2094
+ Logger.info(` ✅ CT${i + 1} "${ct.name}": PASSED → PRESERVADO (${ct.duration || 0}ms)`);
2095
+ }
2096
+ else if (ct.status === 'failed') {
2097
+ // ❌ CT que já tinha falhado antes
2098
+ preservados++;
2099
+ Logger.info(` ❌ CT${i + 1} "${ct.name}": FAILED → PRESERVADO (${ct.duration || 0}ms)`);
2100
+ }
2101
+ else if (ct.status === 'skipped') {
2102
+ preservados++;
2103
+ Logger.info(` ⏭️ CT${i + 1} "${ct.name}": SKIPPED → PRESERVADO (${ct.duration || 0}ms)`);
2104
+ }
2105
+ }
2106
+ if (finalizados > 0) {
2107
+ StatementTracker.saveDataToFile();
2108
+ }
2109
+ }
2110
+ /**
2111
+ * 🧹 LIMPAR DADOS
2112
+ */
2113
+ static clear() {
2114
+ const totalBefore = StatementTracker.getTotalCTs();
2115
+ Logger.info('Stack trace do clear():');
2116
+ StatementTracker.executions.clear();
2117
+ StatementTracker.currentTest = 'Unknown Test';
2118
+ StatementTracker.ctCounter = 0;
2119
+ StatementTracker.currentCTName = null;
2120
+ StatementTracker.lastLoggedCT = null;
2121
+ StatementTracker.lastLoggedRequest = null;
2122
+ StatementTracker.lastRequestTimestamp = 0;
2123
+ }
2124
+ /**
2125
+ * 📡 LOG DE REQUEST COM PROTEÇÃO ANTI-DUPLICAÇÃO
2126
+ * Evita logs duplicados de requisições consecutivas idênticas
2127
+ * @param method Método HTTP (POST, GET, PUT, DELETE)
2128
+ * @param descricao Descrição da requisição
2129
+ */
2130
+ static logRequest(method, descricao) {
2131
+ const now = Date.now();
2132
+ const requestKey = `${method.toUpperCase()}_${descricao}`;
2133
+ // 🔒 PROTEÇÃO: Ignora se é a mesma request nos últimos 500ms
2134
+ if (StatementTracker.lastLoggedRequest === requestKey &&
2135
+ (now - StatementTracker.lastRequestTimestamp) < 500) {
2136
+ return; // Duplicata, ignorar
2137
+ }
2138
+ // Atualizar controle
2139
+ StatementTracker.lastLoggedRequest = requestKey;
2140
+ StatementTracker.lastRequestTimestamp = now;
2141
+ // Formatar horário
2142
+ const time = new Date().toLocaleTimeString('pt-BR', {
2143
+ hour: '2-digit',
2144
+ minute: '2-digit',
2145
+ second: '2-digit'
2146
+ });
2147
+ // Log formatado
2148
+ Logger.info('\n📡 NOVA REQUEST INICIADA');
2149
+ Logger.info(` 🌐 Método: ${method.toUpperCase()}`);
2150
+ Logger.info(` 📋 Descrição: ${descricao}`);
2151
+ Logger.info(` 🕐 Horário: ${time}\n`);
2152
+ }
2153
+ /**
2154
+ * 📋 IMPRIMIR RELATÓRIO
2155
+ */
2156
+ static printReport() {
2157
+ Logger.info('\n📊 === RELATÓRIO AUTOMÁTICO DE CTs ===');
2158
+ const stats = StatementTracker.getStats();
2159
+ Logger.info(`📈 Total: ${stats.totalCTs} CTs`);
2160
+ Logger.info(`✅ Passou: ${stats.passedCTs} CTs`);
2161
+ Logger.info(`❌ Falhou: ${stats.failedCTs} CTs`);
2162
+ Logger.info(`⏱️ Tempo Total: ${stats.totalDuration}ms`);
2163
+ Logger.info('\n📋 Detalhes por teste:');
2164
+ for (const testData of StatementTracker.executions.values()) {
2165
+ Logger.info(`\n🧪 ${testData.testName}`);
2166
+ Logger.info(` 📊 ${testData.totalCTs} CTs - ✅${testData.passedCTs} ❌${testData.failedCTs} - ${testData.totalDuration}ms`);
2167
+ for (let i = 0; i < testData.cts.length; i++) {
2168
+ const ct = testData.cts[i];
2169
+ const statusIcon = ct.status === 'passed' ? '✅' : ct.status === 'failed' ? '❌' : '⏳';
2170
+ Logger.info(` CT${i + 1}: ${statusIcon} ${ct.name} - ${ct.duration || 0}ms`);
2171
+ if (ct.executedActions && ct.executedActions.length > 0) {
2172
+ Logger.info(' 🔍 Ações executadas:');
2173
+ for (const action of ct.executedActions) {
2174
+ const actionStatus = action.success ? '✅' : '❌';
2175
+ Logger.info(` ${actionStatus} ${action.type}: ${action.description}`);
2176
+ }
2177
+ }
2178
+ if (ct.error) {
2179
+ Logger.info(` ❌ ${ct.error}`);
2180
+ }
2181
+ }
2182
+ }
2183
+ }
2184
+ /**
2185
+ * 🚨 MÉTODO DE EMERGÊNCIA: Força criação de CT quando detecção falha
2186
+ * Para projetos consumidores onde a detecção automática não funciona
2187
+ */
2188
+ static forceCreateCT(ctName) {
2189
+ const testName = StatementTracker.getCurrentTestName();
2190
+ // Se não forneceu nome, criar um
2191
+ if (!ctName) {
2192
+ ctName = `CT_${testName}_${Date.now()}`;
2193
+ }
2194
+ Logger.info(`🚨 [forceCreateCT] FORÇANDO criação do CT: "${ctName}"`);
2195
+ Logger.info(` 📋 Para teste: "${testName}"`);
2196
+ const testData = StatementTracker.executions.get(testName);
2197
+ // Verificar se já existe
2198
+ if (testData && testData.cts.length > 0) {
2199
+ const existingCT = testData.cts.find((ct) => ct.name === ctName);
2200
+ if (existingCT) {
2201
+ Logger.warning(`⚠️ [forceCreateCT] CT "${ctName}" já existe - não forçando criação`);
2202
+ return;
2203
+ }
2204
+ }
2205
+ // Força criação
2206
+ StatementTracker.startCT(ctName);
2207
+ StatementTracker.ctAutoCreated = true;
2208
+ Logger.success(`✅ [forceCreateCT] CT forçado criado com sucesso: "${ctName}"`);
2209
+ }
2210
+ /**
2211
+ * ✅ NOVO: Finaliza CT individual com status específico
2212
+ * Chamado pelo CustomReporter quando cada test() termina
2213
+ */
2214
+ static finalizarCT(ctName, status, options) {
2215
+ const targetTestName = options?.testName || StatementTracker.getCurrentTestName();
2216
+ const testData = StatementTracker.executions.get(targetTestName);
2217
+ if (!testData) {
2218
+ // 🔧 Tentar encontrar o CT em qualquer teste existente
2219
+ for (const [name, data] of StatementTracker.executions.entries()) {
2220
+ const ct = data.cts.find((c) => c.name === ctName);
2221
+ if (ct) {
2222
+ // Recursivamente chamar com testName correto
2223
+ StatementTracker.finalizarCT(ctName, status, {
2224
+ testName: name,
2225
+ errorMessage: options?.errorMessage,
2226
+ });
2227
+ return;
2228
+ }
2229
+ }
2230
+ return;
2231
+ }
2232
+ // Encontrar o CT
2233
+ const ct = testData.cts.find((c) => c.name === ctName);
2234
+ if (!ct) {
2235
+ return;
2236
+ }
2237
+ if (!ct.logs || ct.logs.length === 0) {
2238
+ // ✅ CORREÇÃO: Usar hydrateCTLogs que possui a estratégia híbrida robusta
2239
+ StatementTracker.hydrateCTLogs(ct, targetTestName);
2240
+ }
2241
+ // Se já foi finalizado, não sobrescrever
2242
+ if (ct.status === 'passed' || ct.status === 'failed' || ct.status === 'skipped') {
2243
+ return;
2244
+ }
2245
+ // Finalizar o CT
2246
+ ct.endTime = Date.now();
2247
+ ct.duration = ct.endTime - ct.startTime;
2248
+ // ✅ CORREÇÃO CRÍTICA: Respeitar status do Playwright, verificar statements só se status='passed'
2249
+ // Isso garante que CT001 PASSED não seja marcado como FAILED por causa de CT002
2250
+ const statements = Array.isArray(ct.statements) ? ct.statements : [];
2251
+ const hasFailedStatement = statements.some((stmt) => stmt.status === 'failed');
2252
+ // 🎯 LÓGICA CORRIGIDA:
2253
+ // 1. Se Playwright diz PASSED e não há statement failed → PASSED
2254
+ // 2. Se Playwright diz FAILED → FAILED (mesmo que statements passaram)
2255
+ // 3. Se Playwright diz PASSED mas há statement failed → FAILED
2256
+ const resolvedErrorMessage = options?.errorMessage || ct.error ||
2257
+ (status === 'failed'
2258
+ ? 'Teste falhou'
2259
+ : hasFailedStatement
2260
+ ? statements.find((stmt) => stmt.error)?.error || 'Statement falhou'
2261
+ : undefined);
2262
+ if (status === 'passed' && !hasFailedStatement) {
2263
+ // ✅ Teste passou e nenhuma statement falhou
2264
+ ct.status = 'passed';
2265
+ ct.error = undefined;
2266
+ testData.passedCTs++;
2267
+ }
2268
+ else if (status === 'failed') {
2269
+ // ❌ Playwright reportou falha
2270
+ ct.status = 'failed';
2271
+ ct.error = resolvedErrorMessage;
2272
+ testData.failedCTs++;
2273
+ Logger.info(`❌ [finalizarCT] CT "${ctName}": RUNNING → FAILED (${ct.duration}ms) - Playwright reportou erro`);
2274
+ // 🛑 STOP ON FAILURE: Marcar flag se stopOnFailure está ativo
2275
+ if (StatementTracker.isStopOnFailureEnabled()) {
2276
+ StatementTracker.markFailedWithStopOnFailure();
2277
+ }
2278
+ }
2279
+ else if (status === 'passed' && hasFailedStatement) {
2280
+ // ⚠️ Playwright diz passed mas statement falhou
2281
+ ct.status = 'failed';
2282
+ ct.error = resolvedErrorMessage;
2283
+ testData.failedCTs++;
2284
+ Logger.info(`❌ [finalizarCT] CT "${ctName}": RUNNING → FAILED (${ct.duration}ms) - Statement falhou`);
2285
+ // 🛑 STOP ON FAILURE: Marcar flag se stopOnFailure está ativo
2286
+ if (StatementTracker.isStopOnFailureEnabled()) {
2287
+ StatementTracker.markFailedWithStopOnFailure();
2288
+ }
2289
+ }
2290
+ else {
2291
+ // skipped
2292
+ ct.status = 'skipped';
2293
+ ct.error = resolvedErrorMessage;
2294
+ // Não incrementar failed para skipped
2295
+ }
2296
+ testData.totalDuration += ct.duration;
2297
+ StatementTracker.saveDataToFile();
2298
+ }
2299
+ /**
2300
+ * 🎯 OBTER CT ATUAL
2301
+ */
2302
+ static getCurrentCT() {
2303
+ return StatementTracker.currentCTName;
2304
+ }
2305
+ /**
2306
+ * 🛑 Marca que um CT falhou com stopOnFailure ativo
2307
+ */
2308
+ static markFailedWithStopOnFailure() {
2309
+ StatementTracker.hasFailedWithStopOnFailure = true;
2310
+ Logger.info('🛑 [STOP-ON-FAILURE] Flag global ativada - próximos CTs serão pulados');
2311
+ }
2312
+ /**
2313
+ * 🛑 Verifica se algum CT já falhou com stopOnFailure ativo
2314
+ */
2315
+ static hasAnyTestFailedWithStopOnFailure() {
2316
+ return StatementTracker.hasFailedWithStopOnFailure;
2317
+ }
2318
+ /**
2319
+ * 🛑 Reseta a flag de falha (útil para novos describes)
2320
+ */
2321
+ static resetStopOnFailureFlag() {
2322
+ StatementTracker.hasFailedWithStopOnFailure = false;
2323
+ }
2324
+ /**
2325
+ * 🛑 Carrega a flag de stopOnFailure do arquivo JSON (para comunicação entre processos)
2326
+ * Retorna true se a flag foi encontrada e está ativa
2327
+ */
2328
+ static loadStopOnFailureFlagFromFile() {
2329
+ try {
2330
+ const filePath = StatementTracker.getDataFilePath();
2331
+ const fs = require('fs');
2332
+ if (!fs.existsSync(filePath)) {
2333
+ return false;
2334
+ }
2335
+ const content = fs.readFileSync(filePath, 'utf8');
2336
+ const data = JSON.parse(content);
2337
+ if (data.stopOnFailureTriggered === true) {
2338
+ StatementTracker.hasFailedWithStopOnFailure = true;
2339
+ Logger.info('🛑 [STOP-ON-FAILURE] Flag carregada do arquivo - testes subsequentes serão bloqueados');
2340
+ return true;
2341
+ }
2342
+ return false;
2343
+ }
2344
+ catch (error) {
2345
+ // Silenciosamente retorna false se não conseguir ler
2346
+ return false;
2347
+ }
2348
+ }
2349
+ /**
2350
+ * 🛑 Verificar se stopOnFailure está ativo
2351
+ * Esta função permite ao StatementTracker acessar a configuração do TestAnnotations
2352
+ */
2353
+ static isStopOnFailureEnabled() {
2354
+ try {
2355
+ // Importação dinâmica para evitar dependência circular
2356
+ const { TestAnnotations } = require('./TestAnnotations.js');
2357
+ return TestAnnotations?.isStopOnFailureEnabled?.() ?? false;
2358
+ }
2359
+ catch {
2360
+ return false;
2361
+ }
2362
+ }
2363
+ /**
2364
+ * 💾 SALVAR DADOS DOS CTS EM ARQUIVO
2365
+ */
2366
+ static async saveExecutionData(outputPath) {
2367
+ try {
2368
+ Logger.info('💾 [SAVE-EXECUTION] Delegando para sistema de persistência unificado...');
2369
+ // Usar o sistema de persistência unificado
2370
+ StatementTracker.saveDataToFile();
2371
+ // Se outputPath específico foi fornecido, copiar também para lá
2372
+ if (outputPath) {
2373
+ const sourceFilePath = StatementTracker.getDataFilePath();
2374
+ if (fs.existsSync(sourceFilePath)) {
2375
+ const dir = path.dirname(outputPath);
2376
+ if (!fs.existsSync(dir)) {
2377
+ fs.mkdirSync(dir, { recursive: true });
2378
+ }
2379
+ fs.copyFileSync(sourceFilePath, outputPath);
2380
+ Logger.info(`💾 [SAVE-EXECUTION] Dados copiados para: ${outputPath}`);
2381
+ }
2382
+ }
2383
+ Logger.success('💾 [SAVE-EXECUTION] Execução salva com sucesso!');
2384
+ }
2385
+ catch (error) {
2386
+ Logger.error('❌ [SAVE-EXECUTION] Erro ao salvar:', error);
2387
+ }
2388
+ }
2389
+ /**
2390
+ * � OBTER EXECUÇÕES (GETTER PÚBLICO)
2391
+ */
2392
+ static getExecutions() {
2393
+ // Auto-carregar dados se não há execuções carregadas
2394
+ if (StatementTracker.executions.size === 0) {
2395
+ StatementTracker.loadDataFromFile();
2396
+ }
2397
+ return StatementTracker.executions;
2398
+ }
2399
+ /**
2400
+ * �🔗 INTEGRAR COM EXECUTION TRACKER
2401
+ */
2402
+ static integrateWithExecutionTracker() {
2403
+ ;
2404
+ globalThis.__SIMPLE_STATEMENT_TRACKER__ = StatementTracker;
2405
+ globalThis.statementTracker = StatementTracker;
2406
+ globalThis.StatementTracker = StatementTracker;
2407
+ }
2408
+ }
2409
+ // 🔗 AUTO-INTEGRAÇÃO
2410
+ try {
2411
+ StatementTracker.integrateWithExecutionTracker();
2412
+ }
2413
+ catch (error) {
2414
+ Logger.error('❌ Erro na auto-integração:', error);
2415
+ }
2416
+ /**
2417
+ * 🌐 FUNÇÃO AUTOMÁTICA PARA FÁCIL USO
2418
+ */
2419
+ export const interceptStatement = StatementTracker.interceptStatement.bind(StatementTracker);