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