@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,1034 @@
1
+ export function HTMLTemplate(data) {
2
+ const { testes, titulo, subtitulo, totalTestes, testesPassaram, testesFalharam, estatisticasPorTipo, coresDosTipos, chartLabels, chartData, chartColors, barLabels, barData, pieLabels, pieData, getIconePorTipo, projectName, environment, executionDate, autocoreVersion, globalTotalCTs = 0, globalPassedCTs = 0, globalFailedCTs = 0, globalSkippedCTs = 0, } = data;
3
+ // 🔗 Obter dados do StatementTracker se disponível (via data.ctData passado externamente)
4
+ const ctData = data.ctData || null;
5
+ let allCNsData = data.allCNsData || new Map(); // 🆕 Mapa hierárquico CN → CTs → Statements → Actions
6
+ // 🔧 CORREÇÃO: Converter Map para objeto plano se necessário (JSON serialization)
7
+ if (allCNsData && typeof allCNsData === 'object' && !allCNsData.size) {
8
+ allCNsData = new Map(Object.entries(allCNsData));
9
+ }
10
+ const escapeHtml = (value) => typeof value === 'string'
11
+ ? value
12
+ .replace(/&/g, '&')
13
+ .replace(/</g, '&lt;')
14
+ .replace(/>/g, '&gt;')
15
+ .replace(/"/g, '&quot;')
16
+ .replace(/'/g, '&#39;')
17
+ : '';
18
+ return `
19
+ <!DOCTYPE html>
20
+ <html lang="pt-BR">
21
+ <head>
22
+ <meta charset="UTF-8" />
23
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
24
+ <title>${titulo}</title>
25
+ <script src="https://cdn.tailwindcss.com"></script>
26
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
27
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
28
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
29
+ <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
30
+ <style>
31
+ body { font-family: 'Inter', sans-serif; background-color: #0f172a; color: #f1f5f9; min-height: 100vh; }
32
+ .light-mode { background-color: #f9fafb !important; color: #222 !important; }
33
+ .tipo-badge { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 9999px; font-size: 13px; font-weight: 600; color: white; }
34
+ .card { background: #1e293b; border-radius: 1rem; padding: 1.5rem; box-shadow: 0 2px 8px #0002; }
35
+ .light-mode .card { background: #fff; color: #222; }
36
+ .btn { background: #334155; color: #fff; border: none; border-radius: 8px; padding: 0.5rem 1.2rem; font-weight: 600; cursor: pointer; margin-bottom: 0.25rem; transition: background 0.2s; }
37
+ .btn:hover { background: #475569; }
38
+ .light-mode .btn { background: #e2e8f0; color: #222; }
39
+ .table-container { overflow-x: auto; }
40
+ table { border-collapse: collapse; width: 100%; }
41
+ th, td { padding: 0.75rem 1rem; text-align: left; }
42
+ th { background: #1e293b; color: #fbbf24; font-size: 1rem; }
43
+ .light-mode th { background: #f1f5f9; color: #b45309; }
44
+ tr:nth-child(even) { background: #1e293b44; }
45
+ .light-mode tr:nth-child(even) { background: #f1f5f9; }
46
+ .chart-card { background: #1e293b; border-radius: 1rem; padding: 1rem; margin-bottom: 1.5rem; }
47
+ .light-mode .chart-card { background: #fff; }
48
+ .status-pass { color: #22c55e; font-weight: 700; }
49
+ .status-fail { color: #ef4444; font-weight: 700; }
50
+ .status-skip { color: #fbbf24; font-weight: 700; }
51
+ .header-bar { background: #1e293b; padding: 1rem 2rem; border-radius: 1rem 1rem 0 0; margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: space-between; }
52
+ .header-bar .header-title { font-size: 2rem; font-weight: 700; color: #fbbf24; }
53
+ .header-bar .header-meta { font-size: 1rem; color: #f1f5f9; }
54
+ .light-mode .header-bar { background: #f1f5f9; color: #222; }
55
+ .light-mode .header-title { color: #b45309; }
56
+ .light-mode .header-meta { color: #222 !important; }
57
+ .footer-bar { background: #1e293b; color: #fbbf24; padding: 1rem 2rem; border-radius: 0 0 1rem 1rem; margin-top: 2rem; text-align: center; font-size: 1rem; }
58
+ .light-mode .footer-bar { background: #f1f5f9; color: #b45309; }
59
+ .footer-bar a { color: #fbbf24; text-decoration: underline; }
60
+ .light-mode .footer-bar a { color: #b45309; }
61
+ @media (max-width: 900px) {
62
+ .grid-cols-2, .grid-cols-3, .grid-cols-4 { grid-template-columns: 1fr !important; }
63
+ }
64
+ @media print {
65
+ .chart-card canvas {
66
+ width: 300px !important;
67
+ height: 150px !important;
68
+ max-height: 150px !important;
69
+ transform: scale(0.85) !important;
70
+ transform-origin: top left !important;
71
+ }
72
+ }
73
+ .chart-card canvas {
74
+ height: 320px !important;
75
+ max-height: 320px !important;
76
+ width: 100% !important;
77
+ }
78
+ .chart-card-full { width: 100%; }
79
+ #chart-duracao {
80
+ width: 100% !important;
81
+ display: block !important;
82
+ }
83
+ </style>
84
+ </head>
85
+ <body id="body-root">
86
+ <div class="header-bar">
87
+ <div class="header-title">Relatório Unificado de Testes</div>
88
+ <div class="header-meta">
89
+ <span>Projeto: <strong>${projectName || 'TestHUB'}</strong></span> |
90
+ <span>Ambiente: <strong>${environment || 'N/A'}</strong></span> |
91
+ <span>Data: <strong>${executionDate || new Date().toLocaleString('pt-BR')}</strong></span> |
92
+ <span>Test Hub: <strong>${autocoreVersion || 'v1.1.x'}</strong></span>
93
+ </div>
94
+ </div>
95
+ <div class="flex gap-2 mb-6 justify-end mr-6">
96
+ <button id="toggle-theme" class="btn" type="button" title="Alternar tema">🌙 Tema</button>
97
+ <button id="export-pdf" class="btn" type="button" title="Exportar PDF">📄 PDF</button>
98
+ <button id="export-csv" class="btn" type="button" title="Exportar CSV">📑 CSV</button>
99
+ <button id="export-json" class="btn" type="button" title="Exportar JSON">🗒️ JSON</button>
100
+ <button id="export-xlsx" class="btn" type="button" title="Exportar XLSX">📊 XLSX</button>
101
+ </div>
102
+ <div id="report-root" class="max-w-7xl mx-auto p-6" style="background:inherit;">
103
+ <h1 class="text-4xl font-bold mb-2">${titulo}</h1>
104
+ <p class="text-slate-400 mb-6">${subtitulo}</p>
105
+
106
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
107
+ <div class="card flex flex-col items-center">
108
+ <span class="text-2xl font-bold">${totalTestes}</span>
109
+ <span class="text-slate-400">Total</span>
110
+ </div>
111
+ <div class="card flex flex-col items-center">
112
+ <span class="text-2xl font-bold text-green-400">${testesPassaram}</span>
113
+ <span class="text-green-400">Passou</span>
114
+ </div>
115
+ <div class="card flex flex-col items-center">
116
+ <span class="text-2xl font-bold text-red-400">${testesFalharam}</span>
117
+ <span class="text-red-400">Falhou</span>
118
+ </div>
119
+ </div>
120
+
121
+ ${globalTotalCTs > 0
122
+ ? `
123
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
124
+ <div class="card flex flex-col items-center">
125
+ <span class="text-2xl font-bold">${globalTotalCTs}</span>
126
+ <span class="text-slate-400">Total de CTs</span>
127
+ </div>
128
+ <div class="card flex flex-col items-center">
129
+ <span class="text-2xl font-bold text-green-400">${globalPassedCTs}</span>
130
+ <span class="text-green-400">CTs Passaram</span>
131
+ </div>
132
+ <div class="card flex flex-col items-center">
133
+ <span class="text-2xl font-bold text-red-400">${globalFailedCTs}</span>
134
+ <span class="text-red-400">CTs Falharam</span>
135
+ </div>
136
+ <div class="card flex flex-col items-center">
137
+ <span class="text-2xl font-bold text-yellow-400">${globalSkippedCTs}</span>
138
+ <span class="text-yellow-400">CTs Pulados</span>
139
+ </div>
140
+ </div>
141
+ `
142
+ : ''}
143
+
144
+
145
+ <!-- Gráficos superiores -->
146
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
147
+ <div class="chart-card"><canvas id="chart-tipos" height="180"></canvas></div>
148
+ <div class="chart-card"><canvas id="chart-passfail" height="180"></canvas></div>
149
+ </div>
150
+
151
+ <!-- Gráfico inferior ocupando toda a largura -->
152
+ <div class="chart-card w-full mb-8">
153
+ <canvas id="chart-duracao" height="320" style="width: 100%; display: block;"></canvas>
154
+ </div>
155
+
156
+ <!-- 🆕 NOVA SEÇÃO: Hierarquia Completa de CNs → CTs → Statements → Actions -->
157
+ ${allCNsData && allCNsData.size > 0
158
+ ? `
159
+ <div class="mb-8">
160
+ <h2 class="text-2xl font-bold mb-6 text-center text-cyan-400">
161
+ 🧪 Hierarquia Completa: Casos de Negócio (CNs) e Cenários de Teste (CTs)
162
+ </h2>
163
+
164
+ <div class="card">
165
+ <div class="mb-4 flex items-center justify-between">
166
+ <div class="text-gray-300">
167
+ <span class="bg-cyan-600 px-3 py-1 rounded-full text-sm font-medium mr-2">
168
+ ${allCNsData.size} CN${allCNsData.size > 1 ? 's' : ''}
169
+ </span>
170
+ <span class="bg-blue-600 px-3 py-1 rounded-full text-sm font-medium">
171
+ ${Array.from(allCNsData.values()).reduce((total, cn) => total + (cn.cts?.length || 0), 0)} CT${Array.from(allCNsData.values()).reduce((total, cn) => total + (cn.cts?.length || 0), 0) !== 1 ? 's' : ''}
172
+ </span>
173
+ </div>
174
+ <button onclick="expandAllCNs()" class="btn text-xs px-3 py-1">🔽 Expandir Todos</button>
175
+ </div>
176
+
177
+ <div class="space-y-4">
178
+ ${Array.from(allCNsData.entries())
179
+ .map((entry, cnIndex) => {
180
+ const [cnName, cnData] = entry;
181
+ const cts = cnData.cts || [];
182
+ const totalCTs = cts.length;
183
+ const passedCTs = cts.filter((ct) => ct.status === 'passed').length;
184
+ // ✅ CORREÇÃO: CTs "running" devem ser contados como "failed"
185
+ const failedCTs = cts.filter((ct) => ct.status === 'failed' || ct.status === 'running').length;
186
+ const totalDuration = cts.reduce((sum, ct) => sum + (ct.duration || 0), 0);
187
+ const totalStatements = cts.reduce((sum, ct) => sum + (ct.statements?.length || 0), 0);
188
+ const totalActions = cts.reduce((sum, ct) => sum + (ct.executedActions?.length || 0), 0);
189
+ return `
190
+ <div class="bg-slate-800 rounded-lg overflow-hidden border-l-4 ${failedCTs > 0 ? 'border-red-500' : passedCTs === totalCTs ? 'border-green-500' : 'border-yellow-500'}">
191
+ <!-- Cabeçalho do CN -->
192
+ <button onclick="toggleCN('cn-${cnIndex}')" class="w-full text-left p-4 hover:bg-slate-700 transition-colors flex items-center justify-between">
193
+ <div class="flex items-center gap-3 flex-1">
194
+ <span class="text-2xl">🧪</span>
195
+ <div class="flex-1">
196
+ <h3 class="text-lg font-bold text-white">${cnName}</h3>
197
+ <div class="flex items-center gap-3 mt-1 text-sm text-gray-400">
198
+ <span>📋 ${totalCTs} CT${totalCTs !== 1 ? 's' : ''}</span>
199
+ <span>⏱️ ${(totalDuration / 1000).toFixed(2)}s</span>
200
+ <span>📝 ${totalStatements} Statement${totalStatements !== 1 ? 's' : ''}</span>
201
+ <span>🎬 ${totalActions} Action${totalActions !== 1 ? 's' : ''}</span>
202
+ </div>
203
+ </div>
204
+ <div class="flex items-center gap-2">
205
+ <span class="status-pass text-sm">✅ ${passedCTs}</span>
206
+ <span class="status-fail text-sm">❌ ${failedCTs}</span>
207
+ <span class="text-2xl transform transition-transform" id="arrow-cn-${cnIndex}">▶</span>
208
+ </div>
209
+ </div>
210
+ </button>
211
+
212
+ <!-- Corpo expandível do CN -->
213
+ <div id="cn-${cnIndex}" style="display: none;" class="bg-slate-900 p-4">
214
+ <div class="space-y-3">
215
+ ${cts
216
+ .map((ct, ctIndex) => `
217
+ <div class="bg-slate-800 rounded-lg p-3 border-l-4 ${ct.status === 'passed'
218
+ ? 'border-green-500'
219
+ : ct.status === 'skipped'
220
+ ? 'border-yellow-500'
221
+ : 'border-red-500'}">
222
+ <!-- Cabeçalho do CT -->
223
+ <button onclick="toggleCT('ct-${cnIndex}-${ctIndex}')" class="w-full text-left flex items-start justify-between hover:opacity-80 transition-opacity">
224
+ <div class="flex-1">
225
+ <div class="flex items-center gap-2 mb-1">
226
+ <span class="inline-flex items-center justify-center w-6 h-6 bg-blue-600 text-white text-xs rounded-full">
227
+ ${ctIndex + 1}
228
+ </span>
229
+ <code class="text-xs text-cyan-300 bg-gray-900 px-2 py-1 rounded">${ct.id || `CT${(ctIndex + 1).toString().padStart(3, '0')}`}</code>
230
+ <span class="font-medium text-white">${ct.name || 'Unknown CT'}</span>
231
+ <span class="text-xs transform transition-transform inline-block" id="arrow-ct-${cnIndex}-${ctIndex}">▶</span>
232
+ </div>
233
+ <div class="flex items-center gap-3 text-xs text-gray-400 ml-8">
234
+ <span class="font-mono ${(ct.duration / 1000) > 5
235
+ ? 'text-red-400'
236
+ : ct.duration / 1000 > 2
237
+ ? 'text-yellow-400'
238
+ : 'text-green-400'}">⏱️ ${((ct.duration || 0) / 1000).toFixed(3)}s</span>
239
+ ${ct.statements && ct.statements.length > 0 ? `<span>📝 ${ct.statements.length} Statements</span>` : ''}
240
+ ${ct.executedActions && ct.executedActions.length > 0 ? `<span>🎬 ${ct.executedActions.length} Actions</span>` : ''}
241
+ </div>
242
+ </div>
243
+ <span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${ct.status === 'passed'
244
+ ? 'bg-green-600 text-white'
245
+ : ct.status === 'skipped'
246
+ ? 'bg-yellow-600 text-black'
247
+ : 'bg-red-600 text-white'}">
248
+ ${ct.status === 'passed'
249
+ ? '✅ PASSED'
250
+ : ct.status === 'skipped'
251
+ ? '⏭️ SKIPPED'
252
+ : '❌ FAILED'}
253
+ </span>
254
+ </button>
255
+
256
+ <!-- Corpo expandível do CT -->
257
+ <div id="ct-${cnIndex}-${ctIndex}" style="display: none;" class="mt-3 ml-8 space-y-3">
258
+ <!-- Classe e Método -->
259
+ <div class="bg-slate-700 rounded p-2 text-xs">
260
+ <div class="text-yellow-300 font-mono">📦 ${ct.className || 'Unknown'}</div>
261
+ <div class="text-green-300 font-mono">.${ct.methodName || 'unknownMethod'}()</div>
262
+ </div>
263
+
264
+ ${ct.error
265
+ ? `
266
+ <div class="bg-red-900/30 border border-red-600 rounded p-3">
267
+ <div class="flex items-start gap-2">
268
+ <span class="text-red-400 text-lg">⚠️</span>
269
+ <div>
270
+ <div class="text-sm font-medium text-red-400 mb-1">Erro:</div>
271
+ <pre class="text-xs text-red-200 overflow-x-auto">${ct.error}</pre>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ `
276
+ : ''}
277
+
278
+ <!-- Statements -->
279
+ ${ct.statements && ct.statements.length > 0
280
+ ? `
281
+ <div class="bg-slate-700 rounded-lg p-3">
282
+ <div class="flex items-center gap-2 mb-2">
283
+ <span class="text-lg">📝</span>
284
+ <h5 class="text-sm font-bold text-cyan-400">Statements (${ct.statements.length})</h5>
285
+ </div>
286
+ <div class="space-y-1">
287
+ ${ct.statements
288
+ .map((stmt, stmtIdx) => `
289
+ <div class="bg-slate-800 rounded p-2 flex items-start justify-between gap-2">
290
+ <div class="flex items-start gap-2 flex-1 min-w-0">
291
+ <span class="text-slate-400 font-mono text-xs flex-shrink-0">#${stmtIdx + 1}</span>
292
+ <span class="${stmt.status === 'passed'
293
+ ? 'text-green-400'
294
+ : stmt.status === 'failed'
295
+ ? 'text-red-400'
296
+ : stmt.status === 'skipped'
297
+ ? 'text-yellow-400'
298
+ : 'text-blue-400'}">
299
+ ${stmt.status === 'passed' ? '✅' : stmt.status === 'failed' ? '❌' : stmt.status === 'skipped' ? '⚠️' : '🔄'}
300
+ </span>
301
+ <div class="flex-1 min-w-0">
302
+ <div class="text-xs truncate">
303
+ <code class="text-yellow-300">${stmt.className || 'Statement'}</code>
304
+ <span class="text-gray-400">.</span>
305
+ <code class="text-green-300">${stmt.methodName || 'method'}()</code>
306
+ </div>
307
+ ${stmt.error
308
+ ? `
309
+ <div class="text-xs text-red-300 mt-1 bg-red-900/20 p-1 rounded">
310
+ ${stmt.error}
311
+ </div>
312
+ `
313
+ : ''}
314
+ </div>
315
+ </div>
316
+ <div class="text-right flex-shrink-0">
317
+ <div class="text-xs font-mono ${stmt.duration > 5000
318
+ ? 'text-red-400'
319
+ : stmt.duration > 2000
320
+ ? 'text-yellow-400'
321
+ : 'text-green-400'}">
322
+ ${stmt.duration || 0}ms
323
+ </div>
324
+ <div class="text-xs text-slate-400 capitalize">
325
+ ${stmt.status || 'running'}
326
+ </div>
327
+ </div>
328
+ </div>
329
+ `)
330
+ .join('')}
331
+ </div>
332
+ </div>
333
+ `
334
+ : ''}
335
+
336
+ <!-- Actions -->
337
+ ${ct.executedActions && ct.executedActions.length > 0
338
+ ? `
339
+ <div class="bg-slate-700 rounded-lg p-3">
340
+ <div class="flex items-center gap-2 mb-2">
341
+ <span class="text-lg">🎬</span>
342
+ <h5 class="text-sm font-bold text-purple-400">Actions (${ct.executedActions.length})</h5>
343
+ </div>
344
+ <div class="grid gap-2 md:grid-cols-2">
345
+ ${ct.executedActions
346
+ .map((action, actIdx) => `
347
+ <div class="bg-slate-800 rounded p-2 flex items-start gap-2">
348
+ <span class="text-slate-400 font-mono text-xs flex-shrink-0">#${actIdx + 1}</span>
349
+ <div class="flex-1 min-w-0">
350
+ <div class="flex items-center gap-2 flex-wrap mb-1">
351
+ <span class="${action.type === 'API'
352
+ ? 'bg-green-600'
353
+ : action.type === 'WEB'
354
+ ? 'bg-blue-600'
355
+ : action.type === 'MOBILE'
356
+ ? 'bg-purple-600'
357
+ : action.type === 'SSH'
358
+ ? 'bg-orange-600'
359
+ : action.type === 'DATABASE'
360
+ ? 'bg-yellow-600'
361
+ : 'bg-gray-600'} text-white text-xs px-2 py-0.5 rounded font-bold flex-shrink-0">
362
+ ${action.type === 'API'
363
+ ? '📡 API'
364
+ : action.type === 'WEB'
365
+ ? '🌐 WEB'
366
+ : action.type === 'MOBILE'
367
+ ? '📱 MOBILE'
368
+ : action.type === 'SSH'
369
+ ? '🖥️ SSH'
370
+ : action.type === 'DATABASE'
371
+ ? '🗄️ DB'
372
+ : '⚙️ ' + action.type}
373
+ </span>
374
+ <span class="${action.success ? 'text-green-400' : 'text-red-400'} text-sm">
375
+ ${action.success ? '✅' : '❌'}
376
+ </span>
377
+ <span class="text-xs font-mono ${action.duration > 1000
378
+ ? 'text-red-400'
379
+ : action.duration > 500
380
+ ? 'text-yellow-400'
381
+ : 'text-green-400'}">
382
+ ${action.duration || 0}ms
383
+ </span>
384
+ </div>
385
+ <div class="text-xs text-slate-300 truncate" title="${action.description || action.method || action.url || 'Ação executada'}">
386
+ ${action.description || action.method || action.url || 'Ação executada'}
387
+ </div>
388
+ </div>
389
+ </div>
390
+ `)
391
+ .join('')}
392
+ </div>
393
+ </div>
394
+ `
395
+ : ''}
396
+ </div>
397
+ </div>
398
+ `)
399
+ .join('')}
400
+ </div>
401
+ </div>
402
+ </div>
403
+ `;
404
+ })
405
+ .join('')}
406
+ </div>
407
+ </div>
408
+ </div>
409
+ `
410
+ : ''}
411
+
412
+ <!-- Seção de CTs Detectados Automaticamente -->
413
+ ${ctData && ctData.length > 0
414
+ ? `
415
+ <div class="mb-8">
416
+ <h2 class="text-2xl font-bold mb-6 text-center">🎯 CTs Detectados Automaticamente</h2>
417
+ <div class="card">
418
+ <div class="mb-4 text-center text-gray-300">
419
+ <span class="bg-blue-600 px-3 py-1 rounded-full text-sm font-medium">
420
+ ${ctData.length} CT${ctData.length > 1 ? 's' : ''} detectado${ctData.length > 1 ? 's' : ''}
421
+ </span>
422
+ </div>
423
+
424
+ <div class="overflow-x-auto">
425
+ <table class="w-full text-sm">
426
+ <thead>
427
+ <tr class="bg-gray-700">
428
+ <th class="p-3 text-left">CT#</th>
429
+ <th class="p-3 text-left">Nome do CT</th>
430
+ <th class="p-3 text-center">Duração</th>
431
+ <th class="p-3 text-center">Ações Executadas</th>
432
+ <th class="p-3 text-left">Detalhes</th>
433
+ </tr>
434
+ </thead>
435
+ <tbody>
436
+ ${ctData
437
+ .map((ct, index) => `
438
+ <tr class="border-b border-gray-600">
439
+ <td class="p-3 font-mono text-blue-400">CT${ct.id}</td>
440
+ <td class="p-3 font-medium">${ct.name}</td>
441
+ <td class="p-3 text-center font-mono ${ct.duration > 5000
442
+ ? 'text-red-400'
443
+ : ct.duration > 2000
444
+ ? 'text-yellow-400'
445
+ : 'text-green-400'}">
446
+ ${(ct.duration / 1000).toFixed(3)}s
447
+ </td>
448
+ <td class="p-3 text-center">
449
+ <div class="flex justify-center gap-1 flex-wrap">
450
+ ${ct.executedActions && ct.executedActions.length > 0
451
+ ? ct.executedActions
452
+ .map((action) => `
453
+ <span class="px-2 py-1 rounded text-xs font-medium ${action.type === 'API'
454
+ ? 'bg-green-600 text-white'
455
+ : action.type === 'SSH'
456
+ ? 'bg-purple-600 text-white'
457
+ : action.type === 'DB'
458
+ ? 'bg-blue-600 text-white'
459
+ : action.type === 'UI'
460
+ ? 'bg-orange-600 text-white'
461
+ : action.type === 'MOBILE'
462
+ ? 'bg-pink-600 text-white'
463
+ : 'bg-gray-600 text-white'}" title="${action.description}">
464
+ ${action.type}
465
+ </span>
466
+ `)
467
+ .join('')
468
+ : '<span class="text-gray-400 text-xs">Nenhuma ação detectada</span>'}
469
+ </div>
470
+ </td>
471
+ <td class="p-3 text-xs text-gray-400">
472
+ <div>Iniciado: ${new Date(ct.startTime).toLocaleTimeString()}</div>
473
+ <div>Finalizado: ${new Date(ct.endTime).toLocaleTimeString()}</div>
474
+ </td>
475
+ </tr>
476
+ `)
477
+ .join('')}
478
+ </tbody>
479
+ </table>
480
+ </div>
481
+ </div>
482
+ </div>
483
+ `
484
+ : ''}
485
+
486
+ <!-- 📋 NOVO: Logs do Terminal -->
487
+ ${(() => {
488
+ // Tentar obter logs do TerminalLogCapture via data
489
+ const terminalLogs = data.terminalLogs;
490
+ if (!terminalLogs ||
491
+ !terminalLogs.byTest ||
492
+ Object.keys(terminalLogs.byTest).length === 0) {
493
+ return ''; // Não mostrar seção se não houver logs
494
+ }
495
+ return `
496
+ <div class="card mb-8">
497
+ <h2 class="text-2xl font-bold mb-6 text-center">📋 Logs do Terminal</h2>
498
+
499
+ <div class="mb-4 flex gap-4 justify-center text-sm">
500
+ <div class="px-4 py-2 bg-blue-900/30 rounded-lg border border-blue-700/50">
501
+ <span class="text-gray-400">Total:</span>
502
+ <span class="font-bold text-blue-400">${terminalLogs.totalLogs || 0}</span>
503
+ </div>
504
+ <div class="px-4 py-2 bg-gray-900/30 rounded-lg border border-gray-700/50">
505
+ <span class="text-gray-400">Log:</span>
506
+ <span class="font-bold text-gray-300">${terminalLogs.byLevel?.log || 0}</span>
507
+ </div>
508
+ <div class="px-4 py-2 bg-cyan-900/30 rounded-lg border border-cyan-700/50">
509
+ <span class="text-gray-400">Info:</span>
510
+ <span class="font-bold text-cyan-400">${terminalLogs.byLevel?.info || 0}</span>
511
+ </div>
512
+ <div class="px-4 py-2 bg-yellow-900/30 rounded-lg border border-yellow-700/50">
513
+ <span class="text-gray-400">Warn:</span>
514
+ <span class="font-bold text-yellow-400">${terminalLogs.byLevel?.warn || 0}</span>
515
+ </div>
516
+ <div class="px-4 py-2 bg-red-900/30 rounded-lg border border-red-700/50">
517
+ <span class="text-gray-400">Error:</span>
518
+ <span class="font-bold text-red-400">${terminalLogs.byLevel?.error || 0}</span>
519
+ </div>
520
+ </div>
521
+
522
+ ${Object.entries(terminalLogs.byTest)
523
+ .map(([testName, logs]) => {
524
+ if (!logs || logs.length === 0)
525
+ return '';
526
+ const cleanName = testName.replace(/[^a-zA-Z0-9]/g, '_');
527
+ return `
528
+ <div class="mb-4 border border-gray-700 rounded-lg overflow-hidden">
529
+ <button
530
+ onclick="toggleTerminalLogs('${cleanName}')"
531
+ class="w-full text-left px-4 py-3 bg-gray-800/50 hover:bg-gray-700/50 transition-colors flex items-center justify-between">
532
+ <div class="flex items-center gap-2">
533
+ <span class="text-yellow-400">📋</span>
534
+ <span class="font-medium">${testName}</span>
535
+ <span class="text-xs px-2 py-1 bg-blue-900/50 rounded-full text-blue-300">${logs.length} logs</span>
536
+ </div>
537
+ <span id="terminal-btn-${cleanName}" class="text-xs text-gray-400">▼ Expandir</span>
538
+ </button>
539
+ <div id="terminal-logs-${cleanName}" style="display: none;" class="max-h-96 overflow-y-auto bg-gray-900/50">
540
+ <div class="p-4 font-mono text-xs space-y-1">
541
+ ${logs
542
+ .map((log) => {
543
+ const levelColors = {
544
+ log: 'text-gray-300',
545
+ info: 'text-cyan-400',
546
+ warn: 'text-yellow-400',
547
+ error: 'text-red-400',
548
+ debug: 'text-purple-400',
549
+ };
550
+ const color = levelColors[log.level] || 'text-gray-300';
551
+ const icon = log.level === 'error'
552
+ ? '❌'
553
+ : log.level === 'warn'
554
+ ? '⚠️'
555
+ : log.level === 'info'
556
+ ? 'ℹ️'
557
+ : '📝';
558
+ return `
559
+ <div class="py-1 px-2 hover:bg-gray-800/30 rounded">
560
+ <span class="text-gray-500">[${new Date(log.timestamp).toLocaleTimeString('pt-BR')}]</span>
561
+ <span class="${color}">[${icon} ${log.level.toUpperCase()}]</span>
562
+ <span class="text-gray-200">${log.message.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</span>
563
+ </div>
564
+ `;
565
+ })
566
+ .join('')}
567
+ </div>
568
+ </div>
569
+ </div>
570
+ `;
571
+ })
572
+ .join('')}
573
+ </div>
574
+ `;
575
+ })()}
576
+
577
+ <div class="footer-bar">
578
+ Desenvolvido por <strong>TestHUB</strong> &mdash; Plataforma Unificada de Automação de Testes.<br>
579
+ <span>Relatório gerado automaticamente. Para dúvidas, acesse a <a href="http://brtlvlty0559pl:3002/" target="_blank">website HUB</a>.</span>
580
+ </div>
581
+
582
+ <script>
583
+ document.getElementById('toggle-theme').onclick = () => {
584
+ document.getElementById('body-root').classList.toggle('light-mode')
585
+ document.querySelector('.header-bar').classList.toggle('light-mode')
586
+ document.querySelector('.footer-bar').classList.toggle('light-mode')
587
+ const meta = document.querySelector('.header-meta')
588
+ if (document.getElementById('body-root').classList.contains('light-mode')) {
589
+ meta && (meta.style.color = '#222')
590
+ } else {
591
+ meta && (meta.style.color = '#f1f5f9')
592
+ }
593
+ }
594
+
595
+ // 🆕 Função para expandir/colapsar CTs
596
+ function toggleCTs(testName) {
597
+ const cleanName = testName.replace(/[^a-zA-Z0-9]/g, '_')
598
+ const ctsRow = document.getElementById('cts-' + cleanName)
599
+ const btn = document.getElementById('btn-' + cleanName)
600
+
601
+ if (ctsRow && btn) {
602
+ if (ctsRow.style.display === 'none') {
603
+ ctsRow.style.display = 'table-row'
604
+ btn.innerHTML = '📋 CTs ▲'
605
+ } else {
606
+ ctsRow.style.display = 'none'
607
+ btn.innerHTML = '📋 CTs'
608
+ }
609
+ }
610
+ }
611
+
612
+ // 🆕 Função para expandir/colapsar Logs
613
+ function toggleLogs(testName) {
614
+ const cleanName = testName.replace(/[^a-zA-Z0-9]/g, '_')
615
+ const logsRow = document.getElementById('logs-' + cleanName)
616
+ const btn = document.getElementById('log-btn-' + cleanName)
617
+
618
+ if (logsRow && btn) {
619
+ if (logsRow.style.display === 'none') {
620
+ logsRow.style.display = 'table-row'
621
+ btn.innerHTML = '📜 Logs ▲'
622
+ } else {
623
+ logsRow.style.display = 'none'
624
+ btn.innerHTML = '📜 Logs'
625
+ }
626
+ }
627
+ }
628
+
629
+ // 📋 Função para expandir/colapsar Logs do Terminal
630
+ function toggleTerminalLogs(testName) {
631
+ const logsDiv = document.getElementById('terminal-logs-' + testName)
632
+ const btn = document.getElementById('terminal-btn-' + testName)
633
+
634
+ if (logsDiv && btn) {
635
+ if (logsDiv.style.display === 'none') {
636
+ logsDiv.style.display = 'block'
637
+ btn.innerHTML = '▲ Colapsar'
638
+ } else {
639
+ logsDiv.style.display = 'none'
640
+ btn.innerHTML = '▼ Expandir'
641
+ }
642
+ }
643
+ }
644
+
645
+ // 🆕 Funções para expandir/colapsar CNs e CTs
646
+ function toggleCN(cnId) {
647
+ const cnBody = document.getElementById(cnId)
648
+ const arrow = document.getElementById('arrow-' + cnId)
649
+
650
+ if (cnBody.style.display === 'none') {
651
+ cnBody.style.display = 'block'
652
+ arrow.style.transform = 'rotate(90deg)'
653
+ } else {
654
+ cnBody.style.display = 'none'
655
+ arrow.style.transform = 'rotate(0deg)'
656
+ }
657
+ }
658
+
659
+ function toggleCT(ctId) {
660
+ const ctBody = document.getElementById(ctId)
661
+ const arrow = document.getElementById('arrow-' + ctId)
662
+
663
+ if (ctBody.style.display === 'none') {
664
+ ctBody.style.display = 'block'
665
+ arrow.style.transform = 'rotate(90deg)'
666
+ } else {
667
+ ctBody.style.display = 'none'
668
+ arrow.style.transform = 'rotate(0deg)'
669
+ }
670
+ }
671
+
672
+ function expandAllCNs() {
673
+ const allCNs = document.querySelectorAll('[id^="cn-"]')
674
+ const allArrows = document.querySelectorAll('[id^="arrow-cn-"]')
675
+ const isAnyExpanded = Array.from(allCNs).some(cn => cn.style.display !== 'none')
676
+
677
+ allCNs.forEach((cn, index) => {
678
+ if (isAnyExpanded) {
679
+ cn.style.display = 'none'
680
+ allArrows[index].style.transform = 'rotate(0deg)'
681
+ } else {
682
+ cn.style.display = 'block'
683
+ allArrows[index].style.transform = 'rotate(90deg)'
684
+ }
685
+ })
686
+ }
687
+
688
+ function getTestData() {
689
+ return Array.from(document.querySelectorAll('table tbody tr.test-row')).map(row => {
690
+ const cells = row.querySelectorAll('td')
691
+ return {
692
+ nome: cells[0].innerText,
693
+ tipo: cells[1].innerText,
694
+ resultado: cells[2].innerText,
695
+ duracao: cells[3].innerText,
696
+ cts: cells[4].innerText,
697
+ timestamp: cells[5].innerText
698
+ }
699
+ })
700
+ }
701
+
702
+ document.getElementById('export-pdf').onclick = async () => {
703
+ console.log('🔵 [PDF] Botão clicado - Iniciando geração do PDF unificado...');
704
+
705
+ const element = document.getElementById('report-root');
706
+ if (!element) {
707
+ console.error('❌ [PDF] Elemento report-root não encontrado!');
708
+ alert('❌ Erro: Elemento do relatório não encontrado!');
709
+ return;
710
+ }
711
+
712
+ // Verificar se jsPDF está carregado
713
+ if (typeof jspdf === 'undefined') {
714
+ console.error('❌ [PDF] jsPDF não está carregado!');
715
+ alert('❌ Erro: Biblioteca jsPDF não carregada. Recarregue a página.');
716
+ return;
717
+ }
718
+ console.log('✅ [PDF] jsPDF carregado com sucesso');
719
+
720
+ // 🆕 EXPANDIR TODAS AS SEÇÕES CN, CT, CTs e Logs antes de gerar PDF
721
+ const originalStates = {
722
+ cns: [],
723
+ cts: [],
724
+ ctsRows: [],
725
+ logsRows: []
726
+ };
727
+
728
+ // Expandir todos os CNs
729
+ const allCNs = document.querySelectorAll('[id^="cn-"]');
730
+ const allCNArrows = document.querySelectorAll('[id^="arrow-cn-"]');
731
+ allCNs.forEach((cn, index) => {
732
+ originalStates.cns.push(cn.style.display);
733
+ cn.style.display = 'block';
734
+ if (allCNArrows[index]) {
735
+ allCNArrows[index].style.transform = 'rotate(90deg)';
736
+ }
737
+ });
738
+
739
+ // Expandir todos os CTs dentro dos CNs
740
+ const allCTs = document.querySelectorAll('[id^="ct-"]');
741
+ const allCTArrows = document.querySelectorAll('[id^="arrow-ct-"]');
742
+ allCTs.forEach((ct, index) => {
743
+ originalStates.cts.push(ct.style.display);
744
+ ct.style.display = 'block';
745
+ if (allCTArrows[index]) {
746
+ allCTArrows[index].style.transform = 'rotate(90deg)';
747
+ }
748
+ });
749
+
750
+ // Expandir todas as linhas de CTs nos testes
751
+ const allCTsRows = document.querySelectorAll('[id^="cts-"]');
752
+ allCTsRows.forEach((ctsRow) => {
753
+ originalStates.ctsRows.push(ctsRow.style.display);
754
+ ctsRow.style.display = 'table-row';
755
+ });
756
+
757
+ // Expandir todas as linhas de Logs
758
+ const allLogsRows = document.querySelectorAll('[id^="logs-"]');
759
+ allLogsRows.forEach((logsRow) => {
760
+ originalStates.logsRows.push(logsRow.style.display);
761
+ logsRow.style.display = 'table-row';
762
+ });
763
+
764
+ // Aguardar imagens carregarem
765
+ const images = element.querySelectorAll('img');
766
+ console.log('📸 Total de imagens encontradas:', images.length);
767
+
768
+ await Promise.all(Array.from(images).map(img => {
769
+ if (img.complete) return Promise.resolve();
770
+ return new Promise(resolve => {
771
+ img.onload = resolve;
772
+ img.onerror = resolve;
773
+ setTimeout(resolve, 3000);
774
+ });
775
+ }));
776
+
777
+ console.log('✅ Imagens aguardadas');
778
+ await new Promise(resolve => setTimeout(resolve, 500));
779
+
780
+ // Preparar elemento para PDF
781
+ const originalBg = element.style.background;
782
+ const originalColor = element.style.color;
783
+ element.style.background = '#fff';
784
+ element.style.color = '#222';
785
+
786
+ // Reduzir e alinhar gráficos no PDF
787
+ document.querySelectorAll('.chart-card').forEach((card) => {
788
+ card.style.width = '100%';
789
+ card.style.display = 'block';
790
+ card.style.padding = '0.5rem';
791
+ });
792
+
793
+ document.querySelectorAll('.chart-card canvas').forEach((canvas) => {
794
+ canvas.style.width = '300px';
795
+ canvas.style.height = '150px';
796
+ canvas.style.maxHeight = '150px';
797
+ canvas.style.display = 'block';
798
+ canvas.style.margin = '0 auto';
799
+ canvas.style.transform = 'scale(0.85)';
800
+ canvas.style.transformOrigin = 'top left';
801
+ });
802
+
803
+ console.log('📄 [PDF] Iniciando geração do PDF...');
804
+
805
+ try {
806
+ // Usar html2canvas para capturar o conteúdo como imagem
807
+ const canvas = await html2canvas(element, {
808
+ scale: 2,
809
+ useCORS: false,
810
+ allowTaint: true,
811
+ backgroundColor: '#fff',
812
+ scrollY: 0,
813
+ logging: false,
814
+ imageTimeout: 0
815
+ });
816
+
817
+ // Criar PDF com jsPDF
818
+ const { jsPDF } = jspdf;
819
+ const pdf = new jsPDF({
820
+ orientation: 'portrait',
821
+ unit: 'mm',
822
+ format: 'a4'
823
+ });
824
+
825
+ const imgData = canvas.toDataURL('image/jpeg', 0.98);
826
+ const imgWidth = 210; // A4 width in mm
827
+ const pageHeight = 297; // A4 height in mm
828
+ const imgHeight = (canvas.height * imgWidth) / canvas.width;
829
+ let heightLeft = imgHeight;
830
+ let position = 0;
831
+
832
+ // Adicionar primeira página
833
+ pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
834
+ heightLeft -= pageHeight;
835
+
836
+ // Adicionar páginas extras se necessário
837
+ while (heightLeft > 0) {
838
+ position = heightLeft - imgHeight;
839
+ pdf.addPage();
840
+ pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
841
+ heightLeft -= pageHeight;
842
+ }
843
+
844
+ // Salvar PDF
845
+ pdf.save('relatorio.pdf');
846
+
847
+ console.log('✅ PDF gerado com sucesso!');
848
+ alert('✅ PDF gerado com sucesso! Verifique sua pasta de Downloads.');
849
+ } catch (error) {
850
+ console.error('❌ Erro ao gerar PDF:', error);
851
+ alert('❌ Erro ao gerar PDF: ' + (error.message || error));
852
+ } finally {
853
+ // Restaurar estilos originais
854
+ element.style.background = originalBg;
855
+ element.style.color = originalColor;
856
+
857
+ document.querySelectorAll('.chart-card').forEach((card) => {
858
+ card.style.width = '';
859
+ card.style.display = '';
860
+ card.style.padding = '';
861
+ });
862
+
863
+ document.querySelectorAll('.chart-card canvas').forEach((canvas) => {
864
+ canvas.style.width = '';
865
+ canvas.style.height = '';
866
+ canvas.style.maxHeight = '';
867
+ canvas.style.display = '';
868
+ canvas.style.margin = '';
869
+ canvas.style.transform = '';
870
+ canvas.style.transformOrigin = '';
871
+ });
872
+
873
+ // Restaurar estados originais das seções
874
+ allCNs.forEach((cn, index) => {
875
+ cn.style.display = originalStates.cns[index] || 'none';
876
+ if (allCNArrows[index] && originalStates.cns[index] === 'none') {
877
+ allCNArrows[index].style.transform = 'rotate(0deg)';
878
+ }
879
+ });
880
+
881
+ allCTs.forEach((ct, index) => {
882
+ ct.style.display = originalStates.cts[index] || 'none';
883
+ if (allCTArrows[index] && originalStates.cts[index] === 'none') {
884
+ allCTArrows[index].style.transform = 'rotate(0deg)';
885
+ }
886
+ });
887
+
888
+ allCTsRows.forEach((ctsRow, index) => {
889
+ ctsRow.style.display = originalStates.ctsRows[index] || 'none';
890
+ });
891
+
892
+ allLogsRows.forEach((logsRow, index) => {
893
+ logsRow.style.display = originalStates.logsRows[index] || 'none';
894
+ });
895
+ }
896
+ };
897
+
898
+ document.getElementById('export-csv').onclick = () => {
899
+ const data = getTestData()
900
+ const csv = [
901
+ ['Nome', 'Tipo', 'Resultado', 'Duração', 'Timestamp'],
902
+ ...data.map(d => [d.nome, d.tipo, d.resultado, d.duracao, d.timestamp])
903
+ ].map(e => e.join(';')).join('\\n')
904
+ const blob = new Blob([csv], { type: 'text/csv' })
905
+ const a = document.createElement('a')
906
+ a.href = URL.createObjectURL(blob)
907
+ a.download = 'relatorio.csv'
908
+ a.click()
909
+ }
910
+
911
+ document.getElementById('export-json').onclick = () => {
912
+ const blob = new Blob([JSON.stringify(getTestData(), null, 2)], { type: 'application/json' })
913
+ const a = document.createElement('a')
914
+ a.href = URL.createObjectURL(blob)
915
+ a.download = 'relatorio.json'
916
+ a.click()
917
+ }
918
+
919
+ document.getElementById('export-xlsx').onclick = () => {
920
+ const ws = XLSX.utils.json_to_sheet(getTestData())
921
+ const wb = XLSX.utils.book_new()
922
+ XLSX.utils.book_append_sheet(wb, ws, 'Relatorio')
923
+ XLSX.writeFile(wb, 'relatorio.xlsx')
924
+ }
925
+
926
+ // Gráfico de Rosca - Testes por Tipo
927
+ new Chart(document.getElementById('chart-tipos'), {
928
+ type: 'doughnut',
929
+ data: {
930
+ labels: ${JSON.stringify(chartLabels)},
931
+ datasets: [{
932
+ data: ${JSON.stringify(chartData)},
933
+ backgroundColor: ${JSON.stringify(chartColors)}
934
+ }]
935
+ },
936
+ options: {
937
+ plugins: {
938
+ legend: {
939
+ display: true,
940
+ position: 'bottom',
941
+ labels: {
942
+ font: { size: 10 }
943
+ }
944
+ },
945
+ title: {
946
+ display: true,
947
+ text: 'Testes por Tipo',
948
+ font: { size: 14 }
949
+ }
950
+ },
951
+ layout: {
952
+ padding: 0
953
+ }
954
+ }
955
+ });
956
+
957
+ // Gráfico de Barras - Duração dos Testes
958
+ new Chart(document.getElementById('chart-duracao'), {
959
+ type: 'bar',
960
+ data: {
961
+ labels: ${JSON.stringify(barLabels)},
962
+ datasets: [{
963
+ label: 'Duração (s)',
964
+ data: ${JSON.stringify(barData)},
965
+ backgroundColor: '#8b5cf6'
966
+ }]
967
+ },
968
+ options: {
969
+ plugins: {
970
+ legend: {
971
+ display: false
972
+ },
973
+ title: {
974
+ display: true,
975
+ text: 'Duração dos Testes',
976
+ font: { size: 14 }
977
+ }
978
+ },
979
+ layout: {
980
+ padding: 0
981
+ },
982
+ scales: {
983
+ x: {
984
+ ticks: {
985
+ font: { size: 9 },
986
+ maxRotation: 45,
987
+ minRotation: 45
988
+ }
989
+ },
990
+ y: {
991
+ ticks: {
992
+ font: { size: 9 }
993
+ },
994
+ beginAtZero: true
995
+ }
996
+ }
997
+ }
998
+ });
999
+
1000
+ // Gráfico de Pizza - Passou x Falhou
1001
+ new Chart(document.getElementById('chart-passfail'), {
1002
+ type: 'pie',
1003
+ data: {
1004
+ labels: ${JSON.stringify(pieLabels)},
1005
+ datasets: [{
1006
+ data: ${JSON.stringify(pieData)},
1007
+ backgroundColor: ['#10b981', '#ef4444']
1008
+ }]
1009
+ },
1010
+ options: {
1011
+ plugins: {
1012
+ legend: {
1013
+ display: true,
1014
+ position: 'bottom',
1015
+ labels: {
1016
+ font: { size: 10 }
1017
+ }
1018
+ },
1019
+ title: {
1020
+ display: true,
1021
+ text: 'Passou x Falhou',
1022
+ font: { size: 14 }
1023
+ }
1024
+ },
1025
+ layout: {
1026
+ padding: 0
1027
+ }
1028
+ }
1029
+ });
1030
+ </script>
1031
+ </body>
1032
+ </html>
1033
+ `;
1034
+ }