@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,1632 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { BaseError, IntegrationError, ValidationError, } from '../functions/errors/index.js';
|
|
3
|
+
import { ExecutionTracker } from '../hubdocs/ExecutionTracker.js';
|
|
4
|
+
import { TestContext } from '../testContext/TestContext.js';
|
|
5
|
+
import { UnifiedReportManager } from '../testContext/UnifiedReportManager.js';
|
|
6
|
+
import { Logger } from '../utils/Logger.js';
|
|
7
|
+
import { MobileConnection } from './MobileConnection.js';
|
|
8
|
+
// 🤖 IMPORTAÇÃO CRÍTICA: Força inicialização do StatementTracker para projetos Mobile
|
|
9
|
+
import '../hubdocs/StatementTracker.js';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { StatementTracker } from '../hubdocs/StatementTracker.js';
|
|
12
|
+
import { EvidenceCapture } from '../utils/EvidenceCapture.js';
|
|
13
|
+
import { DeviceFarmViewer } from './DeviceFarmViewer.js';
|
|
14
|
+
// 🚫 REMOVIDO: import { installDatabaseInterceptor } from '../utils/DatabaseInterceptor.js'
|
|
15
|
+
/**
|
|
16
|
+
* Enum para direções de swipe
|
|
17
|
+
*/
|
|
18
|
+
export var Direcao;
|
|
19
|
+
(function (Direcao) {
|
|
20
|
+
Direcao["CIMA"] = "CIMA";
|
|
21
|
+
Direcao["BAIXO"] = "BAIXO";
|
|
22
|
+
Direcao["ESQUERDA"] = "ESQUERDA";
|
|
23
|
+
Direcao["DIREITA"] = "DIREITA";
|
|
24
|
+
})(Direcao || (Direcao = {}));
|
|
25
|
+
/**
|
|
26
|
+
* Classe utilitária para interações com aplicativos mobile via Appium
|
|
27
|
+
* Fornece métodos para manipulação de elementos, gestos e validações
|
|
28
|
+
* Baseada nos padrões Actions do framework mobile
|
|
29
|
+
*/
|
|
30
|
+
export class MobileActions {
|
|
31
|
+
/**
|
|
32
|
+
* 🧹 Limpa pasta de screenshots mobile no início da execução
|
|
33
|
+
*/
|
|
34
|
+
static async cleanMobileScreenshots() {
|
|
35
|
+
try {
|
|
36
|
+
const screenshotDir = path.join(process.cwd(), 'test-results', 'mobile-screenshots');
|
|
37
|
+
if (fs.existsSync(screenshotDir)) {
|
|
38
|
+
// Remove todos os arquivos PNG da pasta
|
|
39
|
+
const files = fs.readdirSync(screenshotDir);
|
|
40
|
+
const pngFiles = files.filter((file) => file.endsWith('.png'));
|
|
41
|
+
for (const file of pngFiles) {
|
|
42
|
+
const filePath = path.join(screenshotDir, file);
|
|
43
|
+
fs.unlinkSync(filePath);
|
|
44
|
+
}
|
|
45
|
+
Logger.info(`🧹 Limpos ${pngFiles.length} screenshot(s) mobile da execução anterior`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Criar diretório se não existir
|
|
49
|
+
fs.mkdirSync(screenshotDir, { recursive: true });
|
|
50
|
+
Logger.info(`📁 Diretório de screenshots mobile criado: ${screenshotDir}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
Logger.warning(`⚠️ Erro ao limpar screenshots mobile: ${String(error)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 🚫 ULTRA-RADICAL SSL KILLER: Initialize before any mobile operation
|
|
59
|
+
*/
|
|
60
|
+
static async initializeSSLKillerForMobile() {
|
|
61
|
+
try {
|
|
62
|
+
// Force global SSL killer from MobileConnection
|
|
63
|
+
await MobileConnection.initializeGlobalSSLKiller();
|
|
64
|
+
// Additional mobile-specific SSL killer
|
|
65
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
66
|
+
process.env.HTTPS_PROXY_REJECT_UNAUTHORIZED = '0';
|
|
67
|
+
process.env.SSL_VERIFY = '0';
|
|
68
|
+
process.env.DISABLE_SSL_VERIFICATION = '1';
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
Logger.warning(`⚠️ SSL Killer initialization warning: ${String(error)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* ✅ NOVO: Detecta o tipo do seletor mobile
|
|
76
|
+
* @private
|
|
77
|
+
* @param {string} selector Seletor a analisar
|
|
78
|
+
* @returns {string} Tipo do seletor
|
|
79
|
+
*/
|
|
80
|
+
static detectMobileSelectorType(selector) {
|
|
81
|
+
// XPath
|
|
82
|
+
if (selector.startsWith('//') ||
|
|
83
|
+
selector.startsWith('/') ||
|
|
84
|
+
selector.includes('[@')) {
|
|
85
|
+
return 'XPath';
|
|
86
|
+
}
|
|
87
|
+
// iOS Accessibility ID (XCUID)
|
|
88
|
+
if (selector.includes('accessibility-id') || selector.includes('~')) {
|
|
89
|
+
return 'iOS Accessibility ID';
|
|
90
|
+
}
|
|
91
|
+
// Android Resource ID
|
|
92
|
+
if (selector.includes(':id/') || selector.includes('android:id/')) {
|
|
93
|
+
return 'Android Resource ID';
|
|
94
|
+
}
|
|
95
|
+
// iOS Predicate
|
|
96
|
+
if (selector.includes('type=') && selector.includes('name=')) {
|
|
97
|
+
return 'iOS Predicate';
|
|
98
|
+
}
|
|
99
|
+
// Android UiSelector
|
|
100
|
+
if (selector.includes('UiSelector') ||
|
|
101
|
+
selector.includes('resourceId') ||
|
|
102
|
+
selector.includes('className')) {
|
|
103
|
+
return 'Android UiSelector';
|
|
104
|
+
}
|
|
105
|
+
// Text/Content Description
|
|
106
|
+
if (selector.startsWith('text=') || selector.startsWith('content-desc=')) {
|
|
107
|
+
return 'Text/Content-Desc';
|
|
108
|
+
}
|
|
109
|
+
// Class Name
|
|
110
|
+
if (selector.includes('android.widget.') ||
|
|
111
|
+
selector.includes('XCUIElementType')) {
|
|
112
|
+
return 'Class Name';
|
|
113
|
+
}
|
|
114
|
+
// ID simples
|
|
115
|
+
if (!(selector.includes(' ') ||
|
|
116
|
+
selector.includes('[') ||
|
|
117
|
+
selector.includes('='))) {
|
|
118
|
+
return 'ID';
|
|
119
|
+
}
|
|
120
|
+
return 'Mobile Selector';
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* ✅ NOVO: Função auxiliar para criar objeto de ação mobile para o AutoDocs
|
|
124
|
+
* @private
|
|
125
|
+
* @param {string} action Nome da ação
|
|
126
|
+
* @param {string} selector Seletor do elemento
|
|
127
|
+
* @param {string} description Descrição da ação
|
|
128
|
+
* @param {any} [value] Valor da ação (opcional)
|
|
129
|
+
* @param {boolean} [captureScreenshot] Se deve capturar screenshot
|
|
130
|
+
* @returns {object} Objeto de ação mobile
|
|
131
|
+
*/
|
|
132
|
+
static async createMobileAction(action, selector, description, value, captureScreenshot = false) {
|
|
133
|
+
const deviceId = MobileConnection.getCurrentDeviceId();
|
|
134
|
+
const platform = MobileConnection.getCurrentPlatform();
|
|
135
|
+
const testName = TestContext.getSafeTestInfo()?.title || 'Mobile Action';
|
|
136
|
+
let screenshotPath;
|
|
137
|
+
// 📸 Capturar screenshot se solicitado
|
|
138
|
+
if (captureScreenshot) {
|
|
139
|
+
try {
|
|
140
|
+
const screenshot = await MobileConnection.captureScreenshotBuffer(`${action}-${description || 'unknown'}`);
|
|
141
|
+
if (screenshot) {
|
|
142
|
+
// Salvar screenshot e obter o caminho
|
|
143
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
144
|
+
const safeName = `${action}-${description || 'action'}`.replace(/[^a-zA-Z0-9\-_]/g, '_');
|
|
145
|
+
const fileName = `mobile-${timestamp}-${safeName}.png`;
|
|
146
|
+
const evidenceDir = './test-results/mobile-screenshots';
|
|
147
|
+
if (!fs.existsSync(evidenceDir)) {
|
|
148
|
+
fs.mkdirSync(evidenceDir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
const filePath = path.join(evidenceDir, fileName);
|
|
151
|
+
fs.writeFileSync(filePath, screenshot);
|
|
152
|
+
screenshotPath = `../test-results/mobile-screenshots/${fileName}`;
|
|
153
|
+
Logger.info(`📸 Screenshot capturada para ação ${action}: ${screenshotPath}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
Logger.warning(`⚠️ Erro ao capturar screenshot para ação ${action}: ${String(error)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
action,
|
|
162
|
+
element: selector || 'unknown',
|
|
163
|
+
device: deviceId || 'unknown',
|
|
164
|
+
platform,
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
testName,
|
|
167
|
+
value: value || description || '',
|
|
168
|
+
selectorType: selector
|
|
169
|
+
? MobileActions.detectMobileSelectorType(selector)
|
|
170
|
+
: undefined,
|
|
171
|
+
coordinates: undefined,
|
|
172
|
+
screenshot: screenshotPath,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* ✅ HELPER: Garante que o driver existe e não é null
|
|
177
|
+
* @private
|
|
178
|
+
*/
|
|
179
|
+
static ensureDriverExists() {
|
|
180
|
+
const driver = MobileConnection.getDriver();
|
|
181
|
+
if (!driver) {
|
|
182
|
+
throw new Error('Driver mobile não inicializado. Execute MobileConnection.connect() primeiro.');
|
|
183
|
+
}
|
|
184
|
+
return driver;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Executa um método específico no contexto mobile com logs unificados.
|
|
188
|
+
* @param nomeMetodo Nome do método para identificação nos logs.
|
|
189
|
+
* @param executarAcao Função que executa a ação específica.
|
|
190
|
+
* @returns Resultado da execução da ação.
|
|
191
|
+
* @private
|
|
192
|
+
*/
|
|
193
|
+
static async executarComLog(nomeMetodo, executarAcao) {
|
|
194
|
+
// 🚫 FORCE SSL KILLER before EVERY mobile operation
|
|
195
|
+
await MobileActions.initializeSSLKillerForMobile();
|
|
196
|
+
const startTime = Date.now();
|
|
197
|
+
Logger.info(`🤖 ${nomeMetodo} - Iniciando ação mobile`);
|
|
198
|
+
// ✅ NOVO: Capturar evidência antes da ação
|
|
199
|
+
let beforeScreenshot = null;
|
|
200
|
+
try {
|
|
201
|
+
beforeScreenshot = await MobileConnection.captureEvidence(`before_${nomeMetodo}`);
|
|
202
|
+
}
|
|
203
|
+
catch (evidenceError) {
|
|
204
|
+
Logger.warning(`⚠️ Erro ao capturar evidência antes: ${String(evidenceError)}`);
|
|
205
|
+
}
|
|
206
|
+
// ✅ NOVO: Delay configurável para visualização (antes da ação)
|
|
207
|
+
const preActionDelay = Number.parseInt(process.env.MOBILE_PRE_ACTION_DELAY || '500', 10);
|
|
208
|
+
if (preActionDelay > 0) {
|
|
209
|
+
Logger.info(`Aguardando ${preActionDelay}ms antes da ação (visualização)`);
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, preActionDelay));
|
|
211
|
+
}
|
|
212
|
+
// Registrar no UnifiedReportManager se o teste estiver configurado
|
|
213
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
214
|
+
if (testInfo?.title) {
|
|
215
|
+
try {
|
|
216
|
+
UnifiedReportManager.adicionarLog(testInfo.title, `🔄 Executando: ${nomeMetodo}`);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Silenciosamente ignorar se o contexto não estiver configurado
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const resultado = await executarAcao();
|
|
224
|
+
const duration = Date.now() - startTime;
|
|
225
|
+
// ✅ NOVO: Capturar evidência após a ação
|
|
226
|
+
let afterScreenshot = null;
|
|
227
|
+
try {
|
|
228
|
+
afterScreenshot = await MobileConnection.captureEvidence(`after_${nomeMetodo}`);
|
|
229
|
+
// 🆕 INTEGRAR COM EVIDENCECAPTURE
|
|
230
|
+
if (testInfo?.title && afterScreenshot) {
|
|
231
|
+
await EvidenceCapture.captureAction(testInfo.title, `Mobile: ${nomeMetodo}`, `✅ Ação concluída com sucesso (${duration}ms)`, { duration, method: nomeMetodo }, afterScreenshot);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (evidenceError) {
|
|
235
|
+
Logger.warning(`⚠️ Erro ao capturar evidência depois: ${String(evidenceError)}`);
|
|
236
|
+
}
|
|
237
|
+
// ✅ NOVO: Delay configurável para visualização (após a ação)
|
|
238
|
+
const postActionDelay = Number.parseInt(process.env.MOBILE_POST_ACTION_DELAY || '1000', 10);
|
|
239
|
+
if (postActionDelay > 0) {
|
|
240
|
+
Logger.info(`Aguardando ${postActionDelay}ms após ação (visualização)`);
|
|
241
|
+
await new Promise((resolve) => setTimeout(resolve, postActionDelay));
|
|
242
|
+
}
|
|
243
|
+
Logger.success(`${nomeMetodo} executado com sucesso (${duration}ms)`);
|
|
244
|
+
// Log de sucesso no UnifiedReportManager
|
|
245
|
+
if (testInfo?.title) {
|
|
246
|
+
try {
|
|
247
|
+
UnifiedReportManager.adicionarLog(testInfo.title, `✅ ${nomeMetodo} executado com sucesso (${duration}ms)`);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Silenciosamente ignorar se o contexto não estiver configurado
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Configurar limpeza automática se ainda não foi configurada
|
|
254
|
+
MobileActions.ensureAutoCleanupConfigured();
|
|
255
|
+
return resultado;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
const duration = Date.now() - startTime;
|
|
259
|
+
const errorMessage = String(error);
|
|
260
|
+
// ✅ NOVO: Capturar evidência do erro
|
|
261
|
+
let errorScreenshot = null;
|
|
262
|
+
try {
|
|
263
|
+
errorScreenshot = await MobileConnection.captureEvidence(`error_${nomeMetodo}`);
|
|
264
|
+
// 🆕 INTEGRAR COM EVIDENCECAPTURE
|
|
265
|
+
if (testInfo?.title && errorScreenshot) {
|
|
266
|
+
await EvidenceCapture.captureAction(testInfo.title, `Mobile: ${nomeMetodo}`, `❌ ERRO: ${errorMessage} (${duration}ms)`, { duration, method: nomeMetodo, error: errorMessage }, errorScreenshot);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (evidenceError) {
|
|
270
|
+
Logger.warning(`⚠️ Erro ao capturar evidência de erro: ${String(evidenceError)}`);
|
|
271
|
+
}
|
|
272
|
+
Logger.error(`❌ Erro ao executar ${nomeMetodo} após ${duration}ms`, error);
|
|
273
|
+
// Log de erro no UnifiedReportManager
|
|
274
|
+
if (testInfo?.title) {
|
|
275
|
+
try {
|
|
276
|
+
UnifiedReportManager.adicionarLog(testInfo.title, `❌ Erro ao executar ${nomeMetodo} (${duration}ms): ${errorMessage}`);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// Silenciosamente ignorar se o contexto não estiver configurado
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Criar um IntegrationError se não for já um erro customizado
|
|
283
|
+
if (error instanceof BaseError) {
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
throw new IntegrationError('MOBILE_ACTION_FAILED', `Mobile action '${nomeMetodo}' failed: ${String(error)}`, `Falha na ação mobile '${nomeMetodo}': ${String(error)}`, error instanceof Error ? error : new Error(String(error)), {
|
|
287
|
+
metadata: { method: nomeMetodo, timestamp: new Date().toISOString() },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Garante que a limpeza automática está configurada para o teste mobile.
|
|
293
|
+
* @private
|
|
294
|
+
*/
|
|
295
|
+
static ensureAutoCleanupConfigured() {
|
|
296
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
297
|
+
if (!(testInfo && MobileConnection.isConnected()))
|
|
298
|
+
return;
|
|
299
|
+
// Verificar se já foi configurado para este teste
|
|
300
|
+
const testTitle = testInfo.title;
|
|
301
|
+
if (global[`mobileCleanup_${testTitle}`]) {
|
|
302
|
+
return; // Já configurado
|
|
303
|
+
}
|
|
304
|
+
// Marcar como configurado
|
|
305
|
+
;
|
|
306
|
+
global[`mobileCleanup_${testTitle}`] = true;
|
|
307
|
+
// Configurar limpeza automática via Promise cleanup
|
|
308
|
+
let cleanupExecuted = false;
|
|
309
|
+
// Interceptar quando o teste realmente termina
|
|
310
|
+
const executeCleanup = async () => {
|
|
311
|
+
if (cleanupExecuted)
|
|
312
|
+
return;
|
|
313
|
+
cleanupExecuted = true;
|
|
314
|
+
try {
|
|
315
|
+
const hasActiveSession = MobileConnection.isConnected();
|
|
316
|
+
if (hasActiveSession) {
|
|
317
|
+
Logger.info('🔚 Finalizando sessão mobile automaticamente...');
|
|
318
|
+
await MobileConnection.closeSession();
|
|
319
|
+
Logger.success('✅ Sessão mobile finalizada automaticamente');
|
|
320
|
+
if (testInfo?.title) {
|
|
321
|
+
UnifiedReportManager.adicionarLog(testInfo.title, '✅ Sessão mobile finalizada automaticamente');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
Logger.warning(`⚠️ Erro na limpeza automática mobile: ${String(error)}`);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
// Estratégia 1: Interceptar via Promise.allSettled no final
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
const originalAllSettled = Promise.allSettled;
|
|
332
|
+
Promise.allSettled = function (...args) {
|
|
333
|
+
const result = originalAllSettled.apply(this, args);
|
|
334
|
+
result.finally(() => {
|
|
335
|
+
setTimeout(executeCleanup, 100);
|
|
336
|
+
});
|
|
337
|
+
return result;
|
|
338
|
+
};
|
|
339
|
+
}, 100);
|
|
340
|
+
// Estratégia 2: Timeout como fallback
|
|
341
|
+
setTimeout(executeCleanup, 30_000); // 30 segundos
|
|
342
|
+
// Estratégia 3: Detectar quando não há mais atividade
|
|
343
|
+
let lastActivity = Date.now();
|
|
344
|
+
const activityChecker = setInterval(() => {
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
if (now - lastActivity > 5000) {
|
|
347
|
+
clearInterval(activityChecker);
|
|
348
|
+
setTimeout(executeCleanup, 500);
|
|
349
|
+
}
|
|
350
|
+
}, 1000);
|
|
351
|
+
global[`updateActivity_${testTitle}`] = () => {
|
|
352
|
+
lastActivity = Date.now();
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Realiza clique inteligente em elemento mobile, com fallback para coordenadas.
|
|
357
|
+
* @param seletor Seletor do elemento.
|
|
358
|
+
* @param descriptionToAction Descrição da ação.
|
|
359
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
360
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
361
|
+
*/
|
|
362
|
+
static async click(seletor, descriptionToAction, getSnapshot = true, // 🔧 MUDANÇA: Agora captura screenshot por padrão
|
|
363
|
+
timeout = TestContext.getProjectTimeout()) {
|
|
364
|
+
const startTime = Date.now();
|
|
365
|
+
// 🆕 Marcar uso automático do Mobile
|
|
366
|
+
UnifiedReportManager.registrarInicioExecucao();
|
|
367
|
+
// 🆕 Registrar statement no StatementTracker
|
|
368
|
+
let statementTimestamp = null;
|
|
369
|
+
try {
|
|
370
|
+
statementTimestamp = StatementTracker.startStatement('MobileActions', `click: ${descriptionToAction}`);
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
Logger.warning(`Não foi possível registrar statement no StatementTracker: ${String(error)}`);
|
|
374
|
+
}
|
|
375
|
+
// 🆕 Criar ação mobile para AutoDocs com screenshot
|
|
376
|
+
const mobileAction = await MobileActions.createMobileAction('click', seletor, descriptionToAction, undefined, getSnapshot);
|
|
377
|
+
// 🆕 Registrar no ExecutionTracker
|
|
378
|
+
ExecutionTracker.trackMobile(mobileAction);
|
|
379
|
+
// Atualizar atividade
|
|
380
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
381
|
+
if (testInfo?.title &&
|
|
382
|
+
global[`updateActivity_${testInfo.title}`]) {
|
|
383
|
+
;
|
|
384
|
+
global[`updateActivity_${testInfo.title}`]();
|
|
385
|
+
}
|
|
386
|
+
const driver = MobileActions.ensureDriverExists();
|
|
387
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
388
|
+
const elemento = await driver.$(seletor);
|
|
389
|
+
await elemento.waitForDisplayed({ timeout });
|
|
390
|
+
// Estratégia 1: Tentar clique direto
|
|
391
|
+
try {
|
|
392
|
+
Logger.info('Tentando clique direto no elemento');
|
|
393
|
+
const presente = await elemento.isDisplayed();
|
|
394
|
+
if (!presente) {
|
|
395
|
+
throw new ValidationError('MOBILE_ELEMENT_NOT_VISIBLE', 'Element is not visible for interaction', 'Elemento não visível para interação', undefined, { metadata: { selector: seletor } });
|
|
396
|
+
}
|
|
397
|
+
// Verificação nativa de habilitação
|
|
398
|
+
try {
|
|
399
|
+
const habilitado = await elemento.isEnabled();
|
|
400
|
+
if (!habilitado) {
|
|
401
|
+
Logger.warning('⚠️ Elemento pode estar desabilitado, tentando clique mesmo assim');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
Logger.info(`Verificação de habilitação ignorada: ${String(error)}`);
|
|
406
|
+
}
|
|
407
|
+
await driver.pause(300);
|
|
408
|
+
await elemento.click();
|
|
409
|
+
Logger.success(`Clique direto bem-sucedido: ${descriptionToAction}`);
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
Logger.warning(`Clique direto falhou, tentando por coordenadas: ${String(error)}`);
|
|
413
|
+
// Estratégia 2: Fallback para clique por coordenadas
|
|
414
|
+
try {
|
|
415
|
+
const location = await elemento.getLocation();
|
|
416
|
+
const size = await elemento.getSize();
|
|
417
|
+
const centerX = location.x + size.width / 2;
|
|
418
|
+
const centerY = location.y + size.height / 2;
|
|
419
|
+
Logger.info(`📍 Clicando por coordenadas: (${centerX}, ${centerY})`);
|
|
420
|
+
await driver.touchAction({
|
|
421
|
+
action: 'tap',
|
|
422
|
+
x: Math.round(centerX),
|
|
423
|
+
y: Math.round(centerY),
|
|
424
|
+
});
|
|
425
|
+
Logger.success(`✅ Clique por coordenadas bem-sucedido: ${descriptionToAction}`);
|
|
426
|
+
}
|
|
427
|
+
catch (coordError) {
|
|
428
|
+
Logger.error('Ambos os métodos de clique falharam');
|
|
429
|
+
// ✅ MELHORAR: Identificar tipo de erro
|
|
430
|
+
const errorMessage = String(error);
|
|
431
|
+
const coordErrorMessage = String(coordError);
|
|
432
|
+
let errorCategory = 'ELEMENTO_NAO_ENCONTRADO';
|
|
433
|
+
let userFriendlyMessage = 'Clique falhou: elemento não encontrado ou não acessível';
|
|
434
|
+
// Detectar problemas de SSL/Certificado/Frame
|
|
435
|
+
if (errorMessage.includes('SELF_SIGNED_CERT_IN_CHAIN') ||
|
|
436
|
+
coordErrorMessage.includes('SELF_SIGNED_CERT_IN_CHAIN') ||
|
|
437
|
+
errorMessage.includes('certificate') ||
|
|
438
|
+
errorMessage.includes('SSL') ||
|
|
439
|
+
errorMessage.includes('TLS')) {
|
|
440
|
+
errorCategory = 'CONEXAO_SSL_FRAME';
|
|
441
|
+
userFriendlyMessage =
|
|
442
|
+
'❌ PROBLEMA DE CONEXÃO: Erro de certificado SSL/TLS com o DeviceFarm. O frame mobile não consegue se comunicar com o servidor devido a problemas de certificado.';
|
|
443
|
+
}
|
|
444
|
+
// Detectar problemas de timeout/rede
|
|
445
|
+
else if (errorMessage.includes('timeout') ||
|
|
446
|
+
errorMessage.includes('ETIMEDOUT') ||
|
|
447
|
+
errorMessage.includes('ECONNRESET')) {
|
|
448
|
+
errorCategory = 'TIMEOUT_FRAME';
|
|
449
|
+
userFriendlyMessage =
|
|
450
|
+
'❌ PROBLEMA DE CONEXÃO: Timeout na comunicação com o DeviceFarm. O frame mobile perdeu conexão.';
|
|
451
|
+
}
|
|
452
|
+
// Detectar problemas de elemento não encontrado
|
|
453
|
+
else if (errorMessage.includes('element not found') ||
|
|
454
|
+
errorMessage.includes('no such element') ||
|
|
455
|
+
errorMessage.includes('element not visible')) {
|
|
456
|
+
errorCategory = 'ELEMENTO_NAO_ENCONTRADO';
|
|
457
|
+
userFriendlyMessage = `❌ PROBLEMA DE ELEMENTO: O elemento '${seletor}' não foi encontrado ou não está visível na tela.`;
|
|
458
|
+
}
|
|
459
|
+
// Detectar problemas de elemento não clicável
|
|
460
|
+
else if (errorMessage.includes('not clickable') ||
|
|
461
|
+
errorMessage.includes('element not interactable')) {
|
|
462
|
+
errorCategory = 'ELEMENTO_NAO_CLICAVEL';
|
|
463
|
+
userFriendlyMessage = `❌ PROBLEMA DE ELEMENTO: O elemento '${seletor}' foi encontrado mas não está clicável (pode estar sobreposto ou desabilitado).`;
|
|
464
|
+
}
|
|
465
|
+
Logger.info(`🔍 Diagnóstico: ${errorCategory}`);
|
|
466
|
+
Logger.info(`💡 ${userFriendlyMessage}`);
|
|
467
|
+
// 📝 NOVO: Registrar ação automaticamente no Statement ativo (erro)
|
|
468
|
+
const duration = Date.now() - startTime;
|
|
469
|
+
try {
|
|
470
|
+
StatementTracker.recordAction('MOBILE', `Click FALHOU: ${descriptionToAction || seletor}`, false, duration, {
|
|
471
|
+
selector: seletor,
|
|
472
|
+
action: 'click',
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch (recordError) {
|
|
476
|
+
// Falha silenciosa - não afetar funcionalidade principal
|
|
477
|
+
}
|
|
478
|
+
throw new IntegrationError(errorCategory, `Mobile click failed: ${errorMessage} | Coordinates: ${coordErrorMessage}`, userFriendlyMessage, error instanceof Error ? error : new Error(String(error)), {
|
|
479
|
+
metadata: {
|
|
480
|
+
selector: seletor,
|
|
481
|
+
originalError: errorMessage,
|
|
482
|
+
coordError: coordErrorMessage,
|
|
483
|
+
errorCategory,
|
|
484
|
+
diagnosticMessage: userFriendlyMessage,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (getSnapshot) {
|
|
490
|
+
// ✅ USAR MobileConnection.captureEvidence para integração com Playwright Report
|
|
491
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
// 📝 NOVO: Registrar ação automaticamente no Statement ativo (sucesso)
|
|
495
|
+
const duration = Date.now() - startTime;
|
|
496
|
+
try {
|
|
497
|
+
StatementTracker.recordAction('MOBILE', descriptionToAction || `Click: ${seletor}`, true, duration, {
|
|
498
|
+
selector: seletor,
|
|
499
|
+
action: 'click',
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
catch (recordError) {
|
|
503
|
+
// Falha silenciosa - não afetar funcionalidade principal
|
|
504
|
+
}
|
|
505
|
+
// 🆕 Finalizar statement com sucesso
|
|
506
|
+
if (statementTimestamp) {
|
|
507
|
+
try {
|
|
508
|
+
StatementTracker.finishStatement(statementTimestamp, true, `✅ ${descriptionToAction} (${duration}ms)`);
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
Logger.warning(`Não foi possível finalizar statement no StatementTracker: ${String(error)}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Realiza clique robusto em elemento mobile, com múltiplas tentativas.
|
|
517
|
+
* @param seletor Seletor do elemento.
|
|
518
|
+
* @param descriptionToAction Descrição da ação.
|
|
519
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
520
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
521
|
+
* @param retry Número de tentativas.
|
|
522
|
+
*/
|
|
523
|
+
static async clickNative(seletor, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout(), retry = 3) {
|
|
524
|
+
const driver = MobileActions.ensureDriverExists();
|
|
525
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
526
|
+
let lastError = null;
|
|
527
|
+
for (let tentativa = 1; tentativa <= retry; tentativa++) {
|
|
528
|
+
try {
|
|
529
|
+
Logger.info(`🔄 Tentativa ${tentativa}/${retry} de clique nativo`);
|
|
530
|
+
const elemento = await driver.$(seletor);
|
|
531
|
+
await elemento.waitForDisplayed({ timeout: timeout / retry });
|
|
532
|
+
const presente = await elemento.isDisplayed();
|
|
533
|
+
if (!presente) {
|
|
534
|
+
throw new Error(`Elemento não visível na tentativa ${tentativa}`);
|
|
535
|
+
}
|
|
536
|
+
await driver.pause(300);
|
|
537
|
+
await elemento.click();
|
|
538
|
+
Logger.success(`✅ Clique nativo bem-sucedido na tentativa ${tentativa}: ${descriptionToAction}`);
|
|
539
|
+
if (getSnapshot) {
|
|
540
|
+
// ✅ USAR MobileConnection.captureEvidence para integração com Playwright Report
|
|
541
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
lastError = error;
|
|
547
|
+
Logger.warning(`⚠️ Tentativa ${tentativa} falhou: ${String(error)}`);
|
|
548
|
+
if (tentativa < retry) {
|
|
549
|
+
await driver.pause(1000);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
throw new Error(`Clique falhou após ${retry} tentativas. Último erro: ${lastError?.message}`);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Realiza clique por coordenadas no centro do elemento.
|
|
558
|
+
* @param seletor Seletor do elemento.
|
|
559
|
+
* @param descriptionToAction Descrição da ação.
|
|
560
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
561
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
562
|
+
*/
|
|
563
|
+
static async clickByCoordinates(seletor, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout()) {
|
|
564
|
+
const driver = MobileActions.ensureDriverExists();
|
|
565
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
566
|
+
const elemento = await driver.$(seletor);
|
|
567
|
+
await elemento.waitForDisplayed({ timeout });
|
|
568
|
+
const location = await elemento.getLocation();
|
|
569
|
+
const size = await elemento.getSize();
|
|
570
|
+
const centerX = location.x + size.width / 2;
|
|
571
|
+
const centerY = location.y + size.height / 2;
|
|
572
|
+
Logger.info(`📍 Clicando nas coordenadas: (${centerX}, ${centerY})`);
|
|
573
|
+
await driver.touchAction({
|
|
574
|
+
action: 'tap',
|
|
575
|
+
x: Math.round(centerX),
|
|
576
|
+
y: Math.round(centerY),
|
|
577
|
+
});
|
|
578
|
+
Logger.success(`✅ Clique por coordenadas bem-sucedido: ${descriptionToAction}`);
|
|
579
|
+
if (getSnapshot) {
|
|
580
|
+
// ✅ USAR MobileConnection.captureEvidence para integração com Playwright Report
|
|
581
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Realiza um clique em uma coordenada específica na tela.
|
|
587
|
+
* @param point Coordenada {x, y}.
|
|
588
|
+
* @param descriptionToAction Descrição da ação.
|
|
589
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
590
|
+
*/
|
|
591
|
+
static async clickPoint(point, descriptionToAction, getSnapshot = false) {
|
|
592
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
593
|
+
if (testInfo?.title &&
|
|
594
|
+
global[`updateActivity_${testInfo.title}`]) {
|
|
595
|
+
;
|
|
596
|
+
global[`updateActivity_${testInfo.title}`]();
|
|
597
|
+
}
|
|
598
|
+
const driver = MobileActions.ensureDriverExists();
|
|
599
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
600
|
+
await driver.touchAction({
|
|
601
|
+
action: 'tap',
|
|
602
|
+
x: point.x,
|
|
603
|
+
y: point.y,
|
|
604
|
+
});
|
|
605
|
+
Logger.success(`✅ Clique na coordenada (${point.x}, ${point.y}): ${descriptionToAction}`);
|
|
606
|
+
if (getSnapshot) {
|
|
607
|
+
// ✅ USAR MobileConnection.captureEvidence para integração com Playwright Report
|
|
608
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Valida se um elemento está presente na tela.
|
|
614
|
+
* @param seletor Seletor do elemento.
|
|
615
|
+
* @param descriptionToAction Descrição da ação.
|
|
616
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
617
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
618
|
+
* @returns True se o elemento está presente.
|
|
619
|
+
*/
|
|
620
|
+
static async validateObject(seletor, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout()) {
|
|
621
|
+
const driver = MobileActions.ensureDriverExists();
|
|
622
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
623
|
+
try {
|
|
624
|
+
const elemento = await driver.$(seletor);
|
|
625
|
+
await elemento.waitForDisplayed({ timeout });
|
|
626
|
+
const presente = await elemento.isDisplayed();
|
|
627
|
+
if (presente) {
|
|
628
|
+
Logger.success(`✅ Validação passou: elemento presente - ${descriptionToAction}`);
|
|
629
|
+
if (getSnapshot) {
|
|
630
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
631
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
632
|
+
}
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
const errorMsg = `Validação falhou: elemento não está visível - ${descriptionToAction}`;
|
|
636
|
+
Logger.error(errorMsg);
|
|
637
|
+
throw new Error(errorMsg);
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
const errorMsg = `Validação falhou: elemento não encontrado - ${descriptionToAction}`;
|
|
641
|
+
Logger.error(errorMsg);
|
|
642
|
+
throw new Error(errorMsg);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Valida que um elemento não está presente na tela.
|
|
648
|
+
* @param seletor Seletor do elemento.
|
|
649
|
+
* @param descriptionToAction Descrição da ação.
|
|
650
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
651
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
652
|
+
* @returns True se o elemento não está presente.
|
|
653
|
+
*/
|
|
654
|
+
static async validateObjectNotExist(seletor, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout()) {
|
|
655
|
+
const driver = MobileActions.ensureDriverExists();
|
|
656
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
657
|
+
try {
|
|
658
|
+
const elemento = await driver.$(seletor);
|
|
659
|
+
await elemento.waitForDisplayed({ timeout, reverse: true });
|
|
660
|
+
const presente = await elemento.isDisplayed();
|
|
661
|
+
if (presente) {
|
|
662
|
+
const errorMsg = `Validação falhou: elemento ainda está presente - ${descriptionToAction}`;
|
|
663
|
+
Logger.error(errorMsg);
|
|
664
|
+
throw new Error(errorMsg);
|
|
665
|
+
}
|
|
666
|
+
Logger.success(`✅ Validação passou: elemento não está presente - ${descriptionToAction}`);
|
|
667
|
+
if (getSnapshot) {
|
|
668
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
669
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
670
|
+
}
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
Logger.success(`✅ Validação passou: elemento não existe - ${descriptionToAction}`);
|
|
675
|
+
if (getSnapshot) {
|
|
676
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
677
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
678
|
+
}
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Define um texto em um campo de entrada.
|
|
685
|
+
* @param seletor Seletor do campo.
|
|
686
|
+
* @param text Texto a ser inserido.
|
|
687
|
+
* @param descriptionToAction Descrição da ação.
|
|
688
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
689
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
690
|
+
*/
|
|
691
|
+
static async setText(seletor, text, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout()) {
|
|
692
|
+
const driver = MobileActions.ensureDriverExists();
|
|
693
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
694
|
+
const elemento = await driver.$(seletor);
|
|
695
|
+
await elemento.waitForDisplayed({ timeout });
|
|
696
|
+
try {
|
|
697
|
+
const habilitado = await elemento.isEnabled();
|
|
698
|
+
if (!habilitado) {
|
|
699
|
+
Logger.warning('⚠️ Elemento pode estar desabilitado');
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
catch (error) {
|
|
703
|
+
Logger.info(`ℹ️ Verificação de habilitação ignorada: ${String(error)}`);
|
|
704
|
+
}
|
|
705
|
+
await elemento.clearValue();
|
|
706
|
+
await elemento.setValue(text);
|
|
707
|
+
Logger.success(`✅ Texto inserido com sucesso: "${text}" - ${descriptionToAction}`);
|
|
708
|
+
if (getSnapshot) {
|
|
709
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
710
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Envia caracteres individualmente para um elemento.
|
|
716
|
+
* @param seletor Seletor do campo.
|
|
717
|
+
* @param text Texto a ser inserido.
|
|
718
|
+
* @param descriptionToAction Descrição da ação.
|
|
719
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
720
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
721
|
+
* @param delayBetweenChars Delay entre caracteres em ms.
|
|
722
|
+
*/
|
|
723
|
+
static async setChar(seletor, text, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout(), delayBetweenChars = 100) {
|
|
724
|
+
const driver = MobileActions.ensureDriverExists();
|
|
725
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
726
|
+
const elemento = await driver.$(seletor);
|
|
727
|
+
await elemento.waitForDisplayed({ timeout });
|
|
728
|
+
try {
|
|
729
|
+
const habilitado = await elemento.isEnabled();
|
|
730
|
+
if (!habilitado) {
|
|
731
|
+
Logger.warning('⚠️ Elemento pode estar desabilitado');
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
Logger.info(`ℹ️ Verificação de habilitação ignorada: ${String(error)}`);
|
|
736
|
+
}
|
|
737
|
+
await elemento.clearValue();
|
|
738
|
+
for (const char of text) {
|
|
739
|
+
await elemento.addValue(char);
|
|
740
|
+
await driver.pause(delayBetweenChars);
|
|
741
|
+
}
|
|
742
|
+
Logger.success(`✅ Caracteres digitados com sucesso: "${text}" - ${descriptionToAction}`);
|
|
743
|
+
if (getSnapshot) {
|
|
744
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
745
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Obtém o texto presente em um elemento.
|
|
751
|
+
* @param seletor Seletor do elemento.
|
|
752
|
+
* @param descriptionToAction Descrição da ação.
|
|
753
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
754
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
755
|
+
* @returns Texto do elemento.
|
|
756
|
+
*/
|
|
757
|
+
static async getText(seletor, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout()) {
|
|
758
|
+
const driver = MobileActions.ensureDriverExists();
|
|
759
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
760
|
+
const elemento = await driver.$(seletor);
|
|
761
|
+
await elemento.waitForDisplayed({ timeout });
|
|
762
|
+
const texto = await elemento.getText();
|
|
763
|
+
Logger.success(`Texto obtido: "${texto}" - ${descriptionToAction}`);
|
|
764
|
+
if (getSnapshot) {
|
|
765
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
766
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
767
|
+
}
|
|
768
|
+
return texto;
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Alterna o contexto da aplicação (ex: Nativo/Webview).
|
|
773
|
+
* @param contextName Nome do contexto.
|
|
774
|
+
* @param descriptionToAction Descrição da ação.
|
|
775
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
776
|
+
*/
|
|
777
|
+
static async switchToContext(contextName, descriptionToAction, getSnapshot = false) {
|
|
778
|
+
const driver = MobileActions.ensureDriverExists();
|
|
779
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
780
|
+
const contextos = await driver.getContexts();
|
|
781
|
+
Logger.info(`📱 Contextos disponíveis: ${contextos.join(', ')}`);
|
|
782
|
+
if (!contextos.includes(contextName)) {
|
|
783
|
+
throw new Error(`Contexto '${contextName}' não encontrado. Disponíveis: ${contextos.join(', ')}`);
|
|
784
|
+
}
|
|
785
|
+
await driver.switchContext(contextName);
|
|
786
|
+
const contextoAtual = await driver.getContext();
|
|
787
|
+
Logger.success(`✅ Contexto alterado para: ${contextoAtual} - ${descriptionToAction}`);
|
|
788
|
+
if (getSnapshot) {
|
|
789
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
790
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Verifica se um elemento está presente na tela.
|
|
796
|
+
* @param seletor Seletor do elemento.
|
|
797
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
798
|
+
* @returns True se o elemento está presente.
|
|
799
|
+
*/
|
|
800
|
+
static async isPresent(seletor, timeout = TestContext.getProjectTimeout()) {
|
|
801
|
+
const driver = MobileActions.ensureDriverExists();
|
|
802
|
+
try {
|
|
803
|
+
const elemento = await driver.$(seletor);
|
|
804
|
+
await elemento.waitForDisplayed({ timeout });
|
|
805
|
+
const presente = await elemento.isDisplayed();
|
|
806
|
+
Logger.info(`ℹ️ Elemento ${presente ? 'presente' : 'não presente'}: ${seletor}`);
|
|
807
|
+
return presente;
|
|
808
|
+
}
|
|
809
|
+
catch {
|
|
810
|
+
Logger.info(`ℹ️ Elemento não presente: ${seletor}`);
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Realiza a ação de voltar na navegação.
|
|
816
|
+
* @param descriptionToAction Descrição da ação.
|
|
817
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
818
|
+
*/
|
|
819
|
+
static async back(descriptionToAction, getSnapshot = false) {
|
|
820
|
+
const driver = MobileActions.ensureDriverExists();
|
|
821
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
822
|
+
await driver.back();
|
|
823
|
+
Logger.success(`Ação de voltar executada - ${descriptionToAction}`);
|
|
824
|
+
if (getSnapshot) {
|
|
825
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
826
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Realiza um gesto de swipe na tela.
|
|
832
|
+
* @param xInit Coordenada X inicial.
|
|
833
|
+
* @param yInit Coordenada Y inicial.
|
|
834
|
+
* @param xFinal Coordenada X final.
|
|
835
|
+
* @param yFinal Coordenada Y final.
|
|
836
|
+
* @param descriptionToAction Descrição da ação.
|
|
837
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
838
|
+
* @param duracao Duração do swipe em ms.
|
|
839
|
+
*/
|
|
840
|
+
static async swipe(xInit, yInit, xFinal, yFinal, descriptionToAction, getSnapshot = false, duracao = 1000) {
|
|
841
|
+
const driver = MobileActions.ensureDriverExists();
|
|
842
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
843
|
+
await driver.touchAction([
|
|
844
|
+
{ action: 'press', x: xInit, y: yInit },
|
|
845
|
+
{ action: 'wait', ms: duracao },
|
|
846
|
+
{ action: 'moveTo', x: xFinal, y: yFinal },
|
|
847
|
+
{ action: 'release' },
|
|
848
|
+
]);
|
|
849
|
+
Logger.success(`✅ Swipe executado: (${xInit}, ${yInit}) → (${xFinal}, ${yFinal}) - ${descriptionToAction}`);
|
|
850
|
+
if (getSnapshot) {
|
|
851
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
852
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Executa swipes até encontrar elemento.
|
|
858
|
+
* @param xInit Coordenada X inicial.
|
|
859
|
+
* @param yInit Coordenada Y inicial.
|
|
860
|
+
* @param xFinal Coordenada X final.
|
|
861
|
+
* @param yFinal Coordenada Y final.
|
|
862
|
+
* @param seletorElementoEsperado Seletor do elemento esperado.
|
|
863
|
+
* @param descriptionToAction Descrição da ação.
|
|
864
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
865
|
+
* @param maxTentativas Número máximo de tentativas.
|
|
866
|
+
* @param duracao Duração do swipe em ms.
|
|
867
|
+
* @returns True se o elemento foi encontrado.
|
|
868
|
+
*/
|
|
869
|
+
static async swipeUntilFind(xInit, yInit, xFinal, yFinal, seletorElementoEsperado, descriptionToAction, getSnapshot = false, maxTentativas = 10, duracao = 1000) {
|
|
870
|
+
const driver = MobileActions.ensureDriverExists();
|
|
871
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
872
|
+
for (let tentativa = 1; tentativa <= maxTentativas; tentativa++) {
|
|
873
|
+
Logger.info(`🔄 Tentativa ${tentativa}/${maxTentativas} de swipe até encontrar elemento`);
|
|
874
|
+
const presente = await MobileActions.isPresent(seletorElementoEsperado, 2000);
|
|
875
|
+
if (presente) {
|
|
876
|
+
Logger.success(`✅ Elemento encontrado após ${tentativa} tentativa(s) - ${descriptionToAction}`);
|
|
877
|
+
if (getSnapshot) {
|
|
878
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
879
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
880
|
+
}
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
if (tentativa < maxTentativas) {
|
|
884
|
+
await MobileActions.swipe(xInit, yInit, xFinal, yFinal, `Swipe ${tentativa} - ${descriptionToAction}`, false, duracao);
|
|
885
|
+
await driver.pause(500);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const errorMsg = `Elemento não encontrado após ${maxTentativas} tentativas de swipe - ${descriptionToAction}`;
|
|
889
|
+
Logger.error(errorMsg);
|
|
890
|
+
throw new Error(errorMsg);
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Obtém valor de atributo de um elemento.
|
|
895
|
+
* @param seletor Seletor do elemento.
|
|
896
|
+
* @param attributeName Nome do atributo.
|
|
897
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
898
|
+
* @returns Valor do atributo.
|
|
899
|
+
*/
|
|
900
|
+
static async getAttribute(seletor, attributeName, timeout = TestContext.getProjectTimeout()) {
|
|
901
|
+
const driver = MobileActions.ensureDriverExists();
|
|
902
|
+
return await MobileActions.executarComLog(`Obter atributo '${attributeName}' do elemento: ${seletor}`, async () => {
|
|
903
|
+
const elemento = await driver.$(seletor);
|
|
904
|
+
await elemento.waitForDisplayed({ timeout });
|
|
905
|
+
const atributo = await elemento.getAttribute(attributeName);
|
|
906
|
+
Logger.success(`Atributo '${attributeName}' obtido: "${atributo}"`);
|
|
907
|
+
return atributo || '';
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Simula cliques no teclado numérico.
|
|
912
|
+
* @param number Número a ser digitado.
|
|
913
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
914
|
+
*/
|
|
915
|
+
static async clickNumberPad(number, getSnapshot = false) {
|
|
916
|
+
const driver = MobileActions.ensureDriverExists();
|
|
917
|
+
await MobileActions.executarComLog(`Digitar número '${number}' no teclado numérico`, async () => {
|
|
918
|
+
for (const digit of number) {
|
|
919
|
+
const digitSelector = `//android.widget.TextView[@text="${digit}"] | //XCUIElementTypeStaticText[@name="${digit}"]`;
|
|
920
|
+
const elemento = await driver.$(digitSelector);
|
|
921
|
+
await elemento.waitForDisplayed({ timeout: 5000 });
|
|
922
|
+
await elemento.click();
|
|
923
|
+
await driver.pause(300);
|
|
924
|
+
}
|
|
925
|
+
Logger.success(`Número '${number}' digitado no teclado numérico`);
|
|
926
|
+
if (getSnapshot) {
|
|
927
|
+
// ✅ USAR MobileConnection.captureEvidence para integração com Playwright Report
|
|
928
|
+
await MobileConnection.captureEvidence(`Teclado numérico - ${number}`);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Executa script personalizado no driver.
|
|
934
|
+
* @param scriptType Tipo do script.
|
|
935
|
+
* @param jsonParams Parâmetros do script.
|
|
936
|
+
* @param descriptionToAction Descrição da ação.
|
|
937
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
938
|
+
* @returns Resultado do script.
|
|
939
|
+
*/
|
|
940
|
+
static async executeScript(scriptType, jsonParams, descriptionToAction, getSnapshot = false) {
|
|
941
|
+
const driver = MobileActions.ensureDriverExists();
|
|
942
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
943
|
+
const result = await driver.executeScript(scriptType, [jsonParams]);
|
|
944
|
+
Logger.success(`✅ Script '${scriptType}' executado - ${descriptionToAction}`);
|
|
945
|
+
if (getSnapshot) {
|
|
946
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
947
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
948
|
+
}
|
|
949
|
+
return result;
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Executa swipe genérico Android.
|
|
954
|
+
* @param direcao Direção do swipe.
|
|
955
|
+
* @param descriptionToAction Descrição da ação.
|
|
956
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
957
|
+
* @param distancia Distância do swipe (0 a 1).
|
|
958
|
+
*/
|
|
959
|
+
static async genericSwipeAndroid(direcao, descriptionToAction, getSnapshot = false, distancia = 0.5) {
|
|
960
|
+
const driver = MobileActions.ensureDriverExists();
|
|
961
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
962
|
+
const { width, height } = await driver.getWindowSize();
|
|
963
|
+
const centerX = width / 2;
|
|
964
|
+
const centerY = height / 2;
|
|
965
|
+
let startX, startY, endX, endY;
|
|
966
|
+
switch (direcao) {
|
|
967
|
+
case Direcao.CIMA:
|
|
968
|
+
startX = centerX;
|
|
969
|
+
startY = centerY + (height * distancia) / 2;
|
|
970
|
+
endX = centerX;
|
|
971
|
+
endY = centerY - (height * distancia) / 2;
|
|
972
|
+
break;
|
|
973
|
+
case Direcao.BAIXO:
|
|
974
|
+
startX = centerX;
|
|
975
|
+
startY = centerY - (height * distancia) / 2;
|
|
976
|
+
endX = centerX;
|
|
977
|
+
endY = centerY + (height * distancia) / 2;
|
|
978
|
+
break;
|
|
979
|
+
case Direcao.ESQUERDA:
|
|
980
|
+
startX = centerX + (width * distancia) / 2;
|
|
981
|
+
startY = centerY;
|
|
982
|
+
endX = centerX - (width * distancia) / 2;
|
|
983
|
+
endY = centerY;
|
|
984
|
+
break;
|
|
985
|
+
case Direcao.DIREITA:
|
|
986
|
+
startX = centerX - (width * distancia) / 2;
|
|
987
|
+
startY = centerY;
|
|
988
|
+
endX = centerX + (width * distancia) / 2;
|
|
989
|
+
endY = centerY;
|
|
990
|
+
break;
|
|
991
|
+
default:
|
|
992
|
+
throw new Error(`Direção inválida: ${direcao}`);
|
|
993
|
+
}
|
|
994
|
+
await driver.touchAction([
|
|
995
|
+
{ action: 'press', x: Math.round(startX), y: Math.round(startY) },
|
|
996
|
+
{ action: 'wait', ms: 1000 },
|
|
997
|
+
{ action: 'moveTo', x: Math.round(endX), y: Math.round(endY) },
|
|
998
|
+
{ action: 'release' },
|
|
999
|
+
]);
|
|
1000
|
+
Logger.success(`✅ Swipe genérico Android executado para ${direcao} - ${descriptionToAction}`);
|
|
1001
|
+
if (getSnapshot) {
|
|
1002
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
1003
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Executa swipe genérico iOS.
|
|
1009
|
+
* @param direcao Direção do swipe.
|
|
1010
|
+
* @param descriptionToAction Descrição da ação.
|
|
1011
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
1012
|
+
* @param distancia Distância do swipe (0 a 1).
|
|
1013
|
+
*/
|
|
1014
|
+
static async genericSwipeIOS(direcao, descriptionToAction, getSnapshot = false, distancia = 0.5) {
|
|
1015
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1016
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1017
|
+
try {
|
|
1018
|
+
let swipeDirection;
|
|
1019
|
+
switch (direcao) {
|
|
1020
|
+
case Direcao.CIMA:
|
|
1021
|
+
swipeDirection = 'up';
|
|
1022
|
+
break;
|
|
1023
|
+
case Direcao.BAIXO:
|
|
1024
|
+
swipeDirection = 'down';
|
|
1025
|
+
break;
|
|
1026
|
+
case Direcao.ESQUERDA:
|
|
1027
|
+
swipeDirection = 'left';
|
|
1028
|
+
break;
|
|
1029
|
+
case Direcao.DIREITA:
|
|
1030
|
+
swipeDirection = 'right';
|
|
1031
|
+
break;
|
|
1032
|
+
default:
|
|
1033
|
+
throw new Error(`Direção inválida: ${direcao}`);
|
|
1034
|
+
}
|
|
1035
|
+
await driver.executeScript('mobile: swipe', [
|
|
1036
|
+
{
|
|
1037
|
+
direction: swipeDirection,
|
|
1038
|
+
velocity: 1000 * distancia,
|
|
1039
|
+
},
|
|
1040
|
+
]);
|
|
1041
|
+
Logger.success(`Swipe genérico iOS executado para ${direcao} - ${descriptionToAction}`);
|
|
1042
|
+
}
|
|
1043
|
+
catch (error) {
|
|
1044
|
+
Logger.warning('⚠️ Comando nativo falhou, usando swipe manual');
|
|
1045
|
+
await MobileActions.genericSwipeAndroid(direcao, descriptionToAction, false, distancia);
|
|
1046
|
+
}
|
|
1047
|
+
if (getSnapshot) {
|
|
1048
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
1049
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Executa swipe genérico Android até encontrar elemento.
|
|
1055
|
+
* @param direcao Direção do swipe.
|
|
1056
|
+
* @param seletorElementoEsperado Seletor do elemento esperado.
|
|
1057
|
+
* @param descriptionToAction Descrição da ação.
|
|
1058
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
1059
|
+
* @param maxTentativas Número máximo de tentativas.
|
|
1060
|
+
* @param distancia Distância do swipe (0 a 1).
|
|
1061
|
+
* @returns True se o elemento foi encontrado.
|
|
1062
|
+
*/
|
|
1063
|
+
static async genericSwipeUntilFindAndroid(direcao, seletorElementoEsperado, descriptionToAction, getSnapshot = false, maxTentativas = 10, distancia = 0.5) {
|
|
1064
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1065
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1066
|
+
for (let tentativa = 1; tentativa <= maxTentativas; tentativa++) {
|
|
1067
|
+
Logger.info(`🔄 Tentativa ${tentativa}/${maxTentativas} de swipe genérico Android até encontrar elemento`);
|
|
1068
|
+
const presente = await MobileActions.isPresent(seletorElementoEsperado, 2000);
|
|
1069
|
+
if (presente) {
|
|
1070
|
+
Logger.success(`✅ Elemento encontrado após ${tentativa} tentativa(s) - ${descriptionToAction}`);
|
|
1071
|
+
if (getSnapshot) {
|
|
1072
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
1073
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1074
|
+
}
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
if (tentativa < maxTentativas) {
|
|
1078
|
+
await MobileActions.genericSwipeAndroid(direcao, `Swipe ${tentativa} - ${descriptionToAction}`, false, distancia);
|
|
1079
|
+
await driver.pause(500);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
const errorMsg = `Elemento não encontrado após ${maxTentativas} tentativas de swipe genérico Android - ${descriptionToAction}`;
|
|
1083
|
+
Logger.error(errorMsg);
|
|
1084
|
+
throw new Error(errorMsg);
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Executa swipe genérico iOS até encontrar elemento.
|
|
1089
|
+
* @param direcao Direção do swipe.
|
|
1090
|
+
* @param seletorElementoEsperado Seletor do elemento esperado.
|
|
1091
|
+
* @param descriptionToAction Descrição da ação.
|
|
1092
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
1093
|
+
* @param maxTentativas Número máximo de tentativas.
|
|
1094
|
+
* @param distancia Distância do swipe (0 a 1).
|
|
1095
|
+
* @returns True se o elemento foi encontrado.
|
|
1096
|
+
*/
|
|
1097
|
+
static async genericSwipeUntilFindIOS(direcao, seletorElementoEsperado, descriptionToAction, getSnapshot = false, maxTentativas = 10, distancia = 0.5) {
|
|
1098
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1099
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1100
|
+
for (let tentativa = 1; tentativa <= maxTentativas; tentativa++) {
|
|
1101
|
+
Logger.info(`🔄 Tentativa ${tentativa}/${maxTentativas} de swipe genérico iOS até encontrar elemento`);
|
|
1102
|
+
const presente = await MobileActions.isPresent(seletorElementoEsperado, 2000);
|
|
1103
|
+
if (presente) {
|
|
1104
|
+
Logger.success(`✅ Elemento encontrado após ${tentativa} tentativa(s) - ${descriptionToAction}`);
|
|
1105
|
+
if (getSnapshot) {
|
|
1106
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
1107
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1108
|
+
}
|
|
1109
|
+
return true;
|
|
1110
|
+
}
|
|
1111
|
+
if (tentativa < maxTentativas) {
|
|
1112
|
+
await MobileActions.genericSwipeIOS(direcao, `Swipe ${tentativa} - ${descriptionToAction}`, false, distancia);
|
|
1113
|
+
await driver.pause(500);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
const errorMsg = `Elemento não encontrado após ${maxTentativas} tentativas de swipe genérico iOS - ${descriptionToAction}`;
|
|
1117
|
+
Logger.error(errorMsg);
|
|
1118
|
+
throw new Error(errorMsg);
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Realiza clique em elemento com verificação de presença.
|
|
1123
|
+
* @param seletor Seletor do elemento.
|
|
1124
|
+
* @param descriptionToAction Descrição da ação.
|
|
1125
|
+
* @param getSnapshot Se deve capturar screenshot.
|
|
1126
|
+
* @param timeout Tempo máximo de espera em ms.
|
|
1127
|
+
*/
|
|
1128
|
+
static async clickOnObjectPresence(seletor, descriptionToAction, getSnapshot = false, timeout = TestContext.getProjectTimeout()) {
|
|
1129
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1130
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1131
|
+
const elemento = await driver.$(seletor);
|
|
1132
|
+
await elemento.waitForDisplayed({ timeout });
|
|
1133
|
+
const presente = await elemento.isDisplayed();
|
|
1134
|
+
if (!presente) {
|
|
1135
|
+
throw new Error(`Elemento não está presente na tela: ${seletor}`);
|
|
1136
|
+
}
|
|
1137
|
+
try {
|
|
1138
|
+
const habilitado = await elemento.isEnabled();
|
|
1139
|
+
if (!habilitado) {
|
|
1140
|
+
Logger.warning(`⚠️ Elemento está presente mas pode estar desabilitado: ${seletor}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
catch (error) {
|
|
1144
|
+
Logger.info(`ℹ️ Verificação de habilitação ignorada: ${String(error)}`);
|
|
1145
|
+
}
|
|
1146
|
+
await elemento.click();
|
|
1147
|
+
Logger.success(`✅ Clique realizado no elemento presente - ${descriptionToAction}`);
|
|
1148
|
+
if (getSnapshot) {
|
|
1149
|
+
// ? USAR MobileConnection.captureEvidence para integra��o com Playwright Report
|
|
1150
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Copia texto para o clipboard (Android/iOS).
|
|
1156
|
+
* @param text Texto a ser copiado.
|
|
1157
|
+
* @param descriptionToAction Descrição da ação.
|
|
1158
|
+
*/
|
|
1159
|
+
static async setClipboardText(text, descriptionToAction = 'Set clipboard text') {
|
|
1160
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1161
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1162
|
+
try {
|
|
1163
|
+
await driver.setClipboard('plaintext', Buffer.from(text).toString('base64'));
|
|
1164
|
+
Logger.success(`Texto copiado para clipboard: "${text}"`);
|
|
1165
|
+
}
|
|
1166
|
+
catch (error) {
|
|
1167
|
+
Logger.warning(`Falha ao copiar texto para clipboard: ${String(error)}`);
|
|
1168
|
+
throw error;
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Obtém texto do clipboard (Android/iOS).
|
|
1174
|
+
* @param descriptionToAction Descrição da ação.
|
|
1175
|
+
* @returns Texto do clipboard.
|
|
1176
|
+
*/
|
|
1177
|
+
static async getClipboardText(descriptionToAction = 'Get clipboard text') {
|
|
1178
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1179
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1180
|
+
try {
|
|
1181
|
+
const base64 = await driver.getClipboard('plaintext');
|
|
1182
|
+
const text = Buffer.from(base64, 'base64').toString('utf-8');
|
|
1183
|
+
Logger.success(`Texto obtido do clipboard: "${text}"`);
|
|
1184
|
+
return text;
|
|
1185
|
+
}
|
|
1186
|
+
catch (error) {
|
|
1187
|
+
Logger.warning(`Falha ao obter texto do clipboard: ${String(error)}`);
|
|
1188
|
+
throw error;
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Executa comando shell (Android).
|
|
1194
|
+
* @param command Comando shell.
|
|
1195
|
+
* @param args Argumentos do comando.
|
|
1196
|
+
* @param descriptionToAction Descrição da ação.
|
|
1197
|
+
* @returns Resultado do comando.
|
|
1198
|
+
*/
|
|
1199
|
+
static async executeShellCommand(command, args = [], descriptionToAction = 'Executar comando shell') {
|
|
1200
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1201
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1202
|
+
try {
|
|
1203
|
+
const result = await driver.execute('mobile: shell', { command, args });
|
|
1204
|
+
Logger.success(`Comando shell executado: ${command} ${args.join(' ')}`);
|
|
1205
|
+
return result;
|
|
1206
|
+
}
|
|
1207
|
+
catch (error) {
|
|
1208
|
+
Logger.warning(`Falha ao executar comando shell: ${String(error)}`);
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Fecha o app e relança (forceAppLaunch).
|
|
1215
|
+
* @param descriptionToAction Descrição da ação.
|
|
1216
|
+
*/
|
|
1217
|
+
static async relaunchApp(descriptionToAction = 'Relaunch app') {
|
|
1218
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1219
|
+
await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1220
|
+
try {
|
|
1221
|
+
await driver.closeApp();
|
|
1222
|
+
await driver.launchApp();
|
|
1223
|
+
Logger.success('App relançado com sucesso');
|
|
1224
|
+
}
|
|
1225
|
+
catch (error) {
|
|
1226
|
+
Logger.warning(`Falha ao relançar app: ${String(error)}`);
|
|
1227
|
+
throw error;
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* ✅ MELHORADO: Captura screenshot robusto com múltiplas tentativas
|
|
1233
|
+
* @param nome Nome do screenshot
|
|
1234
|
+
* @returns Buffer da imagem ou null
|
|
1235
|
+
*/
|
|
1236
|
+
static async capturarScreenshot(nome) {
|
|
1237
|
+
const MAX_RETRIES = 3;
|
|
1238
|
+
const RETRY_DELAY = 2000;
|
|
1239
|
+
const STABILIZATION_DELAY = 1500;
|
|
1240
|
+
const MIN_SCREENSHOT_SIZE = 1000;
|
|
1241
|
+
Logger.info(`📸 Iniciando captura de screenshot mobile: ${nome}`);
|
|
1242
|
+
try {
|
|
1243
|
+
// 1. Verificações preliminares
|
|
1244
|
+
if (!MobileConnection.isConnected()) {
|
|
1245
|
+
Logger.error('❌ Driver mobile não conectado para screenshot');
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1249
|
+
if (!driver) {
|
|
1250
|
+
Logger.error('❌ Driver não disponível para screenshot');
|
|
1251
|
+
return null;
|
|
1252
|
+
}
|
|
1253
|
+
// 2. Verificar conectividade visual DeviceFarm
|
|
1254
|
+
const isVisuallyActive = await MobileConnection.verifyDeviceFarmVisualConnection();
|
|
1255
|
+
if (!isVisuallyActive) {
|
|
1256
|
+
Logger.warning('⚠️ DeviceFarm não está visualmente ativo');
|
|
1257
|
+
}
|
|
1258
|
+
// 3. Múltiplas tentativas de captura
|
|
1259
|
+
let screenshot = null;
|
|
1260
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
1261
|
+
try {
|
|
1262
|
+
Logger.info(`📸 Tentativa ${attempt}/${MAX_RETRIES} de captura: ${nome}`);
|
|
1263
|
+
// Aguardar estabilização
|
|
1264
|
+
await driver.pause(STABILIZATION_DELAY);
|
|
1265
|
+
// Capturar screenshot
|
|
1266
|
+
screenshot = await MobileConnection.captureScreenshotBuffer();
|
|
1267
|
+
if (screenshot && screenshot.length > MIN_SCREENSHOT_SIZE) {
|
|
1268
|
+
Logger.success(`Screenshot capturado: ${nome} (${screenshot.length} bytes)`);
|
|
1269
|
+
break;
|
|
1270
|
+
}
|
|
1271
|
+
Logger.warning(`⚠️ Screenshot inválido na tentativa ${attempt}`);
|
|
1272
|
+
screenshot = null;
|
|
1273
|
+
}
|
|
1274
|
+
catch (attemptError) {
|
|
1275
|
+
Logger.warning(`⚠️ Erro na tentativa ${attempt}: ${String(attemptError)}`);
|
|
1276
|
+
screenshot = null;
|
|
1277
|
+
}
|
|
1278
|
+
// Aguardar antes da próxima tentativa
|
|
1279
|
+
if (attempt < MAX_RETRIES) {
|
|
1280
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (!screenshot) {
|
|
1284
|
+
Logger.error(`❌ Falha em todas as tentativas de screenshot: ${nome}`);
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
// 4. Salvar arquivo local para evidência
|
|
1288
|
+
await MobileActions.saveScreenshotFile(screenshot, nome);
|
|
1289
|
+
// 5. Anexar ao relatório Playwright
|
|
1290
|
+
await MobileActions.attachScreenshotToReport(screenshot, nome);
|
|
1291
|
+
// 6. Registrar no sistema unificado
|
|
1292
|
+
MobileActions.logScreenshotToUnified(nome, screenshot.length);
|
|
1293
|
+
return screenshot;
|
|
1294
|
+
}
|
|
1295
|
+
catch (error) {
|
|
1296
|
+
Logger.error(`ERRO ao capturar screenshot: ${nome} - ${String(error)}`);
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Salva screenshot como arquivo local e integra com DeviceFarmViewer
|
|
1302
|
+
*/
|
|
1303
|
+
static async saveScreenshotFile(screenshot, nome) {
|
|
1304
|
+
try {
|
|
1305
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1306
|
+
const safeName = nome.replace(/[^a-zA-Z0-9\-_]/g, '_');
|
|
1307
|
+
const fileName = `mobile-${timestamp}-${safeName}.png`;
|
|
1308
|
+
const evidenceDir = './test-results/mobile-screenshots';
|
|
1309
|
+
if (!fs.existsSync(evidenceDir)) {
|
|
1310
|
+
fs.mkdirSync(evidenceDir, { recursive: true });
|
|
1311
|
+
}
|
|
1312
|
+
const filePath = path.join(evidenceDir, fileName);
|
|
1313
|
+
fs.writeFileSync(filePath, screenshot);
|
|
1314
|
+
if (fs.existsSync(filePath)) {
|
|
1315
|
+
const fileSize = fs.statSync(filePath).size;
|
|
1316
|
+
Logger.success(`💾 Screenshot salvo: ${filePath} (${fileSize} bytes)`);
|
|
1317
|
+
// ✅ INTEGRAÇÃO: Adicionar ao DeviceFarmViewer para visualização em tempo real
|
|
1318
|
+
try {
|
|
1319
|
+
DeviceFarmViewer.addScreenshot(fileName, nome);
|
|
1320
|
+
Logger.info(`📺 Screenshot adicionado ao viewer: ${nome}`);
|
|
1321
|
+
}
|
|
1322
|
+
catch (viewerError) {
|
|
1323
|
+
// Não falhar se viewer não estiver disponível
|
|
1324
|
+
Logger.info(`📺 Viewer não disponível: ${String(viewerError)}`);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
catch (saveError) {
|
|
1329
|
+
Logger.error(`❌ Erro ao salvar screenshot: ${String(saveError)}`);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Anexa screenshot ao relatório Playwright
|
|
1334
|
+
*/
|
|
1335
|
+
static async attachScreenshotToReport(screenshot, nome) {
|
|
1336
|
+
try {
|
|
1337
|
+
const testInfo = TestContext.testInfo;
|
|
1338
|
+
if (testInfo) {
|
|
1339
|
+
await testInfo.attach(`screenshot-mobile-${nome}`, {
|
|
1340
|
+
body: screenshot,
|
|
1341
|
+
contentType: 'image/png',
|
|
1342
|
+
});
|
|
1343
|
+
Logger.info('📎 Screenshot anexado ao Playwright');
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
catch (attachError) {
|
|
1347
|
+
Logger.warning(`⚠️ Erro ao anexar: ${String(attachError)}`);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Registra screenshot no sistema unificado
|
|
1352
|
+
*/
|
|
1353
|
+
static logScreenshotToUnified(nome, size) {
|
|
1354
|
+
try {
|
|
1355
|
+
const testInfo = TestContext.getSafeTestInfo();
|
|
1356
|
+
if (testInfo?.title) {
|
|
1357
|
+
UnifiedReportManager.adicionarLog(testInfo.title, `📸 Screenshot mobile: ${nome} (${size} bytes)`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
catch {
|
|
1361
|
+
// Silencioso
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* ✅ MELHORADO: Captura screenshot forçado com verificação DeviceFarm
|
|
1366
|
+
* @param nome Nome do screenshot
|
|
1367
|
+
* @returns Promise<boolean> True se conseguiu capturar
|
|
1368
|
+
*/
|
|
1369
|
+
static async capturarScreenshotForcado(nome) {
|
|
1370
|
+
const EXTENDED_STABILIZATION = 2000;
|
|
1371
|
+
const MIN_VALID_SIZE = 1000;
|
|
1372
|
+
try {
|
|
1373
|
+
Logger.info(`🔍 Iniciando captura forçada: ${nome}`);
|
|
1374
|
+
// 1. Verificar conectividade DeviceFarm
|
|
1375
|
+
const isVisuallyActive = await MobileConnection.verifyDeviceFarmVisualConnection();
|
|
1376
|
+
if (!isVisuallyActive) {
|
|
1377
|
+
Logger.warning('⚠️ DeviceFarm não está visualmente ativo');
|
|
1378
|
+
}
|
|
1379
|
+
// 2. Aguardar estabilização extra
|
|
1380
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1381
|
+
await driver.pause(EXTENDED_STABILIZATION);
|
|
1382
|
+
// 3. Tentar múltiplas vezes
|
|
1383
|
+
const maxAttempts = 3;
|
|
1384
|
+
for (let attempts = 1; attempts <= maxAttempts; attempts++) {
|
|
1385
|
+
Logger.info(`📸 Tentativa ${attempts}/${maxAttempts} de captura forçada`);
|
|
1386
|
+
// ✅ USAR MobileConnection.captureEvidence para integração com Playwright Report
|
|
1387
|
+
const screenshot = await MobileConnection.captureEvidence(`${nome}_attempt${attempts}`);
|
|
1388
|
+
if (screenshot) {
|
|
1389
|
+
Logger.success(`Screenshot forçado capturado na tentativa ${attempts}`);
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
if (attempts < maxAttempts) {
|
|
1393
|
+
Logger.warning('⚠️ Tentativa falhou, aguardando para próxima...');
|
|
1394
|
+
await driver.pause(EXTENDED_STABILIZATION);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
Logger.error(`❌ Falha em todas as tentativas de screenshot forçado: ${nome}`);
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
catch (error) {
|
|
1401
|
+
Logger.error(`❌ Erro na captura forçada: ${String(error)}`);
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Valida se dois valores são iguais.
|
|
1407
|
+
* @param atual Valor atual.
|
|
1408
|
+
* @param esperado Valor esperado.
|
|
1409
|
+
* @param descricao Descrição da validação.
|
|
1410
|
+
*/
|
|
1411
|
+
static async validateEquals(atual, esperado, descricao) {
|
|
1412
|
+
await MobileActions.executarComLog(descricao, async () => {
|
|
1413
|
+
if (atual === esperado) {
|
|
1414
|
+
Logger.success(`✅ Validação passou: ${descricao} (${atual} === ${esperado})`);
|
|
1415
|
+
}
|
|
1416
|
+
else {
|
|
1417
|
+
const errorMsg = `❌ Validação falhou: ${descricao} - Esperado: "${esperado}", Atual: "${atual}"`;
|
|
1418
|
+
Logger.error(errorMsg);
|
|
1419
|
+
throw new Error(errorMsg);
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
// ========================================
|
|
1424
|
+
// 🍎 iOS DEVICEFARM HELPERS INTEGRADOS
|
|
1425
|
+
// ========================================
|
|
1426
|
+
/**
|
|
1427
|
+
* 🎯 SELETOR PARA O ELEMENTO Wi-Fi (iOS DeviceFarm)
|
|
1428
|
+
* Baseado nas propriedades: type=XCUIElementTypeCell, name=Wi‑Fi, value=VivoQA_5G
|
|
1429
|
+
*/
|
|
1430
|
+
static getWiFiSettingSelector() {
|
|
1431
|
+
// Múltiplas estratégias de seletor para máxima compatibilidade
|
|
1432
|
+
return [
|
|
1433
|
+
// 1. Por accessibility id (name)
|
|
1434
|
+
'~Wi‑Fi',
|
|
1435
|
+
// 2. Por valor específico (VivoQA_5G)
|
|
1436
|
+
'**/XCUIElementTypeCell[`value == "VivoQA_5G"`]',
|
|
1437
|
+
// 3. Por label
|
|
1438
|
+
'**/XCUIElementTypeCell[`label == "Wi‑Fi"`]',
|
|
1439
|
+
// 4. Por XPath específico (posição)
|
|
1440
|
+
'//XCUIElementTypeTable/XCUIElementTypeCell[5]',
|
|
1441
|
+
// 5. Por combinação de propriedades
|
|
1442
|
+
'**/XCUIElementTypeCell[`name == "Wi‑Fi" AND value == "VivoQA_5G"`]',
|
|
1443
|
+
][0]; // Usar o primeiro (mais confiável)
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* 🎯 SELETORES ALTERNATIVOS para fallback (iOS)
|
|
1447
|
+
*/
|
|
1448
|
+
static getWiFiFallbackSelectors() {
|
|
1449
|
+
return [
|
|
1450
|
+
'~Wi‑Fi', // accessibility id
|
|
1451
|
+
'**/XCUIElementTypeCell[`value == "VivoQA_5G"`]', // por valor
|
|
1452
|
+
'**/XCUIElementTypeCell[`label == "Wi‑Fi"`]', // por label
|
|
1453
|
+
'//XCUIElementTypeTable/XCUIElementTypeCell[5]', // xpath posição
|
|
1454
|
+
'**/XCUIElementTypeCell[`name CONTAINS "Wi"`]', // nome parcial
|
|
1455
|
+
'(//XCUIElementTypeCell)[5]', // índice simples
|
|
1456
|
+
'**/XCUIElementTypeCell[`visible == 1 AND enabled == 1`][@name="Wi‑Fi"]', // completo
|
|
1457
|
+
];
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* 🔍 CLIQUE INTELIGENTE no elemento Wi-Fi com múltiplas estratégias (iOS)
|
|
1461
|
+
*/
|
|
1462
|
+
static async clickWiFiSetting(descriptionToAction = 'Clique no elemento Wi-Fi (iOS DeviceFarm)', getSnapshot = true) {
|
|
1463
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1464
|
+
const selectors = MobileActions.getWiFiFallbackSelectors();
|
|
1465
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1466
|
+
for (let i = 0; i < selectors.length; i++) {
|
|
1467
|
+
const selector = selectors[i];
|
|
1468
|
+
try {
|
|
1469
|
+
Logger.info(`Tentativa ${i + 1}/${selectors.length}: ${selector}`);
|
|
1470
|
+
// Aguardar elemento aparecer
|
|
1471
|
+
const element = await driver.$(selector);
|
|
1472
|
+
await element.waitForExist({ timeout: 10_000 });
|
|
1473
|
+
// Verificar se está visível
|
|
1474
|
+
const isDisplayed = await element.isDisplayed();
|
|
1475
|
+
if (!isDisplayed) {
|
|
1476
|
+
Logger.info('Elemento encontrado mas não visível');
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
// Verificar se está habilitado
|
|
1480
|
+
const isEnabled = await element.isEnabled();
|
|
1481
|
+
if (!isEnabled) {
|
|
1482
|
+
Logger.info('Elemento encontrado mas não habilitado');
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
// Realizar clique
|
|
1486
|
+
await element.click();
|
|
1487
|
+
Logger.info(`Clique realizado com sucesso usando: ${selector}`);
|
|
1488
|
+
// Aguardar transição
|
|
1489
|
+
await driver.pause(2000);
|
|
1490
|
+
if (getSnapshot) {
|
|
1491
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1492
|
+
}
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
catch (error) {
|
|
1496
|
+
Logger.error(`Falha com seletor ${i + 1}: ${String(error).substring(0, 100)}...`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
Logger.error('Todos os seletores falharam para o elemento Wi-Fi');
|
|
1500
|
+
return false;
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* 🔍 CLIQUE POR COORDENADAS do elemento Wi-Fi (iOS)
|
|
1505
|
+
* Baseado nas propriedades: x=20, y=526, width=390, height=45
|
|
1506
|
+
*/
|
|
1507
|
+
static async clickWiFiByCoordinates(descriptionToAction = 'Clique Wi-Fi por coordenadas (iOS)', getSnapshot = true) {
|
|
1508
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1509
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1510
|
+
try {
|
|
1511
|
+
// Coordenadas originais do elemento
|
|
1512
|
+
const x = 20;
|
|
1513
|
+
const y = 526;
|
|
1514
|
+
const width = 390;
|
|
1515
|
+
const height = 45;
|
|
1516
|
+
// Calcular centro do elemento
|
|
1517
|
+
const centerX = x + width / 2; // 20 + 195 = 215
|
|
1518
|
+
const centerY = y + height / 2; // 526 + 22.5 = 548.5
|
|
1519
|
+
Logger.info(`Clicando nas coordenadas: (${centerX}, ${centerY})`);
|
|
1520
|
+
// Realizar clique por coordenadas
|
|
1521
|
+
await driver.touchAction([
|
|
1522
|
+
{ action: 'tap', x: Math.round(centerX), y: Math.round(centerY) },
|
|
1523
|
+
]);
|
|
1524
|
+
Logger.success('✅ Clique por coordenadas realizado');
|
|
1525
|
+
await driver.pause(2000);
|
|
1526
|
+
if (getSnapshot) {
|
|
1527
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1528
|
+
}
|
|
1529
|
+
return true;
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
Logger.error(`❌ Falha no clique por coordenadas: ${String(error)}`);
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* 🔍 ESTRATÉGIA COMBINADA: Tentar seletores primeiro, depois coordenadas (iOS)
|
|
1539
|
+
*/
|
|
1540
|
+
static async clickWiFiCombined(descriptionToAction = 'Clique combinado Wi-Fi (iOS DeviceFarm)', getSnapshot = true) {
|
|
1541
|
+
return await MobileActions.executarComLog(descriptionToAction, async () => {
|
|
1542
|
+
Logger.info('🍎 Iniciando clique no elemento Wi-Fi (VivoQA_5G)');
|
|
1543
|
+
// 1. Tentar seletores inteligentes
|
|
1544
|
+
const selectorSuccess = await MobileActions.clickWiFiSetting('Clique Wi-Fi por seletores', false);
|
|
1545
|
+
if (selectorSuccess) {
|
|
1546
|
+
if (getSnapshot) {
|
|
1547
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1548
|
+
}
|
|
1549
|
+
return true;
|
|
1550
|
+
}
|
|
1551
|
+
Logger.info('🎯 Fallback: Tentando clique por coordenadas...');
|
|
1552
|
+
// 2. Fallback para coordenadas
|
|
1553
|
+
const coordinatesSuccess = await MobileActions.clickWiFiByCoordinates('Fallback clique Wi-Fi por coordenadas', false);
|
|
1554
|
+
if (coordinatesSuccess) {
|
|
1555
|
+
if (getSnapshot) {
|
|
1556
|
+
await MobileConnection.captureEvidence(descriptionToAction);
|
|
1557
|
+
}
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1560
|
+
Logger.error('❌ Todas as estratégias falharam para o elemento Wi-Fi');
|
|
1561
|
+
return false;
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* 🔍 VERIFICAR se elemento Wi-Fi está presente na tela (iOS)
|
|
1566
|
+
*/
|
|
1567
|
+
static async isWiFiSettingVisible() {
|
|
1568
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1569
|
+
const selectors = MobileActions.getWiFiFallbackSelectors();
|
|
1570
|
+
for (const selector of selectors) {
|
|
1571
|
+
try {
|
|
1572
|
+
const element = await driver.$(selector);
|
|
1573
|
+
const exists = await element.isExisting();
|
|
1574
|
+
const displayed = exists ? await element.isDisplayed() : false;
|
|
1575
|
+
if (exists && displayed) {
|
|
1576
|
+
Logger.success(`✅ Elemento Wi-Fi encontrado com: ${selector}`);
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
catch {
|
|
1581
|
+
// Continuar tentando
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
Logger.error('❌ Elemento Wi-Fi não encontrado na tela');
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* 🔍 OBTER informações do elemento Wi-Fi (iOS)
|
|
1589
|
+
*/
|
|
1590
|
+
static async getWiFiElementInfo() {
|
|
1591
|
+
const driver = MobileActions.ensureDriverExists();
|
|
1592
|
+
const selector = MobileActions.getWiFiSettingSelector();
|
|
1593
|
+
try {
|
|
1594
|
+
const element = await driver.$(selector);
|
|
1595
|
+
await element.waitForExist({ timeout: 5000 });
|
|
1596
|
+
const rect = await element.getLocation();
|
|
1597
|
+
const size = await element.getSize();
|
|
1598
|
+
const isDisplayed = await element.isDisplayed();
|
|
1599
|
+
const isEnabled = await element.isEnabled();
|
|
1600
|
+
const text = await element.getText().catch(() => 'N/A');
|
|
1601
|
+
const value = await element.getValue().catch(() => 'N/A');
|
|
1602
|
+
return {
|
|
1603
|
+
selector,
|
|
1604
|
+
rect: { x: rect.x, y: rect.y, width: size.width, height: size.height },
|
|
1605
|
+
isDisplayed,
|
|
1606
|
+
isEnabled,
|
|
1607
|
+
text,
|
|
1608
|
+
value,
|
|
1609
|
+
coordinates: {
|
|
1610
|
+
centerX: rect.x + size.width / 2,
|
|
1611
|
+
centerY: rect.y + size.height / 2,
|
|
1612
|
+
},
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
catch (error) {
|
|
1616
|
+
Logger.error(`❌ Erro ao obter informações: ${String(error)}`);
|
|
1617
|
+
return null;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
// 🚫 AUTO-ATIVAÇÃO REMOVIDA: MobileActions são métodos internos que não devem ser interceptados
|
|
1622
|
+
// MobileActions.tap(), .swipe(), etc. não são CTs válidos e devem ser filtrados
|
|
1623
|
+
// A interceptação foi removida para evitar falsos CTs nos relatórios
|
|
1624
|
+
// CTs reais vêm das classes Statement (StatementsMobile, etc.) via InterceptacaoMagica.ts
|
|
1625
|
+
// CÓDIGO REMOVIDO:
|
|
1626
|
+
// try {
|
|
1627
|
+
// installDatabaseInterceptor(MobileActions, 'MobileActions')
|
|
1628
|
+
// } catch (error) {
|
|
1629
|
+
// if (process.env.AUTOCORE_DEBUG_LOGS === 'true') {
|
|
1630
|
+
// process.stderr.write(`[DatabaseInterceptor] Falha na auto-ativação MobileActions: ${String(error)}\n`)
|
|
1631
|
+
// }
|
|
1632
|
+
// }
|