@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,3586 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📚 HubDocs - Sistema Principal de Documentação Automática (Versão Funcional)
|
|
3
|
+
* @silasfmartins/testhub - Auto Documentation System v1.1.51
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import * as yaml from 'js-yaml';
|
|
8
|
+
import { ExecutionTracker } from './ExecutionTracker.js';
|
|
9
|
+
import { StatementTracker } from './StatementTracker.js';
|
|
10
|
+
import { Logger } from '../utils/Logger.js';
|
|
11
|
+
export class AutoDocs {
|
|
12
|
+
static initialized = false;
|
|
13
|
+
static finalizationExecuted = false;
|
|
14
|
+
static config = {
|
|
15
|
+
enabled: true,
|
|
16
|
+
categories: ['api', 'ssh', 'db', 'ui', 'mobile'],
|
|
17
|
+
outputDir: './docs',
|
|
18
|
+
includeRequestBody: true,
|
|
19
|
+
includeResponseBody: true,
|
|
20
|
+
generateSwagger: true,
|
|
21
|
+
includeScreenshots: true,
|
|
22
|
+
maxScreenshotSize: 1920,
|
|
23
|
+
projectName: 'Test Hub',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* 🚀 Inicializar o sistema AutoDocs
|
|
27
|
+
*/
|
|
28
|
+
static async initialize(options) {
|
|
29
|
+
try {
|
|
30
|
+
if (AutoDocs.initialized) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Aplicar configurações personalizadas
|
|
34
|
+
if (options?.config) {
|
|
35
|
+
AutoDocs.config = { ...AutoDocs.config, ...options.config };
|
|
36
|
+
}
|
|
37
|
+
if (options?.outputDir) {
|
|
38
|
+
AutoDocs.config.outputDir = options.outputDir;
|
|
39
|
+
}
|
|
40
|
+
if (options?.projectName) {
|
|
41
|
+
AutoDocs.config.projectName = options.projectName;
|
|
42
|
+
}
|
|
43
|
+
// Detectar tipo de projeto automaticamente
|
|
44
|
+
const projectType = AutoDocs.detectProjectType({
|
|
45
|
+
apis: [],
|
|
46
|
+
ssh: [],
|
|
47
|
+
db: [],
|
|
48
|
+
ui: [],
|
|
49
|
+
mobile: [],
|
|
50
|
+
});
|
|
51
|
+
// Garantir que o diretório de saída existe
|
|
52
|
+
await AutoDocs.ensureOutputDir();
|
|
53
|
+
// Inicializar ExecutionTracker
|
|
54
|
+
ExecutionTracker.initialize(AutoDocs.config.projectName, projectType);
|
|
55
|
+
// 🎯 REGISTRO GLOBAL para ApiActions
|
|
56
|
+
AutoDocs.registerGlobally();
|
|
57
|
+
AutoDocs.initialized = true;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
Logger.error('❌ AutoDocs: Erro na inicialização:', error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 📖 Gerar documentação completa
|
|
65
|
+
*/
|
|
66
|
+
static async generateDocs() {
|
|
67
|
+
// ✅ EVITAR EXECUÇÃO MÚLTIPLA
|
|
68
|
+
if (AutoDocs.finalizationExecuted) {
|
|
69
|
+
Logger.info('📚 AutoDocs: Documentação já foi gerada, pulando...');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!AutoDocs.initialized) {
|
|
73
|
+
await AutoDocs.initialize();
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
Logger.info('📚 AutoDocs: Iniciando geração de documentação...');
|
|
77
|
+
// Marcar como executado ANTES de começar
|
|
78
|
+
AutoDocs.finalizationExecuted = true;
|
|
79
|
+
// ✅ CRÍTICO: Finalizar coleta de dados PRIMEIRO
|
|
80
|
+
ExecutionTracker.finalize();
|
|
81
|
+
// ✅ NOVO: Forçar carregamento do arquivo persistido para evitar dados zerados
|
|
82
|
+
Logger.info('📊 AutoDocs: Forçando carregamento dos dados do arquivo...');
|
|
83
|
+
const fileData = ExecutionTracker.loadFromFile();
|
|
84
|
+
if (fileData) {
|
|
85
|
+
// Usar dados do arquivo diretamente
|
|
86
|
+
const data = fileData;
|
|
87
|
+
if (AutoDocs.hasDataToGenerate(data)) {
|
|
88
|
+
Logger.info('📊 AutoDocs: Dados encontrados, gerando documentação...');
|
|
89
|
+
// Gerar relatório HTML principal
|
|
90
|
+
await AutoDocs.generateHTMLReport(data);
|
|
91
|
+
// Gerar Swagger se habilitado e há APIs
|
|
92
|
+
if (AutoDocs.config.generateSwagger && data.apis.length > 0) {
|
|
93
|
+
Logger.info(`📋 AutoDocs: Gerando Swagger para ${data.apis.length} APIs...`);
|
|
94
|
+
await AutoDocs.generateSwaggerDocs(data.apis);
|
|
95
|
+
}
|
|
96
|
+
Logger.success(`📚 AutoDocs: Documentação gerada com sucesso em ${AutoDocs.config.outputDir}`);
|
|
97
|
+
Logger.info(`📊 Estatísticas: ${data.apis.length} APIs, ${data.ssh.length} SSH, ${data.db.length} DB, ${data.ui.length} UI, ${data.mobile.length} Mobile`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
Logger.info('📚 AutoDocs: Nenhum dado encontrado para gerar documentação');
|
|
101
|
+
Logger.info('💡 AutoDocs: Execute seus testes para capturar dados automaticamente');
|
|
102
|
+
// ✅ GERAR ARQUIVO BÁSICO MESMO SEM DADOS
|
|
103
|
+
await AutoDocs.generateEmptyDocsReport();
|
|
104
|
+
Logger.success('✅ Arquivo de documentação básico gerado!');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
Logger.info('📚 AutoDocs: Arquivo de dados não encontrado');
|
|
109
|
+
Logger.info('💡 AutoDocs: Execute seus testes para capturar dados automaticamente');
|
|
110
|
+
// ✅ GERAR ARQUIVO BÁSICO MESMO SEM DADOS
|
|
111
|
+
await AutoDocs.generateEmptyDocsReport();
|
|
112
|
+
Logger.success('✅ Arquivo de documentação básico gerado!');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
Logger.error('❌ AutoDocs: Erro na geração de documentação:', error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 📄 Gerar relatório HTML principal
|
|
121
|
+
*/
|
|
122
|
+
static async generateHTMLReport(data) {
|
|
123
|
+
try {
|
|
124
|
+
const htmlContent = AutoDocs.generateHTMLContent(data);
|
|
125
|
+
const outputPath = path.join(AutoDocs.config.outputDir, 'index.html');
|
|
126
|
+
await fs.promises.writeFile(outputPath, htmlContent, 'utf-8');
|
|
127
|
+
Logger.info(`📄 Relatório HTML gerado: ${outputPath}`);
|
|
128
|
+
// ℹ️ Visualização por CTs removida - agora está no relatório unificado
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
Logger.error('❌ Erro ao gerar HTML:', error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 📄 Gerar documentação básica quando não há dados
|
|
136
|
+
*/
|
|
137
|
+
static async generateEmptyDocsReport() {
|
|
138
|
+
try {
|
|
139
|
+
const htmlContent = AutoDocs.generateEmptyHTMLContent();
|
|
140
|
+
const outputPath = path.join(AutoDocs.config.outputDir, 'index.html');
|
|
141
|
+
await fs.promises.writeFile(outputPath, htmlContent, 'utf-8');
|
|
142
|
+
Logger.info(`📄 Documentação básica gerada: ${outputPath}`);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
Logger.error('❌ Erro ao gerar documentação básica:', error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 🔄 Reset do sistema (APENAS EM DESENVOLVIMENTO)
|
|
150
|
+
*/
|
|
151
|
+
static reset() {
|
|
152
|
+
// ✅ PROTEÇÃO: Só permitir reset em desenvolvimento ou com confirmação explícita
|
|
153
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
154
|
+
const forceReset = process.env.FORCE_AUTODOCS_RESET === 'true';
|
|
155
|
+
if (isProduction && !forceReset) {
|
|
156
|
+
Logger.warning('⚠️ AutoDocs.reset: Ignorado em produção. Use FORCE_AUTODOCS_RESET=true se necessário');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
Logger.warning('⚠️ AutoDocs.reset: RESETANDO SISTEMA - Esta ação limpará todos os dados!');
|
|
160
|
+
AutoDocs.initialized = false;
|
|
161
|
+
AutoDocs.finalizationExecuted = false;
|
|
162
|
+
// ✅ NOVO: Só resetar ExecutionTracker se explicitamente solicitado
|
|
163
|
+
if (forceReset) {
|
|
164
|
+
ExecutionTracker.reset();
|
|
165
|
+
Logger.info('📚 AutoDocs.reset: ExecutionTracker também foi resetado');
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
Logger.info('📚 AutoDocs.reset: ExecutionTracker preservado (use FORCE_AUTODOCS_RESET=true para resetar tudo)');
|
|
169
|
+
}
|
|
170
|
+
Logger.info('📚 AutoDocs: Sistema resetado');
|
|
171
|
+
}
|
|
172
|
+
// 🎯 Métodos de interceptação FUNCIONAIS
|
|
173
|
+
/**
|
|
174
|
+
* 🌐 Interceptar chamada de API
|
|
175
|
+
* DESABILITADO: Evita duplicação com ApiActions que já faz interceptação
|
|
176
|
+
*/
|
|
177
|
+
static async interceptAPI(operation, endpoint, config) {
|
|
178
|
+
// ✅ CRÍTICO: Retornar ID vazio para evitar duplicação
|
|
179
|
+
// O sistema ExecutionTracker via ApiActions já faz todo o trabalho
|
|
180
|
+
Logger.info('🔄 AutoDocs.interceptAPI: Delegando para ExecutionTracker via ApiActions');
|
|
181
|
+
return `autodocs-delegated-${Date.now()}`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 🔄 Atualizar resultado de API
|
|
185
|
+
* DESABILITADO: Evita duplicação com ExecutionTracker
|
|
186
|
+
*/
|
|
187
|
+
static async updateAPIResult(docId, response, statusCode, error) {
|
|
188
|
+
// ✅ CRÍTICO: Não fazer update duplicado
|
|
189
|
+
// O ExecutionTracker via ApiActions já faz todo o trabalho
|
|
190
|
+
Logger.info('🔄 AutoDocs.updateAPIResult: Delegando para ExecutionTracker via ApiActions');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 🎯 Registrar AutoDocs globalmente para ApiActions
|
|
195
|
+
*/
|
|
196
|
+
static registerGlobally() {
|
|
197
|
+
const APIACTIONS_CHECK_DELAY = 1000;
|
|
198
|
+
try {
|
|
199
|
+
// Registrar no global para ApiActions
|
|
200
|
+
;
|
|
201
|
+
global.AutoDocs = AutoDocs;
|
|
202
|
+
globalThis.AutoDocs = AutoDocs;
|
|
203
|
+
// ✅ INTERCEPTAÇÃO AUTOMÁTICA DE FETCH: Capturar todas as requisições HTTP
|
|
204
|
+
AutoDocs.setupFetchInterception();
|
|
205
|
+
// ✅ CRÍTICO: Verificar se ApiActions está disponível e conectar
|
|
206
|
+
setTimeout(() => {
|
|
207
|
+
try {
|
|
208
|
+
const apiActions = global.ApiActions ||
|
|
209
|
+
globalThis.ApiActions;
|
|
210
|
+
if (apiActions) {
|
|
211
|
+
process.stdout.write('✅ AutoDocs: ApiActions detectado, sistema pronto para interceptação\n');
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// Não exibir warning, pois fetch já está interceptado e funciona
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Silencioso - não precisa mostrar erro
|
|
219
|
+
}
|
|
220
|
+
}, APIACTIONS_CHECK_DELAY);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
process.stderr.write(`⚠️ AutoDocs: Erro ao registrar globalmente: ${error}\n`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 🔗 Configurar interceptação automática de fetch
|
|
228
|
+
* DESABILITADO: Para evitar duplicação com ApiActions
|
|
229
|
+
*/
|
|
230
|
+
static setupFetchInterception() {
|
|
231
|
+
// ✅ CRÍTICO: Desabilitar interceptação de fetch para evitar duplicação
|
|
232
|
+
// O ApiActions já faz todo o trabalho de tracking/documentação
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* 🎯 Verificar se URL deve ser interceptada
|
|
237
|
+
* CORRIGIDO: Não interceptar URLs que já são processadas pelo ApiActions
|
|
238
|
+
*/
|
|
239
|
+
static shouldInterceptURL(url) {
|
|
240
|
+
// ✅ CRÍTICO: Não interceptar URLs de API externas que já passam pelo ApiActions
|
|
241
|
+
// ApiActions já faz o tracking completo, então não precisamos duplicar
|
|
242
|
+
if (url.includes('api-qa1.telefonica.com.br') ||
|
|
243
|
+
url.includes('telefonica.com') ||
|
|
244
|
+
url.includes('.com.br/') ||
|
|
245
|
+
url.includes('/api/') ||
|
|
246
|
+
url.includes('/v1/') ||
|
|
247
|
+
url.includes('opportunity')) {
|
|
248
|
+
return false; // Deixar o ApiActions gerenciar essas URLs
|
|
249
|
+
}
|
|
250
|
+
// Interceptar apenas URLs específicas que não passam pelo ApiActions
|
|
251
|
+
return (url.includes('localhost:') &&
|
|
252
|
+
(url.includes('/internal/') ||
|
|
253
|
+
url.includes('/health') ||
|
|
254
|
+
url.includes('/metrics')));
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 🔄 Processar fetch interceptado
|
|
258
|
+
*/
|
|
259
|
+
static async handleInterceptedFetch(originalFetch, args, method, url, init) {
|
|
260
|
+
let docId = null;
|
|
261
|
+
try {
|
|
262
|
+
docId = await AutoDocs.interceptAPI(method, url, {
|
|
263
|
+
method,
|
|
264
|
+
headers: init?.headers,
|
|
265
|
+
payload: init?.body,
|
|
266
|
+
url,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Falhou na interceptação, continuar sem documentar
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const response = await originalFetch(...args);
|
|
274
|
+
// Atualizar documentação com resposta
|
|
275
|
+
if (docId) {
|
|
276
|
+
await AutoDocs.updateFetchResponse(docId, response);
|
|
277
|
+
}
|
|
278
|
+
return response;
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
// Atualizar com erro se documentando
|
|
282
|
+
if (docId) {
|
|
283
|
+
await AutoDocs.updateFetchError(docId, error);
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 📥 Atualizar resposta do fetch
|
|
290
|
+
*/
|
|
291
|
+
static async updateFetchResponse(docId, response) {
|
|
292
|
+
try {
|
|
293
|
+
const responseText = await response.clone().text();
|
|
294
|
+
let responseData = responseText;
|
|
295
|
+
try {
|
|
296
|
+
responseData = JSON.parse(responseText);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Não é JSON, usar como string
|
|
300
|
+
}
|
|
301
|
+
await AutoDocs.updateAPIResult(docId, responseData, response.status);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// Falhou na atualização, continuar
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* ❌ Atualizar erro do fetch
|
|
309
|
+
*/
|
|
310
|
+
static async updateFetchError(docId, error) {
|
|
311
|
+
try {
|
|
312
|
+
await AutoDocs.updateAPIResult(docId, { error: String(error) }, 0);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Falhou na atualização de erro, continuar
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* 🔧 NOVO: Método para forçar geração da documentação
|
|
320
|
+
*/
|
|
321
|
+
static async forceGenerate() {
|
|
322
|
+
Logger.info('🔄 AutoDocs: Forçando geração de documentação...');
|
|
323
|
+
try {
|
|
324
|
+
// Verificar dados antes de gerar
|
|
325
|
+
const data = ExecutionTracker.getData();
|
|
326
|
+
Logger.info('🔍 AutoDocs: Dados disponíveis antes da geração:');
|
|
327
|
+
Logger.info(` 📋 APIs: ${data.apis.length}`);
|
|
328
|
+
Logger.info(` 🖥️ SSH: ${data.ssh.length}`);
|
|
329
|
+
Logger.info(` 🗄️ DB: ${data.db.length}`);
|
|
330
|
+
Logger.info(` 🎨 UI: ${data.ui.length}`);
|
|
331
|
+
Logger.info(` 📱 Mobile: ${data.mobile.length}`);
|
|
332
|
+
// Se não há dados, verificar se há arquivo temporário
|
|
333
|
+
if (data.apis.length === 0) {
|
|
334
|
+
const tempData = ExecutionTracker.loadFromFile();
|
|
335
|
+
if (tempData && tempData.apis.length > 0) {
|
|
336
|
+
Logger.info(`📁 AutoDocs: Encontrados ${tempData.apis.length} APIs no arquivo temporário`);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
Logger.info('❌ AutoDocs: Nenhum dado encontrado para gerar documentação');
|
|
340
|
+
Logger.info('💡 AutoDocs: Execute alguns testes com ApiActions primeiro');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
await AutoDocs.generateDocs();
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
Logger.error('❌ AutoDocs: Erro na geração forçada:', error);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* 🖥️ Interceptar comando SSH
|
|
351
|
+
*/
|
|
352
|
+
static async interceptSSH(command, host, details) {
|
|
353
|
+
if (!(AutoDocs.config.enabled && AutoDocs.config.categories.includes('ssh'))) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const sshCommand = {
|
|
357
|
+
command,
|
|
358
|
+
output: '',
|
|
359
|
+
host: host || process.env.SSH_HOST || 'unknown',
|
|
360
|
+
user: details?.user || process.env.SSH_USER || 'unknown',
|
|
361
|
+
timestamp: new Date().toISOString(),
|
|
362
|
+
testName: AutoDocs.getCurrentTestName(),
|
|
363
|
+
duration: 0,
|
|
364
|
+
success: false,
|
|
365
|
+
error: undefined,
|
|
366
|
+
};
|
|
367
|
+
ExecutionTracker.trackSSH(sshCommand);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 🗄️ Interceptar query de banco
|
|
371
|
+
*/
|
|
372
|
+
static interceptDB(query, params, dbType, description, serviceName) {
|
|
373
|
+
if (!(AutoDocs.config.enabled && AutoDocs.config.categories.includes('db'))) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const dbQuery = {
|
|
377
|
+
testName: AutoDocs.getCurrentTestName(),
|
|
378
|
+
environment: process.env.NODE_ENV || 'development',
|
|
379
|
+
query: query.trim(),
|
|
380
|
+
params: params || [],
|
|
381
|
+
type: AutoDocs.detectQueryType(query),
|
|
382
|
+
database: dbType || 'oracle',
|
|
383
|
+
timestamp: new Date().toISOString(),
|
|
384
|
+
duration: 0,
|
|
385
|
+
success: undefined, // ✅ CORRIGIDO: Começar com undefined para melhor detecção
|
|
386
|
+
error: undefined,
|
|
387
|
+
description: description || undefined,
|
|
388
|
+
serviceName: serviceName || undefined,
|
|
389
|
+
};
|
|
390
|
+
Logger.info(`🗄️ [HubDocs] Interceptando query: ${query.substring(0, 50)}...`);
|
|
391
|
+
ExecutionTracker.trackDB(dbQuery);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* 🔄 Atualizar resultado de query de banco
|
|
395
|
+
*/
|
|
396
|
+
static updateDBResult(query, timestamp, success, duration, error, resultCount) {
|
|
397
|
+
if (!(AutoDocs.config.enabled && AutoDocs.config.categories.includes('db')))
|
|
398
|
+
return;
|
|
399
|
+
Logger.info(`🔄 [HubDocs] updateDBResult chamado: success=${success}, duration=${duration}ms, error=${error}`);
|
|
400
|
+
ExecutionTracker.updateDBResult(query, timestamp, success, duration, error, resultCount);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 🔄 Atualizar resultado de comando SSH
|
|
404
|
+
*/
|
|
405
|
+
static updateSSHResult(command, timestamp, success, duration, exitCode, error) {
|
|
406
|
+
if (!(AutoDocs.config.enabled && AutoDocs.config.categories.includes('ssh')))
|
|
407
|
+
return;
|
|
408
|
+
ExecutionTracker.updateSSHResult(command, timestamp, success, duration, exitCode, error);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 🎨 Interceptar ação de UI
|
|
412
|
+
*/
|
|
413
|
+
static async interceptUI(action, element, url, details) {
|
|
414
|
+
if (!(AutoDocs.config.enabled && AutoDocs.config.categories.includes('ui'))) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const uiAction = {
|
|
418
|
+
testName: AutoDocs.getCurrentTestName(),
|
|
419
|
+
action,
|
|
420
|
+
element: element || 'unknown',
|
|
421
|
+
selector: element || 'unknown', // O seletor é o element passado
|
|
422
|
+
description: details?.description || action, // 🆕 Capturar description dos details
|
|
423
|
+
page: url || details?.url || 'unknown',
|
|
424
|
+
timestamp: new Date().toISOString(),
|
|
425
|
+
success: false,
|
|
426
|
+
duration: 0,
|
|
427
|
+
screenshot: details?.screenshot,
|
|
428
|
+
error: undefined,
|
|
429
|
+
};
|
|
430
|
+
ExecutionTracker.trackUI(uiAction);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* 🆕 Atualizar status de sucesso de uma ação UI
|
|
434
|
+
*/
|
|
435
|
+
static updateActionSuccess(action, element, success, duration, error) {
|
|
436
|
+
if (!(AutoDocs.config.enabled && AutoDocs.config.categories.includes('ui'))) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const data = ExecutionTracker.getData();
|
|
441
|
+
// Encontrar a última ação UI que corresponde ao action e element
|
|
442
|
+
for (let i = data.ui.length - 1; i >= 0; i--) {
|
|
443
|
+
const uiAction = data.ui[i];
|
|
444
|
+
if (uiAction.action === action && uiAction.selector === element) {
|
|
445
|
+
uiAction.success = success;
|
|
446
|
+
uiAction.duration = duration;
|
|
447
|
+
if (error) {
|
|
448
|
+
uiAction.error = error;
|
|
449
|
+
}
|
|
450
|
+
// Forçar save usando método interno
|
|
451
|
+
ExecutionTracker['saveToFile']();
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
// Ignorar erros para não quebrar funcionalidade principal
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 📱 Interceptar ação mobile
|
|
462
|
+
*/
|
|
463
|
+
static async interceptMobile(action, element, platform, details) {
|
|
464
|
+
if (!(AutoDocs.config.enabled && AutoDocs.config.categories.includes('mobile'))) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const mobileAction = {
|
|
468
|
+
testName: AutoDocs.getCurrentTestName(),
|
|
469
|
+
action,
|
|
470
|
+
element: element || 'unknown',
|
|
471
|
+
device: details?.device || 'unknown',
|
|
472
|
+
platform: platform || 'Android',
|
|
473
|
+
timestamp: new Date().toISOString(),
|
|
474
|
+
value: details?.value,
|
|
475
|
+
coordinates: details?.coordinates,
|
|
476
|
+
};
|
|
477
|
+
ExecutionTracker.trackMobile(mobileAction);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* 🧹 Extrair nome limpo do CT removendo logs excessivos
|
|
481
|
+
*/
|
|
482
|
+
static extractCleanCTName(ctName) {
|
|
483
|
+
if (!ctName)
|
|
484
|
+
return 'CT Sem Nome';
|
|
485
|
+
// Se tem menos de 50 caracteres, retorna completo
|
|
486
|
+
if (ctName.length <= 50)
|
|
487
|
+
return ctName;
|
|
488
|
+
// Tentar extrair apenas a parte do CT sem logs
|
|
489
|
+
const lines = ctName.split('\n');
|
|
490
|
+
if (lines.length > 0) {
|
|
491
|
+
const firstLine = lines[0].trim();
|
|
492
|
+
// Se a primeira linha parece ser um ID de CT, usar ela
|
|
493
|
+
if (firstLine.includes('CT') || firstLine.length < 100) {
|
|
494
|
+
return firstLine;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Procurar por padrões específicos de CT
|
|
498
|
+
const ctMatch = ctName.match(/CT\d+\s*-\s*([^[\n{(]+)/i);
|
|
499
|
+
if (ctMatch)
|
|
500
|
+
return ctMatch[0].trim();
|
|
501
|
+
// Procurar por método - descrição (sem logs)
|
|
502
|
+
const methodMatch = ctName.match(/(\w+\(\)\s*-\s*[^[\n{(]{1,80})/i);
|
|
503
|
+
if (methodMatch)
|
|
504
|
+
return methodMatch[1].trim();
|
|
505
|
+
// Como último recurso, pegar apenas os primeiros 50 caracteres
|
|
506
|
+
return `${ctName.substring(0, 50)}...`;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* 🔐 Escapar HTML para segurança
|
|
510
|
+
*/
|
|
511
|
+
static escapeHtml(text) {
|
|
512
|
+
if (!text) {
|
|
513
|
+
return '';
|
|
514
|
+
}
|
|
515
|
+
return text
|
|
516
|
+
.replace(/&/g, '&')
|
|
517
|
+
.replace(/</g, '<')
|
|
518
|
+
.replace(/>/g, '>')
|
|
519
|
+
.replace(/"/g, '"')
|
|
520
|
+
.replace(/'/g, ''')
|
|
521
|
+
.replace(/\n/g, '<br>');
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* 🖼️ Resolve caminho de imagem para data URI quando possível
|
|
525
|
+
*/
|
|
526
|
+
static resolveImageSource(imagePath) {
|
|
527
|
+
if (!imagePath) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
const trimmed = imagePath.trim();
|
|
531
|
+
if (!trimmed) {
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
if (/^data:image\//i.test(trimmed) || /^https?:\/\//i.test(trimmed)) {
|
|
535
|
+
return trimmed;
|
|
536
|
+
}
|
|
537
|
+
const normalized = trimmed
|
|
538
|
+
.replace(/^file:\/\//i, '')
|
|
539
|
+
.replace(/\\/g, path.sep);
|
|
540
|
+
const candidates = new Set();
|
|
541
|
+
if (path.isAbsolute(normalized)) {
|
|
542
|
+
candidates.add(path.normalize(normalized));
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
candidates.add(path.normalize(normalized));
|
|
546
|
+
candidates.add(path.join(process.cwd(), normalized));
|
|
547
|
+
candidates.add(path.join(process.cwd(), 'playwright-report', normalized));
|
|
548
|
+
candidates.add(path.join(process.cwd(), 'test-results', normalized));
|
|
549
|
+
}
|
|
550
|
+
for (const candidate of candidates) {
|
|
551
|
+
try {
|
|
552
|
+
if (fs.existsSync(candidate)) {
|
|
553
|
+
const buffer = fs.readFileSync(candidate);
|
|
554
|
+
return AutoDocs.toDataUri(candidate, buffer);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
Logger.warning(`⚠️ [HubDocs] Falha ao embutir imagem (${candidate}): ${error}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return trimmed;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* 🧪 Converte buffer de imagem em data URI
|
|
565
|
+
*/
|
|
566
|
+
static toDataUri(filePath, buffer) {
|
|
567
|
+
const mime = AutoDocs.detectMimeType(filePath);
|
|
568
|
+
return `data:${mime};base64,${buffer.toString('base64')}`;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* 🎨 Detecta MIME-Type básico pelo sufixo do arquivo
|
|
572
|
+
*/
|
|
573
|
+
static detectMimeType(filePath) {
|
|
574
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
575
|
+
case '.jpg':
|
|
576
|
+
case '.jpeg':
|
|
577
|
+
return 'image/jpeg';
|
|
578
|
+
case '.gif':
|
|
579
|
+
return 'image/gif';
|
|
580
|
+
case '.webp':
|
|
581
|
+
return 'image/webp';
|
|
582
|
+
case '.svg':
|
|
583
|
+
return 'image/svg+xml';
|
|
584
|
+
default:
|
|
585
|
+
return 'image/png';
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// 🔧 Métodos utilitários
|
|
589
|
+
/**
|
|
590
|
+
* 🔍 Detectar versão do AutoCore instalada dinamicamente
|
|
591
|
+
*/
|
|
592
|
+
static getProjectVersion() {
|
|
593
|
+
try {
|
|
594
|
+
// ✅ CORRIGIDO: Usar fs e path já importados no topo do arquivo
|
|
595
|
+
// Primeiro, tentar obter informações do projeto do usuário
|
|
596
|
+
const root = process.cwd();
|
|
597
|
+
const pkgPath = path.join(root, 'package.json');
|
|
598
|
+
if (fs.existsSync(pkgPath)) {
|
|
599
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
600
|
+
// Obter nome do projeto do usuário
|
|
601
|
+
const projectName = pkg.name || 'Projeto Test HUB';
|
|
602
|
+
// Obter versão do AutoCore instalada (igual ao UnifiedHtmlGenerator e ExecutionTracker)
|
|
603
|
+
const autocoreVersion = pkg.dependencies?.['@silasfmartins/testhub'] ||
|
|
604
|
+
pkg.devDependencies?.['@silasfmartins/testhub'] ||
|
|
605
|
+
'1.1.X';
|
|
606
|
+
Logger.info(`📦 HUBDocs: Projeto ${projectName} usando Test HUB ${autocoreVersion}`);
|
|
607
|
+
return `TestHub@${autocoreVersion}`;
|
|
608
|
+
}
|
|
609
|
+
// Fallback: tentar buscar em diretórios pai
|
|
610
|
+
let currentDir = root;
|
|
611
|
+
let attempts = 0;
|
|
612
|
+
const maxAttempts = 3;
|
|
613
|
+
while (attempts < maxAttempts) {
|
|
614
|
+
const packagePath = path.join(currentDir, 'package.json');
|
|
615
|
+
if (fs.existsSync(packagePath)) {
|
|
616
|
+
const packageContent = fs.readFileSync(packagePath, 'utf8');
|
|
617
|
+
const packageData = JSON.parse(packageContent);
|
|
618
|
+
const autocoreVersion = packageData.dependencies?.['@silasfmartins/testhub'] ||
|
|
619
|
+
packageData.devDependencies?.['@silasfmartins/testhub'] ||
|
|
620
|
+
'1.1.X';
|
|
621
|
+
Logger.info(`📦 HUBDocs: Test Hub ${autocoreVersion} detectado em ${packagePath}`);
|
|
622
|
+
return `Test Hub@${autocoreVersion}`;
|
|
623
|
+
}
|
|
624
|
+
// Subir um nível no diretório
|
|
625
|
+
const parentDir = path.dirname(currentDir);
|
|
626
|
+
if (parentDir === currentDir)
|
|
627
|
+
break; // Chegou na raiz
|
|
628
|
+
currentDir = parentDir;
|
|
629
|
+
attempts++;
|
|
630
|
+
}
|
|
631
|
+
// Fallback para versão padrão
|
|
632
|
+
Logger.warning('⚠️ AutoDocs: package.json não encontrado, usando versão padrão');
|
|
633
|
+
return 'Test Hub@1.1.X';
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
Logger.warning(`⚠️ AutoDocs: Erro ao detectar versão do Test Hub: ${error instanceof Error ? error.message : String(error)}`);
|
|
637
|
+
return 'Test Hub@1.1.X';
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* 🎨 Gerar conteúdo HTML com dados usando TailwindCSS e funcionalidades completas
|
|
642
|
+
*/
|
|
643
|
+
static generateHTMLContent(data) {
|
|
644
|
+
if (!data || Object.keys(data).length === 0) {
|
|
645
|
+
return AutoDocs.generateEmptyHTMLContent();
|
|
646
|
+
}
|
|
647
|
+
// 🔧 Detectar versão dinâmica do projeto local
|
|
648
|
+
const projectVersion = AutoDocs.getProjectVersion();
|
|
649
|
+
// 🤖 Detectar categorias automaticamente
|
|
650
|
+
const detectedCategories = AutoDocs.detectProjectCategories(data.apis || []);
|
|
651
|
+
const categoryOverview = Object.entries(detectedCategories)
|
|
652
|
+
.filter(([key]) => key !== 'outras')
|
|
653
|
+
.map(([key, catData]) => `${catData.icon} ${key.charAt(0).toUpperCase() + key.slice(1)}`)
|
|
654
|
+
.join(', ');
|
|
655
|
+
const timestamp = new Date().toLocaleString('pt-BR');
|
|
656
|
+
const totalItems = (data.apis?.length || 0) +
|
|
657
|
+
(data.ssh?.length || 0) +
|
|
658
|
+
(data.db?.length || 0) +
|
|
659
|
+
(data.ui?.length || 0) +
|
|
660
|
+
(data.mobile?.length || 0);
|
|
661
|
+
return `<!DOCTYPE html>
|
|
662
|
+
<html lang="pt-BR">
|
|
663
|
+
<head>
|
|
664
|
+
<meta charset="UTF-8">
|
|
665
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
666
|
+
<title>📚 ${AutoDocs.config.projectName} - HubDocs Inteligente</title>
|
|
667
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
668
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
669
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
|
670
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
671
|
+
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
|
|
672
|
+
<style>
|
|
673
|
+
body { font-family: 'Inter', sans-serif; background-color: #0f172a; color: #f1f5f9; min-height: 100vh; }
|
|
674
|
+
.light-mode { background-color: #f9fafb !important; color: #111827 !important; }
|
|
675
|
+
.card { background: #1e293b; border-radius: 1rem; padding: 1.5rem; box-shadow: 0 2px 8px #0002; color: #f1f5f9; }
|
|
676
|
+
.light-mode .card { background: #fff; color: #111827; border: 1px solid #e5e7eb; }
|
|
677
|
+
.btn { background: #334155; color: #fff; border: none; border-radius: 8px; padding: 0.5rem 1.2rem; font-weight: 600; cursor: pointer; margin-bottom: 0.25rem; transition: background 0.2s; }
|
|
678
|
+
.btn:hover { background: #475569; }
|
|
679
|
+
.light-mode .btn { background: #374151; color: #fff; border: 1px solid #d1d5db; }
|
|
680
|
+
.light-mode .btn:hover { background: #4b5563; }
|
|
681
|
+
.header-bar { background: #1e293b; padding: 1rem 2rem; border-radius: 1rem 1rem 0 0; margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: space-between; }
|
|
682
|
+
.header-bar .header-title { font-size: 2rem; font-weight: 700; color: #fbbf24; }
|
|
683
|
+
.header-bar .header-meta { font-size: 1rem; color: #f1f5f9; }
|
|
684
|
+
.light-mode .header-bar { background: #f3f4f6; color: #111827; border: 1px solid #e5e7eb; }
|
|
685
|
+
.light-mode .header-title { color: #d97706; }
|
|
686
|
+
.light-mode .header-meta { color: #374151 !important; }
|
|
687
|
+
.footer-bar { background: #1e293b; color: #fbbf24; padding: 1rem 2rem; border-radius: 0 0 1rem 1rem; margin-top: 2rem; text-align: center; font-size: 1rem; }
|
|
688
|
+
.light-mode .footer-bar { background: #f3f4f6; color: #d97706; border: 1px solid #e5e7eb; }
|
|
689
|
+
.footer-bar a { color: #fbbf24; text-decoration: underline; }
|
|
690
|
+
.light-mode .footer-bar a { color: #d97706; font-weight: 600; }
|
|
691
|
+
.method-badge { display: inline-block; padding: 4px 12px; border-radius: 9999px; font-size: 12px; font-weight: 600; color: white; }
|
|
692
|
+
.method-get { background: #10b981; }
|
|
693
|
+
.method-post { background: #3b82f6; }
|
|
694
|
+
.method-put { background: #f59e0b; }
|
|
695
|
+
.method-delete { background: #ef4444; }
|
|
696
|
+
.method-patch { background: #8b5cf6; }
|
|
697
|
+
.status-badge { display: inline-block; padding: 4px 12px; border-radius: 9999px; font-size: 12px; font-weight: 600; }
|
|
698
|
+
@media (max-width: 900px) {
|
|
699
|
+
.grid-cols-2, .grid-cols-3, .grid-cols-4, .grid-cols-5 { grid-template-columns: 1fr !important; }
|
|
700
|
+
}
|
|
701
|
+
</style>
|
|
702
|
+
</head>
|
|
703
|
+
<body id="body-root" class="min-h-screen bg-slate-900 text-slate-100">
|
|
704
|
+
<div class="header-bar">
|
|
705
|
+
<div>
|
|
706
|
+
<h1 class="header-title">📚 ${AutoDocs.config.projectName} - HubDocs Inteligente</h1>
|
|
707
|
+
<p class="header-meta">🤖 Categorização Inteligente: ${categoryOverview || 'Sistema Adaptativo'}</p>
|
|
708
|
+
</div>
|
|
709
|
+
<div class="flex gap-2">
|
|
710
|
+
<button id="toggle-theme" class="btn" type="button" title="Alternar tema">🌙 Tema</button>
|
|
711
|
+
<button id="export-pdf" class="btn" type="button" title="Exportar PDF">📄 PDF</button>
|
|
712
|
+
<button id="export-csv" class="btn" type="button" title="Exportar CSV">📑 CSV</button>
|
|
713
|
+
<button id="export-json" class="btn" type="button" title="Exportar JSON">🗒️ JSON</button>
|
|
714
|
+
<button id="export-xlsx" class="btn" type="button" title="Exportar XLSX">📊 XLSX</button>
|
|
715
|
+
</div>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<div id="report-root" class="max-w-7xl mx-auto p-6" style="background:inherit;">
|
|
719
|
+
<!-- Overview Cards -->
|
|
720
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8">
|
|
721
|
+
<div class="card bg-gradient-to-br from-green-600 to-green-700">
|
|
722
|
+
<h3 class="text-3xl font-bold text-white">${data.apis?.length || 0}</h3>
|
|
723
|
+
<p class="text-green-100">🌐 APIs Inteligentes</p>
|
|
724
|
+
</div>
|
|
725
|
+
<div class="card bg-gradient-to-br from-blue-600 to-blue-700">
|
|
726
|
+
<h3 class="text-3xl font-bold text-white">${data.ssh?.length || 0}</h3>
|
|
727
|
+
<p class="text-blue-100">🖥️ Comandos SSH</p>
|
|
728
|
+
</div>
|
|
729
|
+
<div class="card bg-gradient-to-br from-purple-600 to-purple-700">
|
|
730
|
+
<h3 class="text-3xl font-bold text-white">${data.db?.length || 0}</h3>
|
|
731
|
+
<p class="text-purple-100">🗄️ Queries Database</p>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="card bg-gradient-to-br from-orange-600 to-orange-700">
|
|
734
|
+
<h3 class="text-3xl font-bold text-white">${data.ui?.length || 0}</h3>
|
|
735
|
+
<p class="text-orange-100">🎨 Ações UI</p>
|
|
736
|
+
</div>
|
|
737
|
+
<div class="card bg-gradient-to-br from-pink-600 to-pink-700">
|
|
738
|
+
<h3 class="text-3xl font-bold text-white">${data.mobile?.length || 0}</h3>
|
|
739
|
+
<p class="text-pink-100">📱 Ações Mobile</p>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
|
|
743
|
+
<!-- Intelligent System Info -->
|
|
744
|
+
<div class="card mb-8 bg-gradient-to-r from-blue-600 to-purple-600">
|
|
745
|
+
<h3 class="text-xl font-bold text-white mb-4">🤖 Sistema de Categorização Inteligente</h3>
|
|
746
|
+
<div class="text-white space-y-2">
|
|
747
|
+
<p><span class="font-semibold">📊 Total de Itens:</span> ${totalItems}</p>
|
|
748
|
+
<p><span class="font-semibold">🎯 Categorias Detectadas:</span> ${categoryOverview || 'Analisando padrões...'}</p>
|
|
749
|
+
<p><span class="font-semibold">🕐 Gerado em:</span> ${timestamp}</p>
|
|
750
|
+
<p class="text-sm text-blue-100 mt-3">✨ Sistema adapta-se automaticamente aos padrões dos seus testes, detectando categorias empresariais como Customer, Billing, Catalog, Simulation, Workflow e mais!</p>
|
|
751
|
+
</div>
|
|
752
|
+
</div>
|
|
753
|
+
|
|
754
|
+
<!-- Agrupamento Inteligente por Categoria -->
|
|
755
|
+
${AutoDocs.generateGroupedSections(data)}
|
|
756
|
+
</div>
|
|
757
|
+
|
|
758
|
+
<div class="footer-bar">
|
|
759
|
+
Desenvolvido por <strong>HubDocs Inteligente (${projectVersion})</strong> — Sistema de Categorização Automática com IA.<br>
|
|
760
|
+
<span>Documentação gerada automaticamente em ${timestamp}. <a href="#" onclick="alert('Sistema AutoDocs: Categorização inteligente baseada em padrões empresariais')">📚 Sobre a IA</a></span>
|
|
761
|
+
</div>
|
|
762
|
+
|
|
763
|
+
<script>
|
|
764
|
+
document.getElementById('toggle-theme').onclick = () => {
|
|
765
|
+
document.getElementById('body-root').classList.toggle('light-mode')
|
|
766
|
+
document.querySelector('.header-bar').classList.toggle('light-mode')
|
|
767
|
+
document.querySelector('.footer-bar').classList.toggle('light-mode')
|
|
768
|
+
document.querySelectorAll('.card').forEach(card => card.classList.toggle('light-mode'))
|
|
769
|
+
const meta = document.querySelector('.header-meta')
|
|
770
|
+
if (document.getElementById('body-root').classList.contains('light-mode')) {
|
|
771
|
+
meta && (meta.style.color = '#222')
|
|
772
|
+
} else {
|
|
773
|
+
meta && (meta.style.color = '#f1f5f9')
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// 🆕 Função para expandir/colapsar seções
|
|
778
|
+
function toggleSection(sectionKey) {
|
|
779
|
+
const section = document.getElementById('section-' + sectionKey)
|
|
780
|
+
const btn = document.getElementById('btn-' + sectionKey)
|
|
781
|
+
|
|
782
|
+
if (section && btn) {
|
|
783
|
+
if (section.style.display === 'none') {
|
|
784
|
+
section.style.display = 'block'
|
|
785
|
+
btn.innerHTML = '📋 Ocultar'
|
|
786
|
+
} else {
|
|
787
|
+
section.style.display = 'none'
|
|
788
|
+
btn.innerHTML = '📋 Detalhes'
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function getAllData() {
|
|
794
|
+
return {
|
|
795
|
+
apis: ${JSON.stringify(data.apis || [])},
|
|
796
|
+
ssh: ${JSON.stringify(data.ssh || [])},
|
|
797
|
+
db: ${JSON.stringify(data.db || [])},
|
|
798
|
+
ui: ${JSON.stringify(data.ui || [])},
|
|
799
|
+
mobile: ${JSON.stringify(data.mobile || [])},
|
|
800
|
+
detectedCategories: ${JSON.stringify(detectedCategories)},
|
|
801
|
+
timestamp: '${timestamp}',
|
|
802
|
+
projectName: '${AutoDocs.config.projectName}'
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
document.getElementById('export-pdf').onclick = async () => {
|
|
807
|
+
console.log('🔵 [PDF] Botão clicado - Iniciando geração do PDF do AutoDocs...');
|
|
808
|
+
|
|
809
|
+
const element = document.getElementById('report-root');
|
|
810
|
+
if (!element) {
|
|
811
|
+
console.error('❌ [PDF] Elemento report-root não encontrado!');
|
|
812
|
+
alert('❌ Erro: Elemento do relatório não encontrado!');
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const hasImages = element.querySelectorAll('img').length > 0;
|
|
817
|
+
if (hasImages) {
|
|
818
|
+
console.log('📸 [PDF] Imagens detectadas - preparando incorporação em base64 para ambiente offline');
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Verificar se jsPDF está carregado
|
|
822
|
+
if (typeof jspdf === 'undefined') {
|
|
823
|
+
console.error('❌ [PDF] jsPDF não está carregado!');
|
|
824
|
+
alert('❌ Erro: Biblioteca jsPDF não carregada. Recarregue a página.');
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
console.log('✅ [PDF] jsPDF carregado com sucesso');
|
|
828
|
+
|
|
829
|
+
// 🆕 EXPANDIR TODAS AS SEÇÕES antes de gerar PDF
|
|
830
|
+
const originalStates = {
|
|
831
|
+
groups: [],
|
|
832
|
+
apis: [],
|
|
833
|
+
screenshots: []
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// Expandir todos os grupos/seções colapsáveis
|
|
837
|
+
const allGroups = element.querySelectorAll('[id*="group-"], [id*="section-"], [id*="action-"]');
|
|
838
|
+
allGroups.forEach((group) => {
|
|
839
|
+
originalStates.groups.push(group.style.display);
|
|
840
|
+
group.style.display = 'block';
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// Expandir detalhes de APIs
|
|
844
|
+
const allApiDetails = element.querySelectorAll('.api-details, .ssh-details, .db-details, .mobile-details');
|
|
845
|
+
allApiDetails.forEach((detail) => {
|
|
846
|
+
originalStates.apis.push(detail.style.display);
|
|
847
|
+
detail.style.display = 'block';
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
// Aguardar imagens carregarem
|
|
851
|
+
const images = element.querySelectorAll('img');
|
|
852
|
+
console.log('📸 Total de imagens encontradas:', images.length);
|
|
853
|
+
|
|
854
|
+
await Promise.all(Array.from(images).map(img => {
|
|
855
|
+
if (img.complete) return Promise.resolve();
|
|
856
|
+
return new Promise(resolve => {
|
|
857
|
+
img.onload = resolve;
|
|
858
|
+
img.onerror = resolve;
|
|
859
|
+
setTimeout(resolve, 2000);
|
|
860
|
+
});
|
|
861
|
+
}));
|
|
862
|
+
|
|
863
|
+
console.log('✅ Imagens carregadas');
|
|
864
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
865
|
+
|
|
866
|
+
// Preparar elemento para PDF
|
|
867
|
+
const originalBg = element.style.background;
|
|
868
|
+
const originalColor = element.style.color;
|
|
869
|
+
element.style.background = '#fff';
|
|
870
|
+
element.style.color = '#222';
|
|
871
|
+
|
|
872
|
+
console.log('📄 [PDF] Iniciando geração do PDF...');
|
|
873
|
+
|
|
874
|
+
try {
|
|
875
|
+
// Usar html2canvas para capturar o conteúdo
|
|
876
|
+
const canvas = await html2canvas(element, {
|
|
877
|
+
scale: 2,
|
|
878
|
+
useCORS: false,
|
|
879
|
+
allowTaint: true,
|
|
880
|
+
backgroundColor: '#fff',
|
|
881
|
+
logging: false,
|
|
882
|
+
imageTimeout: 0
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// Criar PDF com jsPDF
|
|
886
|
+
const { jsPDF } = jspdf;
|
|
887
|
+
const pdf = new jsPDF({
|
|
888
|
+
orientation: 'portrait',
|
|
889
|
+
unit: 'mm',
|
|
890
|
+
format: 'a4'
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
const imgData = canvas.toDataURL('image/jpeg', 0.98);
|
|
894
|
+
const imgWidth = 210;
|
|
895
|
+
const pageHeight = 297;
|
|
896
|
+
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
897
|
+
let heightLeft = imgHeight;
|
|
898
|
+
let position = 0;
|
|
899
|
+
|
|
900
|
+
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
|
|
901
|
+
heightLeft -= pageHeight;
|
|
902
|
+
|
|
903
|
+
while (heightLeft > 0) {
|
|
904
|
+
position = heightLeft - imgHeight;
|
|
905
|
+
pdf.addPage();
|
|
906
|
+
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
|
|
907
|
+
heightLeft -= pageHeight;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
pdf.save('autodocs-inteligente.pdf');
|
|
911
|
+
|
|
912
|
+
console.log('✅ PDF do AutoDocs gerado com sucesso!');
|
|
913
|
+
alert('✅ PDF do AutoDocs gerado com sucesso! Verifique sua pasta de Downloads.');
|
|
914
|
+
} catch (error) {
|
|
915
|
+
console.error('❌ Erro ao gerar PDF do AutoDocs:', error);
|
|
916
|
+
alert('❌ Erro ao gerar PDF: ' + error.message);
|
|
917
|
+
} finally {
|
|
918
|
+
// Restaurar estilos originais
|
|
919
|
+
element.style.background = originalBg;
|
|
920
|
+
element.style.color = originalColor;
|
|
921
|
+
|
|
922
|
+
// Restaurar estados originais das seções
|
|
923
|
+
allGroups.forEach((group, index) => {
|
|
924
|
+
group.style.display = originalStates.groups[index] || '';
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
allApiDetails.forEach((detail, index) => {
|
|
928
|
+
detail.style.display = originalStates.apis[index] || '';
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
document.getElementById('export-csv').onclick = () => {
|
|
934
|
+
const allData = getAllData()
|
|
935
|
+
const csvData = []
|
|
936
|
+
|
|
937
|
+
// Headers
|
|
938
|
+
csvData.push(['Tipo', 'Nome', 'URL/Comando', 'Status', 'Duração', 'Teste', 'Timestamp'])
|
|
939
|
+
|
|
940
|
+
// APIs
|
|
941
|
+
allData.apis.forEach(api => {
|
|
942
|
+
csvData.push(['API', api.testName, api.url, api.success ? 'Sucesso' : 'Erro', api.duration + 'ms', api.testName, new Date(api.timestamp).toLocaleString('pt-BR')])
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
// SSH
|
|
946
|
+
allData.ssh.forEach(ssh => {
|
|
947
|
+
csvData.push(['SSH', ssh.command, ssh.host, ssh.success ? 'Sucesso' : 'Erro', ssh.duration + 'ms', ssh.testName, new Date(ssh.timestamp).toLocaleString('pt-BR')])
|
|
948
|
+
})
|
|
949
|
+
|
|
950
|
+
const csv = csvData.map(row => row.join(';')).join('\\n')
|
|
951
|
+
const blob = new Blob([csv], { type: 'text/csv' })
|
|
952
|
+
const a = document.createElement('a')
|
|
953
|
+
a.href = URL.createObjectURL(blob)
|
|
954
|
+
a.download = 'autodocs-inteligente.csv'
|
|
955
|
+
a.click()
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
document.getElementById('export-json').onclick = () => {
|
|
959
|
+
const blob = new Blob([JSON.stringify(getAllData(), null, 2)], { type: 'application/json' })
|
|
960
|
+
const a = document.createElement('a')
|
|
961
|
+
a.href = URL.createObjectURL(blob)
|
|
962
|
+
a.download = 'autodocs-inteligente.json'
|
|
963
|
+
a.click()
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
document.getElementById('export-xlsx').onclick = () => {
|
|
967
|
+
try {
|
|
968
|
+
const allData = getAllData()
|
|
969
|
+
const workbook = XLSX.utils.book_new()
|
|
970
|
+
let hasData = false
|
|
971
|
+
|
|
972
|
+
// Sheet APIs
|
|
973
|
+
if (allData.apis && allData.apis.length > 0) {
|
|
974
|
+
const apiSheet = XLSX.utils.json_to_sheet(allData.apis.map(api => ({
|
|
975
|
+
'Teste': api.testName || 'N/A',
|
|
976
|
+
'Método': api.method || 'N/A',
|
|
977
|
+
'URL': api.url || 'N/A',
|
|
978
|
+
'Status Code': api.statusCode || 'N/A',
|
|
979
|
+
'Sucesso': api.success ? 'Sim' : 'Não',
|
|
980
|
+
'Duração (ms)': api.duration || 0,
|
|
981
|
+
'Ambiente': api.environment || 'N/A',
|
|
982
|
+
'Timestamp': new Date(api.timestamp).toLocaleString('pt-BR')
|
|
983
|
+
})))
|
|
984
|
+
XLSX.utils.book_append_sheet(workbook, apiSheet, 'APIs')
|
|
985
|
+
hasData = true
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Sheet SSH
|
|
989
|
+
if (allData.ssh && allData.ssh.length > 0) {
|
|
990
|
+
const sshSheet = XLSX.utils.json_to_sheet(allData.ssh.map(ssh => ({
|
|
991
|
+
'Teste': ssh.testName || 'N/A',
|
|
992
|
+
'Comando': ssh.command || 'N/A',
|
|
993
|
+
'Host': ssh.host || 'N/A',
|
|
994
|
+
'Usuário': ssh.user || 'N/A',
|
|
995
|
+
'Sucesso': ssh.success ? 'Sim' : 'Não',
|
|
996
|
+
'Duração (ms)': ssh.duration || 0,
|
|
997
|
+
'Timestamp': new Date(ssh.timestamp).toLocaleString('pt-BR')
|
|
998
|
+
})))
|
|
999
|
+
XLSX.utils.book_append_sheet(workbook, sshSheet, 'SSH')
|
|
1000
|
+
hasData = true
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Sheet Database
|
|
1004
|
+
if (allData.db && allData.db.length > 0) {
|
|
1005
|
+
const dbSheet = XLSX.utils.json_to_sheet(allData.db.map(db => ({
|
|
1006
|
+
'Teste': db.testName || 'N/A',
|
|
1007
|
+
'Query': db.query ? db.query.substring(0, 100) : 'N/A',
|
|
1008
|
+
'Tipo': db.type || 'N/A',
|
|
1009
|
+
'Database': db.database || 'N/A',
|
|
1010
|
+
'Sucesso': db.success ? 'Sim' : 'Não',
|
|
1011
|
+
'Duração (ms)': db.duration || 0,
|
|
1012
|
+
'Resultado Count': db.resultCount || 0,
|
|
1013
|
+
'Timestamp': new Date(db.timestamp).toLocaleString('pt-BR')
|
|
1014
|
+
})))
|
|
1015
|
+
XLSX.utils.book_append_sheet(workbook, dbSheet, 'Database')
|
|
1016
|
+
hasData = true
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Sheet UI
|
|
1020
|
+
if (allData.ui && allData.ui.length > 0) {
|
|
1021
|
+
const uiSheet = XLSX.utils.json_to_sheet(allData.ui.map(ui => ({
|
|
1022
|
+
'Teste': ui.testName || 'N/A',
|
|
1023
|
+
'Ação': ui.action || 'N/A',
|
|
1024
|
+
'Elemento': ui.element || 'N/A',
|
|
1025
|
+
'Página': ui.page || 'N/A',
|
|
1026
|
+
'Sucesso': ui.success ? 'Sim' : 'Não',
|
|
1027
|
+
'Duração (ms)': ui.duration || 0,
|
|
1028
|
+
'Timestamp': new Date(ui.timestamp).toLocaleString('pt-BR')
|
|
1029
|
+
})))
|
|
1030
|
+
XLSX.utils.book_append_sheet(workbook, uiSheet, 'UI')
|
|
1031
|
+
hasData = true
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Sheet Mobile
|
|
1035
|
+
if (allData.mobile && allData.mobile.length > 0) {
|
|
1036
|
+
const mobileSheet = XLSX.utils.json_to_sheet(allData.mobile.map(mobile => ({
|
|
1037
|
+
'Teste': mobile.testName || 'N/A',
|
|
1038
|
+
'Ação': mobile.action || 'N/A',
|
|
1039
|
+
'Elemento': mobile.element || 'N/A',
|
|
1040
|
+
'Device': mobile.device || 'N/A',
|
|
1041
|
+
'Platform': mobile.platform || 'N/A',
|
|
1042
|
+
'Timestamp': new Date(mobile.timestamp).toLocaleString('pt-BR')
|
|
1043
|
+
})))
|
|
1044
|
+
XLSX.utils.book_append_sheet(workbook, mobileSheet, 'Mobile')
|
|
1045
|
+
hasData = true
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Verificar se há dados
|
|
1049
|
+
if (!hasData) {
|
|
1050
|
+
alert('⚠️ Nenhum dado disponível para exportar. Execute testes primeiro!')
|
|
1051
|
+
return
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Gerar arquivo
|
|
1055
|
+
XLSX.writeFile(workbook, 'autodocs-inteligente.xlsx')
|
|
1056
|
+
console.log('✅ XLSX gerado com sucesso!')
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
console.error('❌ Erro ao gerar XLSX:', error)
|
|
1059
|
+
alert('❌ Erro ao gerar XLSX: ' + error.message)
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// ✅ NOVA FUNÇÃO: Carregar e renderizar CNs e CTs do ct-execution-data.json
|
|
1064
|
+
async function loadCtData() {
|
|
1065
|
+
try {
|
|
1066
|
+
const response = await fetch('./ct-execution-data.json')
|
|
1067
|
+
if (!response.ok) {
|
|
1068
|
+
document.getElementById('cn-container').innerHTML = \`
|
|
1069
|
+
<div class="bg-yellow-900/20 border border-yellow-600 rounded p-4">
|
|
1070
|
+
<p class="text-yellow-400">⚠️ Nenhum arquivo <strong>ct-execution-data.json</strong> encontrado.</p>
|
|
1071
|
+
<p class="text-gray-400 text-sm mt-2">Execute testes primeiro para gerar os dados.</p>
|
|
1072
|
+
</div>
|
|
1073
|
+
\`
|
|
1074
|
+
return
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const ctData = await response.json()
|
|
1078
|
+
|
|
1079
|
+
// ✅ SUPORTAR ESTRUTURA NOVA: { tests: [{ cts: [...] }] }
|
|
1080
|
+
let ctArray = []
|
|
1081
|
+
if (ctData.tests && Array.isArray(ctData.tests)) {
|
|
1082
|
+
// Nova estrutura: extrair todos os CTs de todos os testes
|
|
1083
|
+
ctData.tests.forEach(test => {
|
|
1084
|
+
if (test.cts && Array.isArray(test.cts)) {
|
|
1085
|
+
ctArray.push(...test.cts.map(ct => ({
|
|
1086
|
+
...ct,
|
|
1087
|
+
cn: test.testName || ct.cn || 'Sem CN'
|
|
1088
|
+
})))
|
|
1089
|
+
}
|
|
1090
|
+
})
|
|
1091
|
+
} else {
|
|
1092
|
+
// Estrutura antiga (fallback): Object.values
|
|
1093
|
+
ctArray = Object.values(ctData)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (ctArray.length === 0) {
|
|
1097
|
+
document.getElementById('cn-container').innerHTML = \`
|
|
1098
|
+
<div class="bg-gray-800 rounded p-4">
|
|
1099
|
+
<p class="text-gray-400">📊 Nenhum CT executado ainda.</p>
|
|
1100
|
+
</div>
|
|
1101
|
+
\`
|
|
1102
|
+
return
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// Agrupar CTs por CN (Cenário de Negócio)
|
|
1106
|
+
const cnMap = {}
|
|
1107
|
+
ctArray.forEach(ct => {
|
|
1108
|
+
const cnName = ct.cn || 'Sem CN'
|
|
1109
|
+
if (!cnMap[cnName]) {
|
|
1110
|
+
cnMap[cnName] = {
|
|
1111
|
+
name: cnName,
|
|
1112
|
+
cts: [],
|
|
1113
|
+
totalPassed: 0,
|
|
1114
|
+
totalFailed: 0,
|
|
1115
|
+
totalSkipped: 0,
|
|
1116
|
+
totalRunning: 0,
|
|
1117
|
+
totalDuration: 0
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
cnMap[cnName].cts.push(ct)
|
|
1121
|
+
if (ct.status === 'passed') cnMap[cnName].totalPassed++
|
|
1122
|
+
if (ct.status === 'failed') cnMap[cnName].totalFailed++
|
|
1123
|
+
if (ct.status === 'skipped') cnMap[cnName].totalSkipped++
|
|
1124
|
+
if (ct.status === 'running') cnMap[cnName].totalRunning++
|
|
1125
|
+
cnMap[cnName].totalDuration += (ct.duration || 0)
|
|
1126
|
+
})
|
|
1127
|
+
|
|
1128
|
+
// Renderizar CNs
|
|
1129
|
+
const cnHtml = Object.values(cnMap).map((cn, cnIdx) => \`
|
|
1130
|
+
<div class="bg-slate-800 rounded-lg p-4 border-l-4 \${cn.totalFailed > 0 ? 'border-red-500' : cn.totalPassed === cn.cts.length ? 'border-green-500' : 'border-yellow-500'}">
|
|
1131
|
+
<div class="flex items-start justify-between mb-3">
|
|
1132
|
+
<div class="flex-1">
|
|
1133
|
+
<h4 class="text-lg font-bold text-cyan-400">\${cnIdx + 1}. \${cn.name}</h4>
|
|
1134
|
+
<div class="flex gap-3 mt-2 text-sm">
|
|
1135
|
+
<span class="text-green-400">✅ \${cn.totalPassed} Passed</span>
|
|
1136
|
+
<span class="text-red-400">❌ \${cn.totalFailed} Failed</span>
|
|
1137
|
+
<span class="text-yellow-400">⚠️ \${cn.totalSkipped} Skipped</span>
|
|
1138
|
+
\${cn.totalRunning > 0 ? \`<span class="text-blue-400">🔄 \${cn.totalRunning} Running</span>\` : ''}
|
|
1139
|
+
<span class="text-gray-400">⏱️ \${cn.totalDuration}ms total</span>
|
|
1140
|
+
</div>
|
|
1141
|
+
</div>
|
|
1142
|
+
<button onclick="toggleCn('cn-\${cnIdx}')" id="btn-cn-\${cnIdx}" class="btn text-xs" type="button">📋 Ver CTs (\${cn.cts.length})</button>
|
|
1143
|
+
</div>
|
|
1144
|
+
<div id="cn-\${cnIdx}" style="display: none;" class="space-y-2 mt-4">
|
|
1145
|
+
\${cn.cts.map((ct, ctIdx) => \`
|
|
1146
|
+
<div class="bg-slate-700 rounded p-3">
|
|
1147
|
+
<div class="flex items-start justify-between">
|
|
1148
|
+
<div class="flex-1">
|
|
1149
|
+
<div class="flex items-center gap-2">
|
|
1150
|
+
<span class="\${ct.status === 'passed' ? 'text-green-400' : ct.status === 'failed' ? 'text-red-400' : ct.status === 'skipped' ? 'text-yellow-400' : 'text-blue-400'} text-lg">
|
|
1151
|
+
\${ct.status === 'passed' ? '✅' : ct.status === 'failed' ? '❌' : ct.status === 'skipped' ? '⚠️' : '🔄'}
|
|
1152
|
+
</span>
|
|
1153
|
+
<span class="font-mono text-sm text-yellow-300">\${ct.id || 'CT' + (ctIdx + 1)}</span>
|
|
1154
|
+
<span class="text-gray-300">\${ct.name}</span>
|
|
1155
|
+
</div>
|
|
1156
|
+
<div class="flex items-center gap-3 mt-2 text-xs">
|
|
1157
|
+
<span class="\${ct.duration > 5000 ? 'text-red-400' : ct.duration > 2000 ? 'text-yellow-400' : 'text-green-400'}">
|
|
1158
|
+
⏱️ \${ct.duration || 0}ms
|
|
1159
|
+
</span>
|
|
1160
|
+
\${ct.statements && ct.statements.length > 0 ? \`<span class="text-cyan-400">📝 \${ct.statements.length} Statements</span>\` : ''}
|
|
1161
|
+
\${ct.executedActions && ct.executedActions.length > 0 ? \`<span class="text-purple-400">🎬 \${ct.executedActions.length} Actions</span>\` : ''}
|
|
1162
|
+
</div>
|
|
1163
|
+
</div>
|
|
1164
|
+
</div>
|
|
1165
|
+
</div>
|
|
1166
|
+
\`).join('')}
|
|
1167
|
+
</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
\`).join('')
|
|
1170
|
+
|
|
1171
|
+
document.getElementById('cn-container').innerHTML = cnHtml
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
console.error('Erro ao carregar ct-execution-data.json:', error)
|
|
1174
|
+
document.getElementById('cn-container').innerHTML = \`
|
|
1175
|
+
<div class="bg-red-900/20 border border-red-600 rounded p-4">
|
|
1176
|
+
<p class="text-red-400">❌ Erro ao carregar dados dos CTs.</p>
|
|
1177
|
+
<p class="text-gray-400 text-sm mt-2">\${error.message}</p>
|
|
1178
|
+
</div>
|
|
1179
|
+
\`
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function toggleCn(cnId) {
|
|
1184
|
+
const section = document.getElementById(cnId)
|
|
1185
|
+
const btn = document.getElementById('btn-' + cnId)
|
|
1186
|
+
if (section && btn) {
|
|
1187
|
+
if (section.style.display === 'none') {
|
|
1188
|
+
section.style.display = 'block'
|
|
1189
|
+
btn.innerHTML = btn.innerHTML.replace('Ver CTs', 'Ocultar CTs')
|
|
1190
|
+
} else {
|
|
1191
|
+
section.style.display = 'none'
|
|
1192
|
+
btn.innerHTML = btn.innerHTML.replace('Ocultar CTs', 'Ver CTs')
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Carregar CTs automaticamente ao abrir a página
|
|
1198
|
+
loadCtData()
|
|
1199
|
+
|
|
1200
|
+
// Load saved theme
|
|
1201
|
+
const savedTheme = localStorage.getItem('theme');
|
|
1202
|
+
if (savedTheme === 'light') {
|
|
1203
|
+
document.getElementById('toggle-theme').click()
|
|
1204
|
+
}
|
|
1205
|
+
</script>
|
|
1206
|
+
</body>
|
|
1207
|
+
</html>`;
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* 🤖 Detectar tipo de projeto automaticamente
|
|
1211
|
+
*/
|
|
1212
|
+
static detectProjectType(data) {
|
|
1213
|
+
const hasAPIs = data.apis && data.apis.length > 0;
|
|
1214
|
+
const hasSSH = data.ssh && data.ssh.length > 0;
|
|
1215
|
+
const hasDB = data.db && data.db.length > 0;
|
|
1216
|
+
const hasUI = data.ui && data.ui.length > 0;
|
|
1217
|
+
const hasMobile = data.mobile && data.mobile.length > 0;
|
|
1218
|
+
const components = [];
|
|
1219
|
+
if (hasAPIs)
|
|
1220
|
+
components.push('API');
|
|
1221
|
+
if (hasSSH)
|
|
1222
|
+
components.push('SSH');
|
|
1223
|
+
if (hasDB)
|
|
1224
|
+
components.push('Banco');
|
|
1225
|
+
if (hasUI)
|
|
1226
|
+
components.push('Frontend');
|
|
1227
|
+
if (hasMobile)
|
|
1228
|
+
components.push('Mobile');
|
|
1229
|
+
if (components.length === 1)
|
|
1230
|
+
return components[0];
|
|
1231
|
+
if (components.length > 1)
|
|
1232
|
+
return 'Mixed';
|
|
1233
|
+
return 'Unknown';
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* 🔧 Detectar tipo de query SQL
|
|
1237
|
+
*/
|
|
1238
|
+
static detectQueryType(query) {
|
|
1239
|
+
const upperQuery = query.trim().toUpperCase();
|
|
1240
|
+
if (upperQuery.startsWith('SELECT'))
|
|
1241
|
+
return 'SELECT';
|
|
1242
|
+
if (upperQuery.startsWith('INSERT'))
|
|
1243
|
+
return 'INSERT';
|
|
1244
|
+
if (upperQuery.startsWith('UPDATE'))
|
|
1245
|
+
return 'UPDATE';
|
|
1246
|
+
if (upperQuery.startsWith('DELETE'))
|
|
1247
|
+
return 'DELETE';
|
|
1248
|
+
if (upperQuery.startsWith('CREATE'))
|
|
1249
|
+
return 'DDL';
|
|
1250
|
+
if (upperQuery.startsWith('DROP'))
|
|
1251
|
+
return 'DDL';
|
|
1252
|
+
if (upperQuery.startsWith('ALTER'))
|
|
1253
|
+
return 'DDL';
|
|
1254
|
+
return 'OTHER';
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* 🔧 Verificar se há dados para gerar
|
|
1258
|
+
*/
|
|
1259
|
+
static hasDataToGenerate(data) {
|
|
1260
|
+
return (data.apis.length > 0 ||
|
|
1261
|
+
data.ssh.length > 0 ||
|
|
1262
|
+
data.db.length > 0 ||
|
|
1263
|
+
data.ui.length > 0 ||
|
|
1264
|
+
data.mobile.length > 0);
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* 📝 Obter nome do teste atual
|
|
1268
|
+
*/
|
|
1269
|
+
static getCurrentTestName() {
|
|
1270
|
+
try {
|
|
1271
|
+
// Definir tipos para as propriedades globais
|
|
1272
|
+
const globalAny = global;
|
|
1273
|
+
// Para Playwright Test Runner
|
|
1274
|
+
if (globalAny.testInfo?.title) {
|
|
1275
|
+
return globalAny.testInfo.title;
|
|
1276
|
+
}
|
|
1277
|
+
// Para Jest/Vitest
|
|
1278
|
+
if (globalAny.expect?.getState) {
|
|
1279
|
+
const state = globalAny.expect.getState();
|
|
1280
|
+
if (state?.currentTestName) {
|
|
1281
|
+
return state.currentTestName;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
// Para WebDriverIO
|
|
1285
|
+
if (globalAny.browser?.testName) {
|
|
1286
|
+
return globalAny.browser.testName;
|
|
1287
|
+
}
|
|
1288
|
+
// Fallback genérico
|
|
1289
|
+
const stack = new Error('test-name-detection').stack;
|
|
1290
|
+
if (stack) {
|
|
1291
|
+
const testMatch = stack.match(/at.*\/(.*\.(?:test|spec)\.(?:js|ts))/);
|
|
1292
|
+
if (testMatch?.[1]) {
|
|
1293
|
+
return testMatch[1];
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return 'unknown-test';
|
|
1297
|
+
}
|
|
1298
|
+
catch {
|
|
1299
|
+
return 'unknown-test';
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* 📊 Gerar seções agrupadas inteligentemente
|
|
1304
|
+
*/
|
|
1305
|
+
static generateGroupedSections(data) {
|
|
1306
|
+
const sections = [];
|
|
1307
|
+
// Agrupar por prioridade e relevância
|
|
1308
|
+
const groupOrder = [
|
|
1309
|
+
{
|
|
1310
|
+
key: 'apis',
|
|
1311
|
+
data: data.apis || [],
|
|
1312
|
+
title: '🌐 APIs & Endpoints',
|
|
1313
|
+
generator: 'generateAPISection',
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
key: 'ssh',
|
|
1317
|
+
data: data.ssh || [],
|
|
1318
|
+
title: '🖥️ Comandos SSH',
|
|
1319
|
+
generator: 'generateSSHSection',
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
key: 'db',
|
|
1323
|
+
data: data.db || [],
|
|
1324
|
+
title: '🗄️ Consultas Database',
|
|
1325
|
+
generator: 'generateDBSection',
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
key: 'ui',
|
|
1329
|
+
data: data.ui || [],
|
|
1330
|
+
title: '🎨 Interface de Usuário',
|
|
1331
|
+
generator: 'generateUISection',
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
key: 'mobile',
|
|
1335
|
+
data: data.mobile || [],
|
|
1336
|
+
title: '📱 Ações Mobile',
|
|
1337
|
+
generator: 'generateMobileSection',
|
|
1338
|
+
},
|
|
1339
|
+
];
|
|
1340
|
+
let hasAnyData = false;
|
|
1341
|
+
const groupedContent = groupOrder
|
|
1342
|
+
.map((group) => {
|
|
1343
|
+
if (group.data.length === 0)
|
|
1344
|
+
return '';
|
|
1345
|
+
hasAnyData = true;
|
|
1346
|
+
// Determinar estatísticas do grupo
|
|
1347
|
+
const successCount = group.data.filter((item) => item.success !== false).length;
|
|
1348
|
+
const successRate = ((successCount / group.data.length) * 100).toFixed(1);
|
|
1349
|
+
// Agrupar por teste se disponível
|
|
1350
|
+
const byTest = group.data.reduce((acc, item) => {
|
|
1351
|
+
const testName = item.testName || 'Teste não identificado';
|
|
1352
|
+
if (!acc[testName])
|
|
1353
|
+
acc[testName] = [];
|
|
1354
|
+
acc[testName].push(item);
|
|
1355
|
+
return acc;
|
|
1356
|
+
}, {});
|
|
1357
|
+
const testCount = Object.keys(byTest).length;
|
|
1358
|
+
return `
|
|
1359
|
+
<div class="card mb-8" style="border-left: 4px solid ${AutoDocs.getGroupColor(group.key)}">
|
|
1360
|
+
<div class="flex items-center justify-between mb-6">
|
|
1361
|
+
<div>
|
|
1362
|
+
<h2 class="text-2xl font-bold mb-2" style="color: ${AutoDocs.getGroupColor(group.key)}">
|
|
1363
|
+
${group.title}
|
|
1364
|
+
</h2>
|
|
1365
|
+
<div class="flex items-center gap-4 text-sm text-gray-400">
|
|
1366
|
+
<span>📊 ${group.data.length} itens</span>
|
|
1367
|
+
<span>🧪 ${testCount} teste${testCount > 1 ? 's' : ''}</span>
|
|
1368
|
+
<span class="flex items-center gap-1">
|
|
1369
|
+
<span class="${successRate === '100.0' ? 'text-green-400' : Number.parseFloat(successRate) >= 80 ? 'text-yellow-400' : 'text-red-400'}">
|
|
1370
|
+
${successRate === '100.0' ? '✅' : Number.parseFloat(successRate) >= 80 ? '⚠️' : '❌'} ${successRate}% sucesso
|
|
1371
|
+
</span>
|
|
1372
|
+
</span>
|
|
1373
|
+
</div>
|
|
1374
|
+
</div>
|
|
1375
|
+
<button onclick="toggleSection('${group.key}')" class="btn text-sm">
|
|
1376
|
+
<span id="btn-${group.key}">📋 Detalhes</span>
|
|
1377
|
+
</button>
|
|
1378
|
+
</div>
|
|
1379
|
+
|
|
1380
|
+
<!-- Resumo por Teste -->
|
|
1381
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
|
|
1382
|
+
${Object.entries(byTest)
|
|
1383
|
+
.map(([testName, items]) => {
|
|
1384
|
+
const totalDuration = items.reduce((sum, item) => sum + (item.duration || 0), 0);
|
|
1385
|
+
return `
|
|
1386
|
+
<div class="bg-gray-800 rounded-lg p-4">
|
|
1387
|
+
<h4 class="font-semibold text-blue-400 mb-2 truncate" title="${testName}">
|
|
1388
|
+
🧪 ${testName}
|
|
1389
|
+
</h4>
|
|
1390
|
+
<div class="text-sm text-gray-300">
|
|
1391
|
+
<div class="flex justify-between mb-1">
|
|
1392
|
+
<span>Total:</span>
|
|
1393
|
+
<span class="font-mono">${items.length}</span>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="flex justify-between mb-1">
|
|
1396
|
+
<span>Sucesso:</span>
|
|
1397
|
+
<span class="font-mono text-green-400">${items.filter((i) => i.success !== false).length}</span>
|
|
1398
|
+
</div>
|
|
1399
|
+
<div class="flex justify-between mb-1">
|
|
1400
|
+
<span>Taxa:</span>
|
|
1401
|
+
<span class="font-mono ${((items.filter((i) => i.success !== false).length / items.length) * 100) >= 80 ? 'text-green-400' : 'text-yellow-400'}">
|
|
1402
|
+
${((items.filter((i) => i.success !== false).length / items.length) * 100).toFixed(1)}%
|
|
1403
|
+
</span>
|
|
1404
|
+
</div>
|
|
1405
|
+
<div class="flex justify-between">
|
|
1406
|
+
<span>⏱️ Duração:</span>
|
|
1407
|
+
<span class="font-mono text-blue-400">${(totalDuration / 1000).toFixed(2)}s</span>
|
|
1408
|
+
</div>
|
|
1409
|
+
</div>
|
|
1410
|
+
</div>
|
|
1411
|
+
`;
|
|
1412
|
+
})
|
|
1413
|
+
.join('')}
|
|
1414
|
+
</div>
|
|
1415
|
+
|
|
1416
|
+
<!-- Seção detalhada (colapsável) -->
|
|
1417
|
+
<div id="section-${group.key}" style="display: none;">
|
|
1418
|
+
${AutoDocs[group.generator](group.data)}
|
|
1419
|
+
</div>
|
|
1420
|
+
</div>
|
|
1421
|
+
`;
|
|
1422
|
+
})
|
|
1423
|
+
.filter((section) => section !== '');
|
|
1424
|
+
if (!hasAnyData) {
|
|
1425
|
+
return `
|
|
1426
|
+
<div class="card text-center py-12">
|
|
1427
|
+
<div class="text-6xl mb-4">📄</div>
|
|
1428
|
+
<h2 class="text-2xl font-bold text-gray-400 mb-2">Nenhum dado encontrado</h2>
|
|
1429
|
+
<p class="text-gray-500">Execute alguns testes para ver a documentação automática aparecer aqui!</p>
|
|
1430
|
+
</div>
|
|
1431
|
+
`;
|
|
1432
|
+
}
|
|
1433
|
+
return groupedContent.join('');
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* 🎨 Obter cor do grupo
|
|
1437
|
+
*/
|
|
1438
|
+
static getGroupColor(groupKey) {
|
|
1439
|
+
const colors = {
|
|
1440
|
+
apis: '#10b981', // verde
|
|
1441
|
+
ssh: '#3b82f6', // azul
|
|
1442
|
+
db: '#8b5cf6', // roxo
|
|
1443
|
+
ui: '#f59e0b', // laranja
|
|
1444
|
+
mobile: '#ec4899', // rosa
|
|
1445
|
+
};
|
|
1446
|
+
return colors[groupKey] || '#6b7280';
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* 📊 Gerar overview do projeto
|
|
1450
|
+
*/
|
|
1451
|
+
static generateProjectOverview(projectType, data, detectedCategories) {
|
|
1452
|
+
const typeIcons = {
|
|
1453
|
+
API: '🌐',
|
|
1454
|
+
SSH: '🖥️',
|
|
1455
|
+
Banco: '🗄️',
|
|
1456
|
+
Frontend: '🎨',
|
|
1457
|
+
Mobile: '📱',
|
|
1458
|
+
Mixed: '🔧',
|
|
1459
|
+
Unknown: '❓',
|
|
1460
|
+
};
|
|
1461
|
+
const categoryCount = Object.keys(detectedCategories).length;
|
|
1462
|
+
const apiCategories = Object.entries(detectedCategories)
|
|
1463
|
+
.filter(([key]) => key !== 'outras')
|
|
1464
|
+
.map(([key, data]) => `${data.icon} ${key.charAt(0).toUpperCase() + key.slice(1)}`)
|
|
1465
|
+
.join(', ');
|
|
1466
|
+
return `
|
|
1467
|
+
<div class="card mb-6">
|
|
1468
|
+
<h2 class="text-2xl font-bold text-blue-400 mb-4 flex items-center gap-3">
|
|
1469
|
+
${typeIcons[projectType] || '🔧'} Project Overview - ${projectType}
|
|
1470
|
+
</h2>
|
|
1471
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
1472
|
+
<div>
|
|
1473
|
+
<h3 class="text-lg font-semibold text-green-400 mb-3">📋 Componentes Detectados</h3>
|
|
1474
|
+
<div class="space-y-2">
|
|
1475
|
+
${data.apis.length > 0 ? `<div class="flex items-center gap-2"><span class="text-green-400">✅</span> APIs (${data.apis.length})</div>` : ''}
|
|
1476
|
+
${data.ssh.length > 0 ? `<div class="flex items-center gap-2"><span class="text-green-400">✅</span> SSH (${data.ssh.length})</div>` : ''}
|
|
1477
|
+
${data.db.length > 0 ? `<div class="flex items-center gap-2"><span class="text-green-400">✅</span> Banco (${data.db.length})</div>` : ''}
|
|
1478
|
+
${data.ui.length > 0 ? `<div class="flex items-center gap-2"><span class="text-green-400">✅</span> Frontend (${data.ui.length})</div>` : ''}
|
|
1479
|
+
${data.mobile.length > 0 ? `<div class="flex items-center gap-2"><span class="text-green-400">✅</span> Mobile (${data.mobile.length})</div>` : ''}
|
|
1480
|
+
</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
${data.apis.length > 0
|
|
1483
|
+
? `
|
|
1484
|
+
<div>
|
|
1485
|
+
<h3 class="text-lg font-semibold text-purple-400 mb-3">🤖 Categorias API Detectadas</h3>
|
|
1486
|
+
<p class="text-slate-400 text-sm mb-2">Sistema inteligente detectou ${categoryCount} categorias:</p>
|
|
1487
|
+
<div class="text-sm text-slate-300">
|
|
1488
|
+
${apiCategories || 'Nenhuma categoria específica detectada'}
|
|
1489
|
+
</div>
|
|
1490
|
+
</div>
|
|
1491
|
+
`
|
|
1492
|
+
: ''}
|
|
1493
|
+
</div>
|
|
1494
|
+
</div>
|
|
1495
|
+
`;
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* 🧭 Gerar menu de navegação
|
|
1499
|
+
*/
|
|
1500
|
+
static generateNavigationMenu(data) {
|
|
1501
|
+
const sections = [];
|
|
1502
|
+
if (data.apis.length > 0)
|
|
1503
|
+
sections.push({ id: 'apis', name: '🌐 APIs', count: data.apis.length });
|
|
1504
|
+
if (data.ssh.length > 0)
|
|
1505
|
+
sections.push({ id: 'ssh', name: '🖥️ SSH', count: data.ssh.length });
|
|
1506
|
+
if (data.db.length > 0)
|
|
1507
|
+
sections.push({ id: 'database', name: '🗄️ Banco', count: data.db.length });
|
|
1508
|
+
if (data.ui.length > 0)
|
|
1509
|
+
sections.push({
|
|
1510
|
+
id: 'frontend',
|
|
1511
|
+
name: '🎨 Frontend',
|
|
1512
|
+
count: data.ui.length,
|
|
1513
|
+
});
|
|
1514
|
+
if (data.mobile.length > 0)
|
|
1515
|
+
sections.push({
|
|
1516
|
+
id: 'mobile',
|
|
1517
|
+
name: '📱 Mobile',
|
|
1518
|
+
count: data.mobile.length,
|
|
1519
|
+
});
|
|
1520
|
+
if (sections.length === 0)
|
|
1521
|
+
return '';
|
|
1522
|
+
return `
|
|
1523
|
+
<div class="card mb-6">
|
|
1524
|
+
<h3 class="text-lg font-semibold text-yellow-400 mb-4">🧭 Navegação Rápida</h3>
|
|
1525
|
+
<div class="flex flex-wrap gap-3">
|
|
1526
|
+
${sections
|
|
1527
|
+
.map((section) => `
|
|
1528
|
+
<button onclick="jumpToSection('${section.id}')" class="btn bg-slate-700 hover:bg-slate-600">
|
|
1529
|
+
${section.name} (${section.count})
|
|
1530
|
+
</button>
|
|
1531
|
+
`)
|
|
1532
|
+
.join('')}
|
|
1533
|
+
</div>
|
|
1534
|
+
</div>
|
|
1535
|
+
`;
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* 📊 Gerar estatísticas gerais
|
|
1539
|
+
*/
|
|
1540
|
+
static generateOverallStats(data) {
|
|
1541
|
+
const totalItems = data.apis.length +
|
|
1542
|
+
data.ssh.length +
|
|
1543
|
+
data.db.length +
|
|
1544
|
+
data.ui.length +
|
|
1545
|
+
data.mobile.length;
|
|
1546
|
+
const successfulAPIs = data.apis.filter((api) => api.success).length;
|
|
1547
|
+
const failedAPIs = data.apis.length - successfulAPIs;
|
|
1548
|
+
const successRate = data.apis.length > 0
|
|
1549
|
+
? Math.round((successfulAPIs / data.apis.length) * 100)
|
|
1550
|
+
: 0;
|
|
1551
|
+
return `
|
|
1552
|
+
<div class="card mb-8">
|
|
1553
|
+
<h3 class="text-2xl font-bold text-green-400 mb-6">📊 Estatísticas Gerais do Projeto</h3>
|
|
1554
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
1555
|
+
<div class="bg-blue-800 rounded-lg p-4 text-center">
|
|
1556
|
+
<div class="text-3xl font-bold text-white">${totalItems}</div>
|
|
1557
|
+
<div class="text-blue-200 text-sm">Total de Testes</div>
|
|
1558
|
+
</div>
|
|
1559
|
+
<div class="bg-green-800 rounded-lg p-4 text-center">
|
|
1560
|
+
<div class="text-3xl font-bold text-white">${successfulAPIs}</div>
|
|
1561
|
+
<div class="text-green-200 text-sm">APIs Sucesso</div>
|
|
1562
|
+
</div>
|
|
1563
|
+
<div class="bg-red-800 rounded-lg p-4 text-center">
|
|
1564
|
+
<div class="text-3xl font-bold text-white">${failedAPIs}</div>
|
|
1565
|
+
<div class="text-red-200 text-sm">APIs Falha</div>
|
|
1566
|
+
</div>
|
|
1567
|
+
<div class="bg-purple-800 rounded-lg p-4 text-center">
|
|
1568
|
+
<div class="text-3xl font-bold text-white">${successRate}%</div>
|
|
1569
|
+
<div class="text-purple-200 text-sm">Taxa de Sucesso</div>
|
|
1570
|
+
</div>
|
|
1571
|
+
</div>
|
|
1572
|
+
</div>
|
|
1573
|
+
`;
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8">
|
|
1577
|
+
<div class="card bg-gradient-to-br from-green-600 to-green-700">
|
|
1578
|
+
<h3 class="text-3xl font-bold text-white">${data.apis.length}</h3>
|
|
1579
|
+
<p class="text-green-100">🌐 APIs Documentadas</p>
|
|
1580
|
+
</div>
|
|
1581
|
+
<div class="card bg-gradient-to-br from-blue-600 to-blue-700">
|
|
1582
|
+
<h3 class="text-3xl font-bold text-white">${data.ssh.length}</h3>
|
|
1583
|
+
<p class="text-blue-100">🖥️ Comandos SSH</p>
|
|
1584
|
+
</div>
|
|
1585
|
+
<div class="card bg-gradient-to-br from-purple-600 to-purple-700">
|
|
1586
|
+
<h3 class="text-3xl font-bold text-white">${data.db.length}</h3>
|
|
1587
|
+
<p class="text-purple-100">🗄️ Queries Database</p>
|
|
1588
|
+
</div>
|
|
1589
|
+
<div class="card bg-gradient-to-br from-orange-600 to-orange-700">
|
|
1590
|
+
<h3 class="text-3xl font-bold text-white">${data.ui.length}</h3>
|
|
1591
|
+
<p class="text-orange-100">🎨 Ações UI</p>
|
|
1592
|
+
</div>
|
|
1593
|
+
<div class="card bg-gradient-to-br from-pink-600 to-pink-700">
|
|
1594
|
+
<h3 class="text-3xl font-bold text-white">${data.mobile.length}</h3>
|
|
1595
|
+
<p class="text-pink-100">📱 Ações Mobile</p>
|
|
1596
|
+
</div>
|
|
1597
|
+
</div>
|
|
1598
|
+
|
|
1599
|
+
${AutoDocs.generateAPISection(data.apis)}
|
|
1600
|
+
${AutoDocs.generateSSHSection(data.ssh)}
|
|
1601
|
+
${AutoDocs.generateDBSection(data.db)}
|
|
1602
|
+
${AutoDocs.generateUISection(data.ui)}
|
|
1603
|
+
${AutoDocs.generateMobileSection(data.mobile)}
|
|
1604
|
+
|
|
1605
|
+
<!-- Footer -->
|
|
1606
|
+
<div class="footer-bar">
|
|
1607
|
+
<p>Documentação gerada automaticamente pelo AutoDocs (${projectVersion}) em ${new Date().toLocaleString('pt-BR')}</p>
|
|
1608
|
+
</div>
|
|
1609
|
+
</div>
|
|
1610
|
+
|
|
1611
|
+
<script>
|
|
1612
|
+
function toggleTheme() {
|
|
1613
|
+
document.body.classList.toggle('light-mode');
|
|
1614
|
+
localStorage.setItem('theme', document.body.classList.contains('light-mode') ? 'light' : 'dark');
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function exportData(format) {
|
|
1618
|
+
const data = ${JSON.stringify(data)};
|
|
1619
|
+
|
|
1620
|
+
if (format === 'pdf') {
|
|
1621
|
+
const element = document.querySelector('.container');
|
|
1622
|
+
|
|
1623
|
+
html2canvas(element, {
|
|
1624
|
+
scale: 2,
|
|
1625
|
+
useCORS: false,
|
|
1626
|
+
allowTaint: true,
|
|
1627
|
+
backgroundColor: '#fff'
|
|
1628
|
+
}).then(canvas => {
|
|
1629
|
+
const { jsPDF } = jspdf;
|
|
1630
|
+
const pdf = new jsPDF({
|
|
1631
|
+
orientation: 'portrait',
|
|
1632
|
+
unit: 'in',
|
|
1633
|
+
format: 'letter'
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
const imgData = canvas.toDataURL('image/jpeg', 0.98);
|
|
1637
|
+
const imgWidth = 8.5;
|
|
1638
|
+
const pageHeight = 11;
|
|
1639
|
+
const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
1640
|
+
let heightLeft = imgHeight;
|
|
1641
|
+
let position = 0;
|
|
1642
|
+
|
|
1643
|
+
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
|
|
1644
|
+
heightLeft -= pageHeight;
|
|
1645
|
+
|
|
1646
|
+
while (heightLeft > 0) {
|
|
1647
|
+
position = heightLeft - imgHeight;
|
|
1648
|
+
pdf.addPage();
|
|
1649
|
+
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
|
|
1650
|
+
heightLeft -= pageHeight;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
pdf.save('${AutoDocs.config.projectName}_autodocs.pdf');
|
|
1654
|
+
});
|
|
1655
|
+
} else if (format === 'json') {
|
|
1656
|
+
const dataStr = JSON.stringify(data, null, 2);
|
|
1657
|
+
const dataBlob = new Blob([dataStr], {type: 'application/json'});
|
|
1658
|
+
const url = URL.createObjectURL(dataBlob);
|
|
1659
|
+
const link = document.createElement('a');
|
|
1660
|
+
link.href = url;
|
|
1661
|
+
link.download = '${AutoDocs.config.projectName}_autodocs.json';
|
|
1662
|
+
link.click();
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Load saved theme
|
|
1667
|
+
const savedTheme = localStorage.getItem('theme');
|
|
1668
|
+
if (savedTheme === 'light') {
|
|
1669
|
+
document.body.classList.add('light-mode');
|
|
1670
|
+
}
|
|
1671
|
+
</script>
|
|
1672
|
+
</body>
|
|
1673
|
+
</html>`
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/**
|
|
1677
|
+
* 📊 Gerar visualização por CTs (Cenários de Teste)
|
|
1678
|
+
* CORRIGIDO: Usar a mesma estratégia do UnifiedReportManager
|
|
1679
|
+
*/
|
|
1680
|
+
static async generateCTViewHTMLContent(data) {
|
|
1681
|
+
// 🔧 CORREÇÃO: Usar a mesma abordagem do UnifiedReportManager
|
|
1682
|
+
Logger.info('📊 HubDocs: Coletando dados dos CTs...');
|
|
1683
|
+
// Busca direta do StatementTracker (mesma estratégia do UnifiedReportManager)
|
|
1684
|
+
const allExecutions = StatementTracker.getExecutions();
|
|
1685
|
+
const totalTests = Array.from(allExecutions.values()).length;
|
|
1686
|
+
Logger.info(`📊 HubDocs: Total de testes encontrados: ${totalTests}`);
|
|
1687
|
+
let totalCTs = 0;
|
|
1688
|
+
let passedCTs = 0;
|
|
1689
|
+
let failedCTs = 0;
|
|
1690
|
+
let skippedCTs = 0;
|
|
1691
|
+
let runningCTs = 0;
|
|
1692
|
+
const ctsByTest = new Map();
|
|
1693
|
+
if (totalTests > 0) {
|
|
1694
|
+
Logger.success('✅ HubDocs: Processando testes e seus CTs...');
|
|
1695
|
+
// ✅ CORREÇÃO: Cada execution tem um array de CTs dentro
|
|
1696
|
+
Array.from(allExecutions.values()).forEach((testExecution) => {
|
|
1697
|
+
const testName = testExecution.testName || 'Teste Geral';
|
|
1698
|
+
const cts = testExecution.cts || [];
|
|
1699
|
+
Logger.info(`📋 HubDocs: Teste "${testName}" tem ${cts.length} CTs`);
|
|
1700
|
+
if (!ctsByTest.has(testName)) {
|
|
1701
|
+
ctsByTest.set(testName, []);
|
|
1702
|
+
}
|
|
1703
|
+
// Processar cada CT do teste
|
|
1704
|
+
cts.forEach((ct, ctIndex) => {
|
|
1705
|
+
const ctData = {
|
|
1706
|
+
ctId: `CT${(totalCTs + 1).toString().padStart(3, '0')}`,
|
|
1707
|
+
ctName: ct.name || ct.ctName || 'Unknown CT',
|
|
1708
|
+
ctOrder: ctIndex + 1,
|
|
1709
|
+
status: ct.status,
|
|
1710
|
+
duration: ct.duration || 0,
|
|
1711
|
+
timestamp: ct.startTime
|
|
1712
|
+
? new Date(ct.startTime).toISOString()
|
|
1713
|
+
: new Date().toISOString(),
|
|
1714
|
+
error: ct.error,
|
|
1715
|
+
className: ct.name?.split('.')[0] || 'Unknown',
|
|
1716
|
+
methodName: ct.name?.split('.')[1] || ct.name || 'unknown',
|
|
1717
|
+
statements: ct.statements || [], // ✅ NOVO: Incluir Statements
|
|
1718
|
+
executedActions: ct.executedActions || [], // ✅ NOVO: Incluir Actions do CT
|
|
1719
|
+
logs: ct.logs || [], // ✅ NOVO: Incluir Logs do CT
|
|
1720
|
+
};
|
|
1721
|
+
ctsByTest.get(testName).push(ctData);
|
|
1722
|
+
totalCTs++;
|
|
1723
|
+
// ✅ CORRIGIDO: Tratar "running" como "failed" para contadores
|
|
1724
|
+
const finalStatus = ct.status === 'running' ? 'failed' : ct.status;
|
|
1725
|
+
switch (finalStatus) {
|
|
1726
|
+
case 'passed':
|
|
1727
|
+
passedCTs++;
|
|
1728
|
+
break;
|
|
1729
|
+
case 'failed':
|
|
1730
|
+
failedCTs++;
|
|
1731
|
+
break;
|
|
1732
|
+
case 'skipped':
|
|
1733
|
+
skippedCTs++;
|
|
1734
|
+
break;
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
});
|
|
1738
|
+
Logger.success(`✅ HubDocs CTs: Total=${totalCTs}, Passou=${passedCTs}, Falhou=${failedCTs}, Skipped=${skippedCTs}`);
|
|
1739
|
+
}
|
|
1740
|
+
else {
|
|
1741
|
+
Logger.warning('⚠️ HubDocs: Nenhum teste encontrado no StatementTracker');
|
|
1742
|
+
}
|
|
1743
|
+
const timestamp = new Date().toLocaleString('pt-BR');
|
|
1744
|
+
return `<!DOCTYPE html>
|
|
1745
|
+
<html lang="pt-BR">
|
|
1746
|
+
<head>
|
|
1747
|
+
<meta charset="UTF-8">
|
|
1748
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1749
|
+
<title>📋 ${AutoDocs.config.projectName} - Visualização por CTs</title>
|
|
1750
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
1751
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
1752
|
+
<style>
|
|
1753
|
+
body { font-family: 'Inter', sans-serif; background-color: #0f172a; color: #f1f5f9; min-height: 100vh; }
|
|
1754
|
+
.light-mode { background-color: #f9fafb !important; color: #111827 !important; }
|
|
1755
|
+
.card { background: #1e293b; border-radius: 1rem; padding: 1.5rem; box-shadow: 0 2px 8px #0002; color: #f1f5f9; }
|
|
1756
|
+
.light-mode .card { background: #fff; color: #111827; border: 1px solid #e5e7eb; }
|
|
1757
|
+
.btn { background: #334155; color: #fff; border: none; border-radius: 8px; padding: 0.5rem 1.2rem; font-weight: 600; cursor: pointer; margin: 0.25rem; transition: background 0.2s; }
|
|
1758
|
+
.btn:hover { background: #475569; }
|
|
1759
|
+
.light-mode .btn { background: #374151; color: #fff; }
|
|
1760
|
+
.status-pass { color: #22c55e; font-weight: 700; }
|
|
1761
|
+
.status-fail { color: #ef4444; font-weight: 700; }
|
|
1762
|
+
.status-skip { color: #fbbf24; font-weight: 700; }
|
|
1763
|
+
.status-running { color: #8b5cf6; font-weight: 700; }
|
|
1764
|
+
.ct-card { background: #1e293b; border-radius: 0.5rem; padding: 1rem; margin: 0.5rem 0; border-left: 4px solid #3b82f6; }
|
|
1765
|
+
.ct-card.passed { border-left-color: #22c55e; }
|
|
1766
|
+
.ct-card.failed { border-left-color: #ef4444; }
|
|
1767
|
+
.ct-card.skipped { border-left-color: #fbbf24; }
|
|
1768
|
+
.ct-card.running { border-left-color: #8b5cf6; }
|
|
1769
|
+
.light-mode .ct-card { background: #f8fafc; color: #111827; }
|
|
1770
|
+
.test-section { background: #0f172a; border-radius: 1rem; padding: 1.5rem; margin: 1rem 0; border: 1px solid #374151; }
|
|
1771
|
+
.light-mode .test-section { background: #ffffff; border-color: #e5e7eb; color: #111827; }
|
|
1772
|
+
.header-bar { background: #1e293b; padding: 1rem 2rem; border-radius: 1rem 1rem 0 0; margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: space-between; }
|
|
1773
|
+
.header-bar .header-title { font-size: 2rem; font-weight: 700; color: #fbbf24; }
|
|
1774
|
+
.light-mode .header-bar { background: #f3f4f6; border: 1px solid #e5e7eb; }
|
|
1775
|
+
.light-mode .header-title { color: #d97706; }
|
|
1776
|
+
.btn-sm { padding: 0.25rem 0.75rem; font-size: 0.75rem; }
|
|
1777
|
+
.bg-blue-600 { background-color: #2563eb; }
|
|
1778
|
+
.bg-blue-600:hover { background-color: #1d4ed8; }
|
|
1779
|
+
.hidden { display: none; }
|
|
1780
|
+
.overflow-x-auto { overflow-x: auto; }
|
|
1781
|
+
.break-all { word-break: break-all; }
|
|
1782
|
+
.whitespace-pre-wrap { white-space: pre-wrap; }
|
|
1783
|
+
</style>
|
|
1784
|
+
</head>
|
|
1785
|
+
<body id="body-root">
|
|
1786
|
+
<div class="header-bar">
|
|
1787
|
+
<div class="header-title">📋 Visualização por CTs</div>
|
|
1788
|
+
<div class="flex gap-2">
|
|
1789
|
+
<button onclick="window.location.href='index.html'" class="btn">📚 Visão Tradicional</button>
|
|
1790
|
+
<button id="toggle-theme" class="btn">🌙 Tema</button>
|
|
1791
|
+
</div>
|
|
1792
|
+
</div>
|
|
1793
|
+
|
|
1794
|
+
<div class="max-w-7xl mx-auto p-6">
|
|
1795
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
|
1796
|
+
<div class="card flex flex-col items-center">
|
|
1797
|
+
<span class="text-2xl font-bold">${totalCTs}</span>
|
|
1798
|
+
<span class="text-slate-400">Total CTs</span>
|
|
1799
|
+
</div>
|
|
1800
|
+
<div class="card flex flex-col items-center">
|
|
1801
|
+
<span class="text-2xl font-bold text-green-400">${passedCTs}</span>
|
|
1802
|
+
<span class="text-green-400">Passou</span>
|
|
1803
|
+
</div>
|
|
1804
|
+
<div class="card flex flex-col items-center">
|
|
1805
|
+
<span class="text-2xl font-bold text-red-400">${failedCTs}</span>
|
|
1806
|
+
<span class="text-red-400">Falhou</span>
|
|
1807
|
+
</div>
|
|
1808
|
+
<div class="card flex flex-col items-center">
|
|
1809
|
+
<span class="text-2xl font-bold text-yellow-400">${skippedCTs}</span>
|
|
1810
|
+
<span class="text-yellow-400">Pulado</span>
|
|
1811
|
+
</div>
|
|
1812
|
+
</div>
|
|
1813
|
+
|
|
1814
|
+
${Array.from(ctsByTest.entries())
|
|
1815
|
+
.map(([testName, ctList]) => `
|
|
1816
|
+
<div class="test-section">
|
|
1817
|
+
<h2 class="text-2xl font-bold mb-4 text-blue-400">
|
|
1818
|
+
🧪 ${testName}
|
|
1819
|
+
<span class="text-sm font-normal text-gray-400">
|
|
1820
|
+
(${ctList.filter((ct) => ct.status === 'passed').length}/${ctList.length} CTs - ${(ctList.reduce((sum, ct) => sum + (ct.duration || 0), 0) / 1000).toFixed(2)}s)
|
|
1821
|
+
</span>
|
|
1822
|
+
</h2>
|
|
1823
|
+
|
|
1824
|
+
<div class="grid grid-cols-1 gap-3">
|
|
1825
|
+
${ctList
|
|
1826
|
+
.map((ct, index) => `
|
|
1827
|
+
<div class="ct-card ${ct.status}">
|
|
1828
|
+
<div class="flex justify-between items-start">
|
|
1829
|
+
<div class="flex-1">
|
|
1830
|
+
<div class="font-mono text-sm text-blue-300">CT #${ct.ctOrder || index + 1}</div>
|
|
1831
|
+
<div class="font-semibold text-lg mb-2">${AutoDocs.extractCleanCTName(ct.ctName)}</div>
|
|
1832
|
+
<div class="text-sm text-gray-300">
|
|
1833
|
+
<span class="mr-4">📦 ${ct.className}</span>
|
|
1834
|
+
<span>⏱️ ${((ct.duration || 0) / 1000).toFixed(2)}s</span>
|
|
1835
|
+
</div>
|
|
1836
|
+
|
|
1837
|
+
${ct.statements && ct.statements.length > 0
|
|
1838
|
+
? `
|
|
1839
|
+
<div class="mt-3 p-3 bg-slate-800 rounded">
|
|
1840
|
+
<div class="font-semibold text-sm mb-2 text-blue-300">📝 Statements (${ct.statements.length}):</div>
|
|
1841
|
+
${ct.statements
|
|
1842
|
+
.map((stmt, stmtIdx) => `
|
|
1843
|
+
<div class="ml-4 text-xs text-slate-300 py-1 border-l-2 border-slate-600 pl-2">
|
|
1844
|
+
${stmtIdx + 1}. ${stmt.className || ''}.${stmt.methodName || stmt.name || 'unknown'}
|
|
1845
|
+
<span class="ml-2 ${stmt.status === 'passed' ? 'text-green-400' : stmt.status === 'failed' ? 'text-red-400' : 'text-yellow-400'}">
|
|
1846
|
+
(${stmt.status === 'passed' ? '✅' : stmt.status === 'failed' ? '❌' : '⏳'} ${stmt.duration ? stmt.duration + 'ms' : '0ms'})
|
|
1847
|
+
</span>
|
|
1848
|
+
${stmt.error ? `<div class="text-red-400 text-xs mt-1">❌ ${AutoDocs.escapeHtml(stmt.error)}</div>` : ''}
|
|
1849
|
+
</div>
|
|
1850
|
+
`)
|
|
1851
|
+
.join('')}
|
|
1852
|
+
</div>
|
|
1853
|
+
`
|
|
1854
|
+
: ''}
|
|
1855
|
+
|
|
1856
|
+
${ct.executedActions &&
|
|
1857
|
+
ct.executedActions.length > 0
|
|
1858
|
+
? `
|
|
1859
|
+
<div class="mt-3 p-3 bg-slate-800 rounded">
|
|
1860
|
+
<div class="font-semibold text-sm mb-2 text-purple-300">🎬 Actions (${ct.executedActions.length}):</div>
|
|
1861
|
+
${ct.executedActions
|
|
1862
|
+
.map((action, actIdx) => `
|
|
1863
|
+
<div class="ml-4 text-xs text-slate-300 py-1">
|
|
1864
|
+
${actIdx + 1}. [${action.type}] ${action.description}
|
|
1865
|
+
<span class="ml-2 ${action.success ? 'text-green-400' : 'text-red-400'}">
|
|
1866
|
+
(${action.success ? '✅' : '❌'} ${action.duration ? action.duration + 'ms' : ''})
|
|
1867
|
+
</span>
|
|
1868
|
+
</div>
|
|
1869
|
+
`)
|
|
1870
|
+
.join('')}
|
|
1871
|
+
</div>
|
|
1872
|
+
`
|
|
1873
|
+
: ''}
|
|
1874
|
+
|
|
1875
|
+
${ct.logs && ct.logs.length > 0
|
|
1876
|
+
? `
|
|
1877
|
+
<div class="mt-3 p-3 bg-slate-800 rounded">
|
|
1878
|
+
<div class="flex justify-between items-center mb-2">
|
|
1879
|
+
<div class="font-semibold text-sm text-cyan-300">📋 Logs (${ct.logs.length}):</div>
|
|
1880
|
+
<button
|
|
1881
|
+
onclick="toggleLogs('logs-${testName.replace(/[^a-zA-Z0-9]/g, '')}-${index}')"
|
|
1882
|
+
id="btn-logs-${testName.replace(/[^a-zA-Z0-9]/g, '')}-${index}"
|
|
1883
|
+
class="btn btn-sm bg-slate-600 hover:bg-slate-700 text-xs"
|
|
1884
|
+
>
|
|
1885
|
+
👁️ Ver Logs
|
|
1886
|
+
</button>
|
|
1887
|
+
</div>
|
|
1888
|
+
<div id="logs-${testName.replace(/[^a-zA-Z0-9]/g, '')}-${index}" class="hidden max-h-96 overflow-y-auto">
|
|
1889
|
+
${ct.logs
|
|
1890
|
+
.map((log, logIdx) => `
|
|
1891
|
+
<div class="text-xs text-slate-300 py-1 font-mono border-l-2 border-slate-600 pl-2 hover:bg-slate-700">
|
|
1892
|
+
<span class="text-slate-500">${logIdx + 1}.</span> ${AutoDocs.escapeHtml(log.replace('[LOG] ', ''))}
|
|
1893
|
+
</div>
|
|
1894
|
+
`)
|
|
1895
|
+
.join('')}
|
|
1896
|
+
</div>
|
|
1897
|
+
</div>
|
|
1898
|
+
`
|
|
1899
|
+
: ''}
|
|
1900
|
+
|
|
1901
|
+
${ct.ctName && ct.ctName.length > 50
|
|
1902
|
+
? `
|
|
1903
|
+
<div class="mt-3">
|
|
1904
|
+
<button
|
|
1905
|
+
onclick="toggleCTDetails('ct-details-${testName.replace(/[^a-zA-Z0-9]/g, '')}-${index}')"
|
|
1906
|
+
class="btn btn-sm bg-blue-600 hover:bg-blue-700 text-xs"
|
|
1907
|
+
>
|
|
1908
|
+
📋 Ver Detalhes
|
|
1909
|
+
</button>
|
|
1910
|
+
<div id="ct-details-${testName.replace(/[^a-zA-Z0-9]/g, '')}-${index}" class="hidden mt-3 p-3 bg-slate-800 rounded border-l-4 border-blue-500">
|
|
1911
|
+
<div class="text-sm text-slate-300">
|
|
1912
|
+
<strong>🔍 Detalhes Completos:</strong>
|
|
1913
|
+
<pre class="mt-2 text-xs whitespace-pre-wrap break-all overflow-x-auto bg-slate-900 p-2 rounded">${AutoDocs.escapeHtml(ct.ctName)}</pre>
|
|
1914
|
+
</div>
|
|
1915
|
+
</div>
|
|
1916
|
+
</div>
|
|
1917
|
+
`
|
|
1918
|
+
: ''}
|
|
1919
|
+
</div>
|
|
1920
|
+
<div class="text-right">
|
|
1921
|
+
<div class="
|
|
1922
|
+
${ct.status === 'passed'
|
|
1923
|
+
? 'status-pass'
|
|
1924
|
+
: ct.status === 'failed'
|
|
1925
|
+
? 'status-fail'
|
|
1926
|
+
: ct.status === 'skipped'
|
|
1927
|
+
? 'status-skip'
|
|
1928
|
+
: 'status-running'}
|
|
1929
|
+
">
|
|
1930
|
+
${ct.status === 'passed'
|
|
1931
|
+
? '✅ PASS'
|
|
1932
|
+
: ct.status === 'failed'
|
|
1933
|
+
? '❌ FAIL'
|
|
1934
|
+
: ct.status === 'skipped'
|
|
1935
|
+
? '⏭️ SKIP'
|
|
1936
|
+
: '🔄 RUNNING'}
|
|
1937
|
+
</div>
|
|
1938
|
+
</div>
|
|
1939
|
+
</div>
|
|
1940
|
+
${ct.error
|
|
1941
|
+
? `
|
|
1942
|
+
<div class="mt-3 p-3 bg-red-900 bg-opacity-50 rounded border-l-4 border-red-500">
|
|
1943
|
+
<div class="text-red-300 text-sm">
|
|
1944
|
+
<strong>❌ Erro:</strong> ${AutoDocs.escapeHtml(ct.error)}
|
|
1945
|
+
</div>
|
|
1946
|
+
</div>
|
|
1947
|
+
`
|
|
1948
|
+
: ''}
|
|
1949
|
+
</div>
|
|
1950
|
+
`)
|
|
1951
|
+
.join('')}
|
|
1952
|
+
</div>
|
|
1953
|
+
</div>
|
|
1954
|
+
`)
|
|
1955
|
+
.join('')}
|
|
1956
|
+
|
|
1957
|
+
${totalCTs === 0
|
|
1958
|
+
? `
|
|
1959
|
+
<div class="card text-center">
|
|
1960
|
+
<h3 class="text-xl font-bold mb-4">📋 Nenhum CT Encontrado</h3>
|
|
1961
|
+
<p class="text-gray-400 mb-4">Execute seus testes com statements para ver os CTs aqui.</p>
|
|
1962
|
+
<p class="text-sm text-gray-500">
|
|
1963
|
+
Os CTs são capturados automaticamente quando você usa:
|
|
1964
|
+
<code class="bg-gray-800 px-2 py-1 rounded">await new StatementXXX().method()</code>
|
|
1965
|
+
</p>
|
|
1966
|
+
</div>
|
|
1967
|
+
`
|
|
1968
|
+
: ''}
|
|
1969
|
+
</div>
|
|
1970
|
+
|
|
1971
|
+
<div class="bg-slate-800 text-amber-400 p-4 text-center mt-8">
|
|
1972
|
+
Desenvolvido por <strong>Test HUB</strong> | Visualização por CTs gerada em ${timestamp}
|
|
1973
|
+
</div>
|
|
1974
|
+
|
|
1975
|
+
<script>
|
|
1976
|
+
document.getElementById('toggle-theme').onclick = () => {
|
|
1977
|
+
document.getElementById('body-root').classList.toggle('light-mode')
|
|
1978
|
+
document.querySelector('.header-bar').classList.toggle('light-mode')
|
|
1979
|
+
document.querySelectorAll('.card').forEach(el => el.classList.toggle('light-mode'))
|
|
1980
|
+
document.querySelectorAll('.ct-card').forEach(el => el.classList.toggle('light-mode'))
|
|
1981
|
+
document.querySelectorAll('.test-section').forEach(el => el.classList.toggle('light-mode'))
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// Função para expandir/recolher detalhes do CT
|
|
1985
|
+
function toggleCTDetails(elementId) {
|
|
1986
|
+
const element = document.getElementById(elementId)
|
|
1987
|
+
const button = element?.previousElementSibling
|
|
1988
|
+
|
|
1989
|
+
if (element) {
|
|
1990
|
+
if (element.classList.contains('hidden')) {
|
|
1991
|
+
element.classList.remove('hidden')
|
|
1992
|
+
if (button) button.textContent = '📋 Ocultar Detalhes'
|
|
1993
|
+
} else {
|
|
1994
|
+
element.classList.add('hidden')
|
|
1995
|
+
if (button) button.textContent = '📋 Ver Detalhes'
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// ✅ NOVO: Função para expandir/recolher logs do CT
|
|
2001
|
+
function toggleLogs(elementId) {
|
|
2002
|
+
const element = document.getElementById(elementId)
|
|
2003
|
+
const button = document.getElementById('btn-' + elementId)
|
|
2004
|
+
|
|
2005
|
+
if (element && button) {
|
|
2006
|
+
if (element.classList.contains('hidden')) {
|
|
2007
|
+
element.classList.remove('hidden')
|
|
2008
|
+
button.textContent = '👁️ Ocultar Logs'
|
|
2009
|
+
} else {
|
|
2010
|
+
element.classList.add('hidden')
|
|
2011
|
+
button.textContent = '👁️ Ver Logs'
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
</script>
|
|
2016
|
+
</body>
|
|
2017
|
+
</html>`;
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* �🎨 Gerar conteúdo HTML vazio
|
|
2021
|
+
*/
|
|
2022
|
+
static generateEmptyHTMLContent() {
|
|
2023
|
+
const timestamp = new Date().toISOString();
|
|
2024
|
+
return `<!DOCTYPE html>
|
|
2025
|
+
<html lang="pt-BR">
|
|
2026
|
+
<head>
|
|
2027
|
+
<meta charset="UTF-8">
|
|
2028
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2029
|
+
<title>📚 ${AutoDocs.config.projectName} - Documentação AutoDocs</title>
|
|
2030
|
+
<style>
|
|
2031
|
+
body { font-family: 'Inter', sans-serif; background-color: #0f172a; color: #f1f5f9; min-height: 100vh; }
|
|
2032
|
+
.light-mode { background-color: #f9fafb !important; color: #111827 !important; }
|
|
2033
|
+
.card { background: #1e293b; border-radius: 1rem; padding: 1.5rem; box-shadow: 0 2px 8px #0002; color: #f1f5f9; }
|
|
2034
|
+
.light-mode .card { background: #fff; color: #111827; border: 1px solid #e5e7eb; }
|
|
2035
|
+
.btn { background: #334155; color: #fff; border: none; border-radius: 8px; padding: 0.5rem 1.2rem; font-weight: 600; cursor: pointer; margin-bottom: 0.25rem; transition: background 0.2s; }
|
|
2036
|
+
.btn:hover { background: #475569; }
|
|
2037
|
+
.light-mode .btn { background: #374151; color: #fff; border: 1px solid #d1d5db; }
|
|
2038
|
+
.light-mode .btn:hover { background: #4b5563; }
|
|
2039
|
+
.header-bar { background: #1e293b; padding: 1rem 2rem; border-radius: 1rem 1rem 0 0; margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: space-between; }
|
|
2040
|
+
.header-bar .header-title { font-size: 2rem; font-weight: 700; color: #fbbf24; }
|
|
2041
|
+
.header-bar .header-meta { font-size: 1rem; color: #f1f5f9; }
|
|
2042
|
+
.light-mode .header-bar { background: #f3f4f6; color: #111827; border: 1px solid #e5e7eb; }
|
|
2043
|
+
.light-mode .header-title { color: #d97706; }
|
|
2044
|
+
.light-mode .header-meta { color: #374151 !important; }
|
|
2045
|
+
.footer-bar { background: #1e293b; color: #fbbf24; padding: 1rem 2rem; border-radius: 0 0 1rem 1rem; margin-top: 2rem; text-align: center; font-size: 1rem; }
|
|
2046
|
+
.light-mode .footer-bar { background: #f3f4f6; color: #d97706; border: 1px solid #e5e7eb; }
|
|
2047
|
+
</style>
|
|
2048
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
2049
|
+
</head>
|
|
2050
|
+
<body class="min-h-screen bg-slate-900 text-slate-100">
|
|
2051
|
+
<div class="container mx-auto px-4 py-8 max-w-7xl">
|
|
2052
|
+
<!-- Header -->
|
|
2053
|
+
<div class="header-bar">
|
|
2054
|
+
<div>
|
|
2055
|
+
<h1 class="header-title">📚 ${AutoDocs.config.projectName}</h1>
|
|
2056
|
+
</div>
|
|
2057
|
+
<div class="flex gap-2">
|
|
2058
|
+
<button onclick="toggleTheme()" class="btn">🌙/☀️ Tema</button>
|
|
2059
|
+
</div>
|
|
2060
|
+
</div>
|
|
2061
|
+
|
|
2062
|
+
<!-- Empty State -->
|
|
2063
|
+
<div class="text-center py-16">
|
|
2064
|
+
<div class="card max-w-2xl mx-auto">
|
|
2065
|
+
<h2 class="text-3xl font-bold text-green-400 mb-6">🚀 Sistema AutoDocs Ativo</h2>
|
|
2066
|
+
<p class="text-slate-300 mb-8">O sistema de documentação automática está funcionando corretamente!</p>
|
|
2067
|
+
|
|
2068
|
+
<div class="bg-slate-700 p-6 rounded-lg border-l-4 border-green-400 mb-8">
|
|
2069
|
+
<h3 class="text-xl font-bold text-green-400 mb-4">📊 Como funciona</h3>
|
|
2070
|
+
<p class="text-slate-300 mb-4">O AutoDocs detecta automaticamente e documenta:</p>
|
|
2071
|
+
<ul class="text-left space-y-2 text-slate-300">
|
|
2072
|
+
<li class="flex items-center gap-2"><span class="text-green-400">🌐</span> Chamadas de API</li>
|
|
2073
|
+
<li class="flex items-center gap-2"><span class="text-cyan-400">🖥️</span> Comandos SSH</li>
|
|
2074
|
+
<li class="flex items-center gap-2"><span class="text-purple-400">🗄️</span> Queries de banco de dados</li>
|
|
2075
|
+
<li class="flex items-center gap-2"><span class="text-indigo-400">🎨</span> Ações de interface (UI)</li>
|
|
2076
|
+
<li class="flex items-center gap-2"><span class="text-pink-400">📱</span> Interações mobile</li>
|
|
2077
|
+
</ul>
|
|
2078
|
+
</div>
|
|
2079
|
+
|
|
2080
|
+
<div class="bg-gradient-to-r from-green-600 to-blue-600 p-6 rounded-lg text-white">
|
|
2081
|
+
<p class="text-lg font-semibold">Execute seus testes para ver a documentação sendo gerada automaticamente!</p>
|
|
2082
|
+
</div>
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
|
|
2086
|
+
<!-- Footer -->
|
|
2087
|
+
<div class="footer-bar">
|
|
2088
|
+
<p>Documentação gerada automaticamente pelo AutoDocs (${AutoDocs.getProjectVersion()}) em ${new Date(timestamp).toLocaleString('pt-BR')}</p>
|
|
2089
|
+
</div>
|
|
2090
|
+
</div>
|
|
2091
|
+
|
|
2092
|
+
<script>
|
|
2093
|
+
function toggleTheme() {
|
|
2094
|
+
document.body.classList.toggle('light-mode');
|
|
2095
|
+
localStorage.setItem('theme', document.body.classList.contains('light-mode') ? 'light' : 'dark');
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// Load saved theme
|
|
2099
|
+
const savedTheme = localStorage.getItem('theme');
|
|
2100
|
+
if (savedTheme === 'light') {
|
|
2101
|
+
document.body.classList.add('light-mode');
|
|
2102
|
+
}
|
|
2103
|
+
</script>
|
|
2104
|
+
</body>
|
|
2105
|
+
</html>`;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* 🌐 Gerar seção de APIs com detalhes completos
|
|
2109
|
+
*/
|
|
2110
|
+
static generateAPISection(apis) {
|
|
2111
|
+
if (apis.length === 0) {
|
|
2112
|
+
return '';
|
|
2113
|
+
}
|
|
2114
|
+
// 🤖 Detectar categorias automaticamente baseado nos endpoints executados
|
|
2115
|
+
const detectedCategories = AutoDocs.detectProjectCategories(apis);
|
|
2116
|
+
// Categorizar APIs usando detecção inteligente
|
|
2117
|
+
const categorizedApis = apis.reduce((acc, api) => {
|
|
2118
|
+
const category = AutoDocs.categorizeEndpointDynamic(api.url, detectedCategories);
|
|
2119
|
+
if (!acc[category]) {
|
|
2120
|
+
acc[category] = [];
|
|
2121
|
+
}
|
|
2122
|
+
acc[category].push(api);
|
|
2123
|
+
return acc;
|
|
2124
|
+
}, {});
|
|
2125
|
+
// Gerar seções categorizadas
|
|
2126
|
+
const categorySections = Object.entries(categorizedApis)
|
|
2127
|
+
.map(([category, categoryApis]) => {
|
|
2128
|
+
const categoryKey = category.toLowerCase();
|
|
2129
|
+
const config = detectedCategories[categoryKey] || detectedCategories['outras'];
|
|
2130
|
+
const apiItems = categoryApis
|
|
2131
|
+
.map((api) => {
|
|
2132
|
+
const statusIcon = api.success ? '✅' : '❌';
|
|
2133
|
+
const durationColor = api.duration > 5000 ? 'text-orange-400' : 'text-green-400';
|
|
2134
|
+
return `
|
|
2135
|
+
<div class="card bg-slate-800 hover:bg-slate-700 transition-all duration-200 transform hover:-translate-y-1">
|
|
2136
|
+
<div class="flex flex-wrap items-center justify-between mb-4">
|
|
2137
|
+
<div class="flex items-center gap-3">
|
|
2138
|
+
<span class="method-badge method-${api.method.toLowerCase()}">${api.method}</span>
|
|
2139
|
+
<span class="font-mono text-blue-300 font-semibold break-all">${api.url}</span>
|
|
2140
|
+
</div>
|
|
2141
|
+
<span class="status-badge ${api.success ? 'bg-green-600' : 'bg-red-600'} text-white px-3 py-1 rounded-full text-sm">
|
|
2142
|
+
${statusIcon} ${api.statusCode}
|
|
2143
|
+
</span>
|
|
2144
|
+
</div>
|
|
2145
|
+
|
|
2146
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4 text-sm">
|
|
2147
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2148
|
+
<span>📝</span>
|
|
2149
|
+
<span>Teste: <span class="text-yellow-400">${api.testName}</span></span>
|
|
2150
|
+
</div>
|
|
2151
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2152
|
+
<span>🌍</span>
|
|
2153
|
+
<span>Ambiente: <span class="text-blue-400">${api.environment}</span></span>
|
|
2154
|
+
</div>
|
|
2155
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2156
|
+
<span>⏱️</span>
|
|
2157
|
+
<span>Duração: <span class="${durationColor}">${api.duration}ms</span></span>
|
|
2158
|
+
</div>
|
|
2159
|
+
</div>
|
|
2160
|
+
|
|
2161
|
+
${api.headers && Object.keys(api.headers).length > 0
|
|
2162
|
+
? `
|
|
2163
|
+
<details class="mb-4">
|
|
2164
|
+
<summary class="cursor-pointer font-semibold text-purple-400 hover:text-purple-300 transition-colors">
|
|
2165
|
+
📤 Headers da Requisição
|
|
2166
|
+
</summary>
|
|
2167
|
+
<pre class="bg-slate-900 text-purple-300 p-4 rounded-lg mt-2 overflow-x-auto text-sm font-mono">${JSON.stringify(api.headers, null, 2)}</pre>
|
|
2168
|
+
</details>
|
|
2169
|
+
`
|
|
2170
|
+
: ''}
|
|
2171
|
+
|
|
2172
|
+
${api.payload
|
|
2173
|
+
? `
|
|
2174
|
+
<details class="mb-4">
|
|
2175
|
+
<summary class="cursor-pointer font-semibold text-green-400 hover:text-green-300 transition-colors">
|
|
2176
|
+
📋 Payload da Requisição (Exemplo Real)
|
|
2177
|
+
</summary>
|
|
2178
|
+
<pre class="bg-slate-900 text-green-300 p-4 rounded-lg mt-2 overflow-x-auto text-sm font-mono">${JSON.stringify(api.payload, null, 2)}</pre>
|
|
2179
|
+
</details>
|
|
2180
|
+
`
|
|
2181
|
+
: ''}
|
|
2182
|
+
|
|
2183
|
+
${api.response
|
|
2184
|
+
? `
|
|
2185
|
+
<details class="mb-4">
|
|
2186
|
+
<summary class="cursor-pointer font-semibold text-blue-400 hover:text-blue-300 transition-colors">
|
|
2187
|
+
📥 Resposta da API (Exemplo Real)
|
|
2188
|
+
</summary>
|
|
2189
|
+
<pre class="bg-slate-900 text-blue-300 p-4 rounded-lg mt-2 overflow-x-auto text-sm font-mono">${JSON.stringify(api.response, null, 2)}</pre>
|
|
2190
|
+
</details>
|
|
2191
|
+
`
|
|
2192
|
+
: ''}
|
|
2193
|
+
|
|
2194
|
+
${api.error
|
|
2195
|
+
? `
|
|
2196
|
+
<details class="mb-4">
|
|
2197
|
+
<summary class="cursor-pointer font-semibold text-red-400 hover:text-red-300 transition-colors">
|
|
2198
|
+
❌ Erro
|
|
2199
|
+
</summary>
|
|
2200
|
+
<div class="bg-red-900 border border-red-700 text-red-300 p-4 rounded-lg mt-2">
|
|
2201
|
+
<pre class="text-sm font-mono overflow-x-auto">${api.error}</pre>
|
|
2202
|
+
</div>
|
|
2203
|
+
</details>
|
|
2204
|
+
`
|
|
2205
|
+
: ''}
|
|
2206
|
+
|
|
2207
|
+
<div class="text-xs text-slate-400 mt-4 pt-4 border-t border-slate-700">
|
|
2208
|
+
🕐 ${new Date(api.timestamp).toLocaleString('pt-BR')}
|
|
2209
|
+
</div>
|
|
2210
|
+
</div>
|
|
2211
|
+
`;
|
|
2212
|
+
})
|
|
2213
|
+
.join('');
|
|
2214
|
+
const successCount = categoryApis.filter((api) => api.success).length;
|
|
2215
|
+
const failureCount = categoryApis.length - successCount;
|
|
2216
|
+
return `
|
|
2217
|
+
<div class="mb-8 category-section">
|
|
2218
|
+
<h3 class="text-xl font-bold text-blue-400 mb-4 flex items-center gap-3 border-l-4 border-blue-400 pl-4">
|
|
2219
|
+
${config.icon} ${category} (${categoryApis.length} APIs)
|
|
2220
|
+
</h3>
|
|
2221
|
+
<p class="text-slate-400 mb-4 italic">${config.description}</p>
|
|
2222
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
2223
|
+
<div class="bg-green-800 rounded-lg p-3">
|
|
2224
|
+
<div class="text-green-200 text-sm">✅ Sucessos</div>
|
|
2225
|
+
<div class="text-lg font-bold text-white">${successCount}</div>
|
|
2226
|
+
</div>
|
|
2227
|
+
<div class="bg-red-800 rounded-lg p-3">
|
|
2228
|
+
<div class="text-red-200 text-sm">❌ Falhas</div>
|
|
2229
|
+
<div class="text-lg font-bold text-white">${failureCount}</div>
|
|
2230
|
+
</div>
|
|
2231
|
+
</div>
|
|
2232
|
+
<div class="space-y-4">
|
|
2233
|
+
${apiItems}
|
|
2234
|
+
</div>
|
|
2235
|
+
</div>
|
|
2236
|
+
`;
|
|
2237
|
+
})
|
|
2238
|
+
.join('');
|
|
2239
|
+
const totalSuccessCount = apis.filter((api) => api.success).length;
|
|
2240
|
+
const totalFailureCount = apis.length - totalSuccessCount;
|
|
2241
|
+
const avgDuration = apis.reduce((sum, api) => sum + (api.duration || 0), 0) / apis.length;
|
|
2242
|
+
// Gerar overview das categorias detectadas
|
|
2243
|
+
const categoryOverview = Object.entries(detectedCategories)
|
|
2244
|
+
.filter(([key]) => key !== 'outras')
|
|
2245
|
+
.map(([key, data]) => `<span class="inline-block bg-slate-700 px-3 py-1 rounded-full text-sm text-slate-300 mr-2 mb-2">${data.icon} ${key.charAt(0).toUpperCase() + key.slice(1)}</span>`)
|
|
2246
|
+
.join('');
|
|
2247
|
+
return `
|
|
2248
|
+
<div class="mb-8">
|
|
2249
|
+
<h2 class="text-3xl font-bold text-green-400 mb-6 flex items-center gap-3 border-l-4 border-green-400 pl-4">
|
|
2250
|
+
🌐 APIs Documentadas - Detecção Inteligente (${apis.length} total)
|
|
2251
|
+
</h2>
|
|
2252
|
+
|
|
2253
|
+
<div class="bg-slate-800 rounded-lg p-4 mb-6">
|
|
2254
|
+
<h4 class="text-lg font-semibold text-blue-400 mb-3">🤖 Categorias Detectadas Automaticamente:</h4>
|
|
2255
|
+
<div class="flex flex-wrap">
|
|
2256
|
+
${categoryOverview}
|
|
2257
|
+
</div>
|
|
2258
|
+
</div>
|
|
2259
|
+
|
|
2260
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
|
2261
|
+
<div class="bg-green-800 rounded-lg p-4">
|
|
2262
|
+
<div class="text-green-200 text-sm">✅ Total Sucessos</div>
|
|
2263
|
+
<div class="text-3xl font-bold text-white">${totalSuccessCount}</div>
|
|
2264
|
+
</div>
|
|
2265
|
+
<div class="bg-red-800 rounded-lg p-4">
|
|
2266
|
+
<div class="text-red-200 text-sm">❌ Total Falhas</div>
|
|
2267
|
+
<div class="text-3xl font-bold text-white">${totalFailureCount}</div>
|
|
2268
|
+
</div>
|
|
2269
|
+
<div class="bg-blue-800 rounded-lg p-4">
|
|
2270
|
+
<div class="text-blue-200 text-sm">⏱️ Tempo Médio</div>
|
|
2271
|
+
<div class="text-3xl font-bold text-white">${Math.round(avgDuration)}ms</div>
|
|
2272
|
+
</div>
|
|
2273
|
+
</div>
|
|
2274
|
+
|
|
2275
|
+
${categorySections}
|
|
2276
|
+
</div>
|
|
2277
|
+
`;
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* 🖥️ Gerar seção SSH com detalhes completos
|
|
2281
|
+
*/
|
|
2282
|
+
static generateSSHSection(sshCommands) {
|
|
2283
|
+
if (sshCommands.length === 0) {
|
|
2284
|
+
return '';
|
|
2285
|
+
}
|
|
2286
|
+
// 🚫 Filtrar comandos técnicos que não são verdadeiros CTs
|
|
2287
|
+
const filteredSSHCommands = sshCommands.filter((ssh) => {
|
|
2288
|
+
const command = ssh.command?.toLowerCase() || '';
|
|
2289
|
+
// Filtrar comandos técnicos (read, write, connect, etc.)
|
|
2290
|
+
const technicalCommands = [
|
|
2291
|
+
'read:',
|
|
2292
|
+
'write:',
|
|
2293
|
+
'connect:',
|
|
2294
|
+
'disconnect:',
|
|
2295
|
+
'execute:',
|
|
2296
|
+
'setup:',
|
|
2297
|
+
'init:',
|
|
2298
|
+
'cleanup:',
|
|
2299
|
+
];
|
|
2300
|
+
return !technicalCommands.some((tech) => command.startsWith(tech));
|
|
2301
|
+
});
|
|
2302
|
+
if (filteredSSHCommands.length === 0) {
|
|
2303
|
+
return '';
|
|
2304
|
+
}
|
|
2305
|
+
const sshItems = filteredSSHCommands
|
|
2306
|
+
.map((ssh) => {
|
|
2307
|
+
// ✅ CORRIGIR: Tratar success undefined como sucesso (se não tem error)
|
|
2308
|
+
const isSuccess = ssh.success !== false && !ssh.error;
|
|
2309
|
+
const statusIcon = isSuccess ? '✅' : '❌';
|
|
2310
|
+
const durationColor = (ssh.duration || 0) > 3000 ? 'text-orange-400' : 'text-green-400';
|
|
2311
|
+
return `
|
|
2312
|
+
<div class="card bg-slate-800 hover:bg-slate-700 transition-all duration-200 transform hover:-translate-y-1">
|
|
2313
|
+
<div class="flex flex-wrap items-center justify-between mb-4">
|
|
2314
|
+
<div class="flex items-center gap-3">
|
|
2315
|
+
<span class="bg-cyan-600 text-white px-3 py-1 rounded-full text-sm font-bold">SSH</span>
|
|
2316
|
+
<span class="font-mono text-cyan-300 font-semibold break-all">🖥️ ${ssh.command}</span>
|
|
2317
|
+
</div>
|
|
2318
|
+
<span class="status-badge ${isSuccess ? 'bg-green-600' : 'bg-red-600'} text-white px-3 py-1 rounded-full text-sm">
|
|
2319
|
+
${statusIcon} ${isSuccess ? 'Sucesso' : 'Erro'}
|
|
2320
|
+
</span>
|
|
2321
|
+
</div>
|
|
2322
|
+
|
|
2323
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4 text-sm">
|
|
2324
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2325
|
+
<span>🖥️</span>
|
|
2326
|
+
<span>Host: <span class="text-cyan-400">${ssh.host}</span></span>
|
|
2327
|
+
</div>
|
|
2328
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2329
|
+
<span>👤</span>
|
|
2330
|
+
<span>Usuário: <span class="text-blue-400">${ssh.user}</span></span>
|
|
2331
|
+
</div>
|
|
2332
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2333
|
+
<span>📝</span>
|
|
2334
|
+
<span>Teste: <span class="text-yellow-400">${ssh.testName}</span></span>
|
|
2335
|
+
</div>
|
|
2336
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2337
|
+
<span>⏱️</span>
|
|
2338
|
+
<span>Duração: <span class="${durationColor}">${ssh.duration || 0}ms</span></span>
|
|
2339
|
+
</div>
|
|
2340
|
+
</div>
|
|
2341
|
+
|
|
2342
|
+
${ssh.output
|
|
2343
|
+
? `
|
|
2344
|
+
<details class="mb-4">
|
|
2345
|
+
<summary class="cursor-pointer font-semibold text-green-400 hover:text-green-300 transition-colors">
|
|
2346
|
+
📤 Output do Comando
|
|
2347
|
+
</summary>
|
|
2348
|
+
<pre class="bg-slate-900 text-green-300 p-4 rounded-lg mt-2 overflow-x-auto text-sm font-mono">${ssh.output}</pre>
|
|
2349
|
+
</details>
|
|
2350
|
+
`
|
|
2351
|
+
: ''}
|
|
2352
|
+
|
|
2353
|
+
${ssh.error
|
|
2354
|
+
? `
|
|
2355
|
+
<details class="mb-4">
|
|
2356
|
+
<summary class="cursor-pointer font-semibold text-red-400 hover:text-red-300 transition-colors">
|
|
2357
|
+
❌ Erro
|
|
2358
|
+
</summary>
|
|
2359
|
+
<div class="bg-red-900 border border-red-700 text-red-300 p-4 rounded-lg mt-2">
|
|
2360
|
+
<pre class="text-sm font-mono overflow-x-auto">${ssh.error}</pre>
|
|
2361
|
+
</div>
|
|
2362
|
+
</details>
|
|
2363
|
+
`
|
|
2364
|
+
: ''}
|
|
2365
|
+
|
|
2366
|
+
<div class="text-xs text-slate-400 mt-4 pt-4 border-t border-slate-700">
|
|
2367
|
+
🕐 ${new Date(ssh.timestamp).toLocaleString('pt-BR')}
|
|
2368
|
+
</div>
|
|
2369
|
+
</div>
|
|
2370
|
+
`;
|
|
2371
|
+
})
|
|
2372
|
+
.join('');
|
|
2373
|
+
// ✅ CORRIGIR: Contar sucessos considerando success undefined como sucesso
|
|
2374
|
+
const successCount = filteredSSHCommands.filter((ssh) => ssh.success !== false && !ssh.error).length;
|
|
2375
|
+
const failureCount = filteredSSHCommands.length - successCount;
|
|
2376
|
+
return `
|
|
2377
|
+
<div class="mb-8">
|
|
2378
|
+
<h2 class="text-2xl font-bold text-cyan-400 mb-6 flex items-center gap-3 border-l-4 border-cyan-400 pl-4">
|
|
2379
|
+
🖥️ Comandos SSH (${filteredSSHCommands.length})
|
|
2380
|
+
</h2>
|
|
2381
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
|
2382
|
+
<div class="bg-green-800 rounded-lg p-4">
|
|
2383
|
+
<div class="text-green-200 text-sm">✅ Sucessos</div>
|
|
2384
|
+
<div class="text-2xl font-bold text-white">${successCount}</div>
|
|
2385
|
+
</div>
|
|
2386
|
+
<div class="bg-red-800 rounded-lg p-4">
|
|
2387
|
+
<div class="text-red-200 text-sm">❌ Falhas</div>
|
|
2388
|
+
<div class="text-2xl font-bold text-white">${failureCount}</div>
|
|
2389
|
+
</div>
|
|
2390
|
+
</div>
|
|
2391
|
+
<div class="space-y-6">
|
|
2392
|
+
${sshItems}
|
|
2393
|
+
</div>
|
|
2394
|
+
</div>
|
|
2395
|
+
`;
|
|
2396
|
+
}
|
|
2397
|
+
/**
|
|
2398
|
+
* 🎨 Obter classe TailwindCSS para tipo de query
|
|
2399
|
+
*/
|
|
2400
|
+
static getQueryTypeBadgeClass(type) {
|
|
2401
|
+
switch (type.toUpperCase()) {
|
|
2402
|
+
case 'SELECT':
|
|
2403
|
+
return 'bg-green-600';
|
|
2404
|
+
case 'INSERT':
|
|
2405
|
+
return 'bg-blue-600';
|
|
2406
|
+
case 'UPDATE':
|
|
2407
|
+
return 'bg-orange-600';
|
|
2408
|
+
case 'DELETE':
|
|
2409
|
+
return 'bg-red-600';
|
|
2410
|
+
case 'CREATE':
|
|
2411
|
+
return 'bg-purple-600';
|
|
2412
|
+
case 'ALTER':
|
|
2413
|
+
return 'bg-yellow-600';
|
|
2414
|
+
case 'DROP':
|
|
2415
|
+
return 'bg-red-800';
|
|
2416
|
+
default:
|
|
2417
|
+
return 'bg-gray-600';
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* 🗄️ Gerar seção Database com detalhes completos
|
|
2422
|
+
*/
|
|
2423
|
+
static generateDBSection(dbQueries) {
|
|
2424
|
+
if (dbQueries.length === 0) {
|
|
2425
|
+
return '';
|
|
2426
|
+
}
|
|
2427
|
+
const dbItems = dbQueries
|
|
2428
|
+
.map((db) => {
|
|
2429
|
+
// ✅ CORRIGIDO: Melhorar detecção de sucesso
|
|
2430
|
+
// Se success não está definido, assumir sucesso se não há erro e há duração
|
|
2431
|
+
const isSuccess = db.success !== undefined ? db.success : !db.error && db.duration > 0;
|
|
2432
|
+
Logger.info(`🗄️ [HubDocs] Query: ${db.query.substring(0, 30)}... | success: ${db.success} | error: ${db.error} | duration: ${db.duration} | isSuccess: ${isSuccess}`);
|
|
2433
|
+
const statusIcon = isSuccess ? '✅' : '❌';
|
|
2434
|
+
const typeColor = AutoDocs.getQueryTypeBadgeClass(db.type);
|
|
2435
|
+
const queryPreview = db.query.length > 100 ? `${db.query.substring(0, 100)}...` : db.query;
|
|
2436
|
+
const durationColor = db.duration > 2000 ? 'text-orange-400' : 'text-green-400';
|
|
2437
|
+
return `
|
|
2438
|
+
<div class="card bg-slate-800 hover:bg-slate-700 transition-all duration-200 transform hover:-translate-y-1">
|
|
2439
|
+
<div class="flex flex-wrap items-center justify-between mb-4">
|
|
2440
|
+
<div class="flex items-center gap-3">
|
|
2441
|
+
<span class="${typeColor} text-white px-3 py-1 rounded-full text-sm font-bold">${db.type}</span>
|
|
2442
|
+
<span class="font-mono text-purple-300 font-semibold break-all">${queryPreview}</span>
|
|
2443
|
+
</div>
|
|
2444
|
+
<span class="status-badge ${isSuccess ? 'bg-green-600' : 'bg-red-600'} text-white px-3 py-1 rounded-full text-sm">
|
|
2445
|
+
${statusIcon} ${isSuccess ? 'Sucesso' : 'Erro'}
|
|
2446
|
+
</span>
|
|
2447
|
+
</div>
|
|
2448
|
+
|
|
2449
|
+
${db.description
|
|
2450
|
+
? `
|
|
2451
|
+
<div class="mb-4 p-3 bg-slate-900 rounded-lg border-l-4 border-blue-500">
|
|
2452
|
+
<div class="flex items-start gap-2">
|
|
2453
|
+
<span class="text-blue-400 text-lg flex-shrink-0">📖</span>
|
|
2454
|
+
<div>
|
|
2455
|
+
<p class="text-slate-300 text-sm font-medium">Descrição do Serviço</p>
|
|
2456
|
+
<p class="text-blue-300 text-base mt-1">${db.description}</p>
|
|
2457
|
+
</div>
|
|
2458
|
+
</div>
|
|
2459
|
+
</div>
|
|
2460
|
+
`
|
|
2461
|
+
: ''}
|
|
2462
|
+
|
|
2463
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4 text-sm">
|
|
2464
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2465
|
+
<span>🗄️</span>
|
|
2466
|
+
<span>Database: <span class="text-purple-400">${db.database}</span></span>
|
|
2467
|
+
</div>
|
|
2468
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2469
|
+
<span>📝</span>
|
|
2470
|
+
<span>Teste: <span class="text-yellow-400">${db.testName}</span></span>
|
|
2471
|
+
</div>
|
|
2472
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2473
|
+
<span>🌍</span>
|
|
2474
|
+
<span>Ambiente: <span class="text-blue-400">${db.environment}</span></span>
|
|
2475
|
+
</div>
|
|
2476
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2477
|
+
<span>⏱️</span>
|
|
2478
|
+
<span>Duração: <span class="${durationColor}">${db.duration || 0}ms</span></span>
|
|
2479
|
+
</div>
|
|
2480
|
+
${db.serviceName
|
|
2481
|
+
? `
|
|
2482
|
+
<div class="flex items-center gap-2 text-slate-300 md:col-span-2">
|
|
2483
|
+
<span>🔧</span>
|
|
2484
|
+
<span>Serviço: <span class="text-cyan-400">${db.serviceName}</span></span>
|
|
2485
|
+
</div>
|
|
2486
|
+
`
|
|
2487
|
+
: ''}
|
|
2488
|
+
</div>
|
|
2489
|
+
|
|
2490
|
+
<details class="mb-4">
|
|
2491
|
+
<summary class="cursor-pointer font-semibold text-purple-400 hover:text-purple-300 transition-colors">
|
|
2492
|
+
📋 Query Completa
|
|
2493
|
+
</summary>
|
|
2494
|
+
<pre class="bg-slate-900 text-purple-300 p-4 rounded-lg mt-2 overflow-x-auto text-sm font-mono">${db.query}</pre>
|
|
2495
|
+
</details>
|
|
2496
|
+
|
|
2497
|
+
${db.params && db.params.length > 0
|
|
2498
|
+
? `
|
|
2499
|
+
<details class="mb-4">
|
|
2500
|
+
<summary class="cursor-pointer font-semibold text-orange-400 hover:text-orange-300 transition-colors">
|
|
2501
|
+
🔧 Parâmetros
|
|
2502
|
+
</summary>
|
|
2503
|
+
<pre class="bg-slate-900 text-orange-300 p-4 rounded-lg mt-2 overflow-x-auto text-sm font-mono">${JSON.stringify(db.params, null, 2)}</pre>
|
|
2504
|
+
</details>
|
|
2505
|
+
`
|
|
2506
|
+
: ''}
|
|
2507
|
+
|
|
2508
|
+
${db.error && !isSuccess
|
|
2509
|
+
? `
|
|
2510
|
+
<details class="mb-4">
|
|
2511
|
+
<summary class="cursor-pointer font-semibold text-red-400 hover:text-red-300 transition-colors">
|
|
2512
|
+
❌ Erro
|
|
2513
|
+
</summary>
|
|
2514
|
+
<div class="bg-red-900 border border-red-700 text-red-300 p-4 rounded-lg mt-2">
|
|
2515
|
+
<pre class="text-sm font-mono overflow-x-auto">${db.error}</pre>
|
|
2516
|
+
</div>
|
|
2517
|
+
</details>
|
|
2518
|
+
`
|
|
2519
|
+
: ''}
|
|
2520
|
+
|
|
2521
|
+
<div class="text-xs text-slate-400 mt-4 pt-4 border-t border-slate-700">
|
|
2522
|
+
🕐 ${new Date(db.timestamp).toLocaleString('pt-BR')}
|
|
2523
|
+
</div>
|
|
2524
|
+
</div>
|
|
2525
|
+
`;
|
|
2526
|
+
})
|
|
2527
|
+
.join('');
|
|
2528
|
+
// ✅ CORRIGIDO: Usar lógica melhorada de detecção de sucesso
|
|
2529
|
+
const successCount = dbQueries.filter((db) => db.success !== undefined ? db.success : !db.error && db.duration > 0).length;
|
|
2530
|
+
const failureCount = dbQueries.length - successCount;
|
|
2531
|
+
const avgDuration = dbQueries.reduce((sum, db) => sum + (db.duration || 0), 0) /
|
|
2532
|
+
dbQueries.length;
|
|
2533
|
+
return `
|
|
2534
|
+
<div class="mb-8">
|
|
2535
|
+
<h2 class="text-2xl font-bold text-purple-400 mb-6 flex items-center gap-3 border-l-4 border-purple-400 pl-4">
|
|
2536
|
+
🗄️ Queries Database (${dbQueries.length})
|
|
2537
|
+
</h2>
|
|
2538
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
2539
|
+
<div class="bg-green-800 rounded-lg p-4">
|
|
2540
|
+
<div class="text-green-200 text-sm">✅ Sucessos</div>
|
|
2541
|
+
<div class="text-2xl font-bold text-white">${successCount}</div>
|
|
2542
|
+
</div>
|
|
2543
|
+
<div class="bg-red-800 rounded-lg p-4">
|
|
2544
|
+
<div class="text-red-200 text-sm">❌ Falhas</div>
|
|
2545
|
+
<div class="text-2xl font-bold text-white">${failureCount}</div>
|
|
2546
|
+
</div>
|
|
2547
|
+
<div class="bg-orange-800 rounded-lg p-4">
|
|
2548
|
+
<div class="text-orange-200 text-sm">⏱️ Tempo médio</div>
|
|
2549
|
+
<div class="text-2xl font-bold text-white">${Math.round(avgDuration)}ms</div>
|
|
2550
|
+
</div>
|
|
2551
|
+
</div>
|
|
2552
|
+
<div class="space-y-6">
|
|
2553
|
+
${dbItems}
|
|
2554
|
+
</div>
|
|
2555
|
+
</div>
|
|
2556
|
+
`;
|
|
2557
|
+
}
|
|
2558
|
+
/**
|
|
2559
|
+
* 🎨 Obter classe TailwindCSS para ação de UI
|
|
2560
|
+
*/
|
|
2561
|
+
static getUIActionBadgeClass(action) {
|
|
2562
|
+
switch (action.toLowerCase()) {
|
|
2563
|
+
case 'click':
|
|
2564
|
+
return 'bg-blue-600';
|
|
2565
|
+
case 'type':
|
|
2566
|
+
case 'fill':
|
|
2567
|
+
return 'bg-green-600';
|
|
2568
|
+
case 'navigate':
|
|
2569
|
+
return 'bg-purple-600';
|
|
2570
|
+
case 'wait':
|
|
2571
|
+
return 'bg-yellow-600';
|
|
2572
|
+
case 'scroll':
|
|
2573
|
+
return 'bg-indigo-600';
|
|
2574
|
+
case 'hover':
|
|
2575
|
+
return 'bg-cyan-600';
|
|
2576
|
+
case 'select':
|
|
2577
|
+
return 'bg-orange-600';
|
|
2578
|
+
default:
|
|
2579
|
+
return 'bg-gray-600';
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* 🎨 Gerar seção UI com detalhes completos
|
|
2584
|
+
*/
|
|
2585
|
+
static generateUISection(uiActions) {
|
|
2586
|
+
if (uiActions.length === 0) {
|
|
2587
|
+
return '';
|
|
2588
|
+
}
|
|
2589
|
+
const uiItems = uiActions
|
|
2590
|
+
.map((ui) => {
|
|
2591
|
+
const statusIcon = ui.success ? '✅' : '❌';
|
|
2592
|
+
const actionColor = AutoDocs.getUIActionBadgeClass(ui.action);
|
|
2593
|
+
const durationColor = ui.duration > 2000 ? 'text-orange-400' : 'text-green-400';
|
|
2594
|
+
const screenshotSrc = AutoDocs.resolveImageSource(ui.screenshot);
|
|
2595
|
+
return `
|
|
2596
|
+
<div class="card bg-slate-800 hover:bg-slate-700 transition-all duration-200 transform hover:-translate-y-1">
|
|
2597
|
+
<div class="flex flex-wrap items-center justify-between mb-4">
|
|
2598
|
+
<div class="flex items-center gap-3">
|
|
2599
|
+
<span class="${actionColor} text-white px-3 py-1 rounded-full text-sm font-bold">${ui.action}</span>
|
|
2600
|
+
<span class="font-mono text-indigo-300 font-semibold break-all">${ui.element}</span>
|
|
2601
|
+
${ui.methodName && ui.methodName !== 'unknown-method'
|
|
2602
|
+
? `
|
|
2603
|
+
<span class="bg-purple-600 text-white px-2 py-1 rounded text-xs">📋 ${ui.methodName}</span>
|
|
2604
|
+
`
|
|
2605
|
+
: ''}
|
|
2606
|
+
</div>
|
|
2607
|
+
<span class="status-badge ${ui.success ? 'bg-green-600' : 'bg-red-600'} text-white px-3 py-1 rounded-full text-sm">
|
|
2608
|
+
${statusIcon} ${ui.success ? 'Sucesso' : 'Erro'}
|
|
2609
|
+
</span>
|
|
2610
|
+
</div>
|
|
2611
|
+
|
|
2612
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4 text-sm">
|
|
2613
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2614
|
+
<span>🌐</span>
|
|
2615
|
+
<span>Página: <span class="text-indigo-400">${ui.page}</span></span>
|
|
2616
|
+
</div>
|
|
2617
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2618
|
+
<span>📝</span>
|
|
2619
|
+
<span>Teste: <span class="text-yellow-400">${ui.testName}</span></span>
|
|
2620
|
+
</div>
|
|
2621
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2622
|
+
<span>⏱️</span>
|
|
2623
|
+
<span>Duração: <span class="${durationColor}">${ui.duration || 0}ms</span></span>
|
|
2624
|
+
</div>
|
|
2625
|
+
</div>
|
|
2626
|
+
|
|
2627
|
+
${ui.selector
|
|
2628
|
+
? `
|
|
2629
|
+
<details class="mb-4">
|
|
2630
|
+
<summary class="cursor-pointer font-semibold text-indigo-400 hover:text-indigo-300 transition-colors">
|
|
2631
|
+
🎯 Seletor
|
|
2632
|
+
${ui.selectorType ? `<span class="ml-2 bg-cyan-600 text-white px-2 py-1 rounded text-xs">${ui.selectorType}</span>` : ''}
|
|
2633
|
+
</summary>
|
|
2634
|
+
<pre class="bg-slate-900 text-indigo-300 p-4 rounded-lg mt-2 overflow-x-auto text-sm font-mono">${ui.selector}</pre>
|
|
2635
|
+
</details>
|
|
2636
|
+
`
|
|
2637
|
+
: ''}
|
|
2638
|
+
|
|
2639
|
+
${screenshotSrc
|
|
2640
|
+
? `
|
|
2641
|
+
<details class="mb-4">
|
|
2642
|
+
<summary class="cursor-pointer font-semibold text-green-400 hover:text-green-300 transition-colors">
|
|
2643
|
+
📸 Screenshot
|
|
2644
|
+
</summary>
|
|
2645
|
+
<div class="mt-2">
|
|
2646
|
+
<img src="${screenshotSrc}" alt="Screenshot da ação" class="max-w-full rounded-lg border border-slate-600">
|
|
2647
|
+
</div>
|
|
2648
|
+
</details>
|
|
2649
|
+
`
|
|
2650
|
+
: ''}
|
|
2651
|
+
|
|
2652
|
+
${ui.error
|
|
2653
|
+
? `
|
|
2654
|
+
<details class="mb-4">
|
|
2655
|
+
<summary class="cursor-pointer font-semibold text-red-400 hover:text-red-300 transition-colors">
|
|
2656
|
+
❌ Erro
|
|
2657
|
+
</summary>
|
|
2658
|
+
<div class="bg-red-900 border border-red-700 text-red-300 p-4 rounded-lg mt-2">
|
|
2659
|
+
<pre class="text-sm font-mono overflow-x-auto">${ui.error}</pre>
|
|
2660
|
+
</div>
|
|
2661
|
+
</details>
|
|
2662
|
+
`
|
|
2663
|
+
: ''}
|
|
2664
|
+
|
|
2665
|
+
<div class="text-xs text-slate-400 mt-4 pt-4 border-t border-slate-700">
|
|
2666
|
+
🕐 ${new Date(ui.timestamp).toLocaleString('pt-BR')}
|
|
2667
|
+
</div>
|
|
2668
|
+
</div>
|
|
2669
|
+
`;
|
|
2670
|
+
})
|
|
2671
|
+
.join('');
|
|
2672
|
+
const successCount = uiActions.filter((ui) => ui.success).length;
|
|
2673
|
+
const failureCount = uiActions.length - successCount;
|
|
2674
|
+
return `
|
|
2675
|
+
<div class="mb-8">
|
|
2676
|
+
<h2 class="text-2xl font-bold text-indigo-400 mb-6 flex items-center gap-3 border-l-4 border-indigo-400 pl-4">
|
|
2677
|
+
🎨 Ações UI (${uiActions.length})
|
|
2678
|
+
</h2>
|
|
2679
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
|
2680
|
+
<div class="bg-green-800 rounded-lg p-4">
|
|
2681
|
+
<div class="text-green-200 text-sm">✅ Sucessos</div>
|
|
2682
|
+
<div class="text-2xl font-bold text-white">${successCount}</div>
|
|
2683
|
+
</div>
|
|
2684
|
+
<div class="bg-red-800 rounded-lg p-4">
|
|
2685
|
+
<div class="text-red-200 text-sm">❌ Falhas</div>
|
|
2686
|
+
<div class="text-2xl font-bold text-white">${failureCount}</div>
|
|
2687
|
+
</div>
|
|
2688
|
+
</div>
|
|
2689
|
+
<div class="space-y-6">
|
|
2690
|
+
${uiItems}
|
|
2691
|
+
</div>
|
|
2692
|
+
</div>
|
|
2693
|
+
`;
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* 📱 Gerar seção Mobile com detalhes completos
|
|
2697
|
+
*/
|
|
2698
|
+
static generateMobileSection(mobileActions) {
|
|
2699
|
+
if (mobileActions.length === 0) {
|
|
2700
|
+
return '';
|
|
2701
|
+
}
|
|
2702
|
+
const mobileItems = mobileActions
|
|
2703
|
+
.map((mobile) => {
|
|
2704
|
+
const platformColor = mobile.platform === 'iOS' ? 'bg-blue-600' : 'bg-green-600';
|
|
2705
|
+
const actionColor = AutoDocs.getMobileActionBadgeClass(mobile.action);
|
|
2706
|
+
const screenshotSrc = AutoDocs.resolveImageSource(mobile.screenshot);
|
|
2707
|
+
return `
|
|
2708
|
+
<div class="card bg-slate-800 hover:bg-slate-700 transition-all duration-200 transform hover:-translate-y-1">
|
|
2709
|
+
<div class="flex flex-wrap items-center gap-3 mb-4">
|
|
2710
|
+
<span class="${platformColor} text-white px-3 py-1 rounded-full text-sm font-bold">${mobile.platform}</span>
|
|
2711
|
+
<span class="${actionColor} text-white px-3 py-1 rounded-full text-sm font-bold">${mobile.action}</span>
|
|
2712
|
+
<span class="font-mono text-pink-300 font-semibold break-all">${mobile.element}</span>
|
|
2713
|
+
</div>
|
|
2714
|
+
|
|
2715
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4 text-sm">
|
|
2716
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2717
|
+
<span>📱</span>
|
|
2718
|
+
<span>Device: <span class="text-pink-400">${mobile.device}</span></span>
|
|
2719
|
+
</div>
|
|
2720
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2721
|
+
<span>📝</span>
|
|
2722
|
+
<span>Teste: <span class="text-yellow-400">${mobile.testName}</span></span>
|
|
2723
|
+
</div>
|
|
2724
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2725
|
+
<span>🕐</span>
|
|
2726
|
+
<span>Timestamp: <span class="text-blue-400">${new Date(mobile.timestamp).toLocaleString('pt-BR')}</span></span>
|
|
2727
|
+
</div>
|
|
2728
|
+
</div>
|
|
2729
|
+
|
|
2730
|
+
${mobile.value
|
|
2731
|
+
? `
|
|
2732
|
+
<div class="bg-slate-700 p-3 rounded-lg mb-4">
|
|
2733
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2734
|
+
<span class="text-green-400">💬</span>
|
|
2735
|
+
<span>Valor: <span class="text-green-300 font-mono">${mobile.value}</span></span>
|
|
2736
|
+
</div>
|
|
2737
|
+
</div>
|
|
2738
|
+
`
|
|
2739
|
+
: ''}
|
|
2740
|
+
|
|
2741
|
+
${mobile.coordinates
|
|
2742
|
+
? `
|
|
2743
|
+
<div class="bg-slate-700 p-3 rounded-lg mb-4">
|
|
2744
|
+
<div class="flex items-center gap-2 text-slate-300">
|
|
2745
|
+
<span class="text-orange-400">📍</span>
|
|
2746
|
+
<span>Coordenadas: <span class="text-orange-300 font-mono">x: ${mobile.coordinates.x}, y: ${mobile.coordinates.y}</span></span>
|
|
2747
|
+
</div>
|
|
2748
|
+
</div>
|
|
2749
|
+
`
|
|
2750
|
+
: ''}
|
|
2751
|
+
|
|
2752
|
+
${screenshotSrc
|
|
2753
|
+
? `
|
|
2754
|
+
<div class="bg-slate-700 p-3 rounded-lg mb-4">
|
|
2755
|
+
<div class="flex items-center gap-2 text-slate-300 mb-3">
|
|
2756
|
+
<span class="text-purple-400">📸</span>
|
|
2757
|
+
<span>Screenshot da Ação:</span>
|
|
2758
|
+
</div>
|
|
2759
|
+
<div class="bg-slate-600 p-2 rounded-lg">
|
|
2760
|
+
<img
|
|
2761
|
+
src="${screenshotSrc}"
|
|
2762
|
+
alt="Screenshot da ação ${mobile.action} no elemento ${mobile.element}"
|
|
2763
|
+
class="w-full max-w-md mx-auto rounded-lg shadow-lg cursor-pointer hover:shadow-xl transition-shadow duration-200"
|
|
2764
|
+
onclick="window.open(this.src, '_blank')"
|
|
2765
|
+
loading="lazy"
|
|
2766
|
+
/>
|
|
2767
|
+
<p class="text-xs text-slate-400 text-center mt-2">Clique na imagem para visualizar em tamanho completo</p>
|
|
2768
|
+
</div>
|
|
2769
|
+
</div>
|
|
2770
|
+
`
|
|
2771
|
+
: ''}
|
|
2772
|
+
</div>
|
|
2773
|
+
`;
|
|
2774
|
+
})
|
|
2775
|
+
.join('');
|
|
2776
|
+
return `
|
|
2777
|
+
<div class="mb-8">
|
|
2778
|
+
<h2 class="text-2xl font-bold text-pink-400 mb-6 flex items-center gap-3 border-l-4 border-pink-400 pl-4">
|
|
2779
|
+
📱 Ações Mobile (${mobileActions.length})
|
|
2780
|
+
</h2>
|
|
2781
|
+
<div class="space-y-6">
|
|
2782
|
+
${mobileItems}
|
|
2783
|
+
</div>
|
|
2784
|
+
</div>
|
|
2785
|
+
`;
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* 🎨 Obter classe TailwindCSS para ação mobile
|
|
2789
|
+
*/
|
|
2790
|
+
static getMobileActionBadgeClass(action) {
|
|
2791
|
+
switch (action.toLowerCase()) {
|
|
2792
|
+
case 'tap':
|
|
2793
|
+
return 'bg-green-600';
|
|
2794
|
+
case 'type':
|
|
2795
|
+
return 'bg-blue-600';
|
|
2796
|
+
case 'swipe':
|
|
2797
|
+
return 'bg-orange-600';
|
|
2798
|
+
case 'scroll':
|
|
2799
|
+
return 'bg-purple-600';
|
|
2800
|
+
case 'wait':
|
|
2801
|
+
return 'bg-yellow-600';
|
|
2802
|
+
case 'pinch':
|
|
2803
|
+
return 'bg-red-600';
|
|
2804
|
+
case 'rotate':
|
|
2805
|
+
return 'bg-indigo-600';
|
|
2806
|
+
default:
|
|
2807
|
+
return 'bg-gray-600';
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
/**
|
|
2811
|
+
* 🎨 Obter cor do tipo de query
|
|
2812
|
+
*/
|
|
2813
|
+
static getQueryTypeColor(type) {
|
|
2814
|
+
const colors = {
|
|
2815
|
+
SELECT: '#2196F3',
|
|
2816
|
+
INSERT: '#4CAF50',
|
|
2817
|
+
UPDATE: '#FF9800',
|
|
2818
|
+
DELETE: '#f44336',
|
|
2819
|
+
DDL: '#9C27B0',
|
|
2820
|
+
OTHER: '#607D8B',
|
|
2821
|
+
};
|
|
2822
|
+
return colors[type] || colors.OTHER;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* 🎨 Obter cor da ação UI
|
|
2826
|
+
*/
|
|
2827
|
+
static getUIActionColor(action) {
|
|
2828
|
+
const colors = {
|
|
2829
|
+
click: '#4CAF50',
|
|
2830
|
+
type: '#2196F3',
|
|
2831
|
+
wait: '#FF9800',
|
|
2832
|
+
navigate: '#9C27B0',
|
|
2833
|
+
validate: '#00BCD4',
|
|
2834
|
+
};
|
|
2835
|
+
return colors[action.toLowerCase()] || '#607D8B';
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* 🎨 Obter cor da ação mobile
|
|
2839
|
+
*/
|
|
2840
|
+
static getMobileActionColor(action) {
|
|
2841
|
+
const colors = {
|
|
2842
|
+
tap: '#4CAF50',
|
|
2843
|
+
type: '#2196F3',
|
|
2844
|
+
swipe: '#FF9800',
|
|
2845
|
+
scroll: '#9C27B0',
|
|
2846
|
+
wait: '#607D8B',
|
|
2847
|
+
};
|
|
2848
|
+
return colors[action.toLowerCase()] || '#795548';
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* 📋 Gerar documentação Swagger detalhada
|
|
2852
|
+
*/
|
|
2853
|
+
static async generateSwaggerDocs(apis) {
|
|
2854
|
+
try {
|
|
2855
|
+
const swaggerDoc = {
|
|
2856
|
+
openapi: '3.0.0',
|
|
2857
|
+
info: {
|
|
2858
|
+
title: `${AutoDocs.config.projectName} - API Documentation`,
|
|
2859
|
+
version: '1.0.0',
|
|
2860
|
+
description: 'Documentação automática gerada pelo AutoDocs durante execução dos testes',
|
|
2861
|
+
contact: {
|
|
2862
|
+
name: 'Test Hub Framework',
|
|
2863
|
+
url: 'http://brtlvlty0559pl:3002/',
|
|
2864
|
+
},
|
|
2865
|
+
},
|
|
2866
|
+
servers: AutoDocs.extractServers(apis),
|
|
2867
|
+
paths: AutoDocs.generateDetailedSwaggerPaths(apis),
|
|
2868
|
+
components: {
|
|
2869
|
+
schemas: AutoDocs.generateSwaggerSchemas(apis),
|
|
2870
|
+
securitySchemes: {
|
|
2871
|
+
ApiKeyAuth: {
|
|
2872
|
+
type: 'apiKey',
|
|
2873
|
+
in: 'header',
|
|
2874
|
+
name: 'X-API-Key',
|
|
2875
|
+
},
|
|
2876
|
+
BearerAuth: {
|
|
2877
|
+
type: 'http',
|
|
2878
|
+
scheme: 'bearer',
|
|
2879
|
+
},
|
|
2880
|
+
},
|
|
2881
|
+
},
|
|
2882
|
+
tags: AutoDocs.generateSwaggerTags(apis),
|
|
2883
|
+
};
|
|
2884
|
+
const outputPath = path.join(AutoDocs.config.outputDir, 'swagger.json');
|
|
2885
|
+
await fs.promises.writeFile(outputPath, JSON.stringify(swaggerDoc, null, 2), 'utf-8');
|
|
2886
|
+
// Gerar também versão YAML
|
|
2887
|
+
const yamlPath = path.join(AutoDocs.config.outputDir, 'openapi.yaml');
|
|
2888
|
+
const yamlContent = AutoDocs.convertToYaml(swaggerDoc);
|
|
2889
|
+
await fs.promises.writeFile(yamlPath, yamlContent, 'utf-8');
|
|
2890
|
+
Logger.info(`📋 Swagger gerado: ${outputPath}`);
|
|
2891
|
+
Logger.info(`📋 OpenAPI YAML gerado: ${yamlPath}`);
|
|
2892
|
+
}
|
|
2893
|
+
catch (error) {
|
|
2894
|
+
Logger.error('❌ Erro ao gerar Swagger:', error);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* 🖥️ Extrair servidores das URLs das APIs
|
|
2899
|
+
*/
|
|
2900
|
+
static extractServers(apis) {
|
|
2901
|
+
const servers = new Set();
|
|
2902
|
+
apis.forEach((api) => {
|
|
2903
|
+
try {
|
|
2904
|
+
// 🎯 Lidar com URLs relativas e absolutas
|
|
2905
|
+
if (api.url.startsWith('http://') || api.url.startsWith('https://')) {
|
|
2906
|
+
const url = new URL(api.url);
|
|
2907
|
+
servers.add(`${url.protocol}//${url.host}`);
|
|
2908
|
+
}
|
|
2909
|
+
// URLs relativas não têm servidor, ignorar
|
|
2910
|
+
}
|
|
2911
|
+
catch {
|
|
2912
|
+
// Ignorar URLs inválidas
|
|
2913
|
+
}
|
|
2914
|
+
});
|
|
2915
|
+
return Array.from(servers).map((server) => ({
|
|
2916
|
+
url: server,
|
|
2917
|
+
description: 'Servidor detectado automaticamente',
|
|
2918
|
+
}));
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* 🛤️ Gerar paths detalhados do Swagger
|
|
2922
|
+
*/
|
|
2923
|
+
static generateDetailedSwaggerPaths(apis) {
|
|
2924
|
+
const paths = {};
|
|
2925
|
+
apis.forEach((api) => {
|
|
2926
|
+
try {
|
|
2927
|
+
// 🎯 Lidar com URLs relativas e absolutas
|
|
2928
|
+
let pathKey;
|
|
2929
|
+
if (api.url.startsWith('http://') || api.url.startsWith('https://')) {
|
|
2930
|
+
// URL absoluta - extrair pathname
|
|
2931
|
+
const url = new URL(api.url);
|
|
2932
|
+
pathKey = url.pathname || '/unknown';
|
|
2933
|
+
}
|
|
2934
|
+
else {
|
|
2935
|
+
// URL relativa - usar diretamente (remover query string se houver)
|
|
2936
|
+
pathKey = api.url.split('?')[0] || '/unknown';
|
|
2937
|
+
}
|
|
2938
|
+
const method = api.method.toLowerCase();
|
|
2939
|
+
if (!paths[pathKey]) {
|
|
2940
|
+
paths[pathKey] = {};
|
|
2941
|
+
}
|
|
2942
|
+
paths[pathKey][method] = {
|
|
2943
|
+
summary: `${api.method} ${pathKey}`,
|
|
2944
|
+
description: `Detectado no teste: ${api.testName}\\nExecução: ${api.timestamp}`,
|
|
2945
|
+
tags: [AutoDocs.categorizeEndpoint(pathKey)],
|
|
2946
|
+
parameters: AutoDocs.extractParameters(api),
|
|
2947
|
+
requestBody: api.payload
|
|
2948
|
+
? {
|
|
2949
|
+
content: {
|
|
2950
|
+
'application/json': {
|
|
2951
|
+
schema: AutoDocs.inferSchema(api.payload),
|
|
2952
|
+
example: api.payload,
|
|
2953
|
+
},
|
|
2954
|
+
},
|
|
2955
|
+
}
|
|
2956
|
+
: undefined,
|
|
2957
|
+
responses: {
|
|
2958
|
+
[api.statusCode || '200']: {
|
|
2959
|
+
description: api.success ? 'Sucesso' : 'Erro',
|
|
2960
|
+
content: api.response
|
|
2961
|
+
? {
|
|
2962
|
+
'application/json': {
|
|
2963
|
+
schema: AutoDocs.inferSchema(api.response),
|
|
2964
|
+
example: api.response,
|
|
2965
|
+
},
|
|
2966
|
+
}
|
|
2967
|
+
: undefined,
|
|
2968
|
+
},
|
|
2969
|
+
},
|
|
2970
|
+
'x-test-info': {
|
|
2971
|
+
testName: api.testName,
|
|
2972
|
+
environment: api.environment,
|
|
2973
|
+
duration: `${api.duration}ms`,
|
|
2974
|
+
success: api.success,
|
|
2975
|
+
timestamp: api.timestamp,
|
|
2976
|
+
},
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
catch (error) {
|
|
2980
|
+
Logger.warning(`⚠️ Erro ao processar API para Swagger: ${api.url} ${error}`);
|
|
2981
|
+
}
|
|
2982
|
+
});
|
|
2983
|
+
return paths;
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* 📊 Gerar schemas do Swagger baseado nos dados
|
|
2987
|
+
*/
|
|
2988
|
+
static generateSwaggerSchemas(apis) {
|
|
2989
|
+
const schemas = {};
|
|
2990
|
+
apis.forEach((api) => {
|
|
2991
|
+
// Schema do payload
|
|
2992
|
+
if (api.payload) {
|
|
2993
|
+
const schemaName = AutoDocs.generateSchemaName(api.url, 'Request');
|
|
2994
|
+
schemas[schemaName] = AutoDocs.inferSchema(api.payload);
|
|
2995
|
+
}
|
|
2996
|
+
// Schema da response
|
|
2997
|
+
if (api.response) {
|
|
2998
|
+
const schemaName = AutoDocs.generateSchemaName(api.url, 'Response');
|
|
2999
|
+
schemas[schemaName] = AutoDocs.inferSchema(api.response);
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
return schemas;
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* 🏷️ Gerar tags do Swagger
|
|
3006
|
+
*/
|
|
3007
|
+
static generateSwaggerTags(apis) {
|
|
3008
|
+
const tags = new Set();
|
|
3009
|
+
apis.forEach((api) => {
|
|
3010
|
+
try {
|
|
3011
|
+
// 🎯 Lidar com URLs relativas e absolutas
|
|
3012
|
+
let pathname;
|
|
3013
|
+
if (api.url.startsWith('http://') || api.url.startsWith('https://')) {
|
|
3014
|
+
const url = new URL(api.url);
|
|
3015
|
+
pathname = url.pathname;
|
|
3016
|
+
}
|
|
3017
|
+
else {
|
|
3018
|
+
pathname = api.url.split('?')[0]; // URL relativa
|
|
3019
|
+
}
|
|
3020
|
+
const tag = AutoDocs.categorizeEndpoint(pathname);
|
|
3021
|
+
tags.add(tag);
|
|
3022
|
+
}
|
|
3023
|
+
catch {
|
|
3024
|
+
tags.add('Geral');
|
|
3025
|
+
}
|
|
3026
|
+
});
|
|
3027
|
+
// 🏷️ Descrições detalhadas para cada categoria
|
|
3028
|
+
const tagDescriptions = {
|
|
3029
|
+
Cliente: 'APIs relacionadas a consulta e gestão de dados do cliente, produtos associados, contratos e histórico',
|
|
3030
|
+
Catalogo: 'APIs para consulta de produtos, dispositivos, portfolios, preços e ofertas disponíveis',
|
|
3031
|
+
Fatura: 'APIs para consulta de faturas, cobranças e downloads de documentos financeiros',
|
|
3032
|
+
Simulação: 'APIs para criação, consulta e gestão de simulações de venda e negociação',
|
|
3033
|
+
Documentação: 'APIs para geração de documentos, contratos e processos de aceite digital',
|
|
3034
|
+
Workflow: 'APIs para gestão de processos de aprovação, esteira de tramitação e solicitações de serviço',
|
|
3035
|
+
Auxiliar: 'APIs auxiliares para consultas de operadoras, endereços e validações gerais',
|
|
3036
|
+
Autenticação: 'APIs para autenticação, autorização e gestão de tokens',
|
|
3037
|
+
Monitoramento: 'APIs para health checks, status do sistema e monitoramento',
|
|
3038
|
+
Geral: 'APIs gerais e não categorizadas',
|
|
3039
|
+
};
|
|
3040
|
+
return Array.from(tags).map((tag) => ({
|
|
3041
|
+
name: tag,
|
|
3042
|
+
description: tagDescriptions[tag] || `Endpoints da categoria ${tag}`,
|
|
3043
|
+
}));
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* 🤖 Detectar categorias automaticamente baseado nos endpoints executados
|
|
3047
|
+
*/
|
|
3048
|
+
static detectProjectCategories(apis) {
|
|
3049
|
+
const urlPatterns = {};
|
|
3050
|
+
// Analisar todas as URLs para detectar padrões
|
|
3051
|
+
apis.forEach((api) => {
|
|
3052
|
+
const url = api.url.toLowerCase();
|
|
3053
|
+
const pathSegments = url
|
|
3054
|
+
.split('/')
|
|
3055
|
+
.filter((segment) => segment && !segment.includes('http'));
|
|
3056
|
+
pathSegments.forEach((segment) => {
|
|
3057
|
+
// Remover números e IDs para detectar padrões
|
|
3058
|
+
const cleanSegment = segment
|
|
3059
|
+
.replace(/\d+/g, '')
|
|
3060
|
+
.replace(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g, '');
|
|
3061
|
+
if (cleanSegment.length > 2) {
|
|
3062
|
+
if (!urlPatterns[cleanSegment]) {
|
|
3063
|
+
urlPatterns[cleanSegment] = [];
|
|
3064
|
+
}
|
|
3065
|
+
urlPatterns[cleanSegment].push(url);
|
|
3066
|
+
}
|
|
3067
|
+
});
|
|
3068
|
+
});
|
|
3069
|
+
// Detectar categorias baseado na frequência e padrões conhecidos
|
|
3070
|
+
const detectedCategories = {};
|
|
3071
|
+
// Padrões empresariais conhecidos
|
|
3072
|
+
const knownPatterns = {
|
|
3073
|
+
customer: {
|
|
3074
|
+
variations: ['customer', 'customers', 'cliente', 'clientes'],
|
|
3075
|
+
icon: '👥',
|
|
3076
|
+
description: 'Operações relacionadas ao gerenciamento de clientes e dados corporativos',
|
|
3077
|
+
},
|
|
3078
|
+
catalog: {
|
|
3079
|
+
variations: [
|
|
3080
|
+
'catalog',
|
|
3081
|
+
'catalogo',
|
|
3082
|
+
'products',
|
|
3083
|
+
'devices',
|
|
3084
|
+
'portfolio',
|
|
3085
|
+
'offers',
|
|
3086
|
+
'producttypecode',
|
|
3087
|
+
'typetrading',
|
|
3088
|
+
],
|
|
3089
|
+
icon: '📦',
|
|
3090
|
+
description: 'Gestão de produtos, dispositivos e portfólio de ofertas',
|
|
3091
|
+
},
|
|
3092
|
+
billing: {
|
|
3093
|
+
variations: ['billing', 'invoices', 'fatura', 'faturas', 'cobranca'],
|
|
3094
|
+
icon: '💰',
|
|
3095
|
+
description: 'Processos de faturamento e gestão de pagamentos',
|
|
3096
|
+
},
|
|
3097
|
+
simulation: {
|
|
3098
|
+
variations: ['simulation', 'simulations', 'simulacao', 'simulacoes'],
|
|
3099
|
+
icon: '🎯',
|
|
3100
|
+
description: 'Simulações de vendas e análises comerciais',
|
|
3101
|
+
},
|
|
3102
|
+
file: {
|
|
3103
|
+
variations: [
|
|
3104
|
+
'file',
|
|
3105
|
+
'files',
|
|
3106
|
+
'document',
|
|
3107
|
+
'documents',
|
|
3108
|
+
'documentacao',
|
|
3109
|
+
'upload',
|
|
3110
|
+
],
|
|
3111
|
+
icon: '📄',
|
|
3112
|
+
description: 'Gestão de arquivos e documentação',
|
|
3113
|
+
},
|
|
3114
|
+
workflow: {
|
|
3115
|
+
variations: [
|
|
3116
|
+
'workflow',
|
|
3117
|
+
'approval',
|
|
3118
|
+
'esteira',
|
|
3119
|
+
'tramitacao',
|
|
3120
|
+
'servicerequest',
|
|
3121
|
+
],
|
|
3122
|
+
icon: '🔄',
|
|
3123
|
+
description: 'Processos de aprovação e fluxos de trabalho',
|
|
3124
|
+
},
|
|
3125
|
+
account: {
|
|
3126
|
+
variations: ['account', 'accounts', 'conta', 'contas', 'sobjects'],
|
|
3127
|
+
icon: '🏢',
|
|
3128
|
+
description: 'Gestão de contas e informações corporativas',
|
|
3129
|
+
},
|
|
3130
|
+
service: {
|
|
3131
|
+
variations: ['service', 'services', 'servico', 'servicos', 'ss'],
|
|
3132
|
+
icon: '🛠️',
|
|
3133
|
+
description: 'Serviços e requisições de suporte',
|
|
3134
|
+
},
|
|
3135
|
+
order: {
|
|
3136
|
+
variations: ['order', 'orders', 'pedido', 'pedidos'],
|
|
3137
|
+
icon: '📋',
|
|
3138
|
+
description: 'Gestão de pedidos e ordens',
|
|
3139
|
+
},
|
|
3140
|
+
address: {
|
|
3141
|
+
variations: ['address', 'addresses', 'endereco', 'enderecos'],
|
|
3142
|
+
icon: '📍',
|
|
3143
|
+
description: 'Gestão de endereços e localização',
|
|
3144
|
+
},
|
|
3145
|
+
auth: {
|
|
3146
|
+
variations: ['auth', 'login', 'token', 'authentication'],
|
|
3147
|
+
icon: '🔐',
|
|
3148
|
+
description: 'Autenticação e autorização',
|
|
3149
|
+
},
|
|
3150
|
+
monitoring: {
|
|
3151
|
+
variations: ['health', 'status', 'ping', 'monitor'],
|
|
3152
|
+
icon: '📊',
|
|
3153
|
+
description: 'Monitoramento e status do sistema',
|
|
3154
|
+
},
|
|
3155
|
+
};
|
|
3156
|
+
// Detectar categorias baseado nos padrões encontrados
|
|
3157
|
+
for (const [segment, urls] of Object.entries(urlPatterns)) {
|
|
3158
|
+
if (urls.length >= 1) {
|
|
3159
|
+
// Se aparecer pelo menos uma vez
|
|
3160
|
+
for (const [categoryKey, categoryData] of Object.entries(knownPatterns)) {
|
|
3161
|
+
if (categoryData.variations.some((variation) => segment.includes(variation) || variation.includes(segment))) {
|
|
3162
|
+
if (!detectedCategories[categoryKey]) {
|
|
3163
|
+
detectedCategories[categoryKey] = {
|
|
3164
|
+
pattern: categoryData.variations,
|
|
3165
|
+
description: categoryData.description,
|
|
3166
|
+
icon: categoryData.icon,
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
break;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
// Se não encontrou categorias específicas, criar categorias genéricas baseadas nos segmentos mais comuns
|
|
3175
|
+
if (Object.keys(detectedCategories).length === 0) {
|
|
3176
|
+
const sortedSegments = Object.entries(urlPatterns)
|
|
3177
|
+
.sort(([, a], [, b]) => b.length - a.length)
|
|
3178
|
+
.slice(0, 5);
|
|
3179
|
+
sortedSegments.forEach(([segment, urls], index) => {
|
|
3180
|
+
detectedCategories[segment] = {
|
|
3181
|
+
pattern: [segment],
|
|
3182
|
+
description: `Operações relacionadas a ${segment}`,
|
|
3183
|
+
icon: ['🔵', '🟢', '🟡', '🟠', '🔴'][index] || '⚪',
|
|
3184
|
+
};
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3187
|
+
// Adicionar categoria "Outras" para endpoints não categorizados
|
|
3188
|
+
detectedCategories['outras'] = {
|
|
3189
|
+
pattern: [],
|
|
3190
|
+
description: 'Outros endpoints não categorizados',
|
|
3191
|
+
icon: '⚡',
|
|
3192
|
+
};
|
|
3193
|
+
return detectedCategories;
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* 🏷️ Categorizar endpoint com base nas categorias detectadas dinamicamente
|
|
3197
|
+
*/
|
|
3198
|
+
static categorizeEndpointDynamic(url, detectedCategories) {
|
|
3199
|
+
const lowerUrl = url.toLowerCase();
|
|
3200
|
+
// Verificar contra categorias detectadas automaticamente
|
|
3201
|
+
for (const [categoryName, categoryData] of Object.entries(detectedCategories)) {
|
|
3202
|
+
if (categoryName === 'outras')
|
|
3203
|
+
continue; // Pular a categoria "outras" na verificação
|
|
3204
|
+
if (categoryData.pattern.some((pattern) => lowerUrl.includes(`/${pattern}/`) ||
|
|
3205
|
+
lowerUrl.includes(`/${pattern}`) ||
|
|
3206
|
+
lowerUrl.includes(pattern))) {
|
|
3207
|
+
return categoryName.charAt(0).toUpperCase() + categoryName.slice(1);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
return 'Outras';
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* 🎯 Categorizar endpoint
|
|
3214
|
+
*/
|
|
3215
|
+
static categorizeEndpoint(path) {
|
|
3216
|
+
// 🏢 Categorização baseada nos padrões da empresa
|
|
3217
|
+
// Cliente - Operações relacionadas ao cliente
|
|
3218
|
+
if (path.includes('/customers/') || path.includes('/customer/'))
|
|
3219
|
+
return 'Cliente';
|
|
3220
|
+
// Catálogo - Produtos, devices, portfolios, preços
|
|
3221
|
+
if (path.includes('/products/') ||
|
|
3222
|
+
path.includes('/devices/') ||
|
|
3223
|
+
path.includes('/portfolio/') ||
|
|
3224
|
+
path.includes('/productType') ||
|
|
3225
|
+
path.includes('/typeTrading') ||
|
|
3226
|
+
path.includes('/offersCatalog'))
|
|
3227
|
+
return 'Catalogo';
|
|
3228
|
+
// Fatura - Faturas e cobranças
|
|
3229
|
+
if (path.includes('/invoices/') ||
|
|
3230
|
+
path.includes('/billing/') ||
|
|
3231
|
+
path.includes('/payment/'))
|
|
3232
|
+
return 'Fatura';
|
|
3233
|
+
// Simulação - Simulações de venda e negociação
|
|
3234
|
+
if (path.includes('/simulacoes/') ||
|
|
3235
|
+
path.includes('/simulation/') ||
|
|
3236
|
+
path.includes('/quote/'))
|
|
3237
|
+
return 'Simulação';
|
|
3238
|
+
// Documentação - Geração de documentos e contratos
|
|
3239
|
+
if (path.includes('/documentation/') ||
|
|
3240
|
+
path.includes('/documents/') ||
|
|
3241
|
+
path.includes('/gerarDocumentacao') ||
|
|
3242
|
+
path.includes('/aceite') ||
|
|
3243
|
+
path.includes('/gedoc'))
|
|
3244
|
+
return 'Documentação';
|
|
3245
|
+
// Workflow - Processos de aprovação e esteira
|
|
3246
|
+
if (path.includes('/workflow/') ||
|
|
3247
|
+
path.includes('/approval/') ||
|
|
3248
|
+
path.includes('/esteira') ||
|
|
3249
|
+
path.includes('/solicitacoes'))
|
|
3250
|
+
return 'Workflow';
|
|
3251
|
+
// Auxiliar - Serviços de apoio e consultas gerais
|
|
3252
|
+
if (path.includes('/auxiliares/') ||
|
|
3253
|
+
path.includes('/operadoras/') ||
|
|
3254
|
+
path.includes('/geographicAddress') ||
|
|
3255
|
+
path.includes('/validate'))
|
|
3256
|
+
return 'Auxiliar';
|
|
3257
|
+
// Autenticação
|
|
3258
|
+
if (path.includes('/auth/') ||
|
|
3259
|
+
path.includes('/login/') ||
|
|
3260
|
+
path.includes('/token/'))
|
|
3261
|
+
return 'Autenticação';
|
|
3262
|
+
// Health checks e monitoramento
|
|
3263
|
+
if (path.includes('/health') ||
|
|
3264
|
+
path.includes('/status') ||
|
|
3265
|
+
path.includes('/ping'))
|
|
3266
|
+
return 'Monitoramento';
|
|
3267
|
+
// Fallback genérico
|
|
3268
|
+
return 'Geral';
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* 📄 Converter para YAML
|
|
3272
|
+
*/
|
|
3273
|
+
static convertToYaml(obj) {
|
|
3274
|
+
try {
|
|
3275
|
+
// Limpar dados problemáticos antes da conversão
|
|
3276
|
+
const cleanedObj = AutoDocs.cleanObjectForYaml(obj, false);
|
|
3277
|
+
// Usar biblioteca js-yaml para conversão correta
|
|
3278
|
+
const yamlContent = yaml.dump(cleanedObj, {
|
|
3279
|
+
indent: 2,
|
|
3280
|
+
lineWidth: -1,
|
|
3281
|
+
noRefs: true,
|
|
3282
|
+
sortKeys: false,
|
|
3283
|
+
quotingType: '"',
|
|
3284
|
+
forceQuotes: false,
|
|
3285
|
+
noCompatMode: true,
|
|
3286
|
+
});
|
|
3287
|
+
return `# OpenAPI Specification
|
|
3288
|
+
# Gerado automaticamente pelo AutoDocs
|
|
3289
|
+
|
|
3290
|
+
${yamlContent}`;
|
|
3291
|
+
}
|
|
3292
|
+
catch (error) {
|
|
3293
|
+
Logger.error('❌ Erro ao converter para YAML:', error);
|
|
3294
|
+
// Fallback para conversão simples
|
|
3295
|
+
return `# OpenAPI Specification
|
|
3296
|
+
# Gerado automaticamente pelo AutoDocs
|
|
3297
|
+
|
|
3298
|
+
openapi: "3.0.0"
|
|
3299
|
+
info:
|
|
3300
|
+
title: "API Documentation"
|
|
3301
|
+
version: "1.0.0"
|
|
3302
|
+
description: "Erro na conversão YAML - ${error}"
|
|
3303
|
+
`;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* 🧹 Limpar objeto para conversão YAML
|
|
3308
|
+
*/
|
|
3309
|
+
static cleanObjectForYaml(obj, isPathsSection = false) {
|
|
3310
|
+
if (obj === null || obj === undefined) {
|
|
3311
|
+
return obj;
|
|
3312
|
+
}
|
|
3313
|
+
if (typeof obj === 'string') {
|
|
3314
|
+
// Limpar caracteres problemáticos em strings
|
|
3315
|
+
return obj
|
|
3316
|
+
.replace(/\\/g, '\\\\') // Escapar barras invertidas
|
|
3317
|
+
.replace(/\n/g, '\\n') // Escapar quebras de linha
|
|
3318
|
+
.replace(/\t/g, '\\t') // Escapar tabs
|
|
3319
|
+
.replace(/\r/g, '\\r'); // Escapar carriage returns
|
|
3320
|
+
}
|
|
3321
|
+
if (Array.isArray(obj)) {
|
|
3322
|
+
return obj.map((item) => AutoDocs.cleanObjectForYaml(item, false));
|
|
3323
|
+
}
|
|
3324
|
+
if (typeof obj === 'object') {
|
|
3325
|
+
const cleaned = {};
|
|
3326
|
+
Object.keys(obj).forEach((key) => {
|
|
3327
|
+
// 🔧 CORREÇÃO: Preservar paths do OpenAPI intactos
|
|
3328
|
+
let cleanKey;
|
|
3329
|
+
if (isPathsSection && key.startsWith('/')) {
|
|
3330
|
+
// Para paths do OpenAPI, manter o formato original
|
|
3331
|
+
cleanKey = key;
|
|
3332
|
+
}
|
|
3333
|
+
else if (key === 'paths') {
|
|
3334
|
+
// Quando encontrar a seção paths, marcar para tratamento especial
|
|
3335
|
+
cleaned[key] = AutoDocs.cleanObjectForYaml(obj[key], true);
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
else {
|
|
3339
|
+
// Para outras chaves, limpar normalmente
|
|
3340
|
+
cleanKey = key.replace(/[^\w\-_]/g, '_');
|
|
3341
|
+
}
|
|
3342
|
+
cleaned[cleanKey] = AutoDocs.cleanObjectForYaml(obj[key], false);
|
|
3343
|
+
});
|
|
3344
|
+
return cleaned;
|
|
3345
|
+
}
|
|
3346
|
+
return obj;
|
|
3347
|
+
}
|
|
3348
|
+
/**
|
|
3349
|
+
* 🔍 Inferir schema de um objeto
|
|
3350
|
+
* ✅ CORRIGIDO: Tipos válidos OpenAPI 3.0 (array, boolean, integer, number, object, string)
|
|
3351
|
+
* ✅ CORRIGIDO: required só é adicionado se tiver pelo menos 1 item
|
|
3352
|
+
*/
|
|
3353
|
+
static inferSchema(obj) {
|
|
3354
|
+
// 🔧 null -> string com nullable: true (OpenAPI 3.0 não suporta type: 'null')
|
|
3355
|
+
if (obj === null || obj === undefined) {
|
|
3356
|
+
return { type: 'string', nullable: true };
|
|
3357
|
+
}
|
|
3358
|
+
if (typeof obj === 'string')
|
|
3359
|
+
return { type: 'string', example: obj };
|
|
3360
|
+
if (typeof obj === 'number') {
|
|
3361
|
+
// Verificar se é inteiro ou decimal
|
|
3362
|
+
return Number.isInteger(obj)
|
|
3363
|
+
? { type: 'integer', example: obj }
|
|
3364
|
+
: { type: 'number', example: obj };
|
|
3365
|
+
}
|
|
3366
|
+
if (typeof obj === 'boolean')
|
|
3367
|
+
return { type: 'boolean', example: obj };
|
|
3368
|
+
if (Array.isArray(obj)) {
|
|
3369
|
+
return {
|
|
3370
|
+
type: 'array',
|
|
3371
|
+
items: obj.length > 0 ? AutoDocs.inferSchema(obj[0]) : { type: 'string' },
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
if (typeof obj === 'object') {
|
|
3375
|
+
const properties = {};
|
|
3376
|
+
const keys = Object.keys(obj);
|
|
3377
|
+
keys.forEach((key) => {
|
|
3378
|
+
properties[key] = AutoDocs.inferSchema(obj[key]);
|
|
3379
|
+
});
|
|
3380
|
+
// 🔧 FIX: required só é adicionado se tiver pelo menos 1 item (OpenAPI exige min 1)
|
|
3381
|
+
const schema = {
|
|
3382
|
+
type: 'object',
|
|
3383
|
+
properties,
|
|
3384
|
+
};
|
|
3385
|
+
// Só adicionar required se tiver itens
|
|
3386
|
+
if (keys.length > 0) {
|
|
3387
|
+
schema.required = keys;
|
|
3388
|
+
}
|
|
3389
|
+
return schema;
|
|
3390
|
+
}
|
|
3391
|
+
return { type: 'string' };
|
|
3392
|
+
}
|
|
3393
|
+
/**
|
|
3394
|
+
* 📝 Gerar nome de schema
|
|
3395
|
+
* ✅ CORRIGIDO: Sanitiza caracteres inválidos (OpenAPI só permite A-Z a-z 0-9 - . _)
|
|
3396
|
+
*/
|
|
3397
|
+
static generateSchemaName(url, suffix) {
|
|
3398
|
+
try {
|
|
3399
|
+
// 🎯 Lidar com URLs relativas e absolutas
|
|
3400
|
+
let pathname;
|
|
3401
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
3402
|
+
const urlObj = new URL(url);
|
|
3403
|
+
pathname = urlObj.pathname;
|
|
3404
|
+
}
|
|
3405
|
+
else {
|
|
3406
|
+
pathname = url.split('?')[0]; // URL relativa
|
|
3407
|
+
}
|
|
3408
|
+
const pathParts = pathname.split('/').filter((p) => p);
|
|
3409
|
+
let name = pathParts.length > 0 ? pathParts[pathParts.length - 1] : 'Unknown';
|
|
3410
|
+
// 🔧 FIX: Sanitizar nome - OpenAPI só permite: A-Z a-z 0-9 - . _
|
|
3411
|
+
// Substituir caracteres inválidos (|, %, @, #, etc.) por underscore
|
|
3412
|
+
name = AutoDocs.sanitizeSchemaName(name);
|
|
3413
|
+
return `${name}${suffix}`;
|
|
3414
|
+
}
|
|
3415
|
+
catch {
|
|
3416
|
+
return `Unknown${suffix}`;
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
/**
|
|
3420
|
+
* 🔧 Sanitizar nome de schema para OpenAPI
|
|
3421
|
+
* OpenAPI só permite: A-Z a-z 0-9 - . _
|
|
3422
|
+
*/
|
|
3423
|
+
static sanitizeSchemaName(name) {
|
|
3424
|
+
// Decodificar URL encoding primeiro (%7C -> |)
|
|
3425
|
+
let decoded = name;
|
|
3426
|
+
try {
|
|
3427
|
+
decoded = decodeURIComponent(name);
|
|
3428
|
+
}
|
|
3429
|
+
catch {
|
|
3430
|
+
// Se falhar, usar o nome original
|
|
3431
|
+
}
|
|
3432
|
+
// Substituir caracteres inválidos por underscore
|
|
3433
|
+
// Caracteres válidos: A-Z a-z 0-9 - . _
|
|
3434
|
+
return decoded
|
|
3435
|
+
.replace(/[|]/g, '_OR_') // | -> _OR_
|
|
3436
|
+
.replace(/@/g, '_AT_') // @ -> _AT_
|
|
3437
|
+
.replace(/#/g, '_HASH_') // # -> _HASH_
|
|
3438
|
+
.replace(/%/g, '_PCT_') // % -> _PCT_
|
|
3439
|
+
.replace(/[^A-Za-z0-9._-]/g, '_') // Outros inválidos -> _
|
|
3440
|
+
.replace(/_+/g, '_') // Múltiplos underscores -> um só
|
|
3441
|
+
.replace(/^_|_$/g, ''); // Remover underscores no início/fim
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* 🔧 Extrair parâmetros da API
|
|
3445
|
+
*/
|
|
3446
|
+
static extractParameters(api) {
|
|
3447
|
+
const parameters = [];
|
|
3448
|
+
try {
|
|
3449
|
+
// 🎯 Lidar com URLs relativas e absolutas
|
|
3450
|
+
let searchParams;
|
|
3451
|
+
if (api.url.startsWith('http://') || api.url.startsWith('https://')) {
|
|
3452
|
+
const url = new URL(api.url);
|
|
3453
|
+
searchParams = url.searchParams;
|
|
3454
|
+
}
|
|
3455
|
+
else {
|
|
3456
|
+
// URL relativa - extrair query string manualmente
|
|
3457
|
+
const queryString = api.url.split('?')[1];
|
|
3458
|
+
searchParams = new URLSearchParams(queryString || '');
|
|
3459
|
+
}
|
|
3460
|
+
// Query parameters
|
|
3461
|
+
searchParams.forEach((value, key) => {
|
|
3462
|
+
parameters.push({
|
|
3463
|
+
name: key,
|
|
3464
|
+
in: 'query',
|
|
3465
|
+
schema: { type: 'string' },
|
|
3466
|
+
example: value,
|
|
3467
|
+
});
|
|
3468
|
+
});
|
|
3469
|
+
// Headers importantes
|
|
3470
|
+
if (api.headers) {
|
|
3471
|
+
Object.keys(api.headers).forEach((header) => {
|
|
3472
|
+
if (!['content-type', 'user-agent', 'accept'].includes(header.toLowerCase())) {
|
|
3473
|
+
parameters.push({
|
|
3474
|
+
name: header,
|
|
3475
|
+
in: 'header',
|
|
3476
|
+
schema: { type: 'string' },
|
|
3477
|
+
example: api.headers[header],
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
catch {
|
|
3484
|
+
// Ignorar erros de parsing
|
|
3485
|
+
}
|
|
3486
|
+
return parameters;
|
|
3487
|
+
}
|
|
3488
|
+
/**
|
|
3489
|
+
* 🛤️ Gerar paths do Swagger
|
|
3490
|
+
*/
|
|
3491
|
+
static generateSwaggerPaths(apis) {
|
|
3492
|
+
const paths = {};
|
|
3493
|
+
apis.forEach((api) => {
|
|
3494
|
+
const pathKey = api.url.replace(/https?:\/\/[^/]+/, '') || '/unknown';
|
|
3495
|
+
const method = api.method.toLowerCase();
|
|
3496
|
+
if (!paths[pathKey]) {
|
|
3497
|
+
paths[pathKey] = {};
|
|
3498
|
+
}
|
|
3499
|
+
paths[pathKey][method] = {
|
|
3500
|
+
summary: `${api.method} ${pathKey}`,
|
|
3501
|
+
description: `Detectado no teste: ${api.testName}`,
|
|
3502
|
+
responses: {
|
|
3503
|
+
[api.statusCode || '200']: {
|
|
3504
|
+
description: 'Response',
|
|
3505
|
+
},
|
|
3506
|
+
},
|
|
3507
|
+
};
|
|
3508
|
+
});
|
|
3509
|
+
return paths;
|
|
3510
|
+
}
|
|
3511
|
+
static async ensureOutputDir() {
|
|
3512
|
+
try {
|
|
3513
|
+
await fs.promises.mkdir(AutoDocs.config.outputDir, { recursive: true });
|
|
3514
|
+
}
|
|
3515
|
+
catch (error) {
|
|
3516
|
+
Logger.warning(`⚠️ AutoDocs: Erro ao criar diretório de saída: ${error}`);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
// 🚀 AUTO-INICIALIZAÇÃO COMPLETA: Sistema totalmente automático
|
|
3521
|
+
try {
|
|
3522
|
+
// Evitar múltiplas inicializações - usar flag global
|
|
3523
|
+
const globalFlag = 'AUTOCORE_AUTODOCS_INITIALIZED';
|
|
3524
|
+
const isAlreadyInitialized = global[globalFlag] === true;
|
|
3525
|
+
if (!isAlreadyInitialized) {
|
|
3526
|
+
// Detectar ambiente Playwright/Teste de forma mais robusta
|
|
3527
|
+
const isPlaywrightEnvironment = process.env.PLAYWRIGHT_TEST_BASE_URL !== undefined ||
|
|
3528
|
+
process.env.PWTEST_SKIP_TEST_OUTPUT !== undefined ||
|
|
3529
|
+
process.env.NODE_ENV === 'test' ||
|
|
3530
|
+
process.env.VITEST === 'true' ||
|
|
3531
|
+
process.env.JEST_WORKER_ID !== undefined ||
|
|
3532
|
+
typeof globalThis.test === 'function' ||
|
|
3533
|
+
typeof globalThis.expect === 'function' ||
|
|
3534
|
+
typeof globalThis.it === 'function' ||
|
|
3535
|
+
typeof globalThis.describe === 'function' ||
|
|
3536
|
+
process.argv.some((arg) => arg.includes('playwright') ||
|
|
3537
|
+
arg.includes('vitest') ||
|
|
3538
|
+
arg.includes('jest'));
|
|
3539
|
+
if (isPlaywrightEnvironment) {
|
|
3540
|
+
// Marcar como inicializado globalmente
|
|
3541
|
+
;
|
|
3542
|
+
global[globalFlag] = true;
|
|
3543
|
+
// Auto-inicialização no próximo tick
|
|
3544
|
+
process.nextTick(async () => {
|
|
3545
|
+
try {
|
|
3546
|
+
await AutoDocs.initialize({
|
|
3547
|
+
projectName: process.env.npm_package_name || 'Test Hub',
|
|
3548
|
+
outputDir: './docs',
|
|
3549
|
+
config: {
|
|
3550
|
+
enabled: true,
|
|
3551
|
+
categories: ['api', 'ssh', 'db', 'ui', 'mobile'],
|
|
3552
|
+
generateSwagger: true,
|
|
3553
|
+
includeRequestBody: true,
|
|
3554
|
+
includeResponseBody: true,
|
|
3555
|
+
},
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
catch (error) {
|
|
3559
|
+
process.stderr.write(` ⚠️ AutoDocs: Erro na auto-inicialização: ${error}\n`);
|
|
3560
|
+
}
|
|
3561
|
+
});
|
|
3562
|
+
// Auto-finalização quando o processo termina - APENAS UMA VEZ
|
|
3563
|
+
const globalFlagFinalization = 'AUTOCORE_AUTODOCS_FINALIZED';
|
|
3564
|
+
const autoFinalize = async () => {
|
|
3565
|
+
if (global[globalFlagFinalization] === true) {
|
|
3566
|
+
return; // Evitar execuções duplicadas
|
|
3567
|
+
}
|
|
3568
|
+
;
|
|
3569
|
+
global[globalFlagFinalization] = true;
|
|
3570
|
+
try {
|
|
3571
|
+
process.stdout.write('🏁 AutoDocs: Finalizando e gerando documentação automática...\n');
|
|
3572
|
+
await AutoDocs.generateDocs();
|
|
3573
|
+
process.stdout.write('✅ AutoDocs: Documentação gerada automaticamente em ./docs\n');
|
|
3574
|
+
}
|
|
3575
|
+
catch (error) {
|
|
3576
|
+
process.stderr.write(`❌ AutoDocs: Erro na finalização automática: ${error}\n`);
|
|
3577
|
+
}
|
|
3578
|
+
};
|
|
3579
|
+
// Hook de finalização única - evitar múltiplas execuções
|
|
3580
|
+
process.once('beforeExit', autoFinalize);
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
catch {
|
|
3585
|
+
// Ignorar erros de auto-inicialização silenciosamente
|
|
3586
|
+
}
|