@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,1415 @@
1
+ /** Lista de actionTypes válidos para validação */
2
+ export const VALID_ACTION_TYPES = [
3
+ "click",
4
+ "fill",
5
+ "select",
6
+ "hover",
7
+ "check",
8
+ "uncheck",
9
+ "press",
10
+ "wait",
11
+ "navigate",
12
+ "doubleClick",
13
+ ];
14
+ /**
15
+ * Mapeamento de nomes de métodos WebActions para actionTypes do Test Recovery
16
+ * Usado para normalizar o actionType quando vem do wrapper automático
17
+ */
18
+ export const METHOD_TO_ACTION_TYPE = {
19
+ // Fill/Input
20
+ setText: "fill",
21
+ fill: "fill",
22
+ type: "fill",
23
+ preencher: "fill",
24
+ // Click
25
+ click: "click",
26
+ clicar: "click",
27
+ // Double Click
28
+ doubleClick: "doubleClick",
29
+ dblclick: "doubleClick",
30
+ duploClique: "doubleClick",
31
+ // Select
32
+ selectOption: "select",
33
+ selectDropdown: "select",
34
+ selectFromList: "select",
35
+ selectTableOption: "select",
36
+ selecionarOpcao: "select",
37
+ // Hover
38
+ hover: "hover",
39
+ mouseOver: "hover",
40
+ // Press Key
41
+ pressKey: "press",
42
+ press: "press",
43
+ keyboard: "press",
44
+ // Check/Uncheck
45
+ check: "check",
46
+ uncheck: "uncheck",
47
+ marcarCheckbox: "check",
48
+ desmarcarCheckbox: "uncheck",
49
+ // Wait
50
+ waitForVisible: "wait",
51
+ waitForVisibleBoolean: "wait",
52
+ waitForNetworkIdle: "wait",
53
+ waitForSelector: "wait",
54
+ waitForHidden: "wait",
55
+ aguardarVisivel: "wait",
56
+ fixedWait: "wait",
57
+ // Navigate
58
+ navigateTo: "navigate",
59
+ goto: "navigate",
60
+ navigate: "navigate",
61
+ navegar: "navigate",
62
+ // Frame (mapeia para click como fallback)
63
+ switchTo: "click",
64
+ trocarFrame: "click",
65
+ };
66
+ /**
67
+ * Converte nome de método WebActions para actionType do Test Recovery
68
+ * @param methodName Nome do método (ex: 'setText', 'click', 'waitForVisible')
69
+ * @returns ActionType normalizado
70
+ */
71
+ export function methodToActionType(methodName) {
72
+ const mapped = METHOD_TO_ACTION_TYPE[methodName];
73
+ if (mapped)
74
+ return mapped;
75
+ // Fallback: tentar inferir pelo nome
76
+ const lower = methodName.toLowerCase();
77
+ if (lower.includes("wait") || lower.includes("aguardar"))
78
+ return "wait";
79
+ if (lower.includes("fill") ||
80
+ lower.includes("text") ||
81
+ lower.includes("preencher"))
82
+ return "fill";
83
+ if (lower.includes("click") || lower.includes("clicar"))
84
+ return "click";
85
+ if (lower.includes("select") || lower.includes("selecionar"))
86
+ return "select";
87
+ if (lower.includes("hover"))
88
+ return "hover";
89
+ if (lower.includes("press") || lower.includes("key"))
90
+ return "press";
91
+ if (lower.includes("check"))
92
+ return "check";
93
+ if (lower.includes("navigate") ||
94
+ lower.includes("goto") ||
95
+ lower.includes("navegar"))
96
+ return "navigate";
97
+ // Default
98
+ return "click";
99
+ }
100
+ /** Lista de errorTypes válidos para validação */
101
+ export const VALID_ERROR_TYPES = [
102
+ "not_found",
103
+ "multiple_elements",
104
+ "timeout",
105
+ "wrong_navigation",
106
+ "stale_element",
107
+ ];
108
+ /**
109
+ * Valida se um actionType é válido
110
+ */
111
+ export function isValidActionType(action) {
112
+ return !!action && VALID_ACTION_TYPES.includes(action);
113
+ }
114
+ /**
115
+ * Valida se um errorType é válido
116
+ */
117
+ export function isValidErrorType(error) {
118
+ return !!error && VALID_ERROR_TYPES.includes(error);
119
+ }
120
+ /**
121
+ * Infere o actionType baseado no contexto do XPath ou elemento
122
+ * Útil quando o actionType não é fornecido explicitamente
123
+ */
124
+ export function inferActionType(xpath, context) {
125
+ const xpathLower = xpath.toLowerCase();
126
+ const contextText = context?.text?.toLowerCase() ?? "";
127
+ const contextRole = context?.role?.toLowerCase() ?? "";
128
+ const contextType = context?.type?.toLowerCase() ?? "";
129
+ // Checkbox/radio → check
130
+ if (xpathLower.includes("checkbox") ||
131
+ xpathLower.includes('type="checkbox"') ||
132
+ xpathLower.includes('type="radio"') ||
133
+ contextType === "checkbox" ||
134
+ contextType === "radio" ||
135
+ contextRole === "checkbox" ||
136
+ contextRole === "radio") {
137
+ return "check";
138
+ }
139
+ // Select/dropdown → select
140
+ if (xpathLower.includes("select") ||
141
+ xpathLower.includes("dropdown") ||
142
+ xpathLower.includes("combobox") ||
143
+ xpathLower.includes("listbox") ||
144
+ contextRole === "combobox" ||
145
+ contextRole === "listbox" ||
146
+ contextRole === "option") {
147
+ return "select";
148
+ }
149
+ // Input/textarea → fill
150
+ if (xpathLower.includes("input") ||
151
+ xpathLower.includes("textarea") ||
152
+ xpathLower.includes('type="text"') ||
153
+ xpathLower.includes('type="password"') ||
154
+ xpathLower.includes('type="email"') ||
155
+ xpathLower.includes('type="number"') ||
156
+ xpathLower.includes('type="search"') ||
157
+ xpathLower.includes('type="tel"') ||
158
+ xpathLower.includes('type="url"') ||
159
+ contextRole === "textbox" ||
160
+ contextType === "text" ||
161
+ contextType === "password" ||
162
+ contextType === "email") {
163
+ return "fill";
164
+ }
165
+ // Hover keywords
166
+ if (contextText.includes("menu") ||
167
+ contextText.includes("tooltip") ||
168
+ xpathLower.includes("menu") ||
169
+ xpathLower.includes("tooltip") ||
170
+ xpathLower.includes("dropdown-toggle")) {
171
+ return "hover";
172
+ }
173
+ // Keyboard/press
174
+ if (contextText.includes("enter") ||
175
+ contextText.includes("escape") ||
176
+ contextText.includes("tab")) {
177
+ return "press";
178
+ }
179
+ // Default: click (botões, links, etc)
180
+ return "click";
181
+ }
182
+ // Adapted host resolution and resilient fetch (inspired by TestHubClient)
183
+ import * as os from "os";
184
+ import { Logger } from "../Logger.js";
185
+ import { TestContext } from "../../testContext/TestContext.js";
186
+ function isAzureEnvironment() {
187
+ // Forçar modo LOCAL via variável de ambiente (override explícito)
188
+ if (process.env.AUTOCORE_FORCE_LOCAL === "true")
189
+ return false;
190
+ // Variáveis exclusivas do Azure Pipelines / CI
191
+ if (process.env.TF_BUILD === "True" || process.env.AGENT_NAME)
192
+ return true;
193
+ if (process.env.CI === "true" || process.env.BUILD_BUILDID)
194
+ return true;
195
+ // NOTA: os.platform() foi removido propositalmente.
196
+ // macOS (darwin) e Linux são plataformas de desenvolvimento local válidas.
197
+ // Apenas variáveis de CI/CD indicam execução remota.
198
+ return false;
199
+ }
200
+ function isLikelyTestFileEntry(value) {
201
+ const lowered = value.toLowerCase();
202
+ return (lowered.includes(".spec.") ||
203
+ lowered.includes(".test.") ||
204
+ lowered.endsWith(".feature") ||
205
+ lowered.includes("/") ||
206
+ lowered.includes("\\"));
207
+ }
208
+ function parseCnCtFromTitlePath(testInfo) {
209
+ try {
210
+ const rawPath = Array.isArray(testInfo?.titlePath)
211
+ ? testInfo.titlePath
212
+ : [];
213
+ const cleaned = rawPath
214
+ .map((entry) => String(entry || "").trim())
215
+ .filter((entry) => !!entry && !isLikelyTestFileEntry(entry));
216
+ if (cleaned.length >= 2) {
217
+ const ctName = cleaned[cleaned.length - 1];
218
+ const cnName = cleaned[cleaned.length - 2];
219
+ return {
220
+ cnName,
221
+ ctName,
222
+ methodName: ctName,
223
+ };
224
+ }
225
+ const fallbackTitle = String(testInfo?.title || "").trim();
226
+ if (fallbackTitle) {
227
+ return {
228
+ cnName: fallbackTitle,
229
+ ctName: fallbackTitle,
230
+ methodName: fallbackTitle,
231
+ };
232
+ }
233
+ }
234
+ catch {
235
+ // no-op
236
+ }
237
+ return {};
238
+ }
239
+ /**
240
+ * Obtém metadados de recovery a partir de variáveis de ambiente e TestContext (v2.7.43)
241
+ * - executionSource: detecta automaticamente LOCAL vs AZURE
242
+ * - testCaseName/testCaseId/testMethodName: obtém do TestContext.testInfo quando disponível
243
+ */
244
+ export function getRecoveryMetadata() {
245
+ const executionSource = isAzureEnvironment()
246
+ ? "AZURE"
247
+ : "LOCAL";
248
+ let testCaseName;
249
+ let testCaseId;
250
+ let testMethodName;
251
+ // Obter do env (podem ser setados pelo runner/CI)
252
+ testCaseName = process.env.AUTOCORE_TEST_CASE_NAME || undefined;
253
+ testCaseId = process.env.AUTOCORE_TEST_CASE_ID || undefined;
254
+ testMethodName = process.env.AUTOCORE_TEST_METHOD_NAME || undefined;
255
+ // Tentar obter do TestContext.testInfo (Playwright) sem require dinâmico.
256
+ try {
257
+ const testInfo = TestContext?.testInfo || TestContext?.getSafeTestInfo?.();
258
+ if (testInfo) {
259
+ const parsed = parseCnCtFromTitlePath(testInfo);
260
+ if (!testCaseName) {
261
+ testCaseName = parsed.cnName || testInfo.title || undefined;
262
+ }
263
+ if (!testMethodName) {
264
+ testMethodName =
265
+ parsed.methodName || parsed.ctName || testInfo.title || undefined;
266
+ }
267
+ if (!testCaseId) {
268
+ const ctCandidate = parsed.ctName || testInfo.title || "";
269
+ const ctMatch = ctCandidate.match(/\b(CT[-_]?\d+)\b/i);
270
+ testCaseId = ctMatch?.[1] || ctCandidate || undefined;
271
+ }
272
+ }
273
+ }
274
+ catch {
275
+ // TestContext pode não estar disponível
276
+ }
277
+ if (!testCaseName)
278
+ testCaseName = process.env.AUTOCORE_CN_NAME || undefined;
279
+ if (!testCaseId)
280
+ testCaseId = process.env.AUTOCORE_CT_NAME || undefined;
281
+ if (!testMethodName)
282
+ testMethodName =
283
+ process.env.AUTOCORE_TEST_METHOD || testCaseId || undefined;
284
+ return { executionSource, testCaseName, testCaseId, testMethodName };
285
+ }
286
+ const HUB_URL_HOSTNAME = "http://brtlvlty0559pl:3333";
287
+ const HUB_URL_IP = "http://10.129.170.238:3333";
288
+ function getAutoCorehubUrl() {
289
+ if (process.env.AUTOCORE_HUB_URL?.trim()) {
290
+ return process.env.AUTOCORE_HUB_URL.trim();
291
+ }
292
+ if (process.env.AUTOCORE_HUB_IP?.trim()) {
293
+ return process.env.AUTOCORE_HUB_IP.trim();
294
+ }
295
+ if (isAzureEnvironment())
296
+ return HUB_URL_IP;
297
+ return HUB_URL_HOSTNAME;
298
+ }
299
+ function getFallbackHubUrl(currentUrl) {
300
+ if (currentUrl === HUB_URL_HOSTNAME)
301
+ return HUB_URL_IP;
302
+ return null;
303
+ }
304
+ async function fetchWithTimeout(url, options = {}) {
305
+ const { timeout = 60000, ...fetchOptions } = options;
306
+ const controller = new AbortController();
307
+ const id = setTimeout(() => controller.abort(), timeout);
308
+ try {
309
+ const res = await fetch(url, {
310
+ ...fetchOptions,
311
+ signal: controller.signal,
312
+ });
313
+ return res;
314
+ }
315
+ finally {
316
+ clearTimeout(id);
317
+ }
318
+ }
319
+ /**
320
+ * Constrói headers padrão para requisições ao AutoCore Hub
321
+ * Usa o mesmo x-automation-key do TestHubClient
322
+ */
323
+ function authHeaders() {
324
+ return {
325
+ "Content-Type": "application/json",
326
+ "x-automation-key": "d8f3c4a9be7f29d1c6e04fa1a39c2e7bd91f57c8a3de45eb91c4f7a2d0e3b8fa",
327
+ };
328
+ }
329
+ async function postJsonWithFallback(path, body, timeout = 60000, extraHeaders) {
330
+ const base = getAutoCorehubUrl();
331
+ const fallback = getFallbackHubUrl(base);
332
+ const headers = { ...authHeaders(), ...extraHeaders };
333
+ // Se não houver fallback, fazer apenas uma requisição
334
+ if (!fallback) {
335
+ return await fetchWithTimeout(`${base}${path}`, {
336
+ method: "POST",
337
+ headers,
338
+ body: JSON.stringify(body),
339
+ timeout,
340
+ });
341
+ }
342
+ // ✅ Promise.all: Tentar ambas URLs em paralelo (primary + fallback)
343
+ const [primaryResult, fallbackResult] = await Promise.allSettled([
344
+ fetchWithTimeout(`${base}${path}`, {
345
+ method: "POST",
346
+ headers,
347
+ body: JSON.stringify(body),
348
+ timeout,
349
+ }),
350
+ fetchWithTimeout(`${fallback}${path}`, {
351
+ method: "POST",
352
+ headers,
353
+ body: JSON.stringify(body),
354
+ timeout,
355
+ }),
356
+ ]);
357
+ // Priorizar resultado primário se sucesso
358
+ if (primaryResult.status === "fulfilled" && primaryResult.value.ok) {
359
+ return primaryResult.value;
360
+ }
361
+ // Usar fallback se primário falhou
362
+ if (fallbackResult.status === "fulfilled" && fallbackResult.value.ok) {
363
+ return fallbackResult.value;
364
+ }
365
+ // Se ambos falharam, retornar o erro do primário
366
+ if (primaryResult.status === "fulfilled") {
367
+ return primaryResult.value;
368
+ }
369
+ throw primaryResult.reason;
370
+ }
371
+ async function getJsonWithFallback(path, timeout = 60000) {
372
+ const base = getAutoCorehubUrl();
373
+ const fallback = getFallbackHubUrl(base);
374
+ // Se não houver fallback, fazer apenas uma requisição
375
+ if (!fallback) {
376
+ return await fetchWithTimeout(`${base}${path}`, {
377
+ method: "GET",
378
+ headers: authHeaders(),
379
+ timeout,
380
+ });
381
+ }
382
+ // ✅ Promise.all: Tentar ambas URLs em paralelo (primary + fallback)
383
+ const [primaryResult, fallbackResult] = await Promise.allSettled([
384
+ fetchWithTimeout(`${base}${path}`, {
385
+ method: "GET",
386
+ headers: authHeaders(),
387
+ timeout,
388
+ }),
389
+ fetchWithTimeout(`${fallback}${path}`, {
390
+ method: "GET",
391
+ headers: authHeaders(),
392
+ timeout,
393
+ }),
394
+ ]);
395
+ // Priorizar resultado primário se sucesso
396
+ if (primaryResult.status === "fulfilled" && primaryResult.value.ok) {
397
+ return primaryResult.value;
398
+ }
399
+ // Usar fallback se primário falhou
400
+ if (fallbackResult.status === "fulfilled" && fallbackResult.value.ok) {
401
+ return fallbackResult.value;
402
+ }
403
+ // Se ambos falharam, retornar o erro do primário
404
+ if (primaryResult.status === "fulfilled") {
405
+ return primaryResult.value;
406
+ }
407
+ throw primaryResult.reason;
408
+ }
409
+ export async function getRecurringFailures(params = {}) {
410
+ const days = params.days ?? 7;
411
+ const minOccurrences = params.minOccurrences ?? 3;
412
+ const limit = params.limit ?? 20;
413
+ const q = `?days=${encodeURIComponent(String(days))}&minOccurrences=${encodeURIComponent(String(minOccurrences))}&limit=${encodeURIComponent(String(limit))}`;
414
+ try {
415
+ const res = await getJsonWithFallback(`/api/auto-healing/recurring-failures${q}`);
416
+ if (!res || !res.ok)
417
+ return null;
418
+ return await res.json();
419
+ }
420
+ catch {
421
+ return null;
422
+ }
423
+ }
424
+ export async function suggestRefactor(xpath) {
425
+ try {
426
+ const res = await getJsonWithFallback(`/api/auto-healing/suggest-refactor?xpath=${encodeURIComponent(xpath)}`);
427
+ if (!res || !res.ok)
428
+ return null;
429
+ return await res.json();
430
+ }
431
+ catch {
432
+ return null;
433
+ }
434
+ }
435
+ export async function impactAnalysis(xpath) {
436
+ try {
437
+ const res = await getJsonWithFallback(`/api/auto-healing/impact-analysis/${encodeURIComponent(xpath)}`);
438
+ if (!res || !res.ok)
439
+ return null;
440
+ return await res.json();
441
+ }
442
+ catch {
443
+ return null;
444
+ }
445
+ }
446
+ /**
447
+ * Valida se o payload tem estrutura de página (obrigatório para análise)
448
+ */
449
+ function hasPageContext(body) {
450
+ // pageStructure com pelo menos um array preenchido
451
+ if (body.pageStructure) {
452
+ const { buttons, inputs, links, elements, headings } = body.pageStructure;
453
+ if ((buttons && buttons.length > 0) ||
454
+ (inputs && inputs.length > 0) ||
455
+ (links && links.length > 0) ||
456
+ (elements && elements.length > 0) ||
457
+ (headings && headings.length > 0)) {
458
+ return true;
459
+ }
460
+ }
461
+ // pageHtml com conteúdo
462
+ if (body.pageHtml && body.pageHtml.length > 100) {
463
+ return true;
464
+ }
465
+ return false;
466
+ }
467
+ export async function analyzeFailure(body) {
468
+ try {
469
+ // Auto-enriquecer com metadados de recovery quando não fornecidos (v2.7.43)
470
+ const meta = getRecoveryMetadata();
471
+ // Validar e preparar payload
472
+ // Construir actor a partir de UserHeaders se não fornecido explicitamente (item 23 v2.7.44)
473
+ const resolvedActor = body.actor || buildActorFromEnv();
474
+ const payload = {
475
+ ...body,
476
+ actionType: body.actionType,
477
+ errorType: body.errorType,
478
+ executionSource: body.executionSource || meta.executionSource,
479
+ testCaseName: body.testCaseName || meta.testCaseName,
480
+ testCaseId: body.testCaseId || meta.testCaseId,
481
+ testMethodName: body.testMethodName || meta.testMethodName,
482
+ actor: resolvedActor,
483
+ };
484
+ // ⚠️ Validar pageStructure ou pageHtml (obrigatório conforme guia)
485
+ if (!hasPageContext(body)) {
486
+ Logger.warning("[TestRecoveryClient] ⚠️ AVISO: pageStructure ou pageHtml não fornecido. A análise pode ter baixa qualidade. Use extract_page_structure ou page.content() para capturar o contexto.");
487
+ }
488
+ // Validar/inferir actionType
489
+ if (!isValidActionType(payload.actionType)) {
490
+ const contextForInference = body.context
491
+ ? {
492
+ text: body.context.text,
493
+ role: body.context.role,
494
+ type: body.context.type,
495
+ }
496
+ : undefined;
497
+ const inferred = inferActionType(body.failedXPath, contextForInference);
498
+ Logger.warning(`[TestRecoveryClient] actionType "${body.actionType}" inválido ou ausente. Inferido: "${inferred}" baseado no XPath/contexto.`);
499
+ payload.actionType = inferred;
500
+ }
501
+ // Validar errorType
502
+ if (!isValidErrorType(payload.errorType)) {
503
+ Logger.warning(`[TestRecoveryClient] errorType "${body.errorType}" inválido. Usando "not_found" como fallback.`);
504
+ payload.errorType = "not_found";
505
+ }
506
+ // (v2.7.44 - item 14) Enriquecer selectorAliases para fill de username/login
507
+ const actionStr = String(payload.actionType);
508
+ if ((actionStr === "fill" || actionStr === "type") &&
509
+ payload.failedXPath &&
510
+ !payload.context?.selectorAliases) {
511
+ const xp = payload.failedXPath;
512
+ if (!payload.context)
513
+ payload.context = {};
514
+ payload.context.selectorAliases = {
515
+ selector: xp,
516
+ ref: xp,
517
+ xpath: xp,
518
+ };
519
+ }
520
+ // 📤 LOG SIMPLIFICADO DA REQUISIÇÃO
521
+ Logger.info(`[TestRecoveryClient] 📤 Analisando falha no XPath: ${payload.failedXPath}`);
522
+ // CN/CT/Method + executionSource headers (v2.7.44 - item 10)
523
+ const analyzeHeaders = getSingletonUserHeaders();
524
+ if (payload.testCaseName) {
525
+ analyzeHeaders["x-test-case-name"] = payload.testCaseName;
526
+ analyzeHeaders["x-cn"] = payload.testCaseName;
527
+ analyzeHeaders["x-test-name"] = payload.testCaseName;
528
+ analyzeHeaders["x-cn-name"] = payload.testCaseName;
529
+ }
530
+ if (payload.testCaseId) {
531
+ analyzeHeaders["x-test-case-id"] = payload.testCaseId;
532
+ analyzeHeaders["x-ct"] = payload.testCaseId;
533
+ analyzeHeaders["x-test-id"] = payload.testCaseId;
534
+ analyzeHeaders["x-ct-id"] = payload.testCaseId;
535
+ }
536
+ if (payload.testMethodName) {
537
+ analyzeHeaders["x-test-method-name"] = payload.testMethodName;
538
+ analyzeHeaders["x-method"] = payload.testMethodName;
539
+ }
540
+ if (payload.executionSource) {
541
+ analyzeHeaders["x-execution-source"] = payload.executionSource;
542
+ analyzeHeaders["x-execution-origin"] = payload.executionSource;
543
+ }
544
+ const res = await postJsonWithFallback("/api/test-recovery/analyze-failure", payload, 60000, analyzeHeaders);
545
+ // 📥 Tratar resposta 202 (fila assíncrona) - v2.7.43
546
+ if (res && res.status === 202) {
547
+ try {
548
+ const queuedData = (await res.json());
549
+ if (queuedData.queued && queuedData.requestId) {
550
+ Logger.info(`[TestRecoveryClient] ⏳ Requisição enfileirada (posição ${queuedData.position ?? "?"}). Aguardando resultado...`);
551
+ const queueResult = await pollQueueResult(queuedData.requestId);
552
+ if (queueResult) {
553
+ Logger.success(`[TestRecoveryClient] ✅ Resultado da fila recebido!`);
554
+ return queueResult;
555
+ }
556
+ Logger.warning(`[TestRecoveryClient] ⚠️ Timeout/falha na fila assíncrona para requestId: ${queuedData.requestId}`);
557
+ return null;
558
+ }
559
+ }
560
+ catch {
561
+ Logger.warning("[TestRecoveryClient] ⚠️ Erro ao processar resposta 202");
562
+ }
563
+ return null;
564
+ }
565
+ // 📥 LOG SIMPLIFICADO DA RESPOSTA
566
+ if (!res || !res.ok) {
567
+ Logger.error(`[TestRecoveryClient] ❌ Erro na requisição: Status HTTP ${res?.status ?? "no response"}`);
568
+ return null;
569
+ }
570
+ const responseData = (await res.json());
571
+ Logger.success(`[TestRecoveryClient] ✅ AutoHealing executado com sucesso!`);
572
+ Logger.info(` 📍 XPath anterior: ${payload.failedXPath}`);
573
+ Logger.info(` ✨ XPath sugerido: ${responseData.analysis.suggestedXPath} (confiança: ${(responseData.analysis.confidence * 100).toFixed(0)}%)`);
574
+ if (responseData.logId) {
575
+ Logger.info(` 🆔 LogId: ${responseData.logId}`);
576
+ }
577
+ return responseData;
578
+ }
579
+ catch (err) {
580
+ Logger.error(`[TestRecoveryClient] ❌ Exceção: ${err instanceof Error ? err.message : String(err)}`);
581
+ return null;
582
+ }
583
+ }
584
+ export async function fixInCode(payload) {
585
+ try {
586
+ const res = await postJsonWithFallback("/api/test-recovery/fix-in-code", payload, 60000, getSingletonUserHeaders());
587
+ if (!res || !res.ok)
588
+ return null;
589
+ return (await res.json());
590
+ }
591
+ catch {
592
+ return null;
593
+ }
594
+ }
595
+ export async function markSuccess(sessionId, attemptNumber) {
596
+ try {
597
+ const res = await postJsonWithFallback("/api/test-recovery/mark-success", {
598
+ sessionId,
599
+ attemptNumber,
600
+ });
601
+ return !!res && res.ok;
602
+ }
603
+ catch {
604
+ return false;
605
+ }
606
+ }
607
+ function normalizeUserValue(value) {
608
+ return typeof value === "string" ? value.trim() : "";
609
+ }
610
+ function resolveRuntimeUserDefaults() {
611
+ const id = normalizeUserValue(process.env.AUTOCORE_USER_ID || process.env.USER_ID || "");
612
+ const email = normalizeUserValue(process.env.AUTOCORE_USER_EMAIL ||
613
+ process.env.BUILD_REQUESTEDFOREMAIL ||
614
+ process.env.BUILD_REQUESTEDFOR_EMAIL ||
615
+ "");
616
+ const matricula = normalizeUserValue(process.env.AUTOCORE_USER_MATRICULA ||
617
+ process.env.USERNAME ||
618
+ process.env.USER ||
619
+ "");
620
+ let osUserName = "";
621
+ try {
622
+ osUserName = normalizeUserValue(os.userInfo().username);
623
+ }
624
+ catch {
625
+ // noop
626
+ }
627
+ const name = normalizeUserValue(process.env.AUTOCORE_USER_NAME ||
628
+ process.env.USER_FULLNAME ||
629
+ matricula ||
630
+ osUserName);
631
+ return {
632
+ id,
633
+ email,
634
+ matricula,
635
+ name,
636
+ };
637
+ }
638
+ /**
639
+ * Constrói objeto `actor` a partir de variáveis de ambiente e singleton (item 23 v2.7.44).
640
+ * Usado automaticamente em analyzeFailure/reportResult quando nenhum actor é fornecido.
641
+ */
642
+ function buildActorFromEnv() {
643
+ const defaults = resolveRuntimeUserDefaults();
644
+ const id = normalizeUserValue(_singletonUserHeaders?.userId || defaults.id);
645
+ const name = normalizeUserValue(_singletonUserHeaders?.userName || defaults.name);
646
+ const email = normalizeUserValue(_singletonUserHeaders?.userEmail || defaults.email);
647
+ const matricula = normalizeUserValue(_singletonUserHeaders?.userMatricula || defaults.matricula);
648
+ if (!id && !name && !email && !matricula)
649
+ return undefined;
650
+ return {
651
+ ...(id ? { id } : {}),
652
+ ...(name ? { name } : {}),
653
+ ...(email ? { email } : {}),
654
+ ...(matricula ? { matricula } : {}),
655
+ };
656
+ }
657
+ /**
658
+ * Constrói headers extras de identificação do usuário (v2.7.44)
659
+ * Envia múltiplos aliases aceitos pelo backend para maximizar taxa de preenchimento
660
+ * Retorna apenas os headers de usuário (sem Content-Type/key)
661
+ */
662
+ function userHeadersMap(user) {
663
+ const headers = {};
664
+ const defaults = resolveRuntimeUserDefaults();
665
+ // Resolver valores: priorizar user param, depois env vars
666
+ const userId = normalizeUserValue(user?.userId || defaults.id);
667
+ const userEmail = normalizeUserValue(user?.userEmail || defaults.email);
668
+ const userMatricula = normalizeUserValue(user?.userMatricula || defaults.matricula);
669
+ const userName = normalizeUserValue(user?.userName || defaults.name);
670
+ // Aliases primários (item 9)
671
+ if (userId) {
672
+ headers["x-automation-user-id"] = userId;
673
+ headers["x-user-id"] = userId;
674
+ headers["x-userid"] = userId;
675
+ }
676
+ if (userEmail) {
677
+ headers["x-automation-user-email"] = userEmail;
678
+ headers["x-user-email"] = userEmail;
679
+ headers["x-framework-user-email"] = userEmail;
680
+ }
681
+ if (userMatricula) {
682
+ headers["x-automation-user-matricula"] = userMatricula;
683
+ headers["x-user-matricula"] = userMatricula;
684
+ headers["x-matricula"] = userMatricula;
685
+ headers["x-framework-user-matricula"] = userMatricula;
686
+ }
687
+ if (userName) {
688
+ headers["x-automation-user-name"] = userName;
689
+ headers["x-user-name"] = userName;
690
+ headers["x-user"] = userName;
691
+ headers["x-framework-user-name"] = userName;
692
+ }
693
+ if (!userName && userMatricula) {
694
+ headers["x-automation-user-name"] = userMatricula;
695
+ headers["x-user-name"] = userMatricula;
696
+ headers["x-user"] = userMatricula;
697
+ }
698
+ return headers;
699
+ }
700
+ /**
701
+ * Constrói headers completos com identificação do usuário (opcional)
702
+ */
703
+ function authHeadersWithUser(user) {
704
+ return { ...authHeaders(), ...userHeadersMap(user) };
705
+ }
706
+ /**
707
+ * Obtém user headers do singleton (para chamadas automáticas do framework)
708
+ */
709
+ function getSingletonUserHeaders() {
710
+ // Lazy access: singleton é criado no final do arquivo
711
+ try {
712
+ return userHeadersMap(_singletonUserHeaders);
713
+ }
714
+ catch {
715
+ return {};
716
+ }
717
+ }
718
+ // Referência aos userHeaders do singleton - atualizada por setUserHeaders()
719
+ let _singletonUserHeaders = {};
720
+ /**
721
+ * Reporta resultado de tentativa de recovery
722
+ *
723
+ * ⚠️ OBRIGATÓRIO chamar após testar XPath sugerido (v2.6.9)
724
+ * Sem report-result, a tentativa fica como "pendente" no dashboard
725
+ *
726
+ * @param params Parâmetros do resultado
727
+ * @param user Headers opcionais de identificação do usuário
728
+ * @returns Resposta do servidor
729
+ *
730
+ * @example
731
+ * ```typescript
732
+ * // Após testar XPath sugerido
733
+ * await reportResult({
734
+ * logId: result.logId,
735
+ * success: true,
736
+ * autoFixed: true,
737
+ * executionSource: 'AZURE',
738
+ * recoverySource: 'framework',
739
+ * testCaseName: 'Login Test',
740
+ * testCaseId: 'CT-001',
741
+ * testMethodName: 'testLogin'
742
+ * });
743
+ * ```
744
+ */
745
+ export async function reportResult(params, user) {
746
+ try {
747
+ // Auto-enriquecer com metadados quando ausentes (v2.7.44)
748
+ const meta = getRecoveryMetadata();
749
+ const enriched = {
750
+ ...params,
751
+ executionSource: params.executionSource || meta.executionSource,
752
+ testCaseName: params.testCaseName || meta.testCaseName,
753
+ testCaseId: params.testCaseId || meta.testCaseId,
754
+ testMethodName: params.testMethodName || meta.testMethodName,
755
+ actor: params.actor || buildActorFromEnv(),
756
+ };
757
+ const base = getAutoCorehubUrl();
758
+ const fallback = getFallbackHubUrl(base);
759
+ const userH = user || _singletonUserHeaders;
760
+ const headers = authHeadersWithUser(userH);
761
+ // CN/CT/Method headers (v2.7.44 - item 10)
762
+ if (enriched.testCaseName) {
763
+ headers["x-test-case-name"] = enriched.testCaseName;
764
+ headers["x-cn"] = enriched.testCaseName;
765
+ headers["x-test-name"] = enriched.testCaseName;
766
+ headers["x-cn-name"] = enriched.testCaseName;
767
+ }
768
+ if (enriched.testCaseId) {
769
+ headers["x-test-case-id"] = enriched.testCaseId;
770
+ headers["x-ct"] = enriched.testCaseId;
771
+ headers["x-test-id"] = enriched.testCaseId;
772
+ headers["x-ct-id"] = enriched.testCaseId;
773
+ }
774
+ if (enriched.testMethodName) {
775
+ headers["x-test-method-name"] = enriched.testMethodName;
776
+ headers["x-method"] = enriched.testMethodName;
777
+ }
778
+ if (enriched.executionSource) {
779
+ headers["x-execution-source"] = enriched.executionSource;
780
+ headers["x-execution-origin"] = enriched.executionSource;
781
+ }
782
+ const body = JSON.stringify(enriched);
783
+ // Se não houver fallback, fazer apenas uma requisição
784
+ if (!fallback) {
785
+ const res = await fetchWithTimeout(`${base}/api/test-recovery/report-result`, {
786
+ method: "POST",
787
+ headers,
788
+ body,
789
+ timeout: 30000,
790
+ });
791
+ if (!res.ok)
792
+ return null;
793
+ return (await res.json());
794
+ }
795
+ // Promise.all: Tentar ambas URLs em paralelo
796
+ const [primaryResult, fallbackResult] = await Promise.allSettled([
797
+ fetchWithTimeout(`${base}/api/test-recovery/report-result`, {
798
+ method: "POST",
799
+ headers,
800
+ body,
801
+ timeout: 30000,
802
+ }),
803
+ fetchWithTimeout(`${fallback}/api/test-recovery/report-result`, {
804
+ method: "POST",
805
+ headers,
806
+ body,
807
+ timeout: 30000,
808
+ }),
809
+ ]);
810
+ // Priorizar resultado primário se sucesso
811
+ if (primaryResult.status === "fulfilled" && primaryResult.value.ok) {
812
+ return (await primaryResult.value.json());
813
+ }
814
+ // Usar fallback se primário falhou
815
+ if (fallbackResult.status === "fulfilled" && fallbackResult.value.ok) {
816
+ return (await fallbackResult.value.json());
817
+ }
818
+ return null;
819
+ }
820
+ catch (err) {
821
+ Logger.error(`[TestRecoveryClient] Erro ao reportar resultado: ${err instanceof Error ? err.message : String(err)}`, err);
822
+ return null;
823
+ }
824
+ }
825
+ export async function getRecoveryStats(params = {}) {
826
+ const queryParts = [];
827
+ if (params.userId)
828
+ queryParts.push(`userId=${encodeURIComponent(params.userId)}`);
829
+ if (params.startDate)
830
+ queryParts.push(`startDate=${encodeURIComponent(params.startDate)}`);
831
+ if (params.endDate)
832
+ queryParts.push(`endDate=${encodeURIComponent(params.endDate)}`);
833
+ const q = queryParts.length ? `?${queryParts.join("&")}` : "";
834
+ try {
835
+ const res = await getJsonWithFallback(`/api/test-recovery/stats${q}`);
836
+ if (!res || !res.ok)
837
+ return null;
838
+ return (await res.json());
839
+ }
840
+ catch {
841
+ return null;
842
+ }
843
+ }
844
+ export async function getQueueStatus(requestId) {
845
+ try {
846
+ const res = await getJsonWithFallback(`/api/test-recovery/queue/status/${encodeURIComponent(requestId)}`);
847
+ if (!res || !res.ok)
848
+ return null;
849
+ return (await res.json());
850
+ }
851
+ catch {
852
+ return null;
853
+ }
854
+ }
855
+ /**
856
+ * Poll assíncrono para resultado de fila do analyze-failure (v2.7.44)
857
+ * Aguarda até o status ser 'completed' ou 'failed', com timeout de 90s
858
+ * Usa backoff progressivo: 1s, 2s, 3s, 5s, 8s... (item 25.2)
859
+ *
860
+ * @param requestId ID da requisição retornado pelo 202
861
+ * @param maxWaitMs Tempo máximo de espera em ms (default: 90000)
862
+ * @returns RecoveryResponse quando completado, null em timeout/falha
863
+ */
864
+ async function pollQueueResult(requestId, maxWaitMs = 90000) {
865
+ const startTime = Date.now();
866
+ // Backoff progressivo (Fibonacci-like): 1s, 2s, 3s, 5s, 8s, 8s, 8s...
867
+ const backoffSchedule = [1000, 2000, 3000, 5000, 8000];
868
+ let pollIndex = 0;
869
+ while (Date.now() - startTime < maxWaitMs) {
870
+ const delay = backoffSchedule[Math.min(pollIndex, backoffSchedule.length - 1)];
871
+ try {
872
+ const status = await getQueueStatus(requestId);
873
+ if (!status) {
874
+ await new Promise((r) => setTimeout(r, delay));
875
+ pollIndex++;
876
+ continue;
877
+ }
878
+ if (status.status === "completed" && status.result) {
879
+ return status.result;
880
+ }
881
+ if (status.status === "failed" || status.status === "timeout") {
882
+ Logger.error(`[TestRecoveryClient] ❌ Fila ${status.status} para ${requestId}: ${status.error || "unknown"}`);
883
+ return null;
884
+ }
885
+ // pending ou processing - aguardar com backoff
886
+ await new Promise((r) => setTimeout(r, delay));
887
+ pollIndex++;
888
+ }
889
+ catch {
890
+ await new Promise((r) => setTimeout(r, delay));
891
+ pollIndex++;
892
+ }
893
+ }
894
+ Logger.warning(`[TestRecoveryClient] ⏱️ Timeout de ${maxWaitMs}ms ao aguardar fila para ${requestId}`);
895
+ return null;
896
+ }
897
+ /**
898
+ * Obtém logs de recuperação (histórico)
899
+ * GET /api/test-recovery/recovery-logs
900
+ */
901
+ export async function getRecoveryLogs(params = {}) {
902
+ const queryParts = [];
903
+ if (params.sessionId)
904
+ queryParts.push(`sessionId=${encodeURIComponent(params.sessionId)}`);
905
+ if (params.limit)
906
+ queryParts.push(`limit=${encodeURIComponent(String(params.limit))}`);
907
+ const q = queryParts.length ? `?${queryParts.join("&")}` : "";
908
+ try {
909
+ const res = await getJsonWithFallback(`/api/test-recovery/recovery-logs${q}`);
910
+ if (!res || !res.ok)
911
+ return null;
912
+ return (await res.json());
913
+ }
914
+ catch {
915
+ return null;
916
+ }
917
+ }
918
+ // ==========================================
919
+ // TestRecoveryHelper - Classe helper para integração v2.6.1
920
+ // ==========================================
921
+ /**
922
+ * Classe helper para integração com Test Recovery
923
+ * Gerencia o estado de campos preenchidos e facilita a análise de falhas
924
+ *
925
+ * @example
926
+ * ```typescript
927
+ * const recovery = new TestRecoveryHelper();
928
+ *
929
+ * try {
930
+ * await page.locator(xpath).fill(value);
931
+ * recovery.markFieldFilled(xpath, 'fieldId');
932
+ * } catch (error) {
933
+ * const pageStructure = await recovery.extractPageStructure(page);
934
+ * const result = await recovery.analyzeFailure({
935
+ * url: page.url(),
936
+ * failedXPath: xpath,
937
+ * errorType: 'timeout',
938
+ * actionType: 'fill',
939
+ * pageStructure,
940
+ * value,
941
+ * });
942
+ *
943
+ * if (result?.analysis.confidence >= 0.5) {
944
+ * await page.locator(result.analysis.suggestedXPath).fill(value);
945
+ * recovery.markFieldFilled(result.analysis.suggestedXPath);
946
+ * }
947
+ * }
948
+ * ```
949
+ */
950
+ export class TestRecoveryHelper {
951
+ filledFields = [];
952
+ filledInputs = [];
953
+ sessionId;
954
+ userHeaders = {};
955
+ projectRoot;
956
+ /**
957
+ * Cria uma instância do TestRecoveryHelper
958
+ *
959
+ * @param sessionId ID da sessão para correlacionar requisições
960
+ * @param user Headers de identificação do usuário
961
+ * @param projectRoot Caminho absoluto do projeto (para auto-fix local)
962
+ */
963
+ constructor(sessionId, user, projectRoot) {
964
+ this.sessionId = sessionId;
965
+ if (user)
966
+ this.userHeaders = user;
967
+ this.projectRoot = projectRoot;
968
+ }
969
+ /**
970
+ * Define o projectRoot para auto-fix local (v2.7.1)
971
+ */
972
+ setProjectRoot(projectRoot) {
973
+ this.projectRoot = projectRoot;
974
+ }
975
+ /**
976
+ * Obtém o projectRoot atual
977
+ */
978
+ getProjectRoot() {
979
+ return this.projectRoot;
980
+ }
981
+ /**
982
+ * Define o sessionId para manter contexto entre requisições
983
+ */
984
+ setSessionId(sessionId) {
985
+ this.sessionId = sessionId;
986
+ }
987
+ /**
988
+ * Obtém o sessionId atual
989
+ */
990
+ getSessionId() {
991
+ return this.sessionId;
992
+ }
993
+ /**
994
+ * Define headers de identificação do usuário (v2.6.9)
995
+ * Também atualiza headers globais para chamadas do framework
996
+ */
997
+ setUserHeaders(user) {
998
+ this.userHeaders = user;
999
+ _singletonUserHeaders = user;
1000
+ }
1001
+ /**
1002
+ * Analisa falha e retorna XPath corrigido
1003
+ * Envia automaticamente o contexto de campos já preenchidos
1004
+ *
1005
+ * @param params Parâmetros da análise (v2.6.9: inclui executionSource, recoverySource, testCase*)
1006
+ */
1007
+ async analyzeFailure(params) {
1008
+ const request = {
1009
+ url: params.url,
1010
+ failedXPath: params.failedXPath,
1011
+ errorType: params.errorType,
1012
+ actionType: params.actionType,
1013
+ pageStructure: params.pageStructure,
1014
+ pageHtml: params.pageHtml,
1015
+ sessionId: this.sessionId,
1016
+ systemName: params.systemName,
1017
+ environment: params.environment,
1018
+ executionSource: params.executionSource,
1019
+ recoverySource: params.recoverySource ?? "framework",
1020
+ testCaseName: params.testCaseName,
1021
+ testCaseId: params.testCaseId,
1022
+ testMethodName: params.testMethodName,
1023
+ context: {
1024
+ value: params.value,
1025
+ originalMethod: params.actionType,
1026
+ filledInputs: [...this.filledInputs],
1027
+ filledFields: [...this.filledFields],
1028
+ },
1029
+ };
1030
+ return analyzeFailure(request);
1031
+ }
1032
+ /**
1033
+ * Reporta resultado de tentativa de recovery
1034
+ *
1035
+ * ⚠️ OBRIGATÓRIO chamar após testar XPath sugerido (v2.6.9)
1036
+ * Sem report-result, a tentativa fica como "pendente" no dashboard
1037
+ *
1038
+ * @param params Parâmetros do resultado
1039
+ * @returns Resposta do servidor
1040
+ */
1041
+ async reportResult(params) {
1042
+ return reportResult({
1043
+ ...params,
1044
+ sessionId: this.sessionId,
1045
+ recoverySource: params.recoverySource ?? "framework",
1046
+ }, this.userHeaders);
1047
+ }
1048
+ /**
1049
+ * Marca campo como preenchido com sucesso
1050
+ * Deve ser chamado após cada preenchimento bem-sucedido
1051
+ *
1052
+ * @param xpath XPath do elemento preenchido
1053
+ * @param fieldId ID ou name do campo (opcional, melhora a análise)
1054
+ */
1055
+ markFieldFilled(xpath, fieldId) {
1056
+ if (xpath && !this.filledInputs.includes(xpath)) {
1057
+ this.filledInputs.push(xpath);
1058
+ }
1059
+ if (fieldId && !this.filledFields.includes(fieldId)) {
1060
+ this.filledFields.push(fieldId);
1061
+ }
1062
+ }
1063
+ /**
1064
+ * Remove um campo da lista de preenchidos
1065
+ * Útil quando um campo é limpo ou precisa ser repreenchido
1066
+ */
1067
+ unmarkFieldFilled(xpath, fieldId) {
1068
+ const xpathIndex = this.filledInputs.indexOf(xpath);
1069
+ if (xpathIndex > -1) {
1070
+ this.filledInputs.splice(xpathIndex, 1);
1071
+ }
1072
+ if (fieldId) {
1073
+ const fieldIndex = this.filledFields.indexOf(fieldId);
1074
+ if (fieldIndex > -1) {
1075
+ this.filledFields.splice(fieldIndex, 1);
1076
+ }
1077
+ }
1078
+ }
1079
+ /**
1080
+ * Limpa histórico de campos preenchidos
1081
+ * Deve ser chamado no início de cada novo teste ou ao mudar de página
1082
+ */
1083
+ clearFilledFields() {
1084
+ this.filledInputs = [];
1085
+ this.filledFields = [];
1086
+ }
1087
+ /**
1088
+ * Obtém lista de XPaths preenchidos
1089
+ */
1090
+ getFilledInputs() {
1091
+ return [...this.filledInputs];
1092
+ }
1093
+ /**
1094
+ * Obtém lista de IDs/names preenchidos
1095
+ */
1096
+ getFilledFields() {
1097
+ return [...this.filledFields];
1098
+ }
1099
+ /**
1100
+ * Extrai estrutura da página usando Playwright Page
1101
+ * Retorna objeto pronto para enviar ao Test Recovery
1102
+ *
1103
+ * @param page Instância de Page do Playwright
1104
+ * @returns Estrutura da página com inputs, buttons, links e headings
1105
+ */
1106
+ async extractPageStructure(page) {
1107
+ try {
1108
+ const structure = await page.evaluate(() => {
1109
+ const getXPath = (element) => {
1110
+ if (element.id) {
1111
+ return `//*[@id="${element.id}"]`;
1112
+ }
1113
+ const parent = element.parentElement;
1114
+ if (!parent)
1115
+ return "";
1116
+ const siblings = Array.from(parent.children).filter((child) => child.tagName === element.tagName);
1117
+ const index = siblings.indexOf(element) + 1;
1118
+ const tag = element.tagName.toLowerCase();
1119
+ const parentXPath = getXPath(parent);
1120
+ if (siblings.length === 1) {
1121
+ return `${parentXPath}/${tag}`;
1122
+ }
1123
+ return `${parentXPath}/${tag}[${index}]`;
1124
+ };
1125
+ const extractElement = (el) => ({
1126
+ tag: el.tagName.toLowerCase(),
1127
+ text: el.textContent?.trim().slice(0, 200) || "",
1128
+ xpath: getXPath(el),
1129
+ id: el.id || undefined,
1130
+ name: el.getAttribute("name") || undefined,
1131
+ className: el.className || undefined,
1132
+ type: el.getAttribute("type") || undefined,
1133
+ placeholder: el.getAttribute("placeholder") || undefined,
1134
+ ariaLabel: el.getAttribute("aria-label") || undefined,
1135
+ dataTestId: el.getAttribute("data-testid") || undefined,
1136
+ title: el.getAttribute("title") || undefined,
1137
+ role: el.getAttribute("role") || undefined,
1138
+ href: el.getAttribute("href") || undefined,
1139
+ });
1140
+ return {
1141
+ title: document.title,
1142
+ url: window.location.href,
1143
+ buttons: Array.from(document.querySelectorAll('button, input[type="button"], input[type="submit"], [role="button"]')).map(extractElement),
1144
+ inputs: Array.from(document.querySelectorAll('input:not([type="button"]):not([type="submit"]):not([type="hidden"]), textarea, select, [role="textbox"], [role="combobox"]')).map(extractElement),
1145
+ links: Array.from(document.querySelectorAll("a[href]")).map(extractElement),
1146
+ headings: Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6")).map(extractElement),
1147
+ };
1148
+ });
1149
+ return structure;
1150
+ }
1151
+ catch (err) {
1152
+ Logger.warning(`[TestRecoveryHelper] Erro ao extrair estrutura da página: ${err instanceof Error ? err.message : String(err)}`);
1153
+ return undefined;
1154
+ }
1155
+ }
1156
+ /**
1157
+ * Extrai ID ou name de um XPath sugerido
1158
+ * Útil para marcar campo como preenchido após recovery
1159
+ *
1160
+ * @param xpath XPath para extrair o identificador
1161
+ * @returns ID ou name encontrado, ou undefined
1162
+ */
1163
+ extractFieldIdFromXPath(xpath) {
1164
+ // Tentar extrair @id="..."
1165
+ const idMatch = xpath.match(/@id=["']([^"']+)["']/);
1166
+ if (idMatch)
1167
+ return idMatch[1];
1168
+ // Tentar extrair @name="..."
1169
+ const nameMatch = xpath.match(/@name=["']([^"']+)["']/);
1170
+ if (nameMatch)
1171
+ return nameMatch[1];
1172
+ // Tentar extrair @data-testid="..."
1173
+ const testIdMatch = xpath.match(/@data-testid=["']([^"']+)["']/);
1174
+ if (testIdMatch)
1175
+ return testIdMatch[1];
1176
+ return undefined;
1177
+ }
1178
+ /**
1179
+ * Helper para executar ação com recovery automático
1180
+ * Se a ação falhar, tenta recuperar usando Test Recovery
1181
+ *
1182
+ * @param params Parâmetros da ação (v2.6.9: inclui executionSource, testCase*)
1183
+ * @returns Resultado da ação com informações de recovery
1184
+ */
1185
+ async executeWithRecovery(params) {
1186
+ const { page, xpath, actionType, value, timeout = 5000, minConfidence = 0.5, } = params;
1187
+ const attemptNumber = 1;
1188
+ // Tentar ação original
1189
+ try {
1190
+ if (actionType === "fill" && value !== undefined) {
1191
+ await page.locator(xpath).fill(value, { timeout });
1192
+ }
1193
+ else if (actionType === "click") {
1194
+ await page.locator(xpath).click({ timeout });
1195
+ }
1196
+ // Sucesso! Marcar como preenchido
1197
+ const fieldId = this.extractFieldIdFromXPath(xpath);
1198
+ this.markFieldFilled(xpath, fieldId);
1199
+ return { success: true, usedXPath: xpath, wasRecovered: false };
1200
+ }
1201
+ catch {
1202
+ Logger.info(`[TestRecoveryHelper] Falha na ação original (${xpath}), consultando Test Recovery...`);
1203
+ // Extrair estrutura da página
1204
+ const pageStructure = await this.extractPageStructure(page);
1205
+ // Consultar Test Recovery
1206
+ const result = await this.analyzeFailure({
1207
+ url: page.url(),
1208
+ failedXPath: xpath,
1209
+ errorType: "timeout",
1210
+ actionType,
1211
+ pageStructure,
1212
+ value,
1213
+ systemName: params.systemName,
1214
+ environment: params.environment,
1215
+ executionSource: params.executionSource,
1216
+ recoverySource: "framework",
1217
+ testCaseName: params.testCaseName,
1218
+ testCaseId: params.testCaseId,
1219
+ testMethodName: params.testMethodName,
1220
+ });
1221
+ if (!result?.analysis?.suggestedXPath) {
1222
+ Logger.error("[TestRecoveryHelper] Test Recovery não retornou sugestão");
1223
+ return { success: false, usedXPath: xpath, wasRecovered: false };
1224
+ }
1225
+ const { suggestedXPath, confidence, reason } = result.analysis;
1226
+ Logger.info(`[TestRecoveryHelper] Sugestão: ${suggestedXPath}`);
1227
+ Logger.info(`[TestRecoveryHelper] Confiança: ${(confidence * 100).toFixed(1)}%`);
1228
+ Logger.info(`[TestRecoveryHelper] Razão: ${reason}`);
1229
+ if (confidence < minConfidence) {
1230
+ Logger.warning(`[TestRecoveryHelper] Confiança muito baixa (${(confidence * 100).toFixed(1)}% < ${(minConfidence * 100).toFixed(1)}%)`);
1231
+ // ⚠️ OBRIGATÓRIO: Reportar falha (v2.6.9)
1232
+ await this.reportResult({
1233
+ xpath: suggestedXPath,
1234
+ attemptNumber,
1235
+ success: false,
1236
+ executionSource: params.executionSource,
1237
+ testCaseName: params.testCaseName,
1238
+ testCaseId: params.testCaseId,
1239
+ testMethodName: params.testMethodName,
1240
+ });
1241
+ return { success: false, usedXPath: xpath, wasRecovered: false };
1242
+ }
1243
+ // Tentar com XPath sugerido
1244
+ try {
1245
+ if (actionType === "fill" && value !== undefined) {
1246
+ await page.locator(suggestedXPath).fill(value, { timeout });
1247
+ }
1248
+ else if (actionType === "click") {
1249
+ await page.locator(suggestedXPath).click({ timeout });
1250
+ }
1251
+ // Sucesso com recovery!
1252
+ const fieldId = this.extractFieldIdFromXPath(suggestedXPath);
1253
+ this.markFieldFilled(suggestedXPath, fieldId);
1254
+ // ✅ OBRIGATÓRIO: Reportar sucesso (v2.6.9)
1255
+ await this.reportResult({
1256
+ xpath: suggestedXPath,
1257
+ attemptNumber,
1258
+ success: true,
1259
+ autoFixed: true,
1260
+ executionSource: params.executionSource,
1261
+ testCaseName: params.testCaseName,
1262
+ testCaseId: params.testCaseId,
1263
+ testMethodName: params.testMethodName,
1264
+ });
1265
+ return { success: true, usedXPath: suggestedXPath, wasRecovered: true };
1266
+ }
1267
+ catch (recoveryError) {
1268
+ Logger.error(`[TestRecoveryHelper] Falha mesmo com XPath sugerido: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`, recoveryError);
1269
+ // ❌ OBRIGATÓRIO: Reportar falha (v2.6.9)
1270
+ await this.reportResult({
1271
+ xpath: suggestedXPath,
1272
+ attemptNumber,
1273
+ success: false,
1274
+ executionSource: params.executionSource,
1275
+ testCaseName: params.testCaseName,
1276
+ testCaseId: params.testCaseId,
1277
+ testMethodName: params.testMethodName,
1278
+ });
1279
+ return {
1280
+ success: false,
1281
+ usedXPath: suggestedXPath,
1282
+ wasRecovered: false,
1283
+ };
1284
+ }
1285
+ }
1286
+ }
1287
+ /**
1288
+ * Aplica correção de XPath automaticamente nos arquivos do projeto (v2.7.1)
1289
+ *
1290
+ * ⚠️ Requisitos:
1291
+ * - executionSource deve ser 'LOCAL' (não funciona em Azure/CI)
1292
+ * - confidence deve ser >= 0.7 (segurança)
1293
+ * - projectRoot deve ser válido e acessível
1294
+ *
1295
+ * @param params Parâmetros para auto-fix local
1296
+ * @returns Resultado da operação com detalhes da correção
1297
+ *
1298
+ * @example
1299
+ * ```typescript
1300
+ * const recovery = new TestRecoveryHelper();
1301
+ *
1302
+ * // Após analyzeFailure retornar autoFixAvailable=true
1303
+ * const result = await recovery.autoFixLocal({
1304
+ * projectRoot: '/home/user/projeto',
1305
+ * oldXPath: '//input[@id="email"]',
1306
+ * newXPath: '//input[@data-testid="email-input"]',
1307
+ * confidence: 0.85,
1308
+ * attributesFile: 'AttributesLogin.ts'
1309
+ * });
1310
+ *
1311
+ * if (result.success) {
1312
+ * console.log(`Arquivo corrigido: ${result.fix?.filePath}`);
1313
+ * }
1314
+ * ```
1315
+ */
1316
+ async autoFixLocal(params) {
1317
+ // Usa projectRoot do parâmetro ou da instância
1318
+ const projectRoot = params.projectRoot || this.projectRoot;
1319
+ const { oldXPath, newXPath, confidence, autoConfirm = true } = params;
1320
+ // Validar confiança mínima
1321
+ if (confidence < 0.7) {
1322
+ return {
1323
+ success: false,
1324
+ message: `Confiança muito baixa (${(confidence * 100).toFixed(1)}% < 70%). Auto-fix requer mínimo 70% de confiança.`,
1325
+ error: "LOW_CONFIDENCE",
1326
+ };
1327
+ }
1328
+ // Validar que não está em ambiente CI/Azure
1329
+ if (isAzureEnvironment()) {
1330
+ return {
1331
+ success: false,
1332
+ message: "Auto-fix local não disponível em ambiente CI/Azure. Use executionSource=LOCAL.",
1333
+ error: "INVALID_ENVIRONMENT",
1334
+ };
1335
+ }
1336
+ // Validar projectRoot
1337
+ if (!projectRoot || projectRoot.trim() === "") {
1338
+ return {
1339
+ success: false,
1340
+ message: "projectRoot é obrigatório para auto-fix local.",
1341
+ error: "MISSING_PROJECT_ROOT",
1342
+ };
1343
+ }
1344
+ const request = {
1345
+ projectRoot,
1346
+ oldXPath,
1347
+ newXPath,
1348
+ confidence,
1349
+ testFilePath: params.testFilePath,
1350
+ attributesFile: params.attributesFile,
1351
+ className: params.className,
1352
+ locatorProperty: params.locatorProperty,
1353
+ methodName: params.methodName,
1354
+ lineNumber: params.lineNumber,
1355
+ recoverySessionId: this.sessionId,
1356
+ systemName: params.systemName,
1357
+ environmentType: params.environmentType,
1358
+ autoConfirm,
1359
+ executionSource: "Local",
1360
+ };
1361
+ try {
1362
+ // Headers extras: x-project-root como fallback (item 12 v2.7.44)
1363
+ const extraHeaders = {
1364
+ ...userHeadersMap(this.userHeaders),
1365
+ };
1366
+ if (projectRoot) {
1367
+ extraHeaders["x-project-root"] = projectRoot;
1368
+ }
1369
+ const meta = getRecoveryMetadata();
1370
+ if (meta.executionSource)
1371
+ extraHeaders["x-execution-source"] = meta.executionSource;
1372
+ const res = await postJsonWithFallback("/api/test-recovery/auto-fix-local", request, 30000, extraHeaders);
1373
+ if (!res.ok) {
1374
+ const errorText = await res.text().catch(() => "Erro desconhecido");
1375
+ Logger.error(`[TestRecoveryHelper] auto-fix-local falhou: ${res.status} - ${errorText}`);
1376
+ return {
1377
+ success: false,
1378
+ message: `Erro na API: ${res.status}`,
1379
+ error: errorText,
1380
+ };
1381
+ }
1382
+ const data = (await res.json());
1383
+ if (data.success && data.fix) {
1384
+ Logger.success(`[TestRecoveryHelper] ✅ Auto-fix aplicado: ${data.fix.filePath}`);
1385
+ Logger.info(`[TestRecoveryHelper] Linha ${data.fix.lineNumber}: "${data.fix.oldXPath}" → "${data.fix.newXPath}"`);
1386
+ }
1387
+ return data;
1388
+ }
1389
+ catch (err) {
1390
+ const errorMessage = err instanceof Error ? err.message : String(err);
1391
+ Logger.error(`[TestRecoveryHelper] Erro ao chamar auto-fix-local: ${errorMessage}`);
1392
+ return {
1393
+ success: false,
1394
+ message: "Falha na comunicação com a API de auto-fix.",
1395
+ error: errorMessage,
1396
+ };
1397
+ }
1398
+ }
1399
+ }
1400
+ // ==========================================
1401
+ // Instância singleton para uso simplificado
1402
+ // ==========================================
1403
+ /**
1404
+ * Instância singleton do TestRecoveryHelper
1405
+ * Use para casos simples sem necessidade de múltiplas instâncias
1406
+ *
1407
+ * @example
1408
+ * ```typescript
1409
+ * import { testRecoveryHelper } from '@silasfmartins/testhub';
1410
+ *
1411
+ * testRecoveryHelper.markFieldFilled(xpath, 'username');
1412
+ * const result = await testRecoveryHelper.analyzeFailure({ ... });
1413
+ * ```
1414
+ */
1415
+ export const testRecoveryHelper = new TestRecoveryHelper();