@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,718 @@
1
+ import { test as base, expect as baseExpect } from '@playwright/test';
2
+ import { execSync } from 'node:child_process';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import * as path from 'node:path';
6
+ import { TestContext } from '../testContext/TestContext.js';
7
+ /**
8
+ * Classe para capturar e gerenciar logs de Network
9
+ */
10
+ class NetworkLogger {
11
+ logs = [];
12
+ requestTimings = new Map();
13
+ currentPageUrl = '';
14
+ currentPageTitle = '';
15
+ /**
16
+ * Registra o início de uma requisição
17
+ */
18
+ logRequest(request) {
19
+ const timestamp = new Date().toISOString();
20
+ const requestId = `${request.method()}-${request.url()}-${timestamp}`;
21
+ this.requestTimings.set(request.url(), Date.now());
22
+ const entry = {
23
+ timestamp,
24
+ pageUrl: this.currentPageUrl,
25
+ pageTitle: this.currentPageTitle,
26
+ type: 'request',
27
+ method: request.method(),
28
+ url: request.url(),
29
+ resourceType: request.resourceType(),
30
+ headers: request.headers(),
31
+ postData: request.postData() || undefined,
32
+ };
33
+ this.logs.push(entry);
34
+ }
35
+ /**
36
+ * Registra a resposta de uma requisição
37
+ */
38
+ async logResponse(response) {
39
+ const timestamp = new Date().toISOString();
40
+ const startTime = this.requestTimings.get(response.url());
41
+ const duration = startTime ? Date.now() - startTime : undefined;
42
+ let size;
43
+ try {
44
+ const body = await response.body().catch(() => null);
45
+ size = body?.length;
46
+ }
47
+ catch {
48
+ size = undefined;
49
+ }
50
+ const entry = {
51
+ timestamp,
52
+ pageUrl: this.currentPageUrl,
53
+ pageTitle: this.currentPageTitle,
54
+ type: 'response',
55
+ method: response.request().method(),
56
+ url: response.url(),
57
+ status: response.status(),
58
+ statusText: response.statusText(),
59
+ resourceType: response.request().resourceType(),
60
+ duration,
61
+ size,
62
+ headers: response.headers(),
63
+ mimeType: response.headers()['content-type'],
64
+ fromServiceWorker: response.fromServiceWorker(),
65
+ };
66
+ this.logs.push(entry);
67
+ }
68
+ /**
69
+ * Registra requisição que falhou
70
+ */
71
+ logRequestFailed(request) {
72
+ const timestamp = new Date().toISOString();
73
+ const startTime = this.requestTimings.get(request.url());
74
+ const duration = startTime ? Date.now() - startTime : undefined;
75
+ const entry = {
76
+ timestamp,
77
+ pageUrl: this.currentPageUrl,
78
+ pageTitle: this.currentPageTitle,
79
+ type: 'failed',
80
+ method: request.method(),
81
+ url: request.url(),
82
+ resourceType: request.resourceType(),
83
+ duration,
84
+ errorText: request.failure()?.errorText,
85
+ };
86
+ this.logs.push(entry);
87
+ }
88
+ /**
89
+ * Atualiza informações da página atual
90
+ */
91
+ updatePageInfo(url, title) {
92
+ this.currentPageUrl = url;
93
+ this.currentPageTitle = title;
94
+ }
95
+ /**
96
+ * Retorna todos os logs
97
+ */
98
+ getLogs() {
99
+ return this.logs;
100
+ }
101
+ /**
102
+ * Gera relatório formatado em texto
103
+ */
104
+ generateTextReport() {
105
+ if (this.logs.length === 0) {
106
+ return '📡 [Network Logs] Nenhuma requisição capturada';
107
+ }
108
+ const lines = [
109
+ '═══════════════════════════════════════════════════════════════════════════════',
110
+ '📡 NETWORK LOGS - AutoCore',
111
+ `📊 Total de registros: ${this.logs.length}`,
112
+ `🕐 Capturado em: ${new Date().toISOString()}`,
113
+ '═══════════════════════════════════════════════════════════════════════════════',
114
+ '',
115
+ ];
116
+ // Agrupa por página
117
+ const byPage = new Map();
118
+ for (const log of this.logs) {
119
+ const pageKey = `${log.pageTitle || 'Sem título'} (${log.pageUrl || 'URL desconhecida'})`;
120
+ if (!byPage.has(pageKey)) {
121
+ byPage.set(pageKey, []);
122
+ }
123
+ byPage.get(pageKey).push(log);
124
+ }
125
+ for (const [pageKey, pageLogs] of byPage) {
126
+ lines.push(`\n📄 PÁGINA: ${pageKey}`);
127
+ lines.push('─'.repeat(79));
128
+ // Estatísticas da página
129
+ const requests = pageLogs.filter(l => l.type === 'request').length;
130
+ const responses = pageLogs.filter(l => l.type === 'response').length;
131
+ const failed = pageLogs.filter(l => l.type === 'failed').length;
132
+ const errors = pageLogs.filter(l => l.type === 'response' && l.status && l.status >= 400).length;
133
+ lines.push(` 📈 Requisições: ${requests} | Respostas: ${responses} | Falhas: ${failed} | Erros HTTP: ${errors}`);
134
+ lines.push('');
135
+ for (const log of pageLogs) {
136
+ const icon = log.type === 'request' ? '➡️' : log.type === 'response' ? (log.status && log.status >= 400 ? '❌' : '✅') : '💥';
137
+ const status = log.status ? ` [${log.status} ${log.statusText || ''}]` : '';
138
+ const duration = log.duration ? ` (${log.duration}ms)` : '';
139
+ const size = log.size ? ` ${this.formatBytes(log.size)}` : '';
140
+ const sw = log.fromServiceWorker ? ' [SW]' : '';
141
+ lines.push(` ${icon} ${log.timestamp.split('T')[1].split('.')[0]} | ${log.method.padEnd(7)} | ${log.resourceType.padEnd(12)} |${status}${duration}${size}${sw}`);
142
+ lines.push(` 🔗 ${this.truncateUrl(log.url, 100)}`);
143
+ if (log.type === 'failed' && log.errorText) {
144
+ lines.push(` ⚠️ Erro: ${log.errorText}`);
145
+ }
146
+ if (log.postData) {
147
+ lines.push(` 📤 Body: ${this.truncateString(log.postData, 200)}`);
148
+ }
149
+ lines.push('');
150
+ }
151
+ }
152
+ // Resumo final
153
+ lines.push('═══════════════════════════════════════════════════════════════════════════════');
154
+ lines.push('📊 RESUMO');
155
+ lines.push('─'.repeat(79));
156
+ const totalRequests = this.logs.filter(l => l.type === 'request').length;
157
+ const totalResponses = this.logs.filter(l => l.type === 'response').length;
158
+ const totalFailed = this.logs.filter(l => l.type === 'failed').length;
159
+ const totalErrors = this.logs.filter(l => l.type === 'response' && l.status && l.status >= 400).length;
160
+ const avgDuration = this.calculateAvgDuration();
161
+ const totalSize = this.calculateTotalSize();
162
+ lines.push(` 📤 Total Requisições: ${totalRequests}`);
163
+ lines.push(` 📥 Total Respostas: ${totalResponses}`);
164
+ lines.push(` 💥 Requisições Falhas: ${totalFailed}`);
165
+ lines.push(` ❌ Erros HTTP (4xx/5xx): ${totalErrors}`);
166
+ lines.push(` ⏱️ Tempo Médio: ${avgDuration}ms`);
167
+ lines.push(` 📦 Dados Transferidos: ${this.formatBytes(totalSize)}`);
168
+ lines.push('═══════════════════════════════════════════════════════════════════════════════');
169
+ return lines.join('\n');
170
+ }
171
+ /**
172
+ * Gera relatório em JSON
173
+ */
174
+ generateJsonReport() {
175
+ return JSON.stringify({
176
+ capturedAt: new Date().toISOString(),
177
+ totalLogs: this.logs.length,
178
+ summary: {
179
+ requests: this.logs.filter(l => l.type === 'request').length,
180
+ responses: this.logs.filter(l => l.type === 'response').length,
181
+ failed: this.logs.filter(l => l.type === 'failed').length,
182
+ httpErrors: this.logs.filter(l => l.type === 'response' && l.status && l.status >= 400).length,
183
+ avgDurationMs: this.calculateAvgDuration(),
184
+ totalBytes: this.calculateTotalSize(),
185
+ },
186
+ logs: this.logs,
187
+ }, null, 2);
188
+ }
189
+ truncateUrl(url, maxLength) {
190
+ if (url.length <= maxLength)
191
+ return url;
192
+ return url.substring(0, maxLength - 3) + '...';
193
+ }
194
+ truncateString(str, maxLength) {
195
+ if (str.length <= maxLength)
196
+ return str;
197
+ return str.substring(0, maxLength - 3) + '...';
198
+ }
199
+ formatBytes(bytes) {
200
+ if (bytes === 0)
201
+ return '0 B';
202
+ const k = 1024;
203
+ const sizes = ['B', 'KB', 'MB', 'GB'];
204
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
205
+ return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
206
+ }
207
+ calculateAvgDuration() {
208
+ const durations = this.logs.filter(l => l.duration).map(l => l.duration);
209
+ if (durations.length === 0)
210
+ return 0;
211
+ return Math.round(durations.reduce((a, b) => a + b, 0) / durations.length);
212
+ }
213
+ calculateTotalSize() {
214
+ return this.logs.filter(l => l.size).reduce((acc, l) => acc + (l.size || 0), 0);
215
+ }
216
+ /**
217
+ * Limpa os logs
218
+ */
219
+ clear() {
220
+ this.logs = [];
221
+ this.requestTimings.clear();
222
+ this.currentPageUrl = '';
223
+ this.currentPageTitle = '';
224
+ }
225
+ }
226
+ /** Cache de resolução detectada (evita re-detectar a cada teste) */
227
+ let _cachedResolution = null;
228
+ /**
229
+ * Detecta resolução real da tela do SO (não do browser headless).
230
+ * Prioridade:
231
+ * 1) Env vars AUTOCORE_VIEWPORT_WIDTH / AUTOCORE_VIEWPORT_HEIGHT
232
+ * 2) Detecção via SO (Windows PowerShell, Linux xrandr/xdpyinfo)
233
+ * 3) Fallback 1920×1080
234
+ *
235
+ * Sempre clampeia entre 1024×768 (mínimo) e 3840×2160 (4K máximo)
236
+ * para evitar viewports absurdos.
237
+ */
238
+ function detectScreenResolution() {
239
+ if (_cachedResolution)
240
+ return _cachedResolution;
241
+ const MIN_W = 1024, MIN_H = 768, MAX_W = 3840, MAX_H = 2160;
242
+ const FALLBACK = { width: 1920, height: 1080 };
243
+ const clamp = (w, h) => ({
244
+ width: Math.max(MIN_W, Math.min(MAX_W, w)),
245
+ height: Math.max(MIN_H, Math.min(MAX_H, h)),
246
+ });
247
+ // 1) Env vars (override manual)
248
+ const envW = Number(process.env.AUTOCORE_VIEWPORT_WIDTH);
249
+ const envH = Number(process.env.AUTOCORE_VIEWPORT_HEIGHT);
250
+ if (envW > 0 && envH > 0) {
251
+ _cachedResolution = clamp(envW, envH);
252
+ console.log(`📐 [AutoCore] Viewport via env: ${_cachedResolution.width}x${_cachedResolution.height}`);
253
+ return _cachedResolution;
254
+ }
255
+ // 2) Detecção por SO
256
+ try {
257
+ const platform = os.platform();
258
+ if (platform === 'win32') {
259
+ // PowerShell: lê resolução primária via WMI (sem GUI necessário)
260
+ const raw = execSync(`powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $s=[System.Windows.Forms.Screen]::PrimaryScreen.Bounds; Write-Output ('{0}x{1}' -f $s.Width,$s.Height)"`, { timeout: 5000, encoding: 'utf-8', windowsHide: true }).trim();
261
+ const match = raw.match(/(\d+)x(\d+)/);
262
+ if (match) {
263
+ _cachedResolution = clamp(Number(match[1]), Number(match[2]));
264
+ console.log(`📐 [AutoCore] Viewport via Windows screen: ${_cachedResolution.width}x${_cachedResolution.height}`);
265
+ return _cachedResolution;
266
+ }
267
+ }
268
+ else if (platform === 'linux') {
269
+ // xdpyinfo ou xrandr para Linux com X11
270
+ try {
271
+ const raw = execSync('xdpyinfo 2>/dev/null | grep dimensions', { timeout: 3000, encoding: 'utf-8' }).trim();
272
+ const match = raw.match(/(\d+)x(\d+)/);
273
+ if (match) {
274
+ _cachedResolution = clamp(Number(match[1]), Number(match[2]));
275
+ console.log(`📐 [AutoCore] Viewport via xdpyinfo: ${_cachedResolution.width}x${_cachedResolution.height}`);
276
+ return _cachedResolution;
277
+ }
278
+ }
279
+ catch {
280
+ // xdpyinfo indisponível, tentar xrandr
281
+ const raw = execSync('xrandr 2>/dev/null | grep "\\*" | head -1', { timeout: 3000, encoding: 'utf-8' }).trim();
282
+ const match = raw.match(/(\d+)x(\d+)/);
283
+ if (match) {
284
+ _cachedResolution = clamp(Number(match[1]), Number(match[2]));
285
+ console.log(`📐 [AutoCore] Viewport via xrandr: ${_cachedResolution.width}x${_cachedResolution.height}`);
286
+ return _cachedResolution;
287
+ }
288
+ }
289
+ }
290
+ else if (platform === 'darwin') {
291
+ const raw = execSync('system_profiler SPDisplaysDataType 2>/dev/null | grep Resolution', { timeout: 3000, encoding: 'utf-8' }).trim();
292
+ const match = raw.match(/(\d+)\s*x\s*(\d+)/);
293
+ if (match) {
294
+ _cachedResolution = clamp(Number(match[1]), Number(match[2]));
295
+ console.log(`📐 [AutoCore] Viewport via macOS: ${_cachedResolution.width}x${_cachedResolution.height}`);
296
+ return _cachedResolution;
297
+ }
298
+ }
299
+ }
300
+ catch {
301
+ // Detecção falhou silenciosamente
302
+ }
303
+ // 3) Fallback
304
+ _cachedResolution = FALLBACK;
305
+ console.log(`📐 [AutoCore] Viewport fallback: ${FALLBACK.width}x${FALLBACK.height}`);
306
+ return _cachedResolution;
307
+ }
308
+ /**
309
+ * Configura viewport com resolução dinâmica da máquina.
310
+ * Usa detecção de SO com deviceScaleFactor=1 para evitar zoom.
311
+ * Injeta meta viewport tag para garantir zoom 100% no conteúdo.
312
+ */
313
+ async function setupResponsiveViewport(page) {
314
+ try {
315
+ const { width, height } = detectScreenResolution();
316
+ await page.setViewportSize({ width, height });
317
+ // Garantir zoom 100% via meta tag
318
+ await page.addInitScript(() => {
319
+ const meta = document.querySelector('meta[name="viewport"]');
320
+ if (!meta) {
321
+ const m = document.createElement('meta');
322
+ m.name = 'viewport';
323
+ m.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0';
324
+ document.head?.appendChild(m);
325
+ }
326
+ });
327
+ console.log(`📐 [AutoCore-fixtures] Viewport final: ${width}x${height} (zoom 100%)`);
328
+ }
329
+ catch (err) {
330
+ console.warn('⚠️ Não foi possível ajustar viewport:', err);
331
+ }
332
+ }
333
+ /**
334
+ * Configura captura de logs de Network
335
+ */
336
+ function setupNetworkLogging(page, networkLogger) {
337
+ // Atualiza info da página em cada navegação
338
+ page.on('framenavigated', async (frame) => {
339
+ if (frame === page.mainFrame()) {
340
+ try {
341
+ const url = page.url();
342
+ const title = await page.title().catch(() => '');
343
+ networkLogger.updatePageInfo(url, title);
344
+ }
345
+ catch {
346
+ // Ignora erros de página fechada
347
+ }
348
+ }
349
+ });
350
+ // Captura requisições
351
+ page.on('request', (request) => {
352
+ try {
353
+ networkLogger.logRequest(request);
354
+ }
355
+ catch {
356
+ // Ignora erros
357
+ }
358
+ });
359
+ // Captura respostas
360
+ page.on('response', async (response) => {
361
+ try {
362
+ await networkLogger.logResponse(response);
363
+ }
364
+ catch {
365
+ // Ignora erros
366
+ }
367
+ });
368
+ // Captura requisições que falharam
369
+ page.on('requestfailed', (request) => {
370
+ try {
371
+ networkLogger.logRequestFailed(request);
372
+ }
373
+ catch {
374
+ // Ignora erros
375
+ }
376
+ });
377
+ console.log('📡 [AutoCore] Network logging ativado');
378
+ }
379
+ /**
380
+ * Salva arquivos de network logs na pasta test-results/network-logs
381
+ *
382
+ * O Playwright NÃO limpa test-results/ automaticamente, então os arquivos são preservados.
383
+ * Para ter os arquivos dentro de playwright-report/, adicione NetworkLogsReporter ao seu playwright.config.ts:
384
+ *
385
+ * @example
386
+ * import { NetworkLogsReporter } from '@silasfmartins/testhub'
387
+ * export default defineConfig({
388
+ * reporter: [['html'], NetworkLogsReporter],
389
+ * })
390
+ */
391
+ function saveNetworkLogsToReport(testInfo, networkLogger) {
392
+ const logs = networkLogger.getLogs();
393
+ if (logs.length === 0)
394
+ return null;
395
+ try {
396
+ // 🔧 Usa test-results/network-logs que é preservado pelo Playwright
397
+ // O outputDir já é dentro de test-results, então usamos a raiz do projeto + test-results/network-logs
398
+ let projectRoot;
399
+ // outputDir é algo como: C:\...\test-sanity-frontend\test-results\chromium\...
400
+ // Queremos: C:\...\test-sanity-frontend
401
+ if (testInfo.outputDir) {
402
+ // Remove tudo a partir de 'test-results' (incluindo)
403
+ const testResultsIndex = testInfo.outputDir.indexOf('test-results');
404
+ if (testResultsIndex > 0) {
405
+ projectRoot = testInfo.outputDir.substring(0, testResultsIndex).replace(/[\\/]$/, '');
406
+ }
407
+ else {
408
+ projectRoot = testInfo.config?.rootDir || process.cwd();
409
+ }
410
+ }
411
+ else {
412
+ projectRoot = testInfo.config?.rootDir || process.cwd();
413
+ }
414
+ // 🔧 FIX ADICIONAL: Se rootDir termina com 'tests', remove para ir ao projeto raiz
415
+ if (projectRoot.endsWith('tests') || projectRoot.endsWith('tests/') || projectRoot.endsWith('tests\\')) {
416
+ projectRoot = path.dirname(projectRoot);
417
+ }
418
+ // 🔧 Salva em test-results/network-logs (pasta preservada pelo Playwright)
419
+ const reportDir = path.join(projectRoot, 'test-results', 'network-logs');
420
+ console.log(`📂 [AutoCore] Network logs directory:`);
421
+ console.log(` 📂 projectRoot: ${projectRoot}`);
422
+ console.log(` 📂 reportDir: ${reportDir}`);
423
+ // Cria a pasta se não existir
424
+ if (!fs.existsSync(reportDir)) {
425
+ fs.mkdirSync(reportDir, { recursive: true });
426
+ console.log(`📁 [AutoCore] Criada pasta: ${reportDir}`);
427
+ }
428
+ // Gera nome do arquivo baseado no teste (sanitizado)
429
+ const testName = testInfo.title.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 50);
430
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
431
+ const baseName = `${testName}_${timestamp}`;
432
+ // Salva arquivo TXT
433
+ const txtPath = path.join(reportDir, `${baseName}.txt`);
434
+ const textReport = networkLogger.generateTextReport();
435
+ fs.writeFileSync(txtPath, textReport, 'utf-8');
436
+ // Verifica se arquivo foi criado
437
+ if (fs.existsSync(txtPath)) {
438
+ console.log(`✅ [AutoCore] Arquivo TXT criado: ${txtPath}`);
439
+ }
440
+ else {
441
+ console.error(`❌ [AutoCore] FALHA ao criar arquivo TXT: ${txtPath}`);
442
+ }
443
+ // Salva arquivo JSON
444
+ const jsonPath = path.join(reportDir, `${baseName}.json`);
445
+ const jsonReport = networkLogger.generateJsonReport();
446
+ fs.writeFileSync(jsonPath, jsonReport, 'utf-8');
447
+ // Verifica se arquivo foi criado
448
+ if (fs.existsSync(jsonPath)) {
449
+ console.log(`✅ [AutoCore] Arquivo JSON criado: ${jsonPath}`);
450
+ }
451
+ else {
452
+ console.error(`❌ [AutoCore] FALHA ao criar arquivo JSON: ${jsonPath}`);
453
+ }
454
+ console.log(`📡 [AutoCore] Network logs salvos em: ${reportDir}`);
455
+ return { txtPath, jsonPath };
456
+ }
457
+ catch (err) {
458
+ console.error('❌ [AutoCore] Erro ao salvar network logs:', err);
459
+ console.error(` 📂 outputDir: ${testInfo.outputDir || '(não definido)'}`);
460
+ console.error(` 📂 config.rootDir: ${testInfo.config?.rootDir || '(não definido)'}`);
461
+ console.error(` 📂 cwd: ${process.cwd()}`);
462
+ return null;
463
+ }
464
+ }
465
+ /**
466
+ * Anexa logs de Network ao relatório do teste como Annotations
467
+ */
468
+ async function attachNetworkLogs(testInfo, networkLogger) {
469
+ const logs = networkLogger.getLogs();
470
+ if (logs.length === 0) {
471
+ console.log('📡 [AutoCore] Nenhum log de network para anexar');
472
+ return;
473
+ }
474
+ try {
475
+ // 1. Salva arquivos na pasta playwright-report
476
+ const savedFiles = saveNetworkLogsToReport(testInfo, networkLogger);
477
+ // 2. Anexa relatório em texto (legível) ao teste
478
+ const textReport = networkLogger.generateTextReport();
479
+ await testInfo.attach('network-logs.txt', {
480
+ body: Buffer.from(textReport, 'utf-8'),
481
+ contentType: 'text/plain',
482
+ });
483
+ // 3. Anexa relatório em JSON (para processamento)
484
+ const jsonReport = networkLogger.generateJsonReport();
485
+ await testInfo.attach('network-logs.json', {
486
+ body: Buffer.from(jsonReport, 'utf-8'),
487
+ contentType: 'application/json',
488
+ });
489
+ // 4. Adiciona Annotations para aparecer no relatório HTML
490
+ const requests = logs.filter(l => l.type === 'request').length;
491
+ const responses = logs.filter(l => l.type === 'response').length;
492
+ const failed = logs.filter(l => l.type === 'failed').length;
493
+ const errors = logs.filter(l => l.type === 'response' && l.status && l.status >= 400).length;
494
+ const totalSize = logs.filter(l => l.size).reduce((acc, l) => acc + (l.size || 0), 0);
495
+ const avgDuration = logs.filter(l => l.duration).length > 0
496
+ ? Math.round(logs.filter(l => l.duration).reduce((acc, l) => acc + (l.duration || 0), 0) / logs.filter(l => l.duration).length)
497
+ : 0;
498
+ // Annotation com resumo de Network
499
+ testInfo.annotations.push({
500
+ type: '📡 Network Summary',
501
+ description: `Requests: ${requests} | Responses: ${responses} | Failed: ${failed} | HTTP Errors: ${errors}`,
502
+ });
503
+ testInfo.annotations.push({
504
+ type: '📊 Network Stats',
505
+ description: `Total Size: ${formatBytesStatic(totalSize)} | Avg Duration: ${avgDuration}ms`,
506
+ });
507
+ // Lista as URLs únicas visitadas
508
+ const uniquePages = [...new Set(logs.filter(l => l.pageUrl).map(l => l.pageUrl))];
509
+ if (uniquePages.length > 0) {
510
+ testInfo.annotations.push({
511
+ type: '🌐 Pages Visited',
512
+ description: uniquePages.slice(0, 5).join(' → ') + (uniquePages.length > 5 ? ` (+${uniquePages.length - 5} more)` : ''),
513
+ });
514
+ }
515
+ // Lista erros HTTP se houver
516
+ const httpErrors = logs.filter(l => l.type === 'response' && l.status && l.status >= 400);
517
+ if (httpErrors.length > 0) {
518
+ const errorSummary = httpErrors.slice(0, 3).map(e => `${e.status} ${e.method} ${e.url.substring(0, 50)}...`).join(' | ');
519
+ testInfo.annotations.push({
520
+ type: '❌ HTTP Errors',
521
+ description: errorSummary + (httpErrors.length > 3 ? ` (+${httpErrors.length - 3} more)` : ''),
522
+ });
523
+ }
524
+ // Lista requisições que falharam se houver
525
+ const failedRequests = logs.filter(l => l.type === 'failed');
526
+ if (failedRequests.length > 0) {
527
+ const failedSummary = failedRequests.slice(0, 3).map(f => `${f.method} ${f.url.substring(0, 50)}... (${f.errorText || 'unknown'})`).join(' | ');
528
+ testInfo.annotations.push({
529
+ type: '💥 Failed Requests',
530
+ description: failedSummary + (failedRequests.length > 3 ? ` (+${failedRequests.length - 3} more)` : ''),
531
+ });
532
+ }
533
+ // Adiciona link para os arquivos salvos
534
+ if (savedFiles) {
535
+ testInfo.annotations.push({
536
+ type: '📁 Network Logs Files',
537
+ description: `TXT: ${path.basename(savedFiles.txtPath)} | JSON: ${path.basename(savedFiles.jsonPath)}`,
538
+ });
539
+ }
540
+ // 5. Imprime resumo no terminal para visibilidade durante execução
541
+ console.log('');
542
+ console.log('═══════════════════════════════════════════════════════════════════');
543
+ console.log('📡 [AutoCore] NETWORK LOGS SUMMARY');
544
+ console.log('═══════════════════════════════════════════════════════════════════');
545
+ console.log(` 📊 Total: ${logs.length} logs capturados`);
546
+ console.log(` ➡️ Requests: ${requests}`);
547
+ console.log(` ✅ Responses: ${responses}`);
548
+ console.log(` 💥 Failed: ${failed}`);
549
+ console.log(` ❌ HTTP Errors (4xx/5xx): ${errors}`);
550
+ console.log(` 📦 Total Size: ${formatBytesStatic(totalSize)}`);
551
+ console.log(` ⏱️ Avg Duration: ${avgDuration}ms`);
552
+ if (uniquePages.length > 0) {
553
+ console.log(` 🌐 Pages Visited: ${uniquePages.length}`);
554
+ uniquePages.slice(0, 3).forEach((page, i) => {
555
+ console.log(` ${i + 1}. ${page}`);
556
+ });
557
+ if (uniquePages.length > 3) {
558
+ console.log(` ... +${uniquePages.length - 3} more`);
559
+ }
560
+ }
561
+ if (httpErrors.length > 0) {
562
+ console.log(` ❌ HTTP Errors Details:`);
563
+ httpErrors.slice(0, 5).forEach(e => {
564
+ console.log(` • ${e.status} ${e.method} ${e.url.substring(0, 80)}${e.url.length > 80 ? '...' : ''}`);
565
+ });
566
+ if (httpErrors.length > 5) {
567
+ console.log(` ... +${httpErrors.length - 5} more errors`);
568
+ }
569
+ }
570
+ if (failedRequests.length > 0) {
571
+ console.log(` 💥 Failed Requests Details:`);
572
+ failedRequests.slice(0, 5).forEach(f => {
573
+ console.log(` • ${f.method} ${f.url.substring(0, 60)}${f.url.length > 60 ? '...' : ''} (${f.errorText || 'unknown'})`);
574
+ });
575
+ if (failedRequests.length > 5) {
576
+ console.log(` ... +${failedRequests.length - 5} more failures`);
577
+ }
578
+ }
579
+ if (savedFiles) {
580
+ console.log(` 📁 Files saved to: evidence/network-logs/`);
581
+ console.log(` • ${path.basename(savedFiles.txtPath)}`);
582
+ console.log(` • ${path.basename(savedFiles.jsonPath)}`);
583
+ console.log(` ℹ️ Serão movidos para playwright-report/evidence/network-logs/`);
584
+ }
585
+ console.log('═══════════════════════════════════════════════════════════════════');
586
+ console.log('');
587
+ }
588
+ catch (err) {
589
+ console.warn('⚠️ [AutoCore] Erro ao anexar logs de network:', err);
590
+ }
591
+ }
592
+ /**
593
+ * Formata bytes em formato legível (helper estático)
594
+ */
595
+ function formatBytesStatic(bytes) {
596
+ if (bytes === 0)
597
+ return '0 B';
598
+ const k = 1024;
599
+ const sizes = ['B', 'KB', 'MB', 'GB'];
600
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
601
+ return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
602
+ }
603
+ /**
604
+ * 🔧 TEST UNIVERSAL - FUNCIONA PARA WEB E PROJETOS SEM BROWSER
605
+ *
606
+ * O browser SÓ é lançado quando o teste usa:
607
+ * - TestAnnotations.Frontend.testInfo = testInfo
608
+ * - TestAnnotations.Mixed.testInfo = testInfo
609
+ *
610
+ * Para testes API, SSH, Banco, Mobile: NÃO lança browser.
611
+ *
612
+ * O mecanismo é lazy: o fixture armazena uma factory no TestContext.
613
+ * Quando o setter de Frontend/Mixed é chamado, dispara a criação assíncrona.
614
+ * O primeiro await subsequente (ex.: setSystem) aguarda a conclusão.
615
+ *
616
+ * 📡 NETWORK LOGS: Captura automaticamente todos os logs de rede (quando browser ativo).
617
+ *
618
+ * @example Web (com browser)
619
+ * ```typescript
620
+ * import { test, expect } from '@silasfmartins/testhub'
621
+ *
622
+ * test('Web test', async ({}, testInfo) => {
623
+ * TestAnnotations.Frontend.testInfo = testInfo
624
+ * await TestAnnotations.setSystem('MEU_SISTEMA')
625
+ * await new MinhaPage().minhaAcao()
626
+ * })
627
+ * ```
628
+ *
629
+ * @example SSH/API/Banco (sem browser) - mesmo import!
630
+ * ```typescript
631
+ * import { test, expect, SSHActions } from '@silasfmartins/testhub'
632
+ *
633
+ * test('SSH test', async ({}, testInfo) => {
634
+ * TestAnnotations.SSH.testInfo = testInfo
635
+ * await SSHActions.connect({...})
636
+ * })
637
+ * ```
638
+ */
639
+ // biome-ignore lint/suspicious/noConfusingVoidType: Playwright fixture pattern
640
+ export const test = base.extend({
641
+ // 🔧 Network Logger - captura todos os logs de rede
642
+ networkLogs: [async ({}, use) => {
643
+ const logger = new NetworkLogger();
644
+ await use(logger);
645
+ }, { scope: 'test' }],
646
+ // 🔧 Auto-inject: armazena factory lazy ANTES do teste, limpa DEPOIS
647
+ autoInjectPage: [async ({ playwright, networkLogs }, use, testInfo) => {
648
+ let page;
649
+ let context;
650
+ let browser;
651
+ // Armazena factory lazy no TestContext — só executa quando Frontend/Mixed chamar requestBrowserCreation()
652
+ TestContext.setPageFactory(async () => {
653
+ if (page)
654
+ return page;
655
+ // ⚠️ PROTEÇÃO: Se o tipo de teste foi definido como não-browser, não criar
656
+ if (TestContext.isNonBrowserTest()) {
657
+ console.log(`🚫 [AutoCore] Teste ${TestContext.getTestType()} detectado - browser não será criado`);
658
+ return null;
659
+ }
660
+ const browserName = testInfo.project?.use?.browserName || 'chromium';
661
+ console.log(`🌐 [AutoCore] Lançando browser lazy: ${browserName}`);
662
+ const browserType = playwright[browserName];
663
+ const resolution = detectScreenResolution();
664
+ browser = await browserType.launch();
665
+ context = await browser.newContext({
666
+ viewport: { width: resolution.width, height: resolution.height },
667
+ deviceScaleFactor: 1,
668
+ });
669
+ page = await context.newPage();
670
+ // Injeta no TestContext
671
+ TestContext.setPage(page);
672
+ console.log('🎭 [AutoCore] Page injetada automaticamente no TestContext (lazy)');
673
+ // Configura
674
+ setupNetworkLogging(page, networkLogs);
675
+ await setupResponsiveViewport(page);
676
+ return page;
677
+ });
678
+ // ---------- TEST BODY EXECUTA AQUI ----------
679
+ await use();
680
+ // ---------- CLEANUP (pós-teste) ----------
681
+ TestContext.setPageFactory(null);
682
+ if (page) {
683
+ // Anexa network logs ao relatório se browser foi usado
684
+ await attachNetworkLogs(testInfo, networkLogs);
685
+ await page.close().catch(() => { });
686
+ await context?.close().catch(() => { });
687
+ await browser?.close().catch(() => { });
688
+ }
689
+ else {
690
+ const testType = TestContext.getTestType() || 'desconhecido';
691
+ console.log(`🔧 [AutoCore] Teste "${testInfo.title}" executou sem browser (tipo: ${testType})`);
692
+ // ✅ NOVO: Anexar logs de API ao relatório mesmo sem browser
693
+ if (testType === 'API') {
694
+ try {
695
+ const { ApiActions } = await import('../api/ApiActions.js');
696
+ if (ApiActions.hasLogsForTest(testInfo.title)) {
697
+ const logs = ApiActions.getLogsForTest(testInfo.title);
698
+ if (logs.length > 0) {
699
+ await testInfo.attach('api-logs.txt', {
700
+ body: Buffer.from(logs.join('\n'), 'utf-8'),
701
+ contentType: 'text/plain',
702
+ });
703
+ }
704
+ }
705
+ // Anexar relatório final de API
706
+ await ApiActions.anexarResumoDeLogs();
707
+ }
708
+ catch {
709
+ // Silenciosamente ignorar se ApiActions não estiver disponível
710
+ }
711
+ }
712
+ }
713
+ // Resetar tipo de teste para o próximo
714
+ TestContext.resetTestType();
715
+ }, { auto: true }],
716
+ });
717
+ export { NetworkLogger };
718
+ export const expect = baseExpect;