@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,1415 @@
|
|
|
1
|
+
/** Lista de actionTypes válidos para validação */
|
|
2
|
+
export const VALID_ACTION_TYPES = [
|
|
3
|
+
"click",
|
|
4
|
+
"fill",
|
|
5
|
+
"select",
|
|
6
|
+
"hover",
|
|
7
|
+
"check",
|
|
8
|
+
"uncheck",
|
|
9
|
+
"press",
|
|
10
|
+
"wait",
|
|
11
|
+
"navigate",
|
|
12
|
+
"doubleClick",
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Mapeamento de nomes de métodos WebActions para actionTypes do Test Recovery
|
|
16
|
+
* Usado para normalizar o actionType quando vem do wrapper automático
|
|
17
|
+
*/
|
|
18
|
+
export const METHOD_TO_ACTION_TYPE = {
|
|
19
|
+
// Fill/Input
|
|
20
|
+
setText: "fill",
|
|
21
|
+
fill: "fill",
|
|
22
|
+
type: "fill",
|
|
23
|
+
preencher: "fill",
|
|
24
|
+
// Click
|
|
25
|
+
click: "click",
|
|
26
|
+
clicar: "click",
|
|
27
|
+
// Double Click
|
|
28
|
+
doubleClick: "doubleClick",
|
|
29
|
+
dblclick: "doubleClick",
|
|
30
|
+
duploClique: "doubleClick",
|
|
31
|
+
// Select
|
|
32
|
+
selectOption: "select",
|
|
33
|
+
selectDropdown: "select",
|
|
34
|
+
selectFromList: "select",
|
|
35
|
+
selectTableOption: "select",
|
|
36
|
+
selecionarOpcao: "select",
|
|
37
|
+
// Hover
|
|
38
|
+
hover: "hover",
|
|
39
|
+
mouseOver: "hover",
|
|
40
|
+
// Press Key
|
|
41
|
+
pressKey: "press",
|
|
42
|
+
press: "press",
|
|
43
|
+
keyboard: "press",
|
|
44
|
+
// Check/Uncheck
|
|
45
|
+
check: "check",
|
|
46
|
+
uncheck: "uncheck",
|
|
47
|
+
marcarCheckbox: "check",
|
|
48
|
+
desmarcarCheckbox: "uncheck",
|
|
49
|
+
// Wait
|
|
50
|
+
waitForVisible: "wait",
|
|
51
|
+
waitForVisibleBoolean: "wait",
|
|
52
|
+
waitForNetworkIdle: "wait",
|
|
53
|
+
waitForSelector: "wait",
|
|
54
|
+
waitForHidden: "wait",
|
|
55
|
+
aguardarVisivel: "wait",
|
|
56
|
+
fixedWait: "wait",
|
|
57
|
+
// Navigate
|
|
58
|
+
navigateTo: "navigate",
|
|
59
|
+
goto: "navigate",
|
|
60
|
+
navigate: "navigate",
|
|
61
|
+
navegar: "navigate",
|
|
62
|
+
// Frame (mapeia para click como fallback)
|
|
63
|
+
switchTo: "click",
|
|
64
|
+
trocarFrame: "click",
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Converte nome de método WebActions para actionType do Test Recovery
|
|
68
|
+
* @param methodName Nome do método (ex: 'setText', 'click', 'waitForVisible')
|
|
69
|
+
* @returns ActionType normalizado
|
|
70
|
+
*/
|
|
71
|
+
export function methodToActionType(methodName) {
|
|
72
|
+
const mapped = METHOD_TO_ACTION_TYPE[methodName];
|
|
73
|
+
if (mapped)
|
|
74
|
+
return mapped;
|
|
75
|
+
// Fallback: tentar inferir pelo nome
|
|
76
|
+
const lower = methodName.toLowerCase();
|
|
77
|
+
if (lower.includes("wait") || lower.includes("aguardar"))
|
|
78
|
+
return "wait";
|
|
79
|
+
if (lower.includes("fill") ||
|
|
80
|
+
lower.includes("text") ||
|
|
81
|
+
lower.includes("preencher"))
|
|
82
|
+
return "fill";
|
|
83
|
+
if (lower.includes("click") || lower.includes("clicar"))
|
|
84
|
+
return "click";
|
|
85
|
+
if (lower.includes("select") || lower.includes("selecionar"))
|
|
86
|
+
return "select";
|
|
87
|
+
if (lower.includes("hover"))
|
|
88
|
+
return "hover";
|
|
89
|
+
if (lower.includes("press") || lower.includes("key"))
|
|
90
|
+
return "press";
|
|
91
|
+
if (lower.includes("check"))
|
|
92
|
+
return "check";
|
|
93
|
+
if (lower.includes("navigate") ||
|
|
94
|
+
lower.includes("goto") ||
|
|
95
|
+
lower.includes("navegar"))
|
|
96
|
+
return "navigate";
|
|
97
|
+
// Default
|
|
98
|
+
return "click";
|
|
99
|
+
}
|
|
100
|
+
/** Lista de errorTypes válidos para validação */
|
|
101
|
+
export const VALID_ERROR_TYPES = [
|
|
102
|
+
"not_found",
|
|
103
|
+
"multiple_elements",
|
|
104
|
+
"timeout",
|
|
105
|
+
"wrong_navigation",
|
|
106
|
+
"stale_element",
|
|
107
|
+
];
|
|
108
|
+
/**
|
|
109
|
+
* Valida se um actionType é válido
|
|
110
|
+
*/
|
|
111
|
+
export function isValidActionType(action) {
|
|
112
|
+
return !!action && VALID_ACTION_TYPES.includes(action);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Valida se um errorType é válido
|
|
116
|
+
*/
|
|
117
|
+
export function isValidErrorType(error) {
|
|
118
|
+
return !!error && VALID_ERROR_TYPES.includes(error);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Infere o actionType baseado no contexto do XPath ou elemento
|
|
122
|
+
* Útil quando o actionType não é fornecido explicitamente
|
|
123
|
+
*/
|
|
124
|
+
export function inferActionType(xpath, context) {
|
|
125
|
+
const xpathLower = xpath.toLowerCase();
|
|
126
|
+
const contextText = context?.text?.toLowerCase() ?? "";
|
|
127
|
+
const contextRole = context?.role?.toLowerCase() ?? "";
|
|
128
|
+
const contextType = context?.type?.toLowerCase() ?? "";
|
|
129
|
+
// Checkbox/radio → check
|
|
130
|
+
if (xpathLower.includes("checkbox") ||
|
|
131
|
+
xpathLower.includes('type="checkbox"') ||
|
|
132
|
+
xpathLower.includes('type="radio"') ||
|
|
133
|
+
contextType === "checkbox" ||
|
|
134
|
+
contextType === "radio" ||
|
|
135
|
+
contextRole === "checkbox" ||
|
|
136
|
+
contextRole === "radio") {
|
|
137
|
+
return "check";
|
|
138
|
+
}
|
|
139
|
+
// Select/dropdown → select
|
|
140
|
+
if (xpathLower.includes("select") ||
|
|
141
|
+
xpathLower.includes("dropdown") ||
|
|
142
|
+
xpathLower.includes("combobox") ||
|
|
143
|
+
xpathLower.includes("listbox") ||
|
|
144
|
+
contextRole === "combobox" ||
|
|
145
|
+
contextRole === "listbox" ||
|
|
146
|
+
contextRole === "option") {
|
|
147
|
+
return "select";
|
|
148
|
+
}
|
|
149
|
+
// Input/textarea → fill
|
|
150
|
+
if (xpathLower.includes("input") ||
|
|
151
|
+
xpathLower.includes("textarea") ||
|
|
152
|
+
xpathLower.includes('type="text"') ||
|
|
153
|
+
xpathLower.includes('type="password"') ||
|
|
154
|
+
xpathLower.includes('type="email"') ||
|
|
155
|
+
xpathLower.includes('type="number"') ||
|
|
156
|
+
xpathLower.includes('type="search"') ||
|
|
157
|
+
xpathLower.includes('type="tel"') ||
|
|
158
|
+
xpathLower.includes('type="url"') ||
|
|
159
|
+
contextRole === "textbox" ||
|
|
160
|
+
contextType === "text" ||
|
|
161
|
+
contextType === "password" ||
|
|
162
|
+
contextType === "email") {
|
|
163
|
+
return "fill";
|
|
164
|
+
}
|
|
165
|
+
// Hover keywords
|
|
166
|
+
if (contextText.includes("menu") ||
|
|
167
|
+
contextText.includes("tooltip") ||
|
|
168
|
+
xpathLower.includes("menu") ||
|
|
169
|
+
xpathLower.includes("tooltip") ||
|
|
170
|
+
xpathLower.includes("dropdown-toggle")) {
|
|
171
|
+
return "hover";
|
|
172
|
+
}
|
|
173
|
+
// Keyboard/press
|
|
174
|
+
if (contextText.includes("enter") ||
|
|
175
|
+
contextText.includes("escape") ||
|
|
176
|
+
contextText.includes("tab")) {
|
|
177
|
+
return "press";
|
|
178
|
+
}
|
|
179
|
+
// Default: click (botões, links, etc)
|
|
180
|
+
return "click";
|
|
181
|
+
}
|
|
182
|
+
// Adapted host resolution and resilient fetch (inspired by TestHubClient)
|
|
183
|
+
import * as os from "os";
|
|
184
|
+
import { Logger } from "../Logger.js";
|
|
185
|
+
import { TestContext } from "../../testContext/TestContext.js";
|
|
186
|
+
function isAzureEnvironment() {
|
|
187
|
+
// Forçar modo LOCAL via variável de ambiente (override explícito)
|
|
188
|
+
if (process.env.AUTOCORE_FORCE_LOCAL === "true")
|
|
189
|
+
return false;
|
|
190
|
+
// Variáveis exclusivas do Azure Pipelines / CI
|
|
191
|
+
if (process.env.TF_BUILD === "True" || process.env.AGENT_NAME)
|
|
192
|
+
return true;
|
|
193
|
+
if (process.env.CI === "true" || process.env.BUILD_BUILDID)
|
|
194
|
+
return true;
|
|
195
|
+
// NOTA: os.platform() foi removido propositalmente.
|
|
196
|
+
// macOS (darwin) e Linux são plataformas de desenvolvimento local válidas.
|
|
197
|
+
// Apenas variáveis de CI/CD indicam execução remota.
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
function isLikelyTestFileEntry(value) {
|
|
201
|
+
const lowered = value.toLowerCase();
|
|
202
|
+
return (lowered.includes(".spec.") ||
|
|
203
|
+
lowered.includes(".test.") ||
|
|
204
|
+
lowered.endsWith(".feature") ||
|
|
205
|
+
lowered.includes("/") ||
|
|
206
|
+
lowered.includes("\\"));
|
|
207
|
+
}
|
|
208
|
+
function parseCnCtFromTitlePath(testInfo) {
|
|
209
|
+
try {
|
|
210
|
+
const rawPath = Array.isArray(testInfo?.titlePath)
|
|
211
|
+
? testInfo.titlePath
|
|
212
|
+
: [];
|
|
213
|
+
const cleaned = rawPath
|
|
214
|
+
.map((entry) => String(entry || "").trim())
|
|
215
|
+
.filter((entry) => !!entry && !isLikelyTestFileEntry(entry));
|
|
216
|
+
if (cleaned.length >= 2) {
|
|
217
|
+
const ctName = cleaned[cleaned.length - 1];
|
|
218
|
+
const cnName = cleaned[cleaned.length - 2];
|
|
219
|
+
return {
|
|
220
|
+
cnName,
|
|
221
|
+
ctName,
|
|
222
|
+
methodName: ctName,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
const fallbackTitle = String(testInfo?.title || "").trim();
|
|
226
|
+
if (fallbackTitle) {
|
|
227
|
+
return {
|
|
228
|
+
cnName: fallbackTitle,
|
|
229
|
+
ctName: fallbackTitle,
|
|
230
|
+
methodName: fallbackTitle,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// no-op
|
|
236
|
+
}
|
|
237
|
+
return {};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Obtém metadados de recovery a partir de variáveis de ambiente e TestContext (v2.7.43)
|
|
241
|
+
* - executionSource: detecta automaticamente LOCAL vs AZURE
|
|
242
|
+
* - testCaseName/testCaseId/testMethodName: obtém do TestContext.testInfo quando disponível
|
|
243
|
+
*/
|
|
244
|
+
export function getRecoveryMetadata() {
|
|
245
|
+
const executionSource = isAzureEnvironment()
|
|
246
|
+
? "AZURE"
|
|
247
|
+
: "LOCAL";
|
|
248
|
+
let testCaseName;
|
|
249
|
+
let testCaseId;
|
|
250
|
+
let testMethodName;
|
|
251
|
+
// Obter do env (podem ser setados pelo runner/CI)
|
|
252
|
+
testCaseName = process.env.AUTOCORE_TEST_CASE_NAME || undefined;
|
|
253
|
+
testCaseId = process.env.AUTOCORE_TEST_CASE_ID || undefined;
|
|
254
|
+
testMethodName = process.env.AUTOCORE_TEST_METHOD_NAME || undefined;
|
|
255
|
+
// Tentar obter do TestContext.testInfo (Playwright) sem require dinâmico.
|
|
256
|
+
try {
|
|
257
|
+
const testInfo = TestContext?.testInfo || TestContext?.getSafeTestInfo?.();
|
|
258
|
+
if (testInfo) {
|
|
259
|
+
const parsed = parseCnCtFromTitlePath(testInfo);
|
|
260
|
+
if (!testCaseName) {
|
|
261
|
+
testCaseName = parsed.cnName || testInfo.title || undefined;
|
|
262
|
+
}
|
|
263
|
+
if (!testMethodName) {
|
|
264
|
+
testMethodName =
|
|
265
|
+
parsed.methodName || parsed.ctName || testInfo.title || undefined;
|
|
266
|
+
}
|
|
267
|
+
if (!testCaseId) {
|
|
268
|
+
const ctCandidate = parsed.ctName || testInfo.title || "";
|
|
269
|
+
const ctMatch = ctCandidate.match(/\b(CT[-_]?\d+)\b/i);
|
|
270
|
+
testCaseId = ctMatch?.[1] || ctCandidate || undefined;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// TestContext pode não estar disponível
|
|
276
|
+
}
|
|
277
|
+
if (!testCaseName)
|
|
278
|
+
testCaseName = process.env.AUTOCORE_CN_NAME || undefined;
|
|
279
|
+
if (!testCaseId)
|
|
280
|
+
testCaseId = process.env.AUTOCORE_CT_NAME || undefined;
|
|
281
|
+
if (!testMethodName)
|
|
282
|
+
testMethodName =
|
|
283
|
+
process.env.AUTOCORE_TEST_METHOD || testCaseId || undefined;
|
|
284
|
+
return { executionSource, testCaseName, testCaseId, testMethodName };
|
|
285
|
+
}
|
|
286
|
+
const HUB_URL_HOSTNAME = "http://brtlvlty0559pl:3333";
|
|
287
|
+
const HUB_URL_IP = "http://10.129.170.238:3333";
|
|
288
|
+
function getAutoCorehubUrl() {
|
|
289
|
+
if (process.env.AUTOCORE_HUB_URL?.trim()) {
|
|
290
|
+
return process.env.AUTOCORE_HUB_URL.trim();
|
|
291
|
+
}
|
|
292
|
+
if (process.env.AUTOCORE_HUB_IP?.trim()) {
|
|
293
|
+
return process.env.AUTOCORE_HUB_IP.trim();
|
|
294
|
+
}
|
|
295
|
+
if (isAzureEnvironment())
|
|
296
|
+
return HUB_URL_IP;
|
|
297
|
+
return HUB_URL_HOSTNAME;
|
|
298
|
+
}
|
|
299
|
+
function getFallbackHubUrl(currentUrl) {
|
|
300
|
+
if (currentUrl === HUB_URL_HOSTNAME)
|
|
301
|
+
return HUB_URL_IP;
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
305
|
+
const { timeout = 60000, ...fetchOptions } = options;
|
|
306
|
+
const controller = new AbortController();
|
|
307
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
308
|
+
try {
|
|
309
|
+
const res = await fetch(url, {
|
|
310
|
+
...fetchOptions,
|
|
311
|
+
signal: controller.signal,
|
|
312
|
+
});
|
|
313
|
+
return res;
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
clearTimeout(id);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Constrói headers padrão para requisições ao AutoCore Hub
|
|
321
|
+
* Usa o mesmo x-automation-key do TestHubClient
|
|
322
|
+
*/
|
|
323
|
+
function authHeaders() {
|
|
324
|
+
return {
|
|
325
|
+
"Content-Type": "application/json",
|
|
326
|
+
"x-automation-key": "d8f3c4a9be7f29d1c6e04fa1a39c2e7bd91f57c8a3de45eb91c4f7a2d0e3b8fa",
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async function postJsonWithFallback(path, body, timeout = 60000, extraHeaders) {
|
|
330
|
+
const base = getAutoCorehubUrl();
|
|
331
|
+
const fallback = getFallbackHubUrl(base);
|
|
332
|
+
const headers = { ...authHeaders(), ...extraHeaders };
|
|
333
|
+
// Se não houver fallback, fazer apenas uma requisição
|
|
334
|
+
if (!fallback) {
|
|
335
|
+
return await fetchWithTimeout(`${base}${path}`, {
|
|
336
|
+
method: "POST",
|
|
337
|
+
headers,
|
|
338
|
+
body: JSON.stringify(body),
|
|
339
|
+
timeout,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// ✅ Promise.all: Tentar ambas URLs em paralelo (primary + fallback)
|
|
343
|
+
const [primaryResult, fallbackResult] = await Promise.allSettled([
|
|
344
|
+
fetchWithTimeout(`${base}${path}`, {
|
|
345
|
+
method: "POST",
|
|
346
|
+
headers,
|
|
347
|
+
body: JSON.stringify(body),
|
|
348
|
+
timeout,
|
|
349
|
+
}),
|
|
350
|
+
fetchWithTimeout(`${fallback}${path}`, {
|
|
351
|
+
method: "POST",
|
|
352
|
+
headers,
|
|
353
|
+
body: JSON.stringify(body),
|
|
354
|
+
timeout,
|
|
355
|
+
}),
|
|
356
|
+
]);
|
|
357
|
+
// Priorizar resultado primário se sucesso
|
|
358
|
+
if (primaryResult.status === "fulfilled" && primaryResult.value.ok) {
|
|
359
|
+
return primaryResult.value;
|
|
360
|
+
}
|
|
361
|
+
// Usar fallback se primário falhou
|
|
362
|
+
if (fallbackResult.status === "fulfilled" && fallbackResult.value.ok) {
|
|
363
|
+
return fallbackResult.value;
|
|
364
|
+
}
|
|
365
|
+
// Se ambos falharam, retornar o erro do primário
|
|
366
|
+
if (primaryResult.status === "fulfilled") {
|
|
367
|
+
return primaryResult.value;
|
|
368
|
+
}
|
|
369
|
+
throw primaryResult.reason;
|
|
370
|
+
}
|
|
371
|
+
async function getJsonWithFallback(path, timeout = 60000) {
|
|
372
|
+
const base = getAutoCorehubUrl();
|
|
373
|
+
const fallback = getFallbackHubUrl(base);
|
|
374
|
+
// Se não houver fallback, fazer apenas uma requisição
|
|
375
|
+
if (!fallback) {
|
|
376
|
+
return await fetchWithTimeout(`${base}${path}`, {
|
|
377
|
+
method: "GET",
|
|
378
|
+
headers: authHeaders(),
|
|
379
|
+
timeout,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
// ✅ Promise.all: Tentar ambas URLs em paralelo (primary + fallback)
|
|
383
|
+
const [primaryResult, fallbackResult] = await Promise.allSettled([
|
|
384
|
+
fetchWithTimeout(`${base}${path}`, {
|
|
385
|
+
method: "GET",
|
|
386
|
+
headers: authHeaders(),
|
|
387
|
+
timeout,
|
|
388
|
+
}),
|
|
389
|
+
fetchWithTimeout(`${fallback}${path}`, {
|
|
390
|
+
method: "GET",
|
|
391
|
+
headers: authHeaders(),
|
|
392
|
+
timeout,
|
|
393
|
+
}),
|
|
394
|
+
]);
|
|
395
|
+
// Priorizar resultado primário se sucesso
|
|
396
|
+
if (primaryResult.status === "fulfilled" && primaryResult.value.ok) {
|
|
397
|
+
return primaryResult.value;
|
|
398
|
+
}
|
|
399
|
+
// Usar fallback se primário falhou
|
|
400
|
+
if (fallbackResult.status === "fulfilled" && fallbackResult.value.ok) {
|
|
401
|
+
return fallbackResult.value;
|
|
402
|
+
}
|
|
403
|
+
// Se ambos falharam, retornar o erro do primário
|
|
404
|
+
if (primaryResult.status === "fulfilled") {
|
|
405
|
+
return primaryResult.value;
|
|
406
|
+
}
|
|
407
|
+
throw primaryResult.reason;
|
|
408
|
+
}
|
|
409
|
+
export async function getRecurringFailures(params = {}) {
|
|
410
|
+
const days = params.days ?? 7;
|
|
411
|
+
const minOccurrences = params.minOccurrences ?? 3;
|
|
412
|
+
const limit = params.limit ?? 20;
|
|
413
|
+
const q = `?days=${encodeURIComponent(String(days))}&minOccurrences=${encodeURIComponent(String(minOccurrences))}&limit=${encodeURIComponent(String(limit))}`;
|
|
414
|
+
try {
|
|
415
|
+
const res = await getJsonWithFallback(`/api/auto-healing/recurring-failures${q}`);
|
|
416
|
+
if (!res || !res.ok)
|
|
417
|
+
return null;
|
|
418
|
+
return await res.json();
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
export async function suggestRefactor(xpath) {
|
|
425
|
+
try {
|
|
426
|
+
const res = await getJsonWithFallback(`/api/auto-healing/suggest-refactor?xpath=${encodeURIComponent(xpath)}`);
|
|
427
|
+
if (!res || !res.ok)
|
|
428
|
+
return null;
|
|
429
|
+
return await res.json();
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
export async function impactAnalysis(xpath) {
|
|
436
|
+
try {
|
|
437
|
+
const res = await getJsonWithFallback(`/api/auto-healing/impact-analysis/${encodeURIComponent(xpath)}`);
|
|
438
|
+
if (!res || !res.ok)
|
|
439
|
+
return null;
|
|
440
|
+
return await res.json();
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Valida se o payload tem estrutura de página (obrigatório para análise)
|
|
448
|
+
*/
|
|
449
|
+
function hasPageContext(body) {
|
|
450
|
+
// pageStructure com pelo menos um array preenchido
|
|
451
|
+
if (body.pageStructure) {
|
|
452
|
+
const { buttons, inputs, links, elements, headings } = body.pageStructure;
|
|
453
|
+
if ((buttons && buttons.length > 0) ||
|
|
454
|
+
(inputs && inputs.length > 0) ||
|
|
455
|
+
(links && links.length > 0) ||
|
|
456
|
+
(elements && elements.length > 0) ||
|
|
457
|
+
(headings && headings.length > 0)) {
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// pageHtml com conteúdo
|
|
462
|
+
if (body.pageHtml && body.pageHtml.length > 100) {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
export async function analyzeFailure(body) {
|
|
468
|
+
try {
|
|
469
|
+
// Auto-enriquecer com metadados de recovery quando não fornecidos (v2.7.43)
|
|
470
|
+
const meta = getRecoveryMetadata();
|
|
471
|
+
// Validar e preparar payload
|
|
472
|
+
// Construir actor a partir de UserHeaders se não fornecido explicitamente (item 23 v2.7.44)
|
|
473
|
+
const resolvedActor = body.actor || buildActorFromEnv();
|
|
474
|
+
const payload = {
|
|
475
|
+
...body,
|
|
476
|
+
actionType: body.actionType,
|
|
477
|
+
errorType: body.errorType,
|
|
478
|
+
executionSource: body.executionSource || meta.executionSource,
|
|
479
|
+
testCaseName: body.testCaseName || meta.testCaseName,
|
|
480
|
+
testCaseId: body.testCaseId || meta.testCaseId,
|
|
481
|
+
testMethodName: body.testMethodName || meta.testMethodName,
|
|
482
|
+
actor: resolvedActor,
|
|
483
|
+
};
|
|
484
|
+
// ⚠️ Validar pageStructure ou pageHtml (obrigatório conforme guia)
|
|
485
|
+
if (!hasPageContext(body)) {
|
|
486
|
+
Logger.warning("[TestRecoveryClient] ⚠️ AVISO: pageStructure ou pageHtml não fornecido. A análise pode ter baixa qualidade. Use extract_page_structure ou page.content() para capturar o contexto.");
|
|
487
|
+
}
|
|
488
|
+
// Validar/inferir actionType
|
|
489
|
+
if (!isValidActionType(payload.actionType)) {
|
|
490
|
+
const contextForInference = body.context
|
|
491
|
+
? {
|
|
492
|
+
text: body.context.text,
|
|
493
|
+
role: body.context.role,
|
|
494
|
+
type: body.context.type,
|
|
495
|
+
}
|
|
496
|
+
: undefined;
|
|
497
|
+
const inferred = inferActionType(body.failedXPath, contextForInference);
|
|
498
|
+
Logger.warning(`[TestRecoveryClient] actionType "${body.actionType}" inválido ou ausente. Inferido: "${inferred}" baseado no XPath/contexto.`);
|
|
499
|
+
payload.actionType = inferred;
|
|
500
|
+
}
|
|
501
|
+
// Validar errorType
|
|
502
|
+
if (!isValidErrorType(payload.errorType)) {
|
|
503
|
+
Logger.warning(`[TestRecoveryClient] errorType "${body.errorType}" inválido. Usando "not_found" como fallback.`);
|
|
504
|
+
payload.errorType = "not_found";
|
|
505
|
+
}
|
|
506
|
+
// (v2.7.44 - item 14) Enriquecer selectorAliases para fill de username/login
|
|
507
|
+
const actionStr = String(payload.actionType);
|
|
508
|
+
if ((actionStr === "fill" || actionStr === "type") &&
|
|
509
|
+
payload.failedXPath &&
|
|
510
|
+
!payload.context?.selectorAliases) {
|
|
511
|
+
const xp = payload.failedXPath;
|
|
512
|
+
if (!payload.context)
|
|
513
|
+
payload.context = {};
|
|
514
|
+
payload.context.selectorAliases = {
|
|
515
|
+
selector: xp,
|
|
516
|
+
ref: xp,
|
|
517
|
+
xpath: xp,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
// 📤 LOG SIMPLIFICADO DA REQUISIÇÃO
|
|
521
|
+
Logger.info(`[TestRecoveryClient] 📤 Analisando falha no XPath: ${payload.failedXPath}`);
|
|
522
|
+
// CN/CT/Method + executionSource headers (v2.7.44 - item 10)
|
|
523
|
+
const analyzeHeaders = getSingletonUserHeaders();
|
|
524
|
+
if (payload.testCaseName) {
|
|
525
|
+
analyzeHeaders["x-test-case-name"] = payload.testCaseName;
|
|
526
|
+
analyzeHeaders["x-cn"] = payload.testCaseName;
|
|
527
|
+
analyzeHeaders["x-test-name"] = payload.testCaseName;
|
|
528
|
+
analyzeHeaders["x-cn-name"] = payload.testCaseName;
|
|
529
|
+
}
|
|
530
|
+
if (payload.testCaseId) {
|
|
531
|
+
analyzeHeaders["x-test-case-id"] = payload.testCaseId;
|
|
532
|
+
analyzeHeaders["x-ct"] = payload.testCaseId;
|
|
533
|
+
analyzeHeaders["x-test-id"] = payload.testCaseId;
|
|
534
|
+
analyzeHeaders["x-ct-id"] = payload.testCaseId;
|
|
535
|
+
}
|
|
536
|
+
if (payload.testMethodName) {
|
|
537
|
+
analyzeHeaders["x-test-method-name"] = payload.testMethodName;
|
|
538
|
+
analyzeHeaders["x-method"] = payload.testMethodName;
|
|
539
|
+
}
|
|
540
|
+
if (payload.executionSource) {
|
|
541
|
+
analyzeHeaders["x-execution-source"] = payload.executionSource;
|
|
542
|
+
analyzeHeaders["x-execution-origin"] = payload.executionSource;
|
|
543
|
+
}
|
|
544
|
+
const res = await postJsonWithFallback("/api/test-recovery/analyze-failure", payload, 60000, analyzeHeaders);
|
|
545
|
+
// 📥 Tratar resposta 202 (fila assíncrona) - v2.7.43
|
|
546
|
+
if (res && res.status === 202) {
|
|
547
|
+
try {
|
|
548
|
+
const queuedData = (await res.json());
|
|
549
|
+
if (queuedData.queued && queuedData.requestId) {
|
|
550
|
+
Logger.info(`[TestRecoveryClient] ⏳ Requisição enfileirada (posição ${queuedData.position ?? "?"}). Aguardando resultado...`);
|
|
551
|
+
const queueResult = await pollQueueResult(queuedData.requestId);
|
|
552
|
+
if (queueResult) {
|
|
553
|
+
Logger.success(`[TestRecoveryClient] ✅ Resultado da fila recebido!`);
|
|
554
|
+
return queueResult;
|
|
555
|
+
}
|
|
556
|
+
Logger.warning(`[TestRecoveryClient] ⚠️ Timeout/falha na fila assíncrona para requestId: ${queuedData.requestId}`);
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
Logger.warning("[TestRecoveryClient] ⚠️ Erro ao processar resposta 202");
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
// 📥 LOG SIMPLIFICADO DA RESPOSTA
|
|
566
|
+
if (!res || !res.ok) {
|
|
567
|
+
Logger.error(`[TestRecoveryClient] ❌ Erro na requisição: Status HTTP ${res?.status ?? "no response"}`);
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
const responseData = (await res.json());
|
|
571
|
+
Logger.success(`[TestRecoveryClient] ✅ AutoHealing executado com sucesso!`);
|
|
572
|
+
Logger.info(` 📍 XPath anterior: ${payload.failedXPath}`);
|
|
573
|
+
Logger.info(` ✨ XPath sugerido: ${responseData.analysis.suggestedXPath} (confiança: ${(responseData.analysis.confidence * 100).toFixed(0)}%)`);
|
|
574
|
+
if (responseData.logId) {
|
|
575
|
+
Logger.info(` 🆔 LogId: ${responseData.logId}`);
|
|
576
|
+
}
|
|
577
|
+
return responseData;
|
|
578
|
+
}
|
|
579
|
+
catch (err) {
|
|
580
|
+
Logger.error(`[TestRecoveryClient] ❌ Exceção: ${err instanceof Error ? err.message : String(err)}`);
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
export async function fixInCode(payload) {
|
|
585
|
+
try {
|
|
586
|
+
const res = await postJsonWithFallback("/api/test-recovery/fix-in-code", payload, 60000, getSingletonUserHeaders());
|
|
587
|
+
if (!res || !res.ok)
|
|
588
|
+
return null;
|
|
589
|
+
return (await res.json());
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
export async function markSuccess(sessionId, attemptNumber) {
|
|
596
|
+
try {
|
|
597
|
+
const res = await postJsonWithFallback("/api/test-recovery/mark-success", {
|
|
598
|
+
sessionId,
|
|
599
|
+
attemptNumber,
|
|
600
|
+
});
|
|
601
|
+
return !!res && res.ok;
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function normalizeUserValue(value) {
|
|
608
|
+
return typeof value === "string" ? value.trim() : "";
|
|
609
|
+
}
|
|
610
|
+
function resolveRuntimeUserDefaults() {
|
|
611
|
+
const id = normalizeUserValue(process.env.AUTOCORE_USER_ID || process.env.USER_ID || "");
|
|
612
|
+
const email = normalizeUserValue(process.env.AUTOCORE_USER_EMAIL ||
|
|
613
|
+
process.env.BUILD_REQUESTEDFOREMAIL ||
|
|
614
|
+
process.env.BUILD_REQUESTEDFOR_EMAIL ||
|
|
615
|
+
"");
|
|
616
|
+
const matricula = normalizeUserValue(process.env.AUTOCORE_USER_MATRICULA ||
|
|
617
|
+
process.env.USERNAME ||
|
|
618
|
+
process.env.USER ||
|
|
619
|
+
"");
|
|
620
|
+
let osUserName = "";
|
|
621
|
+
try {
|
|
622
|
+
osUserName = normalizeUserValue(os.userInfo().username);
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
// noop
|
|
626
|
+
}
|
|
627
|
+
const name = normalizeUserValue(process.env.AUTOCORE_USER_NAME ||
|
|
628
|
+
process.env.USER_FULLNAME ||
|
|
629
|
+
matricula ||
|
|
630
|
+
osUserName);
|
|
631
|
+
return {
|
|
632
|
+
id,
|
|
633
|
+
email,
|
|
634
|
+
matricula,
|
|
635
|
+
name,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Constrói objeto `actor` a partir de variáveis de ambiente e singleton (item 23 v2.7.44).
|
|
640
|
+
* Usado automaticamente em analyzeFailure/reportResult quando nenhum actor é fornecido.
|
|
641
|
+
*/
|
|
642
|
+
function buildActorFromEnv() {
|
|
643
|
+
const defaults = resolveRuntimeUserDefaults();
|
|
644
|
+
const id = normalizeUserValue(_singletonUserHeaders?.userId || defaults.id);
|
|
645
|
+
const name = normalizeUserValue(_singletonUserHeaders?.userName || defaults.name);
|
|
646
|
+
const email = normalizeUserValue(_singletonUserHeaders?.userEmail || defaults.email);
|
|
647
|
+
const matricula = normalizeUserValue(_singletonUserHeaders?.userMatricula || defaults.matricula);
|
|
648
|
+
if (!id && !name && !email && !matricula)
|
|
649
|
+
return undefined;
|
|
650
|
+
return {
|
|
651
|
+
...(id ? { id } : {}),
|
|
652
|
+
...(name ? { name } : {}),
|
|
653
|
+
...(email ? { email } : {}),
|
|
654
|
+
...(matricula ? { matricula } : {}),
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Constrói headers extras de identificação do usuário (v2.7.44)
|
|
659
|
+
* Envia múltiplos aliases aceitos pelo backend para maximizar taxa de preenchimento
|
|
660
|
+
* Retorna apenas os headers de usuário (sem Content-Type/key)
|
|
661
|
+
*/
|
|
662
|
+
function userHeadersMap(user) {
|
|
663
|
+
const headers = {};
|
|
664
|
+
const defaults = resolveRuntimeUserDefaults();
|
|
665
|
+
// Resolver valores: priorizar user param, depois env vars
|
|
666
|
+
const userId = normalizeUserValue(user?.userId || defaults.id);
|
|
667
|
+
const userEmail = normalizeUserValue(user?.userEmail || defaults.email);
|
|
668
|
+
const userMatricula = normalizeUserValue(user?.userMatricula || defaults.matricula);
|
|
669
|
+
const userName = normalizeUserValue(user?.userName || defaults.name);
|
|
670
|
+
// Aliases primários (item 9)
|
|
671
|
+
if (userId) {
|
|
672
|
+
headers["x-automation-user-id"] = userId;
|
|
673
|
+
headers["x-user-id"] = userId;
|
|
674
|
+
headers["x-userid"] = userId;
|
|
675
|
+
}
|
|
676
|
+
if (userEmail) {
|
|
677
|
+
headers["x-automation-user-email"] = userEmail;
|
|
678
|
+
headers["x-user-email"] = userEmail;
|
|
679
|
+
headers["x-framework-user-email"] = userEmail;
|
|
680
|
+
}
|
|
681
|
+
if (userMatricula) {
|
|
682
|
+
headers["x-automation-user-matricula"] = userMatricula;
|
|
683
|
+
headers["x-user-matricula"] = userMatricula;
|
|
684
|
+
headers["x-matricula"] = userMatricula;
|
|
685
|
+
headers["x-framework-user-matricula"] = userMatricula;
|
|
686
|
+
}
|
|
687
|
+
if (userName) {
|
|
688
|
+
headers["x-automation-user-name"] = userName;
|
|
689
|
+
headers["x-user-name"] = userName;
|
|
690
|
+
headers["x-user"] = userName;
|
|
691
|
+
headers["x-framework-user-name"] = userName;
|
|
692
|
+
}
|
|
693
|
+
if (!userName && userMatricula) {
|
|
694
|
+
headers["x-automation-user-name"] = userMatricula;
|
|
695
|
+
headers["x-user-name"] = userMatricula;
|
|
696
|
+
headers["x-user"] = userMatricula;
|
|
697
|
+
}
|
|
698
|
+
return headers;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Constrói headers completos com identificação do usuário (opcional)
|
|
702
|
+
*/
|
|
703
|
+
function authHeadersWithUser(user) {
|
|
704
|
+
return { ...authHeaders(), ...userHeadersMap(user) };
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Obtém user headers do singleton (para chamadas automáticas do framework)
|
|
708
|
+
*/
|
|
709
|
+
function getSingletonUserHeaders() {
|
|
710
|
+
// Lazy access: singleton é criado no final do arquivo
|
|
711
|
+
try {
|
|
712
|
+
return userHeadersMap(_singletonUserHeaders);
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
return {};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// Referência aos userHeaders do singleton - atualizada por setUserHeaders()
|
|
719
|
+
let _singletonUserHeaders = {};
|
|
720
|
+
/**
|
|
721
|
+
* Reporta resultado de tentativa de recovery
|
|
722
|
+
*
|
|
723
|
+
* ⚠️ OBRIGATÓRIO chamar após testar XPath sugerido (v2.6.9)
|
|
724
|
+
* Sem report-result, a tentativa fica como "pendente" no dashboard
|
|
725
|
+
*
|
|
726
|
+
* @param params Parâmetros do resultado
|
|
727
|
+
* @param user Headers opcionais de identificação do usuário
|
|
728
|
+
* @returns Resposta do servidor
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* ```typescript
|
|
732
|
+
* // Após testar XPath sugerido
|
|
733
|
+
* await reportResult({
|
|
734
|
+
* logId: result.logId,
|
|
735
|
+
* success: true,
|
|
736
|
+
* autoFixed: true,
|
|
737
|
+
* executionSource: 'AZURE',
|
|
738
|
+
* recoverySource: 'framework',
|
|
739
|
+
* testCaseName: 'Login Test',
|
|
740
|
+
* testCaseId: 'CT-001',
|
|
741
|
+
* testMethodName: 'testLogin'
|
|
742
|
+
* });
|
|
743
|
+
* ```
|
|
744
|
+
*/
|
|
745
|
+
export async function reportResult(params, user) {
|
|
746
|
+
try {
|
|
747
|
+
// Auto-enriquecer com metadados quando ausentes (v2.7.44)
|
|
748
|
+
const meta = getRecoveryMetadata();
|
|
749
|
+
const enriched = {
|
|
750
|
+
...params,
|
|
751
|
+
executionSource: params.executionSource || meta.executionSource,
|
|
752
|
+
testCaseName: params.testCaseName || meta.testCaseName,
|
|
753
|
+
testCaseId: params.testCaseId || meta.testCaseId,
|
|
754
|
+
testMethodName: params.testMethodName || meta.testMethodName,
|
|
755
|
+
actor: params.actor || buildActorFromEnv(),
|
|
756
|
+
};
|
|
757
|
+
const base = getAutoCorehubUrl();
|
|
758
|
+
const fallback = getFallbackHubUrl(base);
|
|
759
|
+
const userH = user || _singletonUserHeaders;
|
|
760
|
+
const headers = authHeadersWithUser(userH);
|
|
761
|
+
// CN/CT/Method headers (v2.7.44 - item 10)
|
|
762
|
+
if (enriched.testCaseName) {
|
|
763
|
+
headers["x-test-case-name"] = enriched.testCaseName;
|
|
764
|
+
headers["x-cn"] = enriched.testCaseName;
|
|
765
|
+
headers["x-test-name"] = enriched.testCaseName;
|
|
766
|
+
headers["x-cn-name"] = enriched.testCaseName;
|
|
767
|
+
}
|
|
768
|
+
if (enriched.testCaseId) {
|
|
769
|
+
headers["x-test-case-id"] = enriched.testCaseId;
|
|
770
|
+
headers["x-ct"] = enriched.testCaseId;
|
|
771
|
+
headers["x-test-id"] = enriched.testCaseId;
|
|
772
|
+
headers["x-ct-id"] = enriched.testCaseId;
|
|
773
|
+
}
|
|
774
|
+
if (enriched.testMethodName) {
|
|
775
|
+
headers["x-test-method-name"] = enriched.testMethodName;
|
|
776
|
+
headers["x-method"] = enriched.testMethodName;
|
|
777
|
+
}
|
|
778
|
+
if (enriched.executionSource) {
|
|
779
|
+
headers["x-execution-source"] = enriched.executionSource;
|
|
780
|
+
headers["x-execution-origin"] = enriched.executionSource;
|
|
781
|
+
}
|
|
782
|
+
const body = JSON.stringify(enriched);
|
|
783
|
+
// Se não houver fallback, fazer apenas uma requisição
|
|
784
|
+
if (!fallback) {
|
|
785
|
+
const res = await fetchWithTimeout(`${base}/api/test-recovery/report-result`, {
|
|
786
|
+
method: "POST",
|
|
787
|
+
headers,
|
|
788
|
+
body,
|
|
789
|
+
timeout: 30000,
|
|
790
|
+
});
|
|
791
|
+
if (!res.ok)
|
|
792
|
+
return null;
|
|
793
|
+
return (await res.json());
|
|
794
|
+
}
|
|
795
|
+
// Promise.all: Tentar ambas URLs em paralelo
|
|
796
|
+
const [primaryResult, fallbackResult] = await Promise.allSettled([
|
|
797
|
+
fetchWithTimeout(`${base}/api/test-recovery/report-result`, {
|
|
798
|
+
method: "POST",
|
|
799
|
+
headers,
|
|
800
|
+
body,
|
|
801
|
+
timeout: 30000,
|
|
802
|
+
}),
|
|
803
|
+
fetchWithTimeout(`${fallback}/api/test-recovery/report-result`, {
|
|
804
|
+
method: "POST",
|
|
805
|
+
headers,
|
|
806
|
+
body,
|
|
807
|
+
timeout: 30000,
|
|
808
|
+
}),
|
|
809
|
+
]);
|
|
810
|
+
// Priorizar resultado primário se sucesso
|
|
811
|
+
if (primaryResult.status === "fulfilled" && primaryResult.value.ok) {
|
|
812
|
+
return (await primaryResult.value.json());
|
|
813
|
+
}
|
|
814
|
+
// Usar fallback se primário falhou
|
|
815
|
+
if (fallbackResult.status === "fulfilled" && fallbackResult.value.ok) {
|
|
816
|
+
return (await fallbackResult.value.json());
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
catch (err) {
|
|
821
|
+
Logger.error(`[TestRecoveryClient] Erro ao reportar resultado: ${err instanceof Error ? err.message : String(err)}`, err);
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
export async function getRecoveryStats(params = {}) {
|
|
826
|
+
const queryParts = [];
|
|
827
|
+
if (params.userId)
|
|
828
|
+
queryParts.push(`userId=${encodeURIComponent(params.userId)}`);
|
|
829
|
+
if (params.startDate)
|
|
830
|
+
queryParts.push(`startDate=${encodeURIComponent(params.startDate)}`);
|
|
831
|
+
if (params.endDate)
|
|
832
|
+
queryParts.push(`endDate=${encodeURIComponent(params.endDate)}`);
|
|
833
|
+
const q = queryParts.length ? `?${queryParts.join("&")}` : "";
|
|
834
|
+
try {
|
|
835
|
+
const res = await getJsonWithFallback(`/api/test-recovery/stats${q}`);
|
|
836
|
+
if (!res || !res.ok)
|
|
837
|
+
return null;
|
|
838
|
+
return (await res.json());
|
|
839
|
+
}
|
|
840
|
+
catch {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
export async function getQueueStatus(requestId) {
|
|
845
|
+
try {
|
|
846
|
+
const res = await getJsonWithFallback(`/api/test-recovery/queue/status/${encodeURIComponent(requestId)}`);
|
|
847
|
+
if (!res || !res.ok)
|
|
848
|
+
return null;
|
|
849
|
+
return (await res.json());
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Poll assíncrono para resultado de fila do analyze-failure (v2.7.44)
|
|
857
|
+
* Aguarda até o status ser 'completed' ou 'failed', com timeout de 90s
|
|
858
|
+
* Usa backoff progressivo: 1s, 2s, 3s, 5s, 8s... (item 25.2)
|
|
859
|
+
*
|
|
860
|
+
* @param requestId ID da requisição retornado pelo 202
|
|
861
|
+
* @param maxWaitMs Tempo máximo de espera em ms (default: 90000)
|
|
862
|
+
* @returns RecoveryResponse quando completado, null em timeout/falha
|
|
863
|
+
*/
|
|
864
|
+
async function pollQueueResult(requestId, maxWaitMs = 90000) {
|
|
865
|
+
const startTime = Date.now();
|
|
866
|
+
// Backoff progressivo (Fibonacci-like): 1s, 2s, 3s, 5s, 8s, 8s, 8s...
|
|
867
|
+
const backoffSchedule = [1000, 2000, 3000, 5000, 8000];
|
|
868
|
+
let pollIndex = 0;
|
|
869
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
870
|
+
const delay = backoffSchedule[Math.min(pollIndex, backoffSchedule.length - 1)];
|
|
871
|
+
try {
|
|
872
|
+
const status = await getQueueStatus(requestId);
|
|
873
|
+
if (!status) {
|
|
874
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
875
|
+
pollIndex++;
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (status.status === "completed" && status.result) {
|
|
879
|
+
return status.result;
|
|
880
|
+
}
|
|
881
|
+
if (status.status === "failed" || status.status === "timeout") {
|
|
882
|
+
Logger.error(`[TestRecoveryClient] ❌ Fila ${status.status} para ${requestId}: ${status.error || "unknown"}`);
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
// pending ou processing - aguardar com backoff
|
|
886
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
887
|
+
pollIndex++;
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
891
|
+
pollIndex++;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
Logger.warning(`[TestRecoveryClient] ⏱️ Timeout de ${maxWaitMs}ms ao aguardar fila para ${requestId}`);
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Obtém logs de recuperação (histórico)
|
|
899
|
+
* GET /api/test-recovery/recovery-logs
|
|
900
|
+
*/
|
|
901
|
+
export async function getRecoveryLogs(params = {}) {
|
|
902
|
+
const queryParts = [];
|
|
903
|
+
if (params.sessionId)
|
|
904
|
+
queryParts.push(`sessionId=${encodeURIComponent(params.sessionId)}`);
|
|
905
|
+
if (params.limit)
|
|
906
|
+
queryParts.push(`limit=${encodeURIComponent(String(params.limit))}`);
|
|
907
|
+
const q = queryParts.length ? `?${queryParts.join("&")}` : "";
|
|
908
|
+
try {
|
|
909
|
+
const res = await getJsonWithFallback(`/api/test-recovery/recovery-logs${q}`);
|
|
910
|
+
if (!res || !res.ok)
|
|
911
|
+
return null;
|
|
912
|
+
return (await res.json());
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
// ==========================================
|
|
919
|
+
// TestRecoveryHelper - Classe helper para integração v2.6.1
|
|
920
|
+
// ==========================================
|
|
921
|
+
/**
|
|
922
|
+
* Classe helper para integração com Test Recovery
|
|
923
|
+
* Gerencia o estado de campos preenchidos e facilita a análise de falhas
|
|
924
|
+
*
|
|
925
|
+
* @example
|
|
926
|
+
* ```typescript
|
|
927
|
+
* const recovery = new TestRecoveryHelper();
|
|
928
|
+
*
|
|
929
|
+
* try {
|
|
930
|
+
* await page.locator(xpath).fill(value);
|
|
931
|
+
* recovery.markFieldFilled(xpath, 'fieldId');
|
|
932
|
+
* } catch (error) {
|
|
933
|
+
* const pageStructure = await recovery.extractPageStructure(page);
|
|
934
|
+
* const result = await recovery.analyzeFailure({
|
|
935
|
+
* url: page.url(),
|
|
936
|
+
* failedXPath: xpath,
|
|
937
|
+
* errorType: 'timeout',
|
|
938
|
+
* actionType: 'fill',
|
|
939
|
+
* pageStructure,
|
|
940
|
+
* value,
|
|
941
|
+
* });
|
|
942
|
+
*
|
|
943
|
+
* if (result?.analysis.confidence >= 0.5) {
|
|
944
|
+
* await page.locator(result.analysis.suggestedXPath).fill(value);
|
|
945
|
+
* recovery.markFieldFilled(result.analysis.suggestedXPath);
|
|
946
|
+
* }
|
|
947
|
+
* }
|
|
948
|
+
* ```
|
|
949
|
+
*/
|
|
950
|
+
export class TestRecoveryHelper {
|
|
951
|
+
filledFields = [];
|
|
952
|
+
filledInputs = [];
|
|
953
|
+
sessionId;
|
|
954
|
+
userHeaders = {};
|
|
955
|
+
projectRoot;
|
|
956
|
+
/**
|
|
957
|
+
* Cria uma instância do TestRecoveryHelper
|
|
958
|
+
*
|
|
959
|
+
* @param sessionId ID da sessão para correlacionar requisições
|
|
960
|
+
* @param user Headers de identificação do usuário
|
|
961
|
+
* @param projectRoot Caminho absoluto do projeto (para auto-fix local)
|
|
962
|
+
*/
|
|
963
|
+
constructor(sessionId, user, projectRoot) {
|
|
964
|
+
this.sessionId = sessionId;
|
|
965
|
+
if (user)
|
|
966
|
+
this.userHeaders = user;
|
|
967
|
+
this.projectRoot = projectRoot;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Define o projectRoot para auto-fix local (v2.7.1)
|
|
971
|
+
*/
|
|
972
|
+
setProjectRoot(projectRoot) {
|
|
973
|
+
this.projectRoot = projectRoot;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Obtém o projectRoot atual
|
|
977
|
+
*/
|
|
978
|
+
getProjectRoot() {
|
|
979
|
+
return this.projectRoot;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Define o sessionId para manter contexto entre requisições
|
|
983
|
+
*/
|
|
984
|
+
setSessionId(sessionId) {
|
|
985
|
+
this.sessionId = sessionId;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Obtém o sessionId atual
|
|
989
|
+
*/
|
|
990
|
+
getSessionId() {
|
|
991
|
+
return this.sessionId;
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Define headers de identificação do usuário (v2.6.9)
|
|
995
|
+
* Também atualiza headers globais para chamadas do framework
|
|
996
|
+
*/
|
|
997
|
+
setUserHeaders(user) {
|
|
998
|
+
this.userHeaders = user;
|
|
999
|
+
_singletonUserHeaders = user;
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Analisa falha e retorna XPath corrigido
|
|
1003
|
+
* Envia automaticamente o contexto de campos já preenchidos
|
|
1004
|
+
*
|
|
1005
|
+
* @param params Parâmetros da análise (v2.6.9: inclui executionSource, recoverySource, testCase*)
|
|
1006
|
+
*/
|
|
1007
|
+
async analyzeFailure(params) {
|
|
1008
|
+
const request = {
|
|
1009
|
+
url: params.url,
|
|
1010
|
+
failedXPath: params.failedXPath,
|
|
1011
|
+
errorType: params.errorType,
|
|
1012
|
+
actionType: params.actionType,
|
|
1013
|
+
pageStructure: params.pageStructure,
|
|
1014
|
+
pageHtml: params.pageHtml,
|
|
1015
|
+
sessionId: this.sessionId,
|
|
1016
|
+
systemName: params.systemName,
|
|
1017
|
+
environment: params.environment,
|
|
1018
|
+
executionSource: params.executionSource,
|
|
1019
|
+
recoverySource: params.recoverySource ?? "framework",
|
|
1020
|
+
testCaseName: params.testCaseName,
|
|
1021
|
+
testCaseId: params.testCaseId,
|
|
1022
|
+
testMethodName: params.testMethodName,
|
|
1023
|
+
context: {
|
|
1024
|
+
value: params.value,
|
|
1025
|
+
originalMethod: params.actionType,
|
|
1026
|
+
filledInputs: [...this.filledInputs],
|
|
1027
|
+
filledFields: [...this.filledFields],
|
|
1028
|
+
},
|
|
1029
|
+
};
|
|
1030
|
+
return analyzeFailure(request);
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Reporta resultado de tentativa de recovery
|
|
1034
|
+
*
|
|
1035
|
+
* ⚠️ OBRIGATÓRIO chamar após testar XPath sugerido (v2.6.9)
|
|
1036
|
+
* Sem report-result, a tentativa fica como "pendente" no dashboard
|
|
1037
|
+
*
|
|
1038
|
+
* @param params Parâmetros do resultado
|
|
1039
|
+
* @returns Resposta do servidor
|
|
1040
|
+
*/
|
|
1041
|
+
async reportResult(params) {
|
|
1042
|
+
return reportResult({
|
|
1043
|
+
...params,
|
|
1044
|
+
sessionId: this.sessionId,
|
|
1045
|
+
recoverySource: params.recoverySource ?? "framework",
|
|
1046
|
+
}, this.userHeaders);
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Marca campo como preenchido com sucesso
|
|
1050
|
+
* Deve ser chamado após cada preenchimento bem-sucedido
|
|
1051
|
+
*
|
|
1052
|
+
* @param xpath XPath do elemento preenchido
|
|
1053
|
+
* @param fieldId ID ou name do campo (opcional, melhora a análise)
|
|
1054
|
+
*/
|
|
1055
|
+
markFieldFilled(xpath, fieldId) {
|
|
1056
|
+
if (xpath && !this.filledInputs.includes(xpath)) {
|
|
1057
|
+
this.filledInputs.push(xpath);
|
|
1058
|
+
}
|
|
1059
|
+
if (fieldId && !this.filledFields.includes(fieldId)) {
|
|
1060
|
+
this.filledFields.push(fieldId);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Remove um campo da lista de preenchidos
|
|
1065
|
+
* Útil quando um campo é limpo ou precisa ser repreenchido
|
|
1066
|
+
*/
|
|
1067
|
+
unmarkFieldFilled(xpath, fieldId) {
|
|
1068
|
+
const xpathIndex = this.filledInputs.indexOf(xpath);
|
|
1069
|
+
if (xpathIndex > -1) {
|
|
1070
|
+
this.filledInputs.splice(xpathIndex, 1);
|
|
1071
|
+
}
|
|
1072
|
+
if (fieldId) {
|
|
1073
|
+
const fieldIndex = this.filledFields.indexOf(fieldId);
|
|
1074
|
+
if (fieldIndex > -1) {
|
|
1075
|
+
this.filledFields.splice(fieldIndex, 1);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Limpa histórico de campos preenchidos
|
|
1081
|
+
* Deve ser chamado no início de cada novo teste ou ao mudar de página
|
|
1082
|
+
*/
|
|
1083
|
+
clearFilledFields() {
|
|
1084
|
+
this.filledInputs = [];
|
|
1085
|
+
this.filledFields = [];
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Obtém lista de XPaths preenchidos
|
|
1089
|
+
*/
|
|
1090
|
+
getFilledInputs() {
|
|
1091
|
+
return [...this.filledInputs];
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Obtém lista de IDs/names preenchidos
|
|
1095
|
+
*/
|
|
1096
|
+
getFilledFields() {
|
|
1097
|
+
return [...this.filledFields];
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Extrai estrutura da página usando Playwright Page
|
|
1101
|
+
* Retorna objeto pronto para enviar ao Test Recovery
|
|
1102
|
+
*
|
|
1103
|
+
* @param page Instância de Page do Playwright
|
|
1104
|
+
* @returns Estrutura da página com inputs, buttons, links e headings
|
|
1105
|
+
*/
|
|
1106
|
+
async extractPageStructure(page) {
|
|
1107
|
+
try {
|
|
1108
|
+
const structure = await page.evaluate(() => {
|
|
1109
|
+
const getXPath = (element) => {
|
|
1110
|
+
if (element.id) {
|
|
1111
|
+
return `//*[@id="${element.id}"]`;
|
|
1112
|
+
}
|
|
1113
|
+
const parent = element.parentElement;
|
|
1114
|
+
if (!parent)
|
|
1115
|
+
return "";
|
|
1116
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === element.tagName);
|
|
1117
|
+
const index = siblings.indexOf(element) + 1;
|
|
1118
|
+
const tag = element.tagName.toLowerCase();
|
|
1119
|
+
const parentXPath = getXPath(parent);
|
|
1120
|
+
if (siblings.length === 1) {
|
|
1121
|
+
return `${parentXPath}/${tag}`;
|
|
1122
|
+
}
|
|
1123
|
+
return `${parentXPath}/${tag}[${index}]`;
|
|
1124
|
+
};
|
|
1125
|
+
const extractElement = (el) => ({
|
|
1126
|
+
tag: el.tagName.toLowerCase(),
|
|
1127
|
+
text: el.textContent?.trim().slice(0, 200) || "",
|
|
1128
|
+
xpath: getXPath(el),
|
|
1129
|
+
id: el.id || undefined,
|
|
1130
|
+
name: el.getAttribute("name") || undefined,
|
|
1131
|
+
className: el.className || undefined,
|
|
1132
|
+
type: el.getAttribute("type") || undefined,
|
|
1133
|
+
placeholder: el.getAttribute("placeholder") || undefined,
|
|
1134
|
+
ariaLabel: el.getAttribute("aria-label") || undefined,
|
|
1135
|
+
dataTestId: el.getAttribute("data-testid") || undefined,
|
|
1136
|
+
title: el.getAttribute("title") || undefined,
|
|
1137
|
+
role: el.getAttribute("role") || undefined,
|
|
1138
|
+
href: el.getAttribute("href") || undefined,
|
|
1139
|
+
});
|
|
1140
|
+
return {
|
|
1141
|
+
title: document.title,
|
|
1142
|
+
url: window.location.href,
|
|
1143
|
+
buttons: Array.from(document.querySelectorAll('button, input[type="button"], input[type="submit"], [role="button"]')).map(extractElement),
|
|
1144
|
+
inputs: Array.from(document.querySelectorAll('input:not([type="button"]):not([type="submit"]):not([type="hidden"]), textarea, select, [role="textbox"], [role="combobox"]')).map(extractElement),
|
|
1145
|
+
links: Array.from(document.querySelectorAll("a[href]")).map(extractElement),
|
|
1146
|
+
headings: Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6")).map(extractElement),
|
|
1147
|
+
};
|
|
1148
|
+
});
|
|
1149
|
+
return structure;
|
|
1150
|
+
}
|
|
1151
|
+
catch (err) {
|
|
1152
|
+
Logger.warning(`[TestRecoveryHelper] Erro ao extrair estrutura da página: ${err instanceof Error ? err.message : String(err)}`);
|
|
1153
|
+
return undefined;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Extrai ID ou name de um XPath sugerido
|
|
1158
|
+
* Útil para marcar campo como preenchido após recovery
|
|
1159
|
+
*
|
|
1160
|
+
* @param xpath XPath para extrair o identificador
|
|
1161
|
+
* @returns ID ou name encontrado, ou undefined
|
|
1162
|
+
*/
|
|
1163
|
+
extractFieldIdFromXPath(xpath) {
|
|
1164
|
+
// Tentar extrair @id="..."
|
|
1165
|
+
const idMatch = xpath.match(/@id=["']([^"']+)["']/);
|
|
1166
|
+
if (idMatch)
|
|
1167
|
+
return idMatch[1];
|
|
1168
|
+
// Tentar extrair @name="..."
|
|
1169
|
+
const nameMatch = xpath.match(/@name=["']([^"']+)["']/);
|
|
1170
|
+
if (nameMatch)
|
|
1171
|
+
return nameMatch[1];
|
|
1172
|
+
// Tentar extrair @data-testid="..."
|
|
1173
|
+
const testIdMatch = xpath.match(/@data-testid=["']([^"']+)["']/);
|
|
1174
|
+
if (testIdMatch)
|
|
1175
|
+
return testIdMatch[1];
|
|
1176
|
+
return undefined;
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Helper para executar ação com recovery automático
|
|
1180
|
+
* Se a ação falhar, tenta recuperar usando Test Recovery
|
|
1181
|
+
*
|
|
1182
|
+
* @param params Parâmetros da ação (v2.6.9: inclui executionSource, testCase*)
|
|
1183
|
+
* @returns Resultado da ação com informações de recovery
|
|
1184
|
+
*/
|
|
1185
|
+
async executeWithRecovery(params) {
|
|
1186
|
+
const { page, xpath, actionType, value, timeout = 5000, minConfidence = 0.5, } = params;
|
|
1187
|
+
const attemptNumber = 1;
|
|
1188
|
+
// Tentar ação original
|
|
1189
|
+
try {
|
|
1190
|
+
if (actionType === "fill" && value !== undefined) {
|
|
1191
|
+
await page.locator(xpath).fill(value, { timeout });
|
|
1192
|
+
}
|
|
1193
|
+
else if (actionType === "click") {
|
|
1194
|
+
await page.locator(xpath).click({ timeout });
|
|
1195
|
+
}
|
|
1196
|
+
// Sucesso! Marcar como preenchido
|
|
1197
|
+
const fieldId = this.extractFieldIdFromXPath(xpath);
|
|
1198
|
+
this.markFieldFilled(xpath, fieldId);
|
|
1199
|
+
return { success: true, usedXPath: xpath, wasRecovered: false };
|
|
1200
|
+
}
|
|
1201
|
+
catch {
|
|
1202
|
+
Logger.info(`[TestRecoveryHelper] Falha na ação original (${xpath}), consultando Test Recovery...`);
|
|
1203
|
+
// Extrair estrutura da página
|
|
1204
|
+
const pageStructure = await this.extractPageStructure(page);
|
|
1205
|
+
// Consultar Test Recovery
|
|
1206
|
+
const result = await this.analyzeFailure({
|
|
1207
|
+
url: page.url(),
|
|
1208
|
+
failedXPath: xpath,
|
|
1209
|
+
errorType: "timeout",
|
|
1210
|
+
actionType,
|
|
1211
|
+
pageStructure,
|
|
1212
|
+
value,
|
|
1213
|
+
systemName: params.systemName,
|
|
1214
|
+
environment: params.environment,
|
|
1215
|
+
executionSource: params.executionSource,
|
|
1216
|
+
recoverySource: "framework",
|
|
1217
|
+
testCaseName: params.testCaseName,
|
|
1218
|
+
testCaseId: params.testCaseId,
|
|
1219
|
+
testMethodName: params.testMethodName,
|
|
1220
|
+
});
|
|
1221
|
+
if (!result?.analysis?.suggestedXPath) {
|
|
1222
|
+
Logger.error("[TestRecoveryHelper] Test Recovery não retornou sugestão");
|
|
1223
|
+
return { success: false, usedXPath: xpath, wasRecovered: false };
|
|
1224
|
+
}
|
|
1225
|
+
const { suggestedXPath, confidence, reason } = result.analysis;
|
|
1226
|
+
Logger.info(`[TestRecoveryHelper] Sugestão: ${suggestedXPath}`);
|
|
1227
|
+
Logger.info(`[TestRecoveryHelper] Confiança: ${(confidence * 100).toFixed(1)}%`);
|
|
1228
|
+
Logger.info(`[TestRecoveryHelper] Razão: ${reason}`);
|
|
1229
|
+
if (confidence < minConfidence) {
|
|
1230
|
+
Logger.warning(`[TestRecoveryHelper] Confiança muito baixa (${(confidence * 100).toFixed(1)}% < ${(minConfidence * 100).toFixed(1)}%)`);
|
|
1231
|
+
// ⚠️ OBRIGATÓRIO: Reportar falha (v2.6.9)
|
|
1232
|
+
await this.reportResult({
|
|
1233
|
+
xpath: suggestedXPath,
|
|
1234
|
+
attemptNumber,
|
|
1235
|
+
success: false,
|
|
1236
|
+
executionSource: params.executionSource,
|
|
1237
|
+
testCaseName: params.testCaseName,
|
|
1238
|
+
testCaseId: params.testCaseId,
|
|
1239
|
+
testMethodName: params.testMethodName,
|
|
1240
|
+
});
|
|
1241
|
+
return { success: false, usedXPath: xpath, wasRecovered: false };
|
|
1242
|
+
}
|
|
1243
|
+
// Tentar com XPath sugerido
|
|
1244
|
+
try {
|
|
1245
|
+
if (actionType === "fill" && value !== undefined) {
|
|
1246
|
+
await page.locator(suggestedXPath).fill(value, { timeout });
|
|
1247
|
+
}
|
|
1248
|
+
else if (actionType === "click") {
|
|
1249
|
+
await page.locator(suggestedXPath).click({ timeout });
|
|
1250
|
+
}
|
|
1251
|
+
// Sucesso com recovery!
|
|
1252
|
+
const fieldId = this.extractFieldIdFromXPath(suggestedXPath);
|
|
1253
|
+
this.markFieldFilled(suggestedXPath, fieldId);
|
|
1254
|
+
// ✅ OBRIGATÓRIO: Reportar sucesso (v2.6.9)
|
|
1255
|
+
await this.reportResult({
|
|
1256
|
+
xpath: suggestedXPath,
|
|
1257
|
+
attemptNumber,
|
|
1258
|
+
success: true,
|
|
1259
|
+
autoFixed: true,
|
|
1260
|
+
executionSource: params.executionSource,
|
|
1261
|
+
testCaseName: params.testCaseName,
|
|
1262
|
+
testCaseId: params.testCaseId,
|
|
1263
|
+
testMethodName: params.testMethodName,
|
|
1264
|
+
});
|
|
1265
|
+
return { success: true, usedXPath: suggestedXPath, wasRecovered: true };
|
|
1266
|
+
}
|
|
1267
|
+
catch (recoveryError) {
|
|
1268
|
+
Logger.error(`[TestRecoveryHelper] Falha mesmo com XPath sugerido: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`, recoveryError);
|
|
1269
|
+
// ❌ OBRIGATÓRIO: Reportar falha (v2.6.9)
|
|
1270
|
+
await this.reportResult({
|
|
1271
|
+
xpath: suggestedXPath,
|
|
1272
|
+
attemptNumber,
|
|
1273
|
+
success: false,
|
|
1274
|
+
executionSource: params.executionSource,
|
|
1275
|
+
testCaseName: params.testCaseName,
|
|
1276
|
+
testCaseId: params.testCaseId,
|
|
1277
|
+
testMethodName: params.testMethodName,
|
|
1278
|
+
});
|
|
1279
|
+
return {
|
|
1280
|
+
success: false,
|
|
1281
|
+
usedXPath: suggestedXPath,
|
|
1282
|
+
wasRecovered: false,
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Aplica correção de XPath automaticamente nos arquivos do projeto (v2.7.1)
|
|
1289
|
+
*
|
|
1290
|
+
* ⚠️ Requisitos:
|
|
1291
|
+
* - executionSource deve ser 'LOCAL' (não funciona em Azure/CI)
|
|
1292
|
+
* - confidence deve ser >= 0.7 (segurança)
|
|
1293
|
+
* - projectRoot deve ser válido e acessível
|
|
1294
|
+
*
|
|
1295
|
+
* @param params Parâmetros para auto-fix local
|
|
1296
|
+
* @returns Resultado da operação com detalhes da correção
|
|
1297
|
+
*
|
|
1298
|
+
* @example
|
|
1299
|
+
* ```typescript
|
|
1300
|
+
* const recovery = new TestRecoveryHelper();
|
|
1301
|
+
*
|
|
1302
|
+
* // Após analyzeFailure retornar autoFixAvailable=true
|
|
1303
|
+
* const result = await recovery.autoFixLocal({
|
|
1304
|
+
* projectRoot: '/home/user/projeto',
|
|
1305
|
+
* oldXPath: '//input[@id="email"]',
|
|
1306
|
+
* newXPath: '//input[@data-testid="email-input"]',
|
|
1307
|
+
* confidence: 0.85,
|
|
1308
|
+
* attributesFile: 'AttributesLogin.ts'
|
|
1309
|
+
* });
|
|
1310
|
+
*
|
|
1311
|
+
* if (result.success) {
|
|
1312
|
+
* console.log(`Arquivo corrigido: ${result.fix?.filePath}`);
|
|
1313
|
+
* }
|
|
1314
|
+
* ```
|
|
1315
|
+
*/
|
|
1316
|
+
async autoFixLocal(params) {
|
|
1317
|
+
// Usa projectRoot do parâmetro ou da instância
|
|
1318
|
+
const projectRoot = params.projectRoot || this.projectRoot;
|
|
1319
|
+
const { oldXPath, newXPath, confidence, autoConfirm = true } = params;
|
|
1320
|
+
// Validar confiança mínima
|
|
1321
|
+
if (confidence < 0.7) {
|
|
1322
|
+
return {
|
|
1323
|
+
success: false,
|
|
1324
|
+
message: `Confiança muito baixa (${(confidence * 100).toFixed(1)}% < 70%). Auto-fix requer mínimo 70% de confiança.`,
|
|
1325
|
+
error: "LOW_CONFIDENCE",
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
// Validar que não está em ambiente CI/Azure
|
|
1329
|
+
if (isAzureEnvironment()) {
|
|
1330
|
+
return {
|
|
1331
|
+
success: false,
|
|
1332
|
+
message: "Auto-fix local não disponível em ambiente CI/Azure. Use executionSource=LOCAL.",
|
|
1333
|
+
error: "INVALID_ENVIRONMENT",
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
// Validar projectRoot
|
|
1337
|
+
if (!projectRoot || projectRoot.trim() === "") {
|
|
1338
|
+
return {
|
|
1339
|
+
success: false,
|
|
1340
|
+
message: "projectRoot é obrigatório para auto-fix local.",
|
|
1341
|
+
error: "MISSING_PROJECT_ROOT",
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
const request = {
|
|
1345
|
+
projectRoot,
|
|
1346
|
+
oldXPath,
|
|
1347
|
+
newXPath,
|
|
1348
|
+
confidence,
|
|
1349
|
+
testFilePath: params.testFilePath,
|
|
1350
|
+
attributesFile: params.attributesFile,
|
|
1351
|
+
className: params.className,
|
|
1352
|
+
locatorProperty: params.locatorProperty,
|
|
1353
|
+
methodName: params.methodName,
|
|
1354
|
+
lineNumber: params.lineNumber,
|
|
1355
|
+
recoverySessionId: this.sessionId,
|
|
1356
|
+
systemName: params.systemName,
|
|
1357
|
+
environmentType: params.environmentType,
|
|
1358
|
+
autoConfirm,
|
|
1359
|
+
executionSource: "Local",
|
|
1360
|
+
};
|
|
1361
|
+
try {
|
|
1362
|
+
// Headers extras: x-project-root como fallback (item 12 v2.7.44)
|
|
1363
|
+
const extraHeaders = {
|
|
1364
|
+
...userHeadersMap(this.userHeaders),
|
|
1365
|
+
};
|
|
1366
|
+
if (projectRoot) {
|
|
1367
|
+
extraHeaders["x-project-root"] = projectRoot;
|
|
1368
|
+
}
|
|
1369
|
+
const meta = getRecoveryMetadata();
|
|
1370
|
+
if (meta.executionSource)
|
|
1371
|
+
extraHeaders["x-execution-source"] = meta.executionSource;
|
|
1372
|
+
const res = await postJsonWithFallback("/api/test-recovery/auto-fix-local", request, 30000, extraHeaders);
|
|
1373
|
+
if (!res.ok) {
|
|
1374
|
+
const errorText = await res.text().catch(() => "Erro desconhecido");
|
|
1375
|
+
Logger.error(`[TestRecoveryHelper] auto-fix-local falhou: ${res.status} - ${errorText}`);
|
|
1376
|
+
return {
|
|
1377
|
+
success: false,
|
|
1378
|
+
message: `Erro na API: ${res.status}`,
|
|
1379
|
+
error: errorText,
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
const data = (await res.json());
|
|
1383
|
+
if (data.success && data.fix) {
|
|
1384
|
+
Logger.success(`[TestRecoveryHelper] ✅ Auto-fix aplicado: ${data.fix.filePath}`);
|
|
1385
|
+
Logger.info(`[TestRecoveryHelper] Linha ${data.fix.lineNumber}: "${data.fix.oldXPath}" → "${data.fix.newXPath}"`);
|
|
1386
|
+
}
|
|
1387
|
+
return data;
|
|
1388
|
+
}
|
|
1389
|
+
catch (err) {
|
|
1390
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1391
|
+
Logger.error(`[TestRecoveryHelper] Erro ao chamar auto-fix-local: ${errorMessage}`);
|
|
1392
|
+
return {
|
|
1393
|
+
success: false,
|
|
1394
|
+
message: "Falha na comunicação com a API de auto-fix.",
|
|
1395
|
+
error: errorMessage,
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
// ==========================================
|
|
1401
|
+
// Instância singleton para uso simplificado
|
|
1402
|
+
// ==========================================
|
|
1403
|
+
/**
|
|
1404
|
+
* Instância singleton do TestRecoveryHelper
|
|
1405
|
+
* Use para casos simples sem necessidade de múltiplas instâncias
|
|
1406
|
+
*
|
|
1407
|
+
* @example
|
|
1408
|
+
* ```typescript
|
|
1409
|
+
* import { testRecoveryHelper } from '@silasfmartins/testhub';
|
|
1410
|
+
*
|
|
1411
|
+
* testRecoveryHelper.markFieldFilled(xpath, 'username');
|
|
1412
|
+
* const result = await testRecoveryHelper.analyzeFailure({ ... });
|
|
1413
|
+
* ```
|
|
1414
|
+
*/
|
|
1415
|
+
export const testRecoveryHelper = new TestRecoveryHelper();
|