@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,1905 @@
1
+ import { createRequire } from 'node:module';
2
+ import { request as playwrightRequest, } from '@playwright/test';
3
+ import { IntegrationError, ValidationError, } from '../functions/errors/index.js';
4
+ import { TestContext } from '../testContext/TestContext.js';
5
+ import { UnifiedReportManager } from '../testContext/UnifiedReportManager.js';
6
+ import { Logger } from '../utils/Logger.js';
7
+ import { createCertificate } from './Certificate.js';
8
+ import { JsonResponse } from './JsonResponse.js';
9
+ // 🤖 IMPORTAÇÃO CRÍTICA: Força inicialização do StatementTracker
10
+ import '../hubdocs/StatementTracker.js';
11
+ import { ExecutionTracker } from '../hubdocs/ExecutionTracker.js';
12
+ import { StatementTracker } from '../hubdocs/StatementTracker.js';
13
+ import DynamicConfigManager from '../testhub/DynamicConfigManager.js';
14
+ export { createCertificate };
15
+ /**
16
+ * ✅ APIACTIONS DEFINITIVA: Interface Nova Avançada
17
+ *
18
+ * Interface Unificada:
19
+ * - get(request, descrição, ignoreSSL) -> ApiResponse
20
+ * - post(request, descrição, ignoreSSL) -> ApiResponse
21
+ *
22
+ * Melhorias Incluídas:
23
+ * - Tratamento de erros especializado
24
+ * - Logs estruturados e mascaramento de dados sensíveis
25
+ * - Métricas de performance automáticas
26
+ * - Compatibilidade com TestContext automática
27
+ * - Bypass automático do sistema de proteção
28
+ */
29
+ export class ApiActions {
30
+ static successCount = 0;
31
+ static failureCount = 0;
32
+ static inicioExecucao;
33
+ static fimExecucao;
34
+ static logsPerTest = {};
35
+ static duracoes = [];
36
+ static foiUsado = false;
37
+ static requestCount = 0;
38
+ static contextCache = new Map();
39
+ static dadosGerais = {
40
+ inicio: null,
41
+ fim: null,
42
+ totalRequisicoes: 0,
43
+ totalSuccesso: 0,
44
+ totalFalhas: 0,
45
+ };
46
+ static marcarUsoAutomatico() {
47
+ if (!ApiActions.foiUsado) {
48
+ ApiActions.foiUsado = true;
49
+ ApiActions.registrarInicioExecucao();
50
+ }
51
+ }
52
+ /**
53
+ * ✅ Limpa o cache de contextos, descartando recursos corretamente
54
+ * Deve ser chamado quando um novo CT inicia ou quando o sistema muda
55
+ */
56
+ static async clearContextCache() {
57
+ const disposePromises = [];
58
+ for (const ctx of ApiActions.contextCache.values()) {
59
+ disposePromises.push(ctx.dispose().catch(() => { }));
60
+ }
61
+ await Promise.all(disposePromises);
62
+ ApiActions.contextCache.clear();
63
+ }
64
+ /**
65
+ * ✅ Garante que TestContext.apiContext existe
66
+ */
67
+ static async ensureApiContext() {
68
+ try {
69
+ // Tentar usar TestContext.apiContext se disponível
70
+ return await TestContext.getOrCreateApiContext();
71
+ }
72
+ catch {
73
+ // Fallback: criar contexto direto
74
+ return await playwrightRequest.newContext({
75
+ ignoreHTTPSErrors: true,
76
+ });
77
+ }
78
+ }
79
+ static createApiResponse(statusCode, responseHeaders, responseBody, duration) {
80
+ // ✅ VALIDAÇÃO CRÍTICA: Garantir que os parâmetros são válidos
81
+ const safeStatusCode = typeof statusCode === 'number' ? statusCode : 500;
82
+ const safeHeaders = responseHeaders || {};
83
+ const safeBody = typeof responseBody === 'string'
84
+ ? responseBody
85
+ : JSON.stringify({ error: 'Invalid response body' });
86
+ const safeDuration = typeof duration === 'number' ? duration : 0;
87
+ const jsonResponse = new JsonResponse(ApiActions.tryParseJson(safeBody));
88
+ jsonResponse.setStatus(safeStatusCode);
89
+ const apiResponse = {
90
+ status: () => safeStatusCode,
91
+ getStatus: () => safeStatusCode,
92
+ headers: () => safeHeaders,
93
+ text: () => safeBody,
94
+ getString: (key) => jsonResponse.getString(key),
95
+ get: (key) => jsonResponse.get(key),
96
+ getArray: (key) => jsonResponse.getArray(key),
97
+ getObject: (key) => jsonResponse.getObject(key),
98
+ getFromArray: (index) => jsonResponse.getFromArray(index),
99
+ getStringFromArray: (index, field) => jsonResponse.getStringFromArray(index, field),
100
+ getPath: (...path) => jsonResponse.getPath(...path),
101
+ raw: () => jsonResponse.raw(),
102
+ duration: safeDuration,
103
+ };
104
+ // ✅ VALIDAÇÃO FINAL: Verificar se o objeto criado é válido
105
+ if (!apiResponse || typeof apiResponse.getStatus !== 'function') {
106
+ Logger.error('❌ ApiResponse criado é inválido!');
107
+ throw new Error('Falha crítica ao criar ApiResponse válido');
108
+ }
109
+ return apiResponse;
110
+ }
111
+ static foiUtilizado() {
112
+ return ApiActions.foiUsado || ApiActions.requestCount > 0;
113
+ }
114
+ static registrarInicioExecucao() {
115
+ if (!ApiActions.inicioExecucao) {
116
+ ApiActions.inicioExecucao = new Date();
117
+ ApiActions.dadosGerais.inicio = ApiActions.inicioExecucao;
118
+ console.log(`⏱️ Início da execução API registrado: ${ApiActions.inicioExecucao.toLocaleTimeString()}`);
119
+ }
120
+ }
121
+ static registrarFimExecucao() {
122
+ ApiActions.fimExecucao = new Date();
123
+ ApiActions.dadosGerais.totalRequisicoes = ApiActions.requestCount;
124
+ ApiActions.dadosGerais.totalSuccesso = ApiActions.successCount;
125
+ ApiActions.dadosGerais.totalFalhas = ApiActions.failureCount;
126
+ ApiActions.dadosGerais.fim = ApiActions.fimExecucao;
127
+ if (!ApiActions.dadosGerais.inicio) {
128
+ ApiActions.dadosGerais.inicio = ApiActions.inicioExecucao;
129
+ }
130
+ if (ApiActions.inicioExecucao) {
131
+ const duracao = (ApiActions.fimExecucao.getTime() -
132
+ ApiActions.inicioExecucao.getTime()) /
133
+ 1000;
134
+ Logger.info(`⏱️ Execução de APIs concluída em ${duracao.toFixed(2)}s`);
135
+ }
136
+ }
137
+ static logEvent(message) {
138
+ if (message.includes('[API]') || message.includes('Log registrado')) {
139
+ return;
140
+ }
141
+ ApiActions.marcarUsoAutomatico();
142
+ const testInfo = TestContext.getSafeTestInfo();
143
+ const testId = testInfo?.title || 'global';
144
+ if (!ApiActions.logsPerTest[testId])
145
+ ApiActions.logsPerTest[testId] = [];
146
+ ApiActions.logsPerTest[testId].push(message);
147
+ if (testInfo?.title) {
148
+ try {
149
+ UnifiedReportManager.adicionarLog(testInfo.title, message);
150
+ }
151
+ catch {
152
+ // Silenciosamente ignorar
153
+ }
154
+ }
155
+ }
156
+ /**
157
+ * ✅ MÉTODO GET: Interface Nova Avançada
158
+ *
159
+ * @param request Objeto contendo URL, opções e certificado opcional da requisição
160
+ * @param descricao Descrição da ação para logs e relatórios
161
+ * @param ignoreSSL Se true, ignora erros de certificado SSL (mutuamente exclusivo com certificate)
162
+ * @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
163
+ *
164
+ * ⚠️ IMPORTANTE: Não use ignoreSSL=true junto com certificate no request.
165
+ * Use apenas um dos dois: ignoreSSL OU certificate.
166
+ */
167
+ static async get(request, descricao, ignoreSSL = false) {
168
+ ApiActions.marcarUsoAutomatico();
169
+ return ApiActions._request('get', request, descricao, ignoreSSL);
170
+ }
171
+ /**
172
+ * ✅ MÉTODO POST: Interface Nova Avançada
173
+ *
174
+ * @param request Objeto contendo URL, opções e certificado opcional da requisição
175
+ * @param descricao Descrição da ação para logs e relatórios
176
+ * @param ignoreSSL Se true, ignora erros de certificado SSL (mutuamente exclusivo com certificate)
177
+ * @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
178
+ *
179
+ * ⚠️ IMPORTANTE: Não use ignoreSSL=true junto com certificate no request.
180
+ * Use apenas um dos dois: ignoreSSL OU certificate.
181
+ */
182
+ static async post(request, descricao, ignoreSSL = false) {
183
+ ApiActions.marcarUsoAutomatico();
184
+ return ApiActions._request('post', request, descricao, ignoreSSL);
185
+ }
186
+ /**
187
+ * ✅ MÉTODO PUT: Interface Nova Avançada
188
+ *
189
+ * @param request Objeto contendo URL e opções da requisição
190
+ * @param descricao Descrição da ação para logs e relatórios
191
+ * @param ignoreSSL Se true, ignora erros de certificado SSL
192
+ * @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
193
+ */
194
+ static async put(request, descricao, ignoreSSL = false) {
195
+ ApiActions.marcarUsoAutomatico();
196
+ return ApiActions._request('put', request, descricao, ignoreSSL);
197
+ }
198
+ /**
199
+ * ✅ MÉTODO PATCH: Interface Nova Avançada
200
+ *
201
+ * @param request Objeto contendo URL e opções da requisição
202
+ * @param descricao Descrição da ação para logs e relatórios
203
+ * @param ignoreSSL Se true, ignora erros de certificado SSL
204
+ * @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
205
+ */
206
+ static async patch(request, descricao, ignoreSSL = false) {
207
+ ApiActions.marcarUsoAutomatico();
208
+ return ApiActions._request('patch', request, descricao, ignoreSSL);
209
+ }
210
+ /**
211
+ * ✅ MÉTODO DELETE: Interface Nova Avançada
212
+ *
213
+ * @param request Objeto contendo URL e opções da requisição
214
+ * @param descricao Descrição da ação para logs e relatórios
215
+ * @param ignoreSSL Se true, ignora erros de certificado SSL
216
+ * @returns Promise<ApiResponse> - Resposta avançada com métodos especializados
217
+ */
218
+ static async delete(request, descricao, ignoreSSL = false) {
219
+ ApiActions.marcarUsoAutomatico();
220
+ return ApiActions._request('delete', request, descricao, ignoreSSL);
221
+ }
222
+ /**
223
+ * ✅ COMPATIBILIDADE: Método especial para registrar duração
224
+ * @param nome Nome do teste
225
+ * @param duracao Duração em milissegundos
226
+ */
227
+ static registrarDuracaoDoTeste(nome, duracao) {
228
+ ApiActions.duracoes.push({ nome, duracao });
229
+ }
230
+ /**
231
+ * Cria um objeto FormData para requisições multipart/form-data.
232
+ * @param fields Campos simples do formulário (chave/valor).
233
+ * @param files Arquivos para upload, onde o valor pode ser um Buffer ou caminho do arquivo.
234
+ * @returns Instância de FormData pronta para uso em requisições.
235
+ */
236
+ static createFormData(fields, files) {
237
+ // ESM compat: use createRequire to import CommonJS module synchronously
238
+ const require = createRequire(import.meta.url);
239
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires
240
+ const FormData = require('form-data');
241
+ const form = new FormData();
242
+ for (const [key, value] of Object.entries(fields)) {
243
+ form.append(key, value);
244
+ }
245
+ if (files) {
246
+ for (const [key, value] of Object.entries(files)) {
247
+ // biome-ignore lint/suspicious/noExplicitAny: FormData.append accepts Buffer|string but typings are from dynamic require
248
+ form.append(key, value);
249
+ }
250
+ }
251
+ return form;
252
+ }
253
+ /**
254
+ * Cria um contexto de requisição Playwright com certificado de cliente.
255
+ * @param options Opções para configuração do contexto, incluindo caminhos de certificado, headers, timeout, etc.
256
+ * @returns Contexto de requisição configurado para autenticação mútua ou SSL customizado.
257
+ */
258
+ static async prepareContextWithCert(options) {
259
+ const clientCertificates = [];
260
+ if (options.certPath && options.keyPath) {
261
+ clientCertificates.push({
262
+ origin: options.origin,
263
+ certPath: options.certPath,
264
+ keyPath: options.keyPath,
265
+ passphrase: options.passphrase,
266
+ });
267
+ }
268
+ else if (options.pfxPath) {
269
+ clientCertificates.push({
270
+ origin: options.origin,
271
+ pfxPath: options.pfxPath,
272
+ passphrase: options.passphrase,
273
+ });
274
+ }
275
+ return playwrightRequest.newContext({
276
+ ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? true,
277
+ clientCertificates,
278
+ extraHTTPHeaders: options.extraHTTPHeaders,
279
+ timeout: options.timeout,
280
+ userAgent: options.userAgent,
281
+ });
282
+ }
283
+ /**
284
+ * Centraliza execução de requisições HTTP, logs e métricas.
285
+ * @param method Método HTTP a ser executado (get, post, put, patch, delete).
286
+ * @param request Objeto contendo a URL e opções da requisição.
287
+ * @param descricao Descrição da requisição para logs e relatórios.
288
+ * @param ignoreSSL Se true, ignora erros de certificado SSL.
289
+ * @returns Resposta padronizada da API.
290
+ */
291
+ static async _request(method, request, descricao, ignoreSSL) {
292
+ // ✅ Validação de URL obrigatória (boundary check)
293
+ if (!request.url || typeof request.url !== 'string') {
294
+ throw new ValidationError('AC-API-001', 'URL da requisição é obrigatória e deve ser uma string', 'A URL da requisição não foi informada ou não é uma string válida.');
295
+ }
296
+ const trimmedUrl = request.url.trim();
297
+ if (!/^(https?:\/\/.+|\/)/i.test(trimmedUrl)) {
298
+ throw new ValidationError('AC-API-001', `URL inválida: "${trimmedUrl}". A URL deve começar com http://, https:// ou / (relativa com baseURL do sistema)`, `A URL informada ("${trimmedUrl}") não é válida. Use http://, https:// ou caminho relativo iniciando com /.`);
299
+ }
300
+ ApiActions.marcarUsoAutomatico();
301
+ // 🆕 Registrar statement no StatementTracker
302
+ let statementTimestamp = null;
303
+ try {
304
+ statementTimestamp = StatementTracker.startStatement('ApiActions', `${method.toUpperCase()}: ${descricao}`);
305
+ }
306
+ catch (error) {
307
+ console.warn('Não foi possível registrar statement no StatementTracker:', error);
308
+ }
309
+ // 🚀 LÓGICA INTELIGENTE: Certificados sempre usam SSL bypass
310
+ const effectiveIgnoreSSL = ignoreSSL || !!request.certificate;
311
+ // 📡 LOG COM PROTEÇÃO ANTI-DUPLICAÇÃO (via StatementTracker)
312
+ StatementTracker.logRequest(method, descricao);
313
+ ApiActions.logEvent(`🔄 Executando ${method.toUpperCase()}: ${descricao}`);
314
+ const startTime = Date.now();
315
+ // 📚 CRÍTICO: Interceptar para documentação automática e StatementTracker
316
+ let docId = null;
317
+ try {
318
+ // 🔄 ExecutionTracker - Interceptação CRÍTICA para persistência (CORRIGIDO ESM)
319
+ try {
320
+ if (ExecutionTracker) {
321
+ const apiCallData = {
322
+ id: `api_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
323
+ method: method.toUpperCase(),
324
+ url: request.url,
325
+ headers: request.options?.headers || {},
326
+ payload: request.options?.data || {},
327
+ timestamp: new Date().toISOString(),
328
+ testName: descricao || 'current-test',
329
+ environment: process.env.NODE_ENV || 'test',
330
+ success: true, // Will be updated later
331
+ statusCode: 0, // Will be updated later
332
+ duration: 0, // Will be updated later
333
+ response: null, // Will be updated later
334
+ error: null,
335
+ };
336
+ ExecutionTracker.trackAPI(apiCallData);
337
+ docId = apiCallData.id;
338
+ }
339
+ }
340
+ catch (trackerError) {
341
+ console.log(`📚 ExecutionTracker import falhou: ${String(trackerError)}`);
342
+ }
343
+ // ✅ REMOVIDO: AutoDocs interceptação duplicada - usando apenas ExecutionTracker
344
+ }
345
+ catch (error) {
346
+ // Log de debug apenas se debug ativo
347
+ docId = null;
348
+ }
349
+ try {
350
+ // ✅ CORRIGIDO: Obter baseURL ANTES de criar o contexto
351
+ let baseURL;
352
+ const isAbsoluteUrl = request.url.startsWith('http://') || request.url.startsWith('https://');
353
+ // ✅ SEMPRE tentar obter baseURL do AutoCore Hub ANTES de criar contexto
354
+ if (!isAbsoluteUrl) {
355
+ try {
356
+ const configManager = DynamicConfigManager.getInstance();
357
+ const maxAttempts = 10;
358
+ const waitIntervalMs = 60;
359
+ let hasSystemConfigured = null;
360
+ let apiConfig = configManager.getApiConfig();
361
+ const waitForSystemConfig = async () => {
362
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
363
+ try {
364
+ hasSystemConfigured = StatementTracker.getCurrentCTSystem();
365
+ }
366
+ catch {
367
+ hasSystemConfigured = null;
368
+ }
369
+ apiConfig = configManager.getApiConfig();
370
+ if (apiConfig?.baseUrl && hasSystemConfigured) {
371
+ return;
372
+ }
373
+ await new Promise((resolve) => setTimeout(resolve, waitIntervalMs * (attempt + 1)));
374
+ }
375
+ };
376
+ // ✅ Esperar sistema ser configurado quando ainda não houver base ou CT estiver desconhecido
377
+ if (!apiConfig?.baseUrl) {
378
+ console.log('⏳ [AutoCore Hub] Aguardando configuração do sistema (baseUrl indisponível)...');
379
+ await waitForSystemConfig();
380
+ }
381
+ else {
382
+ try {
383
+ hasSystemConfigured = StatementTracker.getCurrentCTSystem();
384
+ }
385
+ catch {
386
+ hasSystemConfigured = null;
387
+ }
388
+ if (!hasSystemConfigured) {
389
+ await waitForSystemConfig();
390
+ }
391
+ }
392
+ if (apiConfig?.baseUrl) {
393
+ // ✅ CORRIGIDO: Remover barra final se existir (Playwright adiciona automaticamente)
394
+ baseURL = apiConfig.baseUrl.replace(/\/$/, '');
395
+ const resolvedSystem = hasSystemConfigured || 'desconhecido';
396
+ }
397
+ else {
398
+ console.log('⚠️ [AutoCore Hub] Nenhuma configuração de sistema encontrada - usando URL absoluta ou chamada sem setSystem()');
399
+ }
400
+ }
401
+ catch (error) {
402
+ console.log(`⚠️ [AutoCore Hub] Erro ao obter baseURL: ${error}`);
403
+ }
404
+ }
405
+ const context = await ApiActions.prepareContext(effectiveIgnoreSSL, request.certificate, request.url, baseURL);
406
+ // Validar que URL relativa tenha baseURL resolvido
407
+ if (!isAbsoluteUrl && !baseURL) {
408
+ throw new ValidationError('AC-API-002', `URL relativa "${request.url}" requer baseURL. Verifique se o sistema foi configurado via setSystem() e possui URL cadastrada.`, 'Use TestAnnotations.setSystem() antes de chamar ApiActions ou forneça uma URL absoluta (http/https).');
409
+ }
410
+ // ✅ LOGS DETALHADOS PARA DEBUG
411
+ console.log('\n-------------------REQUEST-------------------\n');
412
+ // 🎯 Mostrar URL completa no log (baseURL + endpoint apenas se não for absoluta)
413
+ const fullUrl = baseURL && !isAbsoluteUrl
414
+ ? `${baseURL}${request.url}`
415
+ : request.url;
416
+ Logger.info(`ENDPOINT: ${fullUrl}`);
417
+ Logger.info(`METHOD: ${method.toUpperCase()}`);
418
+ Logger.info(`IGNORE_SSL: ${ignoreSSL}`);
419
+ Logger.info(`CONTEXT: ${context ? 'OK' : 'NULL'}`);
420
+ Logger.info(`CERTIFICATE: ${request.certificate ? 'YES' : 'NO'}`);
421
+ if (request.options?.headers) {
422
+ const maskedHeaders = Object.entries(request.options.headers)
423
+ .map(([key, value]) => `${key} : ${ApiActions.shouldMaskHeader(key) ? '***' : value}`)
424
+ .join('][');
425
+ Logger.info(`HEADERS: [${maskedHeaders}]`);
426
+ }
427
+ if (request.options?.data !== undefined) {
428
+ const bodyString = typeof request.options.data === 'string'
429
+ ? request.options.data
430
+ : JSON.stringify(request.options.data);
431
+ const formattedData = ApiActions.formatAndMaskBody(bodyString);
432
+ Logger.info(`BODY:\n${formattedData}`);
433
+ }
434
+ if (request.options?.form !== undefined) {
435
+ Logger.info(`FORM (x-www-form-urlencoded): ${JSON.stringify(request.options.form)}`);
436
+ }
437
+ // ✅ DIAGNÓSTICO: Validar URL (apenas para URLs absolutas)
438
+ if (request.url.startsWith('http://') ||
439
+ request.url.startsWith('https://')) {
440
+ try {
441
+ new URL(request.url); // Validação - lança erro se inválida
442
+ }
443
+ catch (urlError) {
444
+ console.error(`❌ URL inválida: ${request.url}`);
445
+ throw new Error(`URL inválida: ${request.url}`);
446
+ }
447
+ }
448
+ else if (baseURL) {
449
+ // URL relativa com baseURL - validar a combinação
450
+ try {
451
+ // ✅ CORRIGIDO: Playwright concatena baseURL + endpoint (mantém / inicial do endpoint)
452
+ const fullUrlForValidation = `${baseURL}${request.url}`;
453
+ new URL(fullUrlForValidation); // Validação - lança erro se inválida
454
+ }
455
+ catch (urlError) {
456
+ const fullUrl = `${baseURL}${request.url}`;
457
+ console.error(`❌ URL inválida após combinar com baseURL: ${fullUrl}`);
458
+ throw new Error(`URL inválida: ${fullUrl}`);
459
+ }
460
+ }
461
+ // 🎯 Adicionar params (query parameters) à URL se existirem
462
+ let finalUrl = request.url;
463
+ if (request.options?.params) {
464
+ const queryParams = new URLSearchParams();
465
+ for (const [key, value] of Object.entries(request.options.params)) {
466
+ queryParams.append(key, String(value));
467
+ }
468
+ const separator = finalUrl.includes('?') ? '&' : '?';
469
+ finalUrl = `${finalUrl}${separator}${queryParams.toString()}`;
470
+ console.log(`🎯 [AutoCore Hub] Query params adicionados: ${queryParams.toString()}`);
471
+ }
472
+ // ✅ NOVO: Processar form (x-www-form-urlencoded) se existir
473
+ const { form: _formField, ...finalOptions } = { ...request.options };
474
+ if (request.options?.form) {
475
+ const formData = new URLSearchParams();
476
+ for (const [key, value] of Object.entries(request.options.form)) {
477
+ formData.append(key, String(value));
478
+ }
479
+ finalOptions.data = formData.toString();
480
+ finalOptions.headers = {
481
+ ...finalOptions.headers,
482
+ 'Content-Type': 'application/x-www-form-urlencoded',
483
+ };
484
+ Logger.info(`📝 [AutoCore Hub] Form convertido para x-www-form-urlencoded: ${formData.toString()}`);
485
+ }
486
+ // Executa requisição
487
+ const response = await context[method](finalUrl, finalOptions);
488
+ // ✅ VALIDAÇÃO CRÍTICA: Verificar se response é válido
489
+ if (!response) {
490
+ Logger.error(`❌ Response é null/undefined para ${method.toUpperCase()} ${finalUrl}`);
491
+ throw new Error(`Response inválido recebido para ${method.toUpperCase()} ${finalUrl}`);
492
+ }
493
+ // ✅ LOG APÓS RECEBER RESPONSE
494
+ Logger.success(`Response recebida - Status: ${response?.status() || 'UNDEFINED'}`);
495
+ Logger.success(`Response type: ${typeof response}`);
496
+ const endTime = Date.now();
497
+ const duration = endTime - startTime;
498
+ // ✅ VALIDAÇÃO CRÍTICA: Verificar se métodos existem
499
+ let responseBody;
500
+ let statusCode;
501
+ try {
502
+ statusCode = response.status();
503
+ if (typeof statusCode !== 'number') {
504
+ Logger.error(`❌ Status code inválido: ${statusCode}`);
505
+ statusCode = 500;
506
+ }
507
+ }
508
+ catch (error) {
509
+ Logger.error(`❌ Erro ao obter status code: ${error}`);
510
+ statusCode = 500;
511
+ }
512
+ try {
513
+ responseBody = await response.text();
514
+ if (typeof responseBody !== 'string') {
515
+ Logger.error(`❌ Response body inválido: ${typeof responseBody}`);
516
+ responseBody = JSON.stringify({ error: 'Invalid response body' });
517
+ }
518
+ }
519
+ catch (error) {
520
+ Logger.error(`❌ Erro ao obter response body: ${error}`);
521
+ responseBody = JSON.stringify({
522
+ error: 'Failed to read response body',
523
+ details: error instanceof Error ? error.message : String(error),
524
+ });
525
+ }
526
+ // 📚 CRÍTICO: Atualizar documentação automática com resposta
527
+ try {
528
+ // 🔄 ExecutionTracker - Sistema único de atualização (evita duplicação)
529
+ if (docId) {
530
+ if (ExecutionTracker) {
531
+ let responseData = null;
532
+ try {
533
+ responseData = responseBody
534
+ ? JSON.parse(responseBody)
535
+ : responseBody;
536
+ }
537
+ catch {
538
+ responseData = responseBody; // Se não for JSON válido, usar como string
539
+ }
540
+ ExecutionTracker.updateAPIResult(docId, responseData, statusCode);
541
+ }
542
+ }
543
+ // ✅ REMOVIDO: AutoDocs update duplicado - usando apenas ExecutionTracker
544
+ }
545
+ catch (error) {
546
+ // Log de debug apenas se debug ativo
547
+ if (process.env.AUTOCORE_DEBUG_LOGS === 'true') {
548
+ console.log(`📚 AutoDocs update falhou: ${String(error)}`);
549
+ }
550
+ }
551
+ // Log da resposta
552
+ console.log('\n-------------------RESPONSE-------------------\n');
553
+ Logger.info(`STATUS CODE: ${statusCode}`);
554
+ const responseHeaders = response.headers();
555
+ const maskedResponseHeaders = Object.entries(responseHeaders)
556
+ .map(([key, value]) => `${key} : ${ApiActions.shouldMaskHeader(key) ? '***' : value}`)
557
+ .join('][');
558
+ Logger.info(`HEADERS: [${maskedResponseHeaders}]`);
559
+ const formattedBody = ApiActions.formatAndMaskBody(responseBody);
560
+ Logger.info(`BODY:\n${formattedBody}`);
561
+ ApiActions.requestCount++;
562
+ if (statusCode >= 200 && statusCode < 300) {
563
+ ApiActions.successCount++;
564
+ ApiActions.logTestResult(descricao, true, duration);
565
+ }
566
+ else {
567
+ ApiActions.failureCount++;
568
+ ApiActions.logTestResult(descricao, false, duration);
569
+ }
570
+ const apiResponse = ApiActions.createApiResponse(statusCode, responseHeaders, responseBody, duration);
571
+ // 📝 NOVO: Registrar ação automaticamente no Statement ativo COM LOGS COMPLETOS
572
+ try {
573
+ // 🆕 Preparar request headers mascarados
574
+ const maskedReqHeaders = {};
575
+ if (request.options?.headers) {
576
+ for (const [key, value] of Object.entries(request.options.headers)) {
577
+ maskedReqHeaders[key] = ApiActions.shouldMaskHeader(key) ? '***' : String(value);
578
+ }
579
+ }
580
+ // 🆕 Preparar request body
581
+ const reqBody = request.options?.data !== undefined
582
+ ? (typeof request.options.data === 'string'
583
+ ? request.options.data
584
+ : JSON.stringify(request.options.data, null, 2))
585
+ : request.options?.form !== undefined
586
+ ? JSON.stringify(request.options.form)
587
+ : undefined;
588
+ // 🆕 Preparar response headers mascarados
589
+ const maskedRespHeaders = {};
590
+ for (const [key, value] of Object.entries(responseHeaders)) {
591
+ maskedRespHeaders[key] = ApiActions.shouldMaskHeader(key) ? '***' : String(value);
592
+ }
593
+ StatementTracker.recordAction('API', `${method.toUpperCase()} ${request.url}`, statusCode >= 200 && statusCode < 300, duration, {
594
+ method: method.toUpperCase(),
595
+ url: request.url,
596
+ statusCode,
597
+ // 🆕 Logs completos de API
598
+ requestHeaders: maskedReqHeaders,
599
+ requestBody: ApiActions.formatAndMaskBody(reqBody || ''),
600
+ responseHeaders: maskedRespHeaders,
601
+ responseBody: ApiActions.formatAndMaskBody(responseBody),
602
+ });
603
+ }
604
+ catch (error) {
605
+ // Falha silenciosa - não afetar funcionalidade principal
606
+ }
607
+ // 🆕 Finalizar statement com sucesso
608
+ if (statementTimestamp) {
609
+ try {
610
+ const success = statusCode >= 200 && statusCode < 300;
611
+ const statusMessage = success
612
+ ? `✅ ${method.toUpperCase()} ${descricao}: ${statusCode} (${duration}ms)`
613
+ : `⚠️ ${method.toUpperCase()} ${descricao}: ${statusCode} (${duration}ms)`;
614
+ StatementTracker.finishStatement(statementTimestamp, success, statusMessage);
615
+ }
616
+ catch (error) {
617
+ console.warn('Não foi possível finalizar statement no StatementTracker:', error);
618
+ }
619
+ }
620
+ return apiResponse;
621
+ }
622
+ catch (error) {
623
+ const endTime = Date.now();
624
+ const duration = endTime - startTime;
625
+ // 🔐 FALLBACK PROGRESSIVO: Se erro de certificado e existe certificado, tentar outras estratégias
626
+ if (error instanceof Error &&
627
+ request.certificate &&
628
+ (error.message.includes('mac verify failure') ||
629
+ error.message.includes('certificate verify failed') ||
630
+ error.message.includes('ssl routines') ||
631
+ error.message.includes('SSL') ||
632
+ error.message.includes('TLS'))) {
633
+ console.warn(`🔐 SSL ERROR DETECTADO: ${error.message}`);
634
+ console.warn('🚀 FALLBACK PROGRESSIVO: Tentando outras estratégias de certificado...');
635
+ try {
636
+ // ✅ NOVO: Tentar próximas estratégias ordenadas por eficácia (SEM PROXY primeiro)
637
+ const fallbackStrategies = [
638
+ {
639
+ name: 'SEM PROXY',
640
+ useProxy: false,
641
+ ignoreSSL: true,
642
+ certificate: true,
643
+ specialHeaders: false,
644
+ },
645
+ {
646
+ name: 'COMPLETO',
647
+ useProxy: true,
648
+ ignoreSSL: true,
649
+ certificate: true,
650
+ specialHeaders: false,
651
+ },
652
+ {
653
+ name: 'SEM HEADERS',
654
+ useProxy: true,
655
+ ignoreSSL: true,
656
+ certificate: true,
657
+ specialHeaders: false,
658
+ },
659
+ {
660
+ name: 'SSL ONLY',
661
+ useProxy: false,
662
+ ignoreSSL: true,
663
+ certificate: false,
664
+ specialHeaders: false,
665
+ },
666
+ {
667
+ name: 'FALLBACK',
668
+ useProxy: false,
669
+ ignoreSSL: true,
670
+ certificate: false,
671
+ specialHeaders: false,
672
+ },
673
+ {
674
+ name: 'PADRÃO',
675
+ useProxy: false,
676
+ ignoreSSL: false,
677
+ certificate: false,
678
+ specialHeaders: false,
679
+ },
680
+ ];
681
+ let fallbackSuccess = false;
682
+ for (const strategy of fallbackStrategies) {
683
+ console.log(`🔄 FALLBACK: Tentando estratégia ${strategy.name}...`);
684
+ try {
685
+ const fallbackContext = await ApiActions.tryProgressiveStrategy(strategy, request.certificate, request.url, 'http://proxy.redecorp.br:8080');
686
+ if (fallbackContext) {
687
+ console.log(`🔄 Executando requisição com estratégia ${strategy.name}...`);
688
+ const fallbackResponse = await fallbackContext[method](request.url, request.options);
689
+ if (!fallbackResponse) {
690
+ throw new Error(`Response inválido recebido para ${method.toUpperCase()} ${request.url}`);
691
+ }
692
+ const fallbackStatusCode = fallbackResponse.status();
693
+ const fallbackHeaders = await fallbackResponse.headers();
694
+ const fallbackBody = await fallbackResponse.text();
695
+ const fallbackEndTime = Date.now();
696
+ const fallbackDuration = fallbackEndTime - startTime;
697
+ console.log(`✅ FALLBACK FUNCIONOU! Estratégia ${strategy.name} - Status: ${fallbackStatusCode} em ${fallbackDuration}ms`);
698
+ fallbackSuccess = true;
699
+ return ApiActions.createApiResponse(fallbackStatusCode, fallbackHeaders, fallbackBody, fallbackDuration);
700
+ }
701
+ }
702
+ catch (strategyError) {
703
+ console.log(`❌ Estratégia ${strategy.name} falhou: ${strategyError instanceof Error ? strategyError.message : String(strategyError)}`);
704
+ }
705
+ }
706
+ if (!fallbackSuccess) {
707
+ throw new Error('Todas as estratégias de fallback falharam');
708
+ }
709
+ }
710
+ catch (fallbackError) {
711
+ console.error(`❌ FALLBACK PROGRESSIVO também falhou: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
712
+ // Continuar com erro original
713
+ }
714
+ }
715
+ // ✅ DIAGNÓSTICO DETALHADO: Log completo do erro
716
+ console.error('\n-------------------ERROR DETAILS-------------------');
717
+ console.error(`❌ Erro durante ${method.toUpperCase()} para ${request.url}`);
718
+ console.error(`❌ Tipo do erro: ${error instanceof Error ? error.constructor.name : typeof error}`);
719
+ console.error(`❌ Mensagem: ${error instanceof Error ? error.message : String(error)}`);
720
+ console.error(`❌ Stack: ${error instanceof Error ? error.stack : 'N/A'}`);
721
+ console.error(`❌ Duração até falha: ${duration}ms`);
722
+ console.error(`❌ Certificado usado: ${request.certificate ? 'SIM' : 'NÃO'}`);
723
+ console.error(`❌ SSL bypass aplicado: ${effectiveIgnoreSSL}`);
724
+ console.error('---------------------------------------------------\n');
725
+ // 📚 CRÍTICO: Atualizar documentação automática com erro
726
+ try {
727
+ // 🔄 ExecutionTracker - Sistema único de atualização (evita duplicação)
728
+ if (docId) {
729
+ if (ExecutionTracker) {
730
+ ExecutionTracker.updateAPIResult(docId, {
731
+ error: true,
732
+ message: error instanceof Error ? error.message : String(error),
733
+ }, 500, error);
734
+ }
735
+ }
736
+ }
737
+ catch (updateError) {
738
+ // Log de debug apenas se debug ativo
739
+ if (process.env.AUTOCORE_DEBUG_LOGS === 'true') {
740
+ console.log(`📚 AutoDocs error update falhou: ${String(updateError)}`);
741
+ }
742
+ }
743
+ ApiActions.failureCount++;
744
+ ApiActions.logTestResult(descricao, false, duration);
745
+ // 🎯 NOVO: Criar erro especializado com base no tipo da falha
746
+ let apiError;
747
+ if (error instanceof Error) {
748
+ // Verificar tipo específico do erro
749
+ if (error.message.includes('net::ERR_') ||
750
+ error.message.includes('ECONNREFUSED') ||
751
+ error.message.includes('ETIMEDOUT')) {
752
+ // Erro de rede
753
+ apiError = IntegrationError.networkError(request.url, error, {
754
+ metadata: {
755
+ method: method.toUpperCase(),
756
+ description: descricao,
757
+ duration,
758
+ ignoreSSL,
759
+ },
760
+ });
761
+ }
762
+ else if (error.message.includes('timeout')) {
763
+ // Erro de timeout
764
+ apiError = IntegrationError.timeoutError(`${method.toUpperCase()} ${request.url}`, 30_000, {
765
+ metadata: {
766
+ method: method.toUpperCase(),
767
+ description: descricao,
768
+ duration,
769
+ url: request.url,
770
+ },
771
+ });
772
+ }
773
+ else if (error.message.includes('Invalid URL') ||
774
+ error.message.includes('Protocol')) {
775
+ // Erro de URL inválida
776
+ apiError = ValidationError.invalidUrl(request.url, error.message, {
777
+ metadata: {
778
+ method: method.toUpperCase(),
779
+ description: descricao,
780
+ },
781
+ });
782
+ }
783
+ else {
784
+ // Erro genérico de API
785
+ apiError = IntegrationError.apiRequestFailed(request.url, 0, error.message, {
786
+ metadata: {
787
+ method: method.toUpperCase(),
788
+ description: descricao,
789
+ duration,
790
+ originalError: error.message,
791
+ },
792
+ });
793
+ }
794
+ }
795
+ else {
796
+ // Erro desconhecido
797
+ apiError = IntegrationError.apiRequestFailed(request.url, 0, String(error), {
798
+ metadata: {
799
+ method: method.toUpperCase(),
800
+ description: descricao,
801
+ duration,
802
+ originalError: String(error),
803
+ },
804
+ });
805
+ }
806
+ // 📝 Log do erro especializado
807
+ Logger.error(`❌ ${apiError.getUserFriendlyMessage()}`);
808
+ ApiActions.logEvent(`❌ ${apiError.userMessage}`);
809
+ // ✅ CRÍTICO: SEMPRE retornar ApiResponse válida com informações úteis do erro
810
+ const errorResponse = ApiActions.createApiResponse(500, {}, JSON.stringify({
811
+ error: true,
812
+ code: apiError.code,
813
+ message: apiError.userMessage,
814
+ type: 'API_REQUEST_FAILED',
815
+ details: apiError.message,
816
+ suggestions: apiError.suggestions,
817
+ errorId: apiError.id,
818
+ timestamp: apiError.timestamp,
819
+ recoverable: apiError.recoverable,
820
+ }), duration);
821
+ // 📝 NOVO: Registrar ação com falha no Statement ativo
822
+ try {
823
+ StatementTracker.recordAction('API', `${method.toUpperCase()} ${request.url}`, false, duration, {
824
+ method: method.toUpperCase(),
825
+ url: request.url,
826
+ statusCode: 500,
827
+ });
828
+ }
829
+ catch (error) {
830
+ // Falha silenciosa - não afetar funcionalidade principal
831
+ }
832
+ // 🆕 Finalizar statement com erro
833
+ if (statementTimestamp) {
834
+ try {
835
+ StatementTracker.finishStatement(statementTimestamp, false, `❌ ${method.toUpperCase()} ${descricao}: ${apiError.userMessage}`);
836
+ }
837
+ catch (error) {
838
+ console.warn('Não foi possível finalizar statement no StatementTracker:', error);
839
+ }
840
+ }
841
+ Logger.info(`🔧 Retornando ApiResponse com erro estruturado - ID: ${apiError.id}`);
842
+ return errorResponse;
843
+ }
844
+ }
845
+ static logTestResult(descricao, success, duration) {
846
+ const timestamp = new Date().toLocaleTimeString('pt-BR', {
847
+ hour12: false,
848
+ hour: '2-digit',
849
+ minute: '2-digit',
850
+ second: '2-digit',
851
+ fractionalSecondDigits: 3,
852
+ });
853
+ const status = success ? 'PASSED' : 'FAILED';
854
+ const statusIcon = success ? '✅' : '❌';
855
+ Logger.info(`[ STEP ] ${descricao} [ ${status} ] `);
856
+ console.log('');
857
+ const logMessage = `${statusIcon} ${descricao} - ${status} (${duration}ms)`;
858
+ const testInfo = TestContext.getSafeTestInfo();
859
+ const testId = testInfo?.title || 'global';
860
+ if (!ApiActions.logsPerTest[testId])
861
+ ApiActions.logsPerTest[testId] = [];
862
+ ApiActions.logsPerTest[testId].push(logMessage);
863
+ if (testInfo?.title) {
864
+ try {
865
+ UnifiedReportManager.adicionarLog(testInfo.title, logMessage);
866
+ }
867
+ catch {
868
+ // Silenciosamente ignorar
869
+ }
870
+ }
871
+ }
872
+ /**
873
+ * Valida se dois valores são iguais.
874
+ * Registra sucesso ou falha nos logs e incrementa métricas.
875
+ * Lança erro se os valores não forem iguais.
876
+ * @param actual Valor atual obtido.
877
+ * @param expected Valor esperado.
878
+ * @param message Mensagem descritiva da validação.
879
+ * @throws {ValidationError} Se os valores não forem iguais.
880
+ */
881
+ static validateEquals(actual, expected, message) {
882
+ ApiActions.marcarUsoAutomatico();
883
+ const timestamp = new Date().toLocaleTimeString('pt-BR', {
884
+ hour12: false,
885
+ hour: '2-digit',
886
+ minute: '2-digit',
887
+ second: '2-digit',
888
+ fractionalSecondDigits: 3,
889
+ });
890
+ if (actual === expected) {
891
+ ApiActions.successCount++;
892
+ const successMessage = `✅ Validação passou: ${message} (${actual} === ${expected})`;
893
+ ApiActions.logEvent(successMessage);
894
+ Logger.success(`Validação passou: ${message}`);
895
+ }
896
+ else {
897
+ ApiActions.failureCount++;
898
+ console.log(`${timestamp}`);
899
+ console.log(`[ STEP ] ${message} [ FAILED ] `);
900
+ const errorMessage = `❌ Validação falhou: ${message} (${actual} !== ${expected})`;
901
+ ApiActions.logEvent(errorMessage);
902
+ Logger.error(`Validação falhou: ${message} - Esperado: ${expected}, Atual: ${actual}`);
903
+ throw new ValidationError('API_VALIDATION_FAILED', `Validation failed: ${message} - Expected: ${expected}, Actual: ${actual}`, `Validação falhou: ${message} - Esperado: ${expected}, Atual: ${actual}`, undefined, { metadata: { expected, actual, context: message } });
904
+ }
905
+ }
906
+ /**
907
+ * Valida se um valor não é nulo ou undefined.
908
+ * Registra sucesso ou falha nos logs e incrementa métricas.
909
+ * Lança erro se o valor for nulo ou undefined.
910
+ * @param value Valor a ser validado.
911
+ * @param message Mensagem descritiva da validação.
912
+ * @throws {ValidationError} Se o valor for nulo ou undefined.
913
+ */
914
+ static validateNotNull(value, message) {
915
+ ApiActions.marcarUsoAutomatico();
916
+ if (value != null && value !== undefined) {
917
+ ApiActions.successCount++;
918
+ const successMessage = `✅ Validação passou: ${message} (valor não é nulo)`;
919
+ ApiActions.logEvent(successMessage);
920
+ Logger.success(`Validação passou: ${message}`);
921
+ }
922
+ else {
923
+ ApiActions.failureCount++;
924
+ const errorMessage = `❌ Validação falhou: ${message} (valor é nulo/undefined)`;
925
+ ApiActions.logEvent(errorMessage);
926
+ Logger.error(`❌ Validação falhou: ${message} - Valor é nulo ou undefined`);
927
+ throw new ValidationError('API_NULL_VALUE', `Validation failed: ${message} - Value is null or undefined`, `Validação falhou: ${message} - Valor é nulo ou undefined`, undefined, { metadata: { value, context: message } });
928
+ }
929
+ }
930
+ static maskSensitiveDataInBody(body) {
931
+ if (!body)
932
+ return body;
933
+ try {
934
+ const maskedBody = body
935
+ .replace(/"access_token":\s*"[^"]+"/g, '"access_token":"***"')
936
+ .replace(/"password":\s*"[^"]+"/g, '"password":"***"')
937
+ .replace(/password=[^&\s]+/g, 'password=***')
938
+ .replace(/token=[^&\s]+/g, 'token=***');
939
+ return maskedBody;
940
+ }
941
+ catch {
942
+ return body;
943
+ }
944
+ }
945
+ /**
946
+ * 🎨 Formata e mascara corpo JSON para exibição legível
947
+ * @param body String contendo JSON ou texto
948
+ * @returns JSON formatado com indentação ou texto original
949
+ */
950
+ static formatAndMaskBody(body) {
951
+ if (!body)
952
+ return body;
953
+ try {
954
+ // Tenta fazer parse como JSON
955
+ const parsed = JSON.parse(body);
956
+ // Converte para JSON formatado com indentação
957
+ let formatted = JSON.stringify(parsed, null, 2);
958
+ // Aplica mascaramento de dados sensíveis
959
+ formatted = formatted
960
+ .replace(/"access_token":\s*"[^"]+"/g, '"access_token": "***"')
961
+ .replace(/"password":\s*"[^"]+"/g, '"password": "***"')
962
+ .replace(/"token":\s*"[^"]+"/g, '"token": "***"')
963
+ .replace(/"senha":\s*"[^"]+"/g, '"senha": "***"');
964
+ return formatted;
965
+ }
966
+ catch {
967
+ // Se não for JSON válido, aplica mascaramento básico e retorna
968
+ return ApiActions.maskSensitiveDataInBody(body);
969
+ }
970
+ }
971
+ /**
972
+ * 🔧 Normaliza URL removendo barras duplicadas na junção baseURL + endpoint
973
+ * @param baseURL URL base (pode terminar com /)
974
+ * @param endpoint Caminho do endpoint (pode começar com /)
975
+ * @returns URL completa sem barras duplicadas
976
+ */
977
+ static normalizeUrl(baseURL, endpoint) {
978
+ const cleanBase = baseURL.replace(/\/+$/, ''); // Remove trailing slashes
979
+ const cleanPath = endpoint.replace(/^\/+/, ''); // Remove leading slashes
980
+ return `${cleanBase}/${cleanPath}`;
981
+ }
982
+ /**
983
+ * ✅ NOVO: Obtém o timeout do projeto Playwright ou usa padrão
984
+ */
985
+ static tryParseJson(text) {
986
+ try {
987
+ return JSON.parse(text);
988
+ }
989
+ catch {
990
+ return text;
991
+ }
992
+ }
993
+ /**
994
+ * ✅ NOVO: Método utilitário para resolver caminho do certificado
995
+ */
996
+ static resolveCertPath(certName) {
997
+ // Se já tem path completo, usar direto
998
+ if (certName.startsWith('/') || certName.includes(':')) {
999
+ return certName;
1000
+ }
1001
+ // ✅ CORRIGIDO: Usar createRequire ao invés de require direto
1002
+ const require = createRequire(import.meta.url);
1003
+ const path = require('path');
1004
+ return path.resolve(process.cwd(), certName);
1005
+ }
1006
+ /**
1007
+ * ✅ NOVO: Executa uma tentativa específica da estratégia progressiva
1008
+ */
1009
+ static async tryProgressiveStrategy(strategy, certificate, requestUrl, proxyUrl) {
1010
+ const origin = requestUrl ? new URL(requestUrl).origin : 'https://localhost';
1011
+ // ✅ CORRIGIDO: Só obter baseURL do AutoCore Hub se o CT atual tem sistema configurado E a URL for relativa
1012
+ let baseURL;
1013
+ const isAbsoluteUrl = requestUrl &&
1014
+ (requestUrl.startsWith('http://') || requestUrl.startsWith('https://'));
1015
+ // ✅ NOVO: Obter sistema do CT atual via StatementTracker
1016
+ let hasSystemConfigured = null;
1017
+ try {
1018
+ hasSystemConfigured = StatementTracker.getCurrentCTSystem();
1019
+ }
1020
+ catch {
1021
+ // Ignora se não conseguir importar
1022
+ }
1023
+ if (!isAbsoluteUrl && hasSystemConfigured) {
1024
+ try {
1025
+ const configManager = DynamicConfigManager.getInstance();
1026
+ const apiConfig = configManager.getApiConfig();
1027
+ if (apiConfig && apiConfig.baseUrl) {
1028
+ // ✅ CORRIGIDO: Remover barra final se existir (Playwright adiciona automaticamente)
1029
+ baseURL = apiConfig.baseUrl.replace(/\/$/, '');
1030
+ }
1031
+ }
1032
+ catch {
1033
+ // Ignora se não conseguir importar
1034
+ }
1035
+ }
1036
+ // Configurar opções base
1037
+ const contextOptions = {
1038
+ baseURL, // ✅ NOVO: baseURL do sistema selecionado
1039
+ ignoreHTTPSErrors: strategy.ignoreSSL,
1040
+ timeout: TestContext.getProjectTimeout(),
1041
+ extraHTTPHeaders: strategy.specialHeaders
1042
+ ? {
1043
+ 'User-Agent': 'AutoCore-Progressive-Client/1.0',
1044
+ Accept: 'application/json, text/plain, */*',
1045
+ 'Cache-Control': 'no-cache',
1046
+ 'X-AutoCore-Strategy': strategy.name,
1047
+ 'X-Request-ID': `autocore-${Date.now()}`,
1048
+ }
1049
+ : {
1050
+ 'User-Agent': 'AutoCore-Client/1.0',
1051
+ Accept: 'application/json, text/plain, */*',
1052
+ },
1053
+ };
1054
+ // Adicionar proxy se solicitado
1055
+ if (strategy.useProxy && proxyUrl) {
1056
+ contextOptions.proxy = {
1057
+ server: proxyUrl,
1058
+ };
1059
+ }
1060
+ // Adicionar certificado se solicitado
1061
+ if (strategy.certificate) {
1062
+ const certPath = ApiActions.resolveCertPath(certificate.name);
1063
+ if (certificate.name.toLowerCase().endsWith('.p12') ||
1064
+ certificate.name.toLowerCase().endsWith('.pfx')) {
1065
+ // NOVO: Para P12/PFX - tentar múltiplas senhas em ordem (password por último)
1066
+ const passwordAttempts = [
1067
+ certificate.password || '', // Senha fornecida ou string vazia
1068
+ '', // Sem senha (string vazia)
1069
+ undefined, // Sem senha (undefined)
1070
+ 'password', // Senha padrão como ÚLTIMA tentativa
1071
+ ].filter((p, index, arr) => arr.indexOf(p) === index); // Remove duplicatas
1072
+ // Tentar cada senha até uma funcionar - SE QUALQUER FALHAR, LANÇAR ERRO PARA TENTAR PRÓXIMA ESTRATÉGIA
1073
+ let lastError = null;
1074
+ for (const attemptPassword of passwordAttempts) {
1075
+ try {
1076
+ contextOptions.clientCertificates = [
1077
+ {
1078
+ origin,
1079
+ pfxPath: certPath,
1080
+ passphrase: attemptPassword === '' ? undefined : attemptPassword,
1081
+ },
1082
+ ];
1083
+ // Criar contexto de teste apenas para verificar se a senha funciona
1084
+ const testContext = await playwrightRequest.newContext(contextOptions);
1085
+ await testContext.dispose(); // Limpar contexto de teste
1086
+ break; // Sair do loop se a senha funcionou
1087
+ }
1088
+ catch (passwordError) {
1089
+ const errorMsg = passwordError instanceof Error
1090
+ ? passwordError.message
1091
+ : String(passwordError);
1092
+ let passwordDesc = '';
1093
+ if (attemptPassword === '') {
1094
+ passwordDesc = '(vazia)';
1095
+ }
1096
+ else if (attemptPassword === undefined) {
1097
+ passwordDesc = '(undefined)';
1098
+ }
1099
+ else {
1100
+ passwordDesc = "'" + attemptPassword + "'";
1101
+ }
1102
+ console.log('Senha ' +
1103
+ passwordDesc +
1104
+ ' falhou: ' +
1105
+ errorMsg.substring(0, 50) +
1106
+ '...');
1107
+ lastError =
1108
+ passwordError instanceof Error
1109
+ ? passwordError
1110
+ : new Error(String(passwordError));
1111
+ }
1112
+ }
1113
+ // Se todas as senhas falharam, lançar erro para tentar próxima estratégia
1114
+ if (lastError) {
1115
+ throw new Error('Todas as senhas falharam para estrategia ' +
1116
+ strategy.name +
1117
+ ': ' +
1118
+ lastError.message);
1119
+ }
1120
+ }
1121
+ else {
1122
+ // Para PEM (se tiver keyPath)
1123
+ if (certificate.keyPath) {
1124
+ contextOptions.clientCertificates = [
1125
+ {
1126
+ origin,
1127
+ certPath,
1128
+ keyPath: certificate.keyPath,
1129
+ passphrase: certificate.password || undefined,
1130
+ },
1131
+ ];
1132
+ }
1133
+ }
1134
+ }
1135
+ // Tentar criar contexto
1136
+ try {
1137
+ const context = await playwrightRequest.newContext(contextOptions);
1138
+ // ✅ CORREÇÃO: Não fazer teste de conectividade para evitar interferência com AutoDocs
1139
+ // O contexto será testado na requisição real do usuário
1140
+ return context;
1141
+ }
1142
+ catch (error) {
1143
+ const errorMsg = error instanceof Error ? error.message : String(error);
1144
+ console.log('Falha ao criar contexto:', errorMsg);
1145
+ throw error;
1146
+ }
1147
+ }
1148
+ static async prepareContext(ignoreSSL = false, certificate, requestUrl, baseURL) {
1149
+ if (certificate) {
1150
+ // SISTEMA PROGRESSIVO: Múltiplas tentativas do mais completo ao mais simples
1151
+ const corporateProxy = process.env.PROXY_URL || 'http://proxy.redecorp.br:8080';
1152
+ // TENTATIVAS PROGRESSIVAS: 10 estratégias ordenadas por eficácia comprovada
1153
+ const strategies = [
1154
+ // 1. SEM PROXY: COMPROVADAMENTE EFICAZ - Certificado + SSL bypass (sem proxy)
1155
+ {
1156
+ name: 'SEM PROXY',
1157
+ useProxy: false,
1158
+ ignoreSSL: true,
1159
+ certificate: true,
1160
+ specialHeaders: false,
1161
+ },
1162
+ // 2. MÁXIMO: Certificado + Proxy + SSL bypass + Headers especiais
1163
+ {
1164
+ name: 'MÁXIMO',
1165
+ useProxy: true,
1166
+ ignoreSSL: true,
1167
+ certificate: true,
1168
+ specialHeaders: true,
1169
+ },
1170
+ // 3. COMPLETO: Certificado + Proxy + SSL bypass
1171
+ {
1172
+ name: 'COMPLETO',
1173
+ useProxy: true,
1174
+ ignoreSSL: true,
1175
+ certificate: true,
1176
+ specialHeaders: false,
1177
+ },
1178
+ // 4. SEM HEADERS: Certificado + Proxy + SSL bypass (sem headers especiais)
1179
+ {
1180
+ name: 'SEM HEADERS',
1181
+ useProxy: true,
1182
+ ignoreSSL: true,
1183
+ certificate: true,
1184
+ specialHeaders: false,
1185
+ },
1186
+ // 5. SSL ONLY: Apenas SSL bypass (sem proxy nem certificado)
1187
+ {
1188
+ name: 'SSL ONLY',
1189
+ useProxy: false,
1190
+ ignoreSSL: true,
1191
+ certificate: false,
1192
+ specialHeaders: false,
1193
+ },
1194
+ // 6. PROXY ONLY: Apenas Proxy + SSL bypass (sem certificado)
1195
+ {
1196
+ name: 'PROXY ONLY',
1197
+ useProxy: true,
1198
+ ignoreSSL: true,
1199
+ certificate: false,
1200
+ specialHeaders: false,
1201
+ },
1202
+ // 7. CERTIFICADO SIMPLES: Certificado sem extras
1203
+ {
1204
+ name: 'CERT SIMPLES',
1205
+ useProxy: false,
1206
+ ignoreSSL: false,
1207
+ certificate: true,
1208
+ specialHeaders: false,
1209
+ },
1210
+ // 8. PROXY SIMPLES: Proxy sem SSL bypass
1211
+ {
1212
+ name: 'PROXY SIMPLES',
1213
+ useProxy: true,
1214
+ ignoreSSL: false,
1215
+ certificate: false,
1216
+ specialHeaders: false,
1217
+ },
1218
+ // 9. FALLBACK MÍNIMO: SSL bypass básico
1219
+ {
1220
+ name: 'FALLBACK',
1221
+ useProxy: false,
1222
+ ignoreSSL: true,
1223
+ certificate: false,
1224
+ specialHeaders: false,
1225
+ },
1226
+ // 10. PADRÃO: Contexto básico sem nada
1227
+ {
1228
+ name: 'PADRÃO',
1229
+ useProxy: false,
1230
+ ignoreSSL: false,
1231
+ certificate: false,
1232
+ specialHeaders: false,
1233
+ },
1234
+ ];
1235
+ for (let i = 0; i < strategies.length; i++) {
1236
+ const strategy = strategies[i];
1237
+ try {
1238
+ const context = await ApiActions.tryProgressiveStrategy(strategy, certificate, requestUrl, corporateProxy);
1239
+ if (context) {
1240
+ return context;
1241
+ }
1242
+ }
1243
+ catch (error) {
1244
+ const errorMsg = error instanceof Error ? error.message : String(error);
1245
+ console.log('Falha na tentativa ' +
1246
+ (i + 1) +
1247
+ ': ' +
1248
+ errorMsg.substring(0, 100) +
1249
+ '...');
1250
+ // Se for erro crítico, pular para estratégias mais simples
1251
+ if (errorMsg.includes('certificate') || errorMsg.includes('proxy')) {
1252
+ console.log('Erro relacionado a certificado/proxy - continuando...');
1253
+ }
1254
+ }
1255
+ }
1256
+ // Se chegamos aqui, todas as estratégias falharam
1257
+ console.error('TODAS as 10 estrategias falharam para:', certificate.name);
1258
+ console.warn('Continuando com contexto padrão sem certificado...');
1259
+ }
1260
+ // Contexto padrão sem certificado (com controle SSL configurável)
1261
+ // ✅ CORRIGIDO: Incluir baseURL E sistema na chave do cache para evitar reutilização entre CTs
1262
+ // Obter sistema do CT atual para incluir na chave
1263
+ let currentSystem = 'nosystem';
1264
+ try {
1265
+ const systemFromCT = StatementTracker.getCurrentCTSystem();
1266
+ if (systemFromCT) {
1267
+ currentSystem = systemFromCT;
1268
+ }
1269
+ }
1270
+ catch {
1271
+ // Ignora se não conseguir obter sistema
1272
+ }
1273
+ const cacheKey = 'ssl_' + ignoreSSL + '_' + (certificate ? 'cert' : 'nocert') + '_base_' + (baseURL || 'none') + '_sys_' + currentSystem;
1274
+ if (ApiActions.contextCache.has(cacheKey)) {
1275
+ return ApiActions.contextCache.get(cacheKey);
1276
+ }
1277
+ // ✅ SEM CERTIFICADO: SSL configurável via variável de ambiente ou parâmetro
1278
+ const shouldIgnoreSSL = process.env.IGNORE_SSL_ERRORS === 'true' || ignoreSSL;
1279
+ let sslSource = 'parâmetro';
1280
+ if (certificate) {
1281
+ sslSource = 'certificado automático';
1282
+ }
1283
+ else if (process.env.IGNORE_SSL_ERRORS === 'true') {
1284
+ sslSource = 'IGNORE_SSL_ERRORS=true';
1285
+ }
1286
+ console.log('Configuracao SSL: ' +
1287
+ (shouldIgnoreSSL ? 'DESABILITADA' : 'HABILITADA') +
1288
+ ' (via ' +
1289
+ sslSource +
1290
+ ')');
1291
+ // ✅ CORRIGIDO: Usar baseURL do parâmetro (já foi calculado em _request)
1292
+ const context = await playwrightRequest.newContext({
1293
+ baseURL, // ✅ baseURL do sistema selecionado (apenas para URLs relativas e se setSystem() foi chamado)
1294
+ ignoreHTTPSErrors: shouldIgnoreSSL,
1295
+ timeout: TestContext.getProjectTimeout(),
1296
+ extraHTTPHeaders: {
1297
+ 'User-Agent': 'AutoCore-Client/1.0',
1298
+ Accept: 'application/json, text/plain, */*',
1299
+ 'Cache-Control': 'no-cache',
1300
+ },
1301
+ });
1302
+ ApiActions.contextCache.set(cacheKey, context);
1303
+ return context;
1304
+ }
1305
+ /**
1306
+ * ✅ ESTRATÉGIA FINAL: SSL Bypass completo para ambientes QA
1307
+ * @param requestUrl URL da requisição (opcional)
1308
+ * @returns Contexto com SSL completamente desabilitado
1309
+ */
1310
+ static async createSSLBypassContext(requestUrl) {
1311
+ try {
1312
+ console.log('🔓 Criando contexto SSL bypass para ambiente QA');
1313
+ // ✅ FORÇAR SSL bypass sempre em ambientes QA
1314
+ const isQAEnvironment = process.env.NODE_ENV === 'qa' ||
1315
+ process.env.NODE_ENV === 'test' ||
1316
+ (requestUrl &&
1317
+ (requestUrl.includes('qa') || requestUrl.includes('test')));
1318
+ if (!isQAEnvironment) {
1319
+ console.warn('⚠️ SSL Bypass solicitado em ambiente não-QA - usando configuração padrão');
1320
+ }
1321
+ const context = await playwrightRequest.newContext({
1322
+ ignoreHTTPSErrors: true, // ✅ SEMPRE ignorar SSL em bypass
1323
+ timeout: TestContext.getProjectTimeout(),
1324
+ // ✅ NOVO: Proxy corporativo para SSL bypass com certificados
1325
+ proxy: {
1326
+ server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
1327
+ },
1328
+ extraHTTPHeaders: {
1329
+ 'User-Agent': 'AutoCore-QA-Client/1.0',
1330
+ Accept: 'application/json, text/plain, */*',
1331
+ 'Cache-Control': 'no-cache',
1332
+ 'X-QA-Environment': 'ssl-bypass',
1333
+ },
1334
+ });
1335
+ console.log('Contexto SSL bypass criado com sucesso');
1336
+ console.log('Ambiente detectado: ' + (isQAEnvironment ? 'QA' : 'Outro'));
1337
+ return context;
1338
+ }
1339
+ catch (error) {
1340
+ const errorMessage = error instanceof Error ? error.message : String(error);
1341
+ console.error('Falha ao criar contexto SSL bypass:', errorMessage);
1342
+ throw new Error('SSL bypass falhou: ' + errorMessage);
1343
+ }
1344
+ }
1345
+ /**
1346
+ * ✅ NOVA ESTRATÉGIA 1: Usar P12 diretamente com clientCertificates (CORRIGIDO ESM)
1347
+ * @param certificate Certificado P12
1348
+ * @param requestUrl URL da requisição para determinar origem
1349
+ * @returns Contexto configurado com certificado P12 direto
1350
+ */
1351
+ static async tryDirectP12Strategy(certificate, requestUrl) {
1352
+ try {
1353
+ console.log('Configurando P12 direto com clientCertificates:', certificate.name);
1354
+ // ✅ CORRIGIDO: Usar createRequire ao invés de require direto
1355
+ const require = createRequire(import.meta.url);
1356
+ const fs = require('fs');
1357
+ const certPath = ApiActions.resolveCertPath(certificate.name);
1358
+ // Verificar se arquivo existe
1359
+ if (!fs.existsSync(certPath)) {
1360
+ throw new Error('Arquivo P12 nao encontrado: ' + certPath);
1361
+ }
1362
+ // ✅ MÚLTIPLAS TENTATIVAS DE SENHA (password por último como sugerido)
1363
+ const passwordAttempts = [
1364
+ certificate.password || '', // Senha fornecida ou string vazia
1365
+ '', // Sem senha (string vazia)
1366
+ undefined, // Sem senha (undefined)
1367
+ 'password', // Senha padrão como ÚLTIMA tentativa
1368
+ ].filter((p, index, arr) => arr.indexOf(p) === index); // Remove duplicatas
1369
+ let context = null;
1370
+ for (const attemptPassword of passwordAttempts) {
1371
+ try {
1372
+ let passwordDesc = '(fornecida)';
1373
+ if (attemptPassword === '') {
1374
+ passwordDesc = '(vazia)';
1375
+ }
1376
+ else if (attemptPassword === undefined) {
1377
+ passwordDesc = '(undefined)';
1378
+ }
1379
+ console.log('Tentando P12 direto com senha:', passwordDesc);
1380
+ const origin = requestUrl
1381
+ ? new URL(requestUrl).origin
1382
+ : 'https://api-qa1.telefonica.com.br';
1383
+ context = await playwrightRequest.newContext({
1384
+ ignoreHTTPSErrors: true, // ✅ IGNORAR SSL EM QA SEMPRE
1385
+ timeout: TestContext.getProjectTimeout(),
1386
+ clientCertificates: [
1387
+ {
1388
+ origin,
1389
+ pfxPath: certPath,
1390
+ passphrase: attemptPassword === undefined ? undefined : attemptPassword,
1391
+ },
1392
+ ],
1393
+ // ✅ NOVO: Proxy corporativo automático para certificados
1394
+ proxy: {
1395
+ server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
1396
+ },
1397
+ extraHTTPHeaders: {
1398
+ 'User-Agent': 'AutoCore-Client/1.0',
1399
+ Accept: 'application/json, text/plain, */*',
1400
+ 'Cache-Control': 'no-cache',
1401
+ },
1402
+ });
1403
+ let passwordDesc2 = '(fornecida)';
1404
+ if (attemptPassword === '') {
1405
+ passwordDesc2 = '(vazia)';
1406
+ }
1407
+ else if (attemptPassword === undefined) {
1408
+ passwordDesc2 = '(undefined)';
1409
+ }
1410
+ console.log('P12 direto configurado com sucesso usando senha:', passwordDesc2);
1411
+ console.log(`📍 Origin configurado: ${origin}`);
1412
+ return context;
1413
+ }
1414
+ catch (passwordError) {
1415
+ const errorMsg = passwordError instanceof Error
1416
+ ? passwordError.message
1417
+ : String(passwordError);
1418
+ let passwordDesc3 = '(fornecida)';
1419
+ if (attemptPassword === '') {
1420
+ passwordDesc3 = '(vazia)';
1421
+ }
1422
+ else if (attemptPassword === undefined) {
1423
+ passwordDesc3 = '(undefined)';
1424
+ }
1425
+ console.log('P12 direto falhou com senha ' + passwordDesc3 + ':', errorMsg);
1426
+ if (context) {
1427
+ await context.dispose();
1428
+ context = null;
1429
+ }
1430
+ }
1431
+ }
1432
+ throw new Error('P12 direto falhou com todas as tentativas de senha');
1433
+ }
1434
+ catch (error) {
1435
+ const errorMessage = error instanceof Error ? error.message : String(error);
1436
+ console.error(`❌ Falha na estratégia P12 direto: ${errorMessage}`);
1437
+ throw new Error(`P12 direto falhou: ${errorMessage}`);
1438
+ }
1439
+ }
1440
+ /**
1441
+ * ✅ NOVO: Extrai certificado e chave privada de arquivo P12/PKCS12 usando node-forge
1442
+ * @param p12Path Caminho para o arquivo P12/PKCS12
1443
+ * @param password Senha do arquivo P12 (opcional - deixar vazio se não tiver senha)
1444
+ * @returns Objeto com certificado e chave privada em formato PEM
1445
+ */
1446
+ static extractP12Certificate(p12Path, password) {
1447
+ try {
1448
+ console.log('Extraindo certificado P12:', p12Path);
1449
+ // ✅ CORRIGIDO: Usar createRequire para importar dependências
1450
+ const require = createRequire(import.meta.url);
1451
+ const forge = require('node-forge');
1452
+ const fs = require('fs');
1453
+ // Verificar se node-forge foi carregado corretamente
1454
+ if (!(forge && forge.asn1 && forge.pkcs12)) {
1455
+ throw new Error('node-forge não carregado corretamente');
1456
+ }
1457
+ // Ler arquivo P12
1458
+ const p12Data = fs.readFileSync(p12Path);
1459
+ // ✅ ESTRATÉGIA DE FALLBACK: Tentar várias abordagens para senhas
1460
+ const passwordAttempts = [
1461
+ password || '', // Senha fornecida ou string vazia
1462
+ '', // Sem senha (string vazia)
1463
+ undefined, // Sem senha (undefined)
1464
+ null, // Sem senha (null)
1465
+ ].filter((p, index, arr) => arr.indexOf(p) === index); // Remove duplicatas
1466
+ // biome-ignore lint/suspicious/noExplicitAny: node-forge pkcs12FromAsn1 return type is complex
1467
+ let p12 = null;
1468
+ let usedPassword;
1469
+ // Tentar cada abordagem de senha
1470
+ for (const attemptPassword of passwordAttempts) {
1471
+ try {
1472
+ let passwordDesc4 = '(fornecida)';
1473
+ if (attemptPassword === '') {
1474
+ passwordDesc4 = '(vazia)';
1475
+ }
1476
+ else if (attemptPassword === undefined) {
1477
+ passwordDesc4 = '(undefined)';
1478
+ }
1479
+ else if (attemptPassword === null) {
1480
+ passwordDesc4 = '(null)';
1481
+ }
1482
+ console.log('Tentando extrair P12 com senha:', passwordDesc4);
1483
+ const p12Asn1 = forge.asn1.fromDer(p12Data.toString('binary'));
1484
+ p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, attemptPassword);
1485
+ usedPassword = attemptPassword;
1486
+ let passwordDesc5 = '(fornecida)';
1487
+ if (attemptPassword === '') {
1488
+ passwordDesc5 = '(vazia)';
1489
+ }
1490
+ else if (attemptPassword === undefined) {
1491
+ passwordDesc5 = '(undefined)';
1492
+ }
1493
+ else if (attemptPassword === null) {
1494
+ passwordDesc5 = '(null)';
1495
+ }
1496
+ console.log('P12 extraido com sucesso usando senha:', passwordDesc5);
1497
+ break;
1498
+ }
1499
+ catch (passwordError) {
1500
+ const errorMsg = passwordError instanceof Error
1501
+ ? passwordError.message
1502
+ : String(passwordError);
1503
+ let passwordDesc6 = '(fornecida)';
1504
+ if (attemptPassword === '') {
1505
+ passwordDesc6 = '(vazia)';
1506
+ }
1507
+ else if (attemptPassword === undefined) {
1508
+ passwordDesc6 = '(undefined)';
1509
+ }
1510
+ else if (attemptPassword === null) {
1511
+ passwordDesc6 = '(null)';
1512
+ }
1513
+ console.log('Falha com senha ' + passwordDesc6 + ':', errorMsg);
1514
+ }
1515
+ }
1516
+ if (!p12) {
1517
+ throw new Error('Não foi possível extrair o P12 com nenhuma abordagem de senha testada');
1518
+ }
1519
+ // Extrair certificado
1520
+ const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
1521
+ const certBagArray = certBags?.[forge.pki.oids.certBag];
1522
+ if (!certBagArray || certBagArray.length === 0) {
1523
+ throw new Error('Nenhum certificado encontrado no arquivo P12');
1524
+ }
1525
+ const cert = certBagArray[0]?.cert;
1526
+ if (!cert) {
1527
+ throw new Error('Certificado inválido no arquivo P12');
1528
+ }
1529
+ // Extrair chave privada
1530
+ const keyBags = p12.getBags({
1531
+ bagType: forge.pki.oids.pkcs8ShroudedKeyBag,
1532
+ });
1533
+ let privateKey = null;
1534
+ const shroudedKeyBagArray = keyBags?.[forge.pki.oids.pkcs8ShroudedKeyBag];
1535
+ if (shroudedKeyBagArray && shroudedKeyBagArray.length > 0) {
1536
+ privateKey = shroudedKeyBagArray[0]?.key;
1537
+ }
1538
+ else {
1539
+ // Tentar keyBag não shrouded
1540
+ const unshroudedKeyBags = p12.getBags({
1541
+ bagType: forge.pki.oids.keyBag,
1542
+ });
1543
+ const unshroudedKeyBagArray = unshroudedKeyBags?.[forge.pki.oids.keyBag];
1544
+ if (unshroudedKeyBagArray && unshroudedKeyBagArray.length > 0) {
1545
+ privateKey = unshroudedKeyBagArray[0]?.key;
1546
+ }
1547
+ }
1548
+ if (!privateKey) {
1549
+ throw new Error('Chave privada não encontrada no arquivo P12');
1550
+ }
1551
+ // Converter para PEM
1552
+ const certPem = forge.pki.certificateToPem(cert);
1553
+ const keyPem = forge.pki.privateKeyToPem(privateKey);
1554
+ console.log('Certificado P12 extraido com sucesso');
1555
+ console.log(`📋 Certificado válido de: ${cert.subject.getField('CN')?.value || 'N/A'}`);
1556
+ console.log(`📅 Válido até: ${cert.validity.notAfter}`);
1557
+ return { cert: certPem, key: keyPem };
1558
+ }
1559
+ catch (error) {
1560
+ const errorMessage = error instanceof Error ? error.message : String(error);
1561
+ console.error(`❌ Erro ao extrair certificado P12: ${errorMessage}`);
1562
+ throw new Error(`Falha na extração P12: ${errorMessage}`);
1563
+ }
1564
+ }
1565
+ /**
1566
+ * ✅ NOVO: Configura certificado P12 extraído com Playwright
1567
+ * @param origin Origem do certificado (protocolo + host)
1568
+ * @param certPath Caminho para arquivo P12
1569
+ * @param password Senha do arquivo P12
1570
+ * @returns Contexto configurado com certificado extraído
1571
+ */
1572
+ static async configureExtractedP12Certificate(origin, certPath, password) {
1573
+ try {
1574
+ // Extrair certificado e chave do P12
1575
+ const { cert, key } = await ApiActions.extractP12Certificate(certPath, password);
1576
+ // ✅ CORRIGIDO: Usar createRequire para fs e path
1577
+ const require = createRequire(import.meta.url);
1578
+ const fs = require('fs');
1579
+ const path = require('path');
1580
+ // Criar arquivos temporários para cert e key
1581
+ const tempDir = path.join(process.cwd(), '.temp-certs');
1582
+ if (!fs.existsSync(tempDir)) {
1583
+ fs.mkdirSync(tempDir, { recursive: true });
1584
+ }
1585
+ const tempCertPath = path.join(tempDir, 'cert-' + Date.now() + '.pem');
1586
+ const tempKeyPath = path.join(tempDir, 'key-' + Date.now() + '.pem');
1587
+ fs.writeFileSync(tempCertPath, cert);
1588
+ fs.writeFileSync(tempKeyPath, key);
1589
+ console.log('Certificado temporário criado:', tempCertPath);
1590
+ console.log('Chave temporária criada:', tempKeyPath);
1591
+ // Configurar contexto Playwright com certificado extraído
1592
+ const context = await playwrightRequest.newContext({
1593
+ ignoreHTTPSErrors: true, // ✅ SEMPRE IGNORAR SSL EM QA
1594
+ clientCertificates: [
1595
+ {
1596
+ origin,
1597
+ certPath: tempCertPath,
1598
+ keyPath: tempKeyPath,
1599
+ },
1600
+ ],
1601
+ // ✅ NOVO: Proxy corporativo para certificados
1602
+ proxy: {
1603
+ server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
1604
+ },
1605
+ timeout: TestContext.getProjectTimeout(),
1606
+ extraHTTPHeaders: {
1607
+ 'User-Agent': 'AutoCore-P12-Client/1.0',
1608
+ Accept: 'application/json, text/plain, */*',
1609
+ 'Cache-Control': 'no-cache',
1610
+ },
1611
+ });
1612
+ // Limpar arquivos temporários após uso (cleanup assíncrono)
1613
+ setTimeout(() => {
1614
+ try {
1615
+ if (fs.existsSync(tempCertPath))
1616
+ fs.unlinkSync(tempCertPath);
1617
+ if (fs.existsSync(tempKeyPath))
1618
+ fs.unlinkSync(tempKeyPath);
1619
+ console.log('Arquivos temporários de certificado removidos');
1620
+ }
1621
+ catch (cleanupError) {
1622
+ console.warn('Falha ao limpar arquivos temporários:', cleanupError);
1623
+ }
1624
+ }, 30_000); // Limpar após 30 segundos
1625
+ console.log('Contexto P12 configurado com sucesso');
1626
+ return context;
1627
+ }
1628
+ catch (error) {
1629
+ const errorMessage = error instanceof Error ? error.message : String(error);
1630
+ console.error('Erro ao configurar P12:', errorMessage);
1631
+ throw new Error('Falha na configuracao P12: ' + errorMessage);
1632
+ }
1633
+ }
1634
+ /**
1635
+ * ✅ MELHORADO: Tentar configuração de certificado com estratégia P12 + fallback
1636
+ */
1637
+ static async tryClientCertificateConfig(origin, certPath, password) {
1638
+ const baseConfig = {
1639
+ ignoreHTTPSErrors: true,
1640
+ timeout: TestContext.getProjectTimeout(),
1641
+ extraHTTPHeaders: {
1642
+ 'User-Agent': 'AutoCore-Certificate-Client/1.0',
1643
+ Accept: 'application/json, text/plain, */*',
1644
+ 'Cache-Control': 'no-cache',
1645
+ },
1646
+ storageState: undefined,
1647
+ httpCredentials: undefined,
1648
+ // ✅ NOVO: Proxy corporativo automático para certificados
1649
+ proxy: {
1650
+ server: process.env.PROXY_URL || 'http://proxy.redecorp.br:8080',
1651
+ },
1652
+ };
1653
+ // ESTRATEGIA 1: Tentar extração P12 com node-forge (NOVO)
1654
+ try {
1655
+ console.log('Tentativa 1: Extração P12 com node-forge');
1656
+ const context = await ApiActions.configureExtractedP12Certificate(origin, certPath, password);
1657
+ console.log('Certificado P12 extraído e configurado com sucesso');
1658
+ return context;
1659
+ }
1660
+ catch (p12Error) {
1661
+ const p12ErrorMessage = p12Error instanceof Error ? p12Error.message : String(p12Error);
1662
+ console.log('Falha na extração P12:', p12ErrorMessage);
1663
+ // ESTRATEGIA 2: Tentar abordagem original SEM senha (compatível com projetos Java)
1664
+ try {
1665
+ console.log('Tentativa 2: Abordagem original SEM senha');
1666
+ const contextWithoutPassword = await playwrightRequest.newContext({
1667
+ ...baseConfig,
1668
+ clientCertificates: [
1669
+ {
1670
+ origin,
1671
+ pfxPath: certPath,
1672
+ // passphrase omitida intencionalmente
1673
+ },
1674
+ ],
1675
+ });
1676
+ console.log('Certificado configurado com sucesso SEM senha (abordagem original)');
1677
+ return contextWithoutPassword;
1678
+ }
1679
+ catch (errorWithoutPassword) {
1680
+ const errorMessage = errorWithoutPassword instanceof Error
1681
+ ? errorWithoutPassword.message
1682
+ : String(errorWithoutPassword);
1683
+ console.log('Falha sem senha (original):', errorMessage);
1684
+ // ESTRATEGIA 3: Se falhar sem senha E temos senha, tentar COM senha
1685
+ if (password && password.trim() !== '') {
1686
+ try {
1687
+ console.log('Tentativa 3: Abordagem original COM senha');
1688
+ const contextWithPassword = await playwrightRequest.newContext({
1689
+ ...baseConfig,
1690
+ clientCertificates: [
1691
+ {
1692
+ origin,
1693
+ pfxPath: certPath,
1694
+ passphrase: password,
1695
+ },
1696
+ ],
1697
+ });
1698
+ console.log('Certificado configurado com sucesso COM senha (abordagem original)');
1699
+ return contextWithPassword;
1700
+ }
1701
+ catch (errorWithPassword) {
1702
+ const errorWithPasswordMessage = errorWithPassword instanceof Error
1703
+ ? errorWithPassword.message
1704
+ : String(errorWithPassword);
1705
+ console.error('Falha com senha (original):', errorWithPasswordMessage);
1706
+ // ESTRATEGIA 4: FALLBACK - Contexto sem certificado
1707
+ try {
1708
+ console.log('Tentativa 4: FALLBACK sem certificado');
1709
+ const fallbackContext = await playwrightRequest.newContext({
1710
+ ...baseConfig,
1711
+ ignoreHTTPSErrors: true,
1712
+ // Remover certificado e usar apenas ignoreHTTPSErrors
1713
+ });
1714
+ console.log('FALLBACK: Contexto criado SEM certificado (apenas ignoreHTTPSErrors)');
1715
+ console.log('SUGESTAO: Verifique se o certificado P12 está correto ou se o servidor aceita conexões sem certificado');
1716
+ return fallbackContext;
1717
+ }
1718
+ catch (fallbackError) {
1719
+ const fallbackMessage = fallbackError instanceof Error
1720
+ ? fallbackError.message
1721
+ : String(fallbackError);
1722
+ throw new Error('Certificado falhou em todas as tentativas:\\n1. Extração P12: ' +
1723
+ p12ErrorMessage +
1724
+ '\\n2. Original sem senha: ' +
1725
+ errorMessage +
1726
+ '\\n3. Original com senha: ' +
1727
+ errorWithPasswordMessage +
1728
+ '\\n4. Fallback: ' +
1729
+ fallbackMessage);
1730
+ }
1731
+ }
1732
+ }
1733
+ else {
1734
+ // ESTRATEGIA 4: Se não tem senha, tentar FALLBACK
1735
+ try {
1736
+ console.log('Tentativa 3 (sem senha): FALLBACK sem certificado');
1737
+ const fallbackContext = await playwrightRequest.newContext({
1738
+ ...baseConfig,
1739
+ ignoreHTTPSErrors: true,
1740
+ // Remover certificado e usar apenas ignoreHTTPSErrors
1741
+ });
1742
+ console.log('FALLBACK: Contexto criado SEM certificado (apenas ignoreHTTPSErrors)');
1743
+ console.log('SUGESTAO: Verifique se o certificado P12 está correto ou configure uma senha');
1744
+ return fallbackContext;
1745
+ }
1746
+ catch (fallbackError) {
1747
+ const fallbackMessage = fallbackError instanceof Error
1748
+ ? fallbackError.message
1749
+ : String(fallbackError);
1750
+ throw new Error('Certificado falhou em todas as tentativas:\\n1. Extração P12: ' +
1751
+ p12ErrorMessage +
1752
+ '\\n2. Original sem senha: ' +
1753
+ errorMessage +
1754
+ '\\n3. Fallback: ' +
1755
+ fallbackMessage);
1756
+ }
1757
+ }
1758
+ }
1759
+ }
1760
+ }
1761
+ static getMetrics() {
1762
+ return {
1763
+ totalRequisicoes: ApiActions.requestCount,
1764
+ totalSuccesso: ApiActions.successCount,
1765
+ totalFalhas: ApiActions.failureCount,
1766
+ foiUtilizado: ApiActions.foiUsado,
1767
+ logsPerTest: ApiActions.logsPerTest,
1768
+ duracoes: ApiActions.duracoes,
1769
+ dadosGerais: ApiActions.dadosGerais,
1770
+ inicioExecucao: ApiActions.inicioExecucao,
1771
+ fimExecucao: ApiActions.fimExecucao,
1772
+ };
1773
+ }
1774
+ static hasLogsForTest(testName) {
1775
+ return !!(ApiActions.logsPerTest[testName] &&
1776
+ ApiActions.logsPerTest[testName].length > 0);
1777
+ }
1778
+ static getLogsForTest(testName) {
1779
+ return ApiActions.logsPerTest[testName] || [];
1780
+ }
1781
+ static async coletarDadosDoTeste() {
1782
+ try {
1783
+ await UnifiedReportManager.coletarDadosDoTeste();
1784
+ }
1785
+ catch (error) {
1786
+ Logger.error('Erro ao coletar dados do teste via UnifiedReportManager', error);
1787
+ }
1788
+ }
1789
+ /**
1790
+ * ✅ Anexar resumo de logs
1791
+ */
1792
+ static async anexarResumoDeLogs() {
1793
+ try {
1794
+ const testInfo = TestContext.getSafeTestInfo();
1795
+ if (!testInfo)
1796
+ return;
1797
+ const logResumo = Object.entries(ApiActions.logsPerTest)
1798
+ .map(([teste, logs]) => '### ' + teste + '\\n' + logs.map((l) => '- ' + l).join('\\n'))
1799
+ .join('\\n\\n');
1800
+ await testInfo.attach('Resumo de Logs dos Testes', {
1801
+ body: Buffer.from(logResumo, 'utf-8'),
1802
+ contentType: 'text/markdown',
1803
+ });
1804
+ }
1805
+ catch (error) {
1806
+ Logger.error('Erro ao anexar resumo de logs:', error);
1807
+ }
1808
+ }
1809
+ /**
1810
+ * ✅ Anexar relatório final
1811
+ */
1812
+ static async anexarRelatorioFinal() {
1813
+ try {
1814
+ const testInfo = TestContext.getSafeTestInfo();
1815
+ if (!testInfo)
1816
+ return;
1817
+ Logger.bloco('Finalizado contexto de requisição API');
1818
+ const resultados = Object.entries(ApiActions.logsPerTest).map(([nome, logs]) => {
1819
+ const temFalha = logs.some((l) => l.includes('X'));
1820
+ return { nome, resultado: temFalha ? 'FAIL' : 'PASS' };
1821
+ });
1822
+ // Tentar usar UnifiedReportManager se disponível
1823
+ try {
1824
+ await UnifiedReportManager.finalizarTeste();
1825
+ return; // Sucesso, sair da função
1826
+ }
1827
+ catch {
1828
+ // Continuar para fallback
1829
+ }
1830
+ // Fallback para relatório simples em texto
1831
+ const relatorioTexto = '# Relatório de Execução API\n\n' +
1832
+ '## Resumo\n' +
1833
+ '- Total de Testes: ' +
1834
+ resultados.length +
1835
+ '\n' +
1836
+ '- Sucessos: ' +
1837
+ resultados.filter((r) => r.resultado === 'PASS').length +
1838
+ '\n' +
1839
+ '- Falhas: ' +
1840
+ resultados.filter((r) => r.resultado === 'FAIL').length +
1841
+ '\n\n' +
1842
+ '## Detalhes\n' +
1843
+ resultados.map((r) => '- ' + r.nome + ': ' + r.resultado).join('\n');
1844
+ await testInfo.attach('Relatório de Execução API', {
1845
+ body: Buffer.from(relatorioTexto, 'utf-8'),
1846
+ contentType: 'text/markdown',
1847
+ });
1848
+ }
1849
+ catch (error) {
1850
+ Logger.error('Erro ao anexar relatórios finais', error);
1851
+ }
1852
+ }
1853
+ /**
1854
+ * Verifica se o header deve ser mascarado nos logs.
1855
+ * @param key Nome do header.
1856
+ * @returns true se deve mascarar, false caso contrário.
1857
+ */
1858
+ static shouldMaskHeader(key) {
1859
+ const sensitiveHeaders = [
1860
+ 'authorization',
1861
+ 'x-api-key',
1862
+ 'set-cookie',
1863
+ 'cookie',
1864
+ 'proxy-authorization',
1865
+ ];
1866
+ return sensitiveHeaders.includes(key.toLowerCase());
1867
+ }
1868
+ /**
1869
+ * Aguarda um tempo fixo em segundos.
1870
+ * Útil para aguardar processamento assíncrono, propagação de dados,
1871
+ * ou cooldown entre chamadas de API.
1872
+ *
1873
+ * @param segundos - Tempo de espera em segundos
1874
+ * @param descricao - Descrição do motivo da espera (para logs)
1875
+ */
1876
+ static async fixedWait(segundos, descricao = '') {
1877
+ const ms = segundos * 1000;
1878
+ const actionDesc = descricao || `Aguardando ${segundos}s`;
1879
+ Logger.info(`[ApiActions] ⏱️ ${actionDesc}`);
1880
+ let statementTimestamp = null;
1881
+ try {
1882
+ statementTimestamp = StatementTracker.startStatement('ApiActions', `fixedWait: ${actionDesc}`);
1883
+ }
1884
+ catch { }
1885
+ const startTime = Date.now();
1886
+ await new Promise((resolve) => setTimeout(resolve, ms));
1887
+ const duration = Date.now() - startTime;
1888
+ Logger.success(`[ApiActions] ✅ Espera concluída: ${actionDesc} (real: ${duration}ms)`);
1889
+ try {
1890
+ StatementTracker.recordAction('API', actionDesc, true, duration, { action: 'fixedWait' });
1891
+ }
1892
+ catch { }
1893
+ }
1894
+ }
1895
+ // 🚫 AUTO-ATIVAÇÃO REMOVIDA: ApiActions são métodos internos que não devem ser interceptados
1896
+ // ApiActions.post(), .get(), etc. não são CTs válidos e devem ser filtrados
1897
+ // A interceptação foi removida para evitar falsos CTs nos relatórios
1898
+ // CÓDIGO REMOVIDO:
1899
+ // try {
1900
+ // installDatabaseInterceptor(ApiActions, 'ApiActions')
1901
+ // } catch (error) {
1902
+ // if (process.env.AUTOCORE_DEBUG_LOGS === 'true') {
1903
+ // process.stderr.write(`[DatabaseInterceptor] Falha na auto-ativação ApiActions: ${String(error)}\n`)
1904
+ // }
1905
+ // }