sela-core 0.1.0-alpha.1
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/README.md +103 -0
- package/bin/sela.js +3 -0
- package/dist/cli/ErrorHandler.d.ts +10 -0
- package/dist/cli/ErrorHandler.d.ts.map +1 -0
- package/dist/cli/ErrorHandler.js +70 -0
- package/dist/cli/commands/bulk.d.ts +3 -0
- package/dist/cli/commands/bulk.d.ts.map +1 -0
- package/dist/cli/commands/bulk.js +158 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +51 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +166 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +56 -0
- package/dist/cli/commands/merge.d.ts +13 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +119 -0
- package/dist/cli/commands/refactor.d.ts +3 -0
- package/dist/cli/commands/refactor.d.ts.map +1 -0
- package/dist/cli/commands/refactor.js +30 -0
- package/dist/cli/commands/showReport.d.ts +3 -0
- package/dist/cli/commands/showReport.d.ts.map +1 -0
- package/dist/cli/commands/showReport.js +82 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +51 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +131 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +94 -0
- package/dist/cli/ui/DnaTable.d.ts +3 -0
- package/dist/cli/ui/DnaTable.d.ts.map +1 -0
- package/dist/cli/ui/DnaTable.js +77 -0
- package/dist/cli/ui/LocatorPicker.d.ts +8 -0
- package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
- package/dist/cli/ui/LocatorPicker.js +33 -0
- package/dist/cli/ui/ProgressReporter.d.ts +11 -0
- package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
- package/dist/cli/ui/ProgressReporter.js +33 -0
- package/dist/cli/ui/RefactorWizard.d.ts +26 -0
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
- package/dist/cli/ui/RefactorWizard.js +295 -0
- package/dist/config/ConfigLoader.d.ts +16 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +185 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +80 -0
- package/dist/config/PathScopeGuard.d.ts +42 -0
- package/dist/config/PathScopeGuard.d.ts.map +1 -0
- package/dist/config/PathScopeGuard.js +192 -0
- package/dist/config/SelaConfig.d.ts +178 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +141 -0
- package/dist/config/defineConfig.d.ts +3 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +9 -0
- package/dist/engine/FixwrightEngine.d.ts +24 -0
- package/dist/engine/FixwrightEngine.d.ts.map +1 -0
- package/dist/engine/FixwrightEngine.js +403 -0
- package/dist/engine/HealingRegistry.d.ts +40 -0
- package/dist/engine/HealingRegistry.d.ts.map +1 -0
- package/dist/engine/HealingRegistry.js +99 -0
- package/dist/engine/SelaEngine.d.ts +57 -0
- package/dist/engine/SelaEngine.d.ts.map +1 -0
- package/dist/engine/SelaEngine.js +1146 -0
- package/dist/engine/singleton.d.ts +3 -0
- package/dist/engine/singleton.d.ts.map +1 -0
- package/dist/engine/singleton.js +5 -0
- package/dist/errors/SelaError.d.ts +146 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +168 -0
- package/dist/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +246 -0
- package/dist/fixtures/index.d.ts +30 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +765 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +54 -0
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/reporter/SelaReporter.d.ts +165 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +517 -0
- package/dist/services/ASTSourceUpdater.d.ts +143 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3514 -0
- package/dist/services/AnchorResolver.d.ts +157 -0
- package/dist/services/AnchorResolver.d.ts.map +1 -0
- package/dist/services/AnchorResolver.js +289 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
- package/dist/services/ArgumentTypeAnalyzer.js +92 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
- package/dist/services/BlastRadiusAnalyzer.js +107 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +570 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +135 -0
- package/dist/services/DecisionEngine.d.ts +51 -0
- package/dist/services/DecisionEngine.d.ts.map +1 -0
- package/dist/services/DecisionEngine.js +260 -0
- package/dist/services/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +366 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +217 -0
- package/dist/services/DnaIndexService.d.ts +24 -0
- package/dist/services/DnaIndexService.d.ts.map +1 -0
- package/dist/services/DnaIndexService.js +131 -0
- package/dist/services/HealBudget.d.ts +32 -0
- package/dist/services/HealBudget.d.ts.map +1 -0
- package/dist/services/HealBudget.js +85 -0
- package/dist/services/HealReportService.d.ts +161 -0
- package/dist/services/HealReportService.d.ts.map +1 -0
- package/dist/services/HealReportService.js +1039 -0
- package/dist/services/HealingAdvisory.d.ts +30 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +51 -0
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +404 -0
- package/dist/services/HealthReportService.d.ts +10 -0
- package/dist/services/HealthReportService.d.ts.map +1 -0
- package/dist/services/HealthReportService.js +84 -0
- package/dist/services/InitializerUpdater.d.ts +16 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -0
- package/dist/services/InitializerUpdater.js +73 -0
- package/dist/services/IntentAuditor.d.ts +56 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +326 -0
- package/dist/services/InteractiveReview.d.ts +15 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +128 -0
- package/dist/services/LLMService.d.ts +121 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +475 -0
- package/dist/services/MutationApplier.d.ts +69 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +360 -0
- package/dist/services/PRAutomationService.d.ts +50 -0
- package/dist/services/PRAutomationService.d.ts.map +1 -0
- package/dist/services/PRAutomationService.js +760 -0
- package/dist/services/PendingPromptLedger.d.ts +51 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +205 -0
- package/dist/services/ReportGenerator.d.ts +156 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +278 -0
- package/dist/services/ReportMergeService.d.ts +95 -0
- package/dist/services/ReportMergeService.d.ts.map +1 -0
- package/dist/services/ReportMergeService.js +0 -0
- package/dist/services/SafetyGuard.d.ts +112 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +87 -0
- package/dist/services/Sanitizer.d.ts +28 -0
- package/dist/services/Sanitizer.d.ts.map +1 -0
- package/dist/services/Sanitizer.js +142 -0
- package/dist/services/SanitizingAnthropic.d.ts +19 -0
- package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
- package/dist/services/SanitizingAnthropic.js +84 -0
- package/dist/services/SelectorSanitizer.d.ts +52 -0
- package/dist/services/SelectorSanitizer.d.ts.map +1 -0
- package/dist/services/SelectorSanitizer.js +318 -0
- package/dist/services/SnapshotService.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +352 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +165 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1091 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +334 -0
- package/dist/services/TraceBackEngine.d.ts +67 -0
- package/dist/services/TraceBackEngine.d.ts.map +1 -0
- package/dist/services/TraceBackEngine.js +672 -0
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +200 -0
- package/dist/services/safety/PipelineContext.d.ts +63 -0
- package/dist/services/safety/PipelineContext.d.ts.map +1 -0
- package/dist/services/safety/PipelineContext.js +34 -0
- package/dist/services/safety/roleClassifier.d.ts +4 -0
- package/dist/services/safety/roleClassifier.d.ts.map +1 -0
- package/dist/services/safety/roleClassifier.js +233 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
- package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
- package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
- package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
- package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
- package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
- package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
- package/dist/services/safety/rules/VisibilityRule.js +53 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
- package/dist/services/safety/rules/index.d.ts +3 -0
- package/dist/services/safety/rules/index.d.ts.map +1 -0
- package/dist/services/safety/rules/index.js +34 -0
- package/dist/services/types.d.ts +59 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/storage/SnapshotManager.d.ts +5 -0
- package/dist/storage/SnapshotManager.d.ts.map +1 -0
- package/dist/storage/SnapshotManager.js +32 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/DOMUtils.d.ts +49 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +466 -0
- package/dist/utils/FileLock.d.ts +44 -0
- package/dist/utils/FileLock.d.ts.map +1 -0
- package/dist/utils/FileLock.js +189 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +382 -0
- package/dist/utils/StackUtils.d.ts +11 -0
- package/dist/utils/StackUtils.d.ts.map +1 -0
- package/dist/utils/StackUtils.js +120 -0
- package/dist/utils/SummaryTable.d.ts +17 -0
- package/dist/utils/SummaryTable.d.ts.map +1 -0
- package/dist/utils/SummaryTable.js +37 -0
- package/dist/utils/atomicWrite.d.ts +9 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +83 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +123 -0
- package/dist/vendor/enquirer.d.ts +33 -0
- package/dist/vendor/enquirer.d.ts.map +1 -0
- package/dist/vendor/enquirer.js +11 -0
- package/package.json +71 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createHealingExpect = exports.test = void 0;
|
|
37
|
+
exports.resolveSelaProxy = resolveSelaProxy;
|
|
38
|
+
exports.createFrameLocatorProxy = createFrameLocatorProxy;
|
|
39
|
+
exports.createLocatorProxy = createLocatorProxy;
|
|
40
|
+
const test_1 = require("@playwright/test");
|
|
41
|
+
const StackUtils_1 = require("../utils/StackUtils");
|
|
42
|
+
const logger_1 = require("../utils/logger");
|
|
43
|
+
const HealingRegistry_1 = require("../engine/HealingRegistry");
|
|
44
|
+
const singleton_1 = require("../engine/singleton");
|
|
45
|
+
const expectProxy_1 = require("./expectProxy");
|
|
46
|
+
const proxyTag_1 = require("./proxyTag");
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const registry = HealingRegistry_1.HealingRegistry.getInstance();
|
|
50
|
+
const proxyToActiveLocator = new WeakMap();
|
|
51
|
+
// Module-level resolver - moduleExpect.ts uses this to unwrap a Sela
|
|
52
|
+
// locator proxy back to its live underlying Playwright Locator without
|
|
53
|
+
// needing access to the WeakMap directly.
|
|
54
|
+
function resolveSelaProxy(value) {
|
|
55
|
+
if (!value || typeof value !== "object")
|
|
56
|
+
return null;
|
|
57
|
+
const getActive = proxyToActiveLocator.get(value);
|
|
58
|
+
return getActive ? getActive() : null;
|
|
59
|
+
}
|
|
60
|
+
// ─── Action classification (identical to original) ────────────────
|
|
61
|
+
const INTERCEPTED_ACTIONS = [
|
|
62
|
+
"click",
|
|
63
|
+
"fill",
|
|
64
|
+
"check",
|
|
65
|
+
"uncheck",
|
|
66
|
+
"hover",
|
|
67
|
+
"selectOption",
|
|
68
|
+
"innerText",
|
|
69
|
+
"innerHTML",
|
|
70
|
+
"textContent",
|
|
71
|
+
"getAttribute",
|
|
72
|
+
"dblclick",
|
|
73
|
+
"focus",
|
|
74
|
+
"isVisible",
|
|
75
|
+
"isEnabled",
|
|
76
|
+
"isChecked",
|
|
77
|
+
"waitFor",
|
|
78
|
+
"dragTo",
|
|
79
|
+
"pressSequentially",
|
|
80
|
+
"setInputFiles",
|
|
81
|
+
"type",
|
|
82
|
+
"press",
|
|
83
|
+
"clear",
|
|
84
|
+
"tap",
|
|
85
|
+
"count",
|
|
86
|
+
"isHidden",
|
|
87
|
+
"inputValue",
|
|
88
|
+
"dispatchEvent",
|
|
89
|
+
"getProperty",
|
|
90
|
+
"getByRole",
|
|
91
|
+
"getByLabel",
|
|
92
|
+
"getByPlaceholder",
|
|
93
|
+
"getByTestId",
|
|
94
|
+
"getByText",
|
|
95
|
+
"getByAltText",
|
|
96
|
+
"getByTitle",
|
|
97
|
+
"frameLocator",
|
|
98
|
+
"filter",
|
|
99
|
+
"locator",
|
|
100
|
+
"first",
|
|
101
|
+
"last",
|
|
102
|
+
"nth",
|
|
103
|
+
];
|
|
104
|
+
const DIRECT_VALUE_ACTIONS = new Set([
|
|
105
|
+
"fill",
|
|
106
|
+
"selectOption",
|
|
107
|
+
"setInputFiles",
|
|
108
|
+
"type",
|
|
109
|
+
"pressSequentially",
|
|
110
|
+
"dragTo",
|
|
111
|
+
"dispatchEvent",
|
|
112
|
+
]);
|
|
113
|
+
const OPTIONS_ONLY_ACTIONS = new Set([
|
|
114
|
+
"click",
|
|
115
|
+
"hover",
|
|
116
|
+
"dblclick",
|
|
117
|
+
"check",
|
|
118
|
+
"uncheck",
|
|
119
|
+
"tap",
|
|
120
|
+
"focus",
|
|
121
|
+
"press",
|
|
122
|
+
"dispatchEvent",
|
|
123
|
+
"waitFor",
|
|
124
|
+
"isVisible",
|
|
125
|
+
"isEnabled",
|
|
126
|
+
"isChecked",
|
|
127
|
+
"isHidden",
|
|
128
|
+
"dragTo",
|
|
129
|
+
"clear",
|
|
130
|
+
"scrollIntoViewIfNeeded",
|
|
131
|
+
]);
|
|
132
|
+
const NO_ARGS_ACTIONS = new Set([
|
|
133
|
+
"innerText",
|
|
134
|
+
"innerHTML",
|
|
135
|
+
"textContent",
|
|
136
|
+
"inputValue",
|
|
137
|
+
"getAttribute",
|
|
138
|
+
"getProperty",
|
|
139
|
+
"count",
|
|
140
|
+
]);
|
|
141
|
+
// ─── getBy* semantic selector map (identical to original) ─────────
|
|
142
|
+
const GET_BY_METHODS = {
|
|
143
|
+
getByRole: (args) => {
|
|
144
|
+
const role = args[0];
|
|
145
|
+
const opts = args[1] ?? {};
|
|
146
|
+
const name = opts.name ? `[name="${opts.name}"i]` : "";
|
|
147
|
+
return `internal:role=${role}${name}`;
|
|
148
|
+
},
|
|
149
|
+
getByLabel: (args) => `internal:label=${JSON.stringify(args[0])}`,
|
|
150
|
+
getByPlaceholder: (args) => `internal:attr=[placeholder=${JSON.stringify(args[0])}]`,
|
|
151
|
+
getByTestId: (args) => `[data-testid="${args[0]}"]`,
|
|
152
|
+
getByText: (args) => {
|
|
153
|
+
const opts = args[1] ?? {};
|
|
154
|
+
const exact = opts.exact ? "" : "i";
|
|
155
|
+
return `internal:text=${JSON.stringify(args[0])}${exact}`;
|
|
156
|
+
},
|
|
157
|
+
getByAltText: (args) => `internal:attr=[alt=${JSON.stringify(args[0])}]`,
|
|
158
|
+
getByTitle: (args) => `internal:attr=[title=${JSON.stringify(args[0])}]`,
|
|
159
|
+
};
|
|
160
|
+
// ─────────────────────────────────────────────────────────────────
|
|
161
|
+
// buildLiveLocator - registry-aware
|
|
162
|
+
//
|
|
163
|
+
// NEW: resolves the selector through the HealingRegistry before
|
|
164
|
+
// constructing the Playwright Locator. Zero cost when no heal has
|
|
165
|
+
// occurred for this selector.
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────
|
|
167
|
+
function buildLiveLocator(rawPage, fullSelector) {
|
|
168
|
+
// 1. ניקוי אבסולוטי של שאריות Proxy
|
|
169
|
+
const cleanedSelector = sanitizeProxySelector(fullSelector);
|
|
170
|
+
const resolved = registry.resolveSelector(cleanedSelector);
|
|
171
|
+
// 2. פירוק הסלקטור ובנייה חכמה
|
|
172
|
+
const segments = resolved.split(" >> ").map((s) => s.trim());
|
|
173
|
+
let current = rawPage;
|
|
174
|
+
for (const segment of segments) {
|
|
175
|
+
if (!segment)
|
|
176
|
+
continue;
|
|
177
|
+
// טיפול ב-Frames (ללא שינוי)
|
|
178
|
+
if (segment.includes("frame") ||
|
|
179
|
+
segment.startsWith("#outer") ||
|
|
180
|
+
segment.startsWith("#inner")) {
|
|
181
|
+
current = current.frameLocator(segment);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// --- צומת החלטה: האם זה פילטר? (מטפל גם בהזיות AI) ---
|
|
185
|
+
// תבנית א': ה-AI המציא "filter=hasText:..."
|
|
186
|
+
const aiFilterMatch = segment.match(/^filter=hasText:(["']?)(.*?)\1$/);
|
|
187
|
+
// תבנית ב': Playwright Native ":has-text(...)"
|
|
188
|
+
const hasTextMatch = segment.match(/^:has-text\((["'])(.*?)\1\)$/);
|
|
189
|
+
// תבנית ג': ה-AI כתב "text=..." בתוך שרשרת שנועדה להיות פילטר
|
|
190
|
+
const textMatch = segment.match(/^text=(["']?)(.*?)\1$/);
|
|
191
|
+
if (aiFilterMatch || hasTextMatch || textMatch) {
|
|
192
|
+
const textToFilter = (aiFilterMatch?.[2] ||
|
|
193
|
+
hasTextMatch?.[2] ||
|
|
194
|
+
textMatch?.[2]);
|
|
195
|
+
current = current.filter({ hasText: textToFilter });
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// אם הגענו לכאן, זה סלקטור רגיל
|
|
199
|
+
current = current.locator(segment);
|
|
200
|
+
}
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* מנקה בצורה אבסולוטית כל "שארית" של קריאת JS (כמו .filter, .or, .and)
|
|
205
|
+
* הוסף את זה מעל buildLiveLocator אם זה חסר לך.
|
|
206
|
+
*/
|
|
207
|
+
function sanitizeProxySelector(rawSelector) {
|
|
208
|
+
const segments = rawSelector.split(" >> ");
|
|
209
|
+
const cleanedSegments = segments.map((segment) => {
|
|
210
|
+
const jsMethodRegex = /\.[a-zA-Z_$]+\((?:[^"'\)\`]|"[^"]*"|'[^']*'|`[^`]*`)*\)/g;
|
|
211
|
+
return segment.replace(jsMethodRegex, "").trim();
|
|
212
|
+
});
|
|
213
|
+
return cleanedSegments.filter((s) => s.length > 0).join(" >> ");
|
|
214
|
+
}
|
|
215
|
+
// ─── Argument builder (identical to original) ─────────────────────
|
|
216
|
+
function buildHealedArgs(prop, args, timeout = 8000) {
|
|
217
|
+
if (prop === "dragTo") {
|
|
218
|
+
const options = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
219
|
+
return [args[0], { ...options, timeout }];
|
|
220
|
+
}
|
|
221
|
+
if (DIRECT_VALUE_ACTIONS.has(prop)) {
|
|
222
|
+
const opts = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
223
|
+
return [args[0], { ...opts, timeout }];
|
|
224
|
+
}
|
|
225
|
+
if (OPTIONS_ONLY_ACTIONS.has(prop)) {
|
|
226
|
+
const opts = args[0] && typeof args[0] === "object" ? args[0] : {};
|
|
227
|
+
return [{ ...opts, timeout }];
|
|
228
|
+
}
|
|
229
|
+
if (NO_ARGS_ACTIONS.has(prop)) {
|
|
230
|
+
if (args.length > 0 && typeof args[0] === "string") {
|
|
231
|
+
const opts = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
232
|
+
return [args[0], { ...opts, timeout }];
|
|
233
|
+
}
|
|
234
|
+
const opts = args[0] && typeof args[0] === "object" ? args[0] : {};
|
|
235
|
+
return [{ ...opts, timeout }];
|
|
236
|
+
}
|
|
237
|
+
logger_1.logger.debug(`buildHealedArgs: unknown prop "${prop}", passing as-is`);
|
|
238
|
+
return args;
|
|
239
|
+
}
|
|
240
|
+
// ─────────────────────────────────────────────────────────────────
|
|
241
|
+
// runWithHealArgument (unchanged from original)
|
|
242
|
+
// ─────────────────────────────────────────────────────────────────
|
|
243
|
+
async function runWithHealArgument(rawPage, prop, args, fullSelector, stableId, callerInfo, originalError) {
|
|
244
|
+
logger_1.logger.debug(`Element visible, action '${prop}' failed - healing argument…`);
|
|
245
|
+
const oldArgument = typeof args[0] === "string" ? args[0] : JSON.stringify(args[0]);
|
|
246
|
+
const newArgument = await singleton_1.sharedEngine.healArgument(rawPage, fullSelector, prop, oldArgument, stableId, callerInfo.filePath, callerInfo.line);
|
|
247
|
+
if (!newArgument)
|
|
248
|
+
throw originalError;
|
|
249
|
+
const locator = buildLiveLocator(rawPage, fullSelector);
|
|
250
|
+
const healedArgs = buildHealedArgs(prop, [newArgument, ...args.slice(1)], 8000);
|
|
251
|
+
return await locator[prop](...healedArgs);
|
|
252
|
+
}
|
|
253
|
+
// ─────────────────────────────────────────────────────────────────
|
|
254
|
+
// runWithHeal - pre-flight registry check added
|
|
255
|
+
//
|
|
256
|
+
// NEW: Before the first Playwright attempt, resolve the selector
|
|
257
|
+
// through the registry. If a previous heal already mapped it, the
|
|
258
|
+
// first attempt uses the healed selector and succeeds immediately -
|
|
259
|
+
// no timeout, no AI call, no file write.
|
|
260
|
+
// ─────────────────────────────────────────────────────────────────
|
|
261
|
+
async function runWithHeal(rawPage, prop, args, fullSelector, elementSelectorOnly, stableId, callerInfo) {
|
|
262
|
+
// ── Pre-flight: swap to healed selector if one already exists ──
|
|
263
|
+
const preResolved = registry.resolveSelector(fullSelector);
|
|
264
|
+
const effectiveSelector = preResolved !== fullSelector ? preResolved : fullSelector;
|
|
265
|
+
if (effectiveSelector !== fullSelector) {
|
|
266
|
+
logger_1.logger.debug(`Pre-flight swap: "${fullSelector}" → "${effectiveSelector}"`);
|
|
267
|
+
}
|
|
268
|
+
const initialLocator = buildLiveLocator(rawPage, effectiveSelector);
|
|
269
|
+
try {
|
|
270
|
+
const result = await initialLocator[prop](...buildHealedArgs(prop, args, 3000));
|
|
271
|
+
await singleton_1.sharedEngine.captureSuccessfulElement(rawPage, effectiveSelector, stableId);
|
|
272
|
+
return {
|
|
273
|
+
result,
|
|
274
|
+
finalSelector: effectiveSelector,
|
|
275
|
+
finalElementSelector: elementSelectorOnly,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (firstError) {
|
|
279
|
+
logger_1.logger.debug(`'${prop}' failed on: "${effectiveSelector}"`);
|
|
280
|
+
const isVisible = await initialLocator
|
|
281
|
+
.isVisible({ timeout: 1500 })
|
|
282
|
+
.catch(() => false);
|
|
283
|
+
if (isVisible && (prop === "selectOption" || prop === "fill")) {
|
|
284
|
+
const result = await runWithHealArgument(rawPage, prop, args, effectiveSelector, stableId, callerInfo, firstError);
|
|
285
|
+
return {
|
|
286
|
+
result,
|
|
287
|
+
finalSelector: effectiveSelector,
|
|
288
|
+
finalElementSelector: elementSelectorOnly,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (prop === "dragTo") {
|
|
292
|
+
return await runWithHealDragTo(rawPage, args, effectiveSelector, elementSelectorOnly, stableId, callerInfo, firstError, (s) => buildLiveLocator(rawPage, s));
|
|
293
|
+
}
|
|
294
|
+
return await runWithHealSelector(rawPage, prop, args, effectiveSelector, elementSelectorOnly, stableId, callerInfo, firstError);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// ─────────────────────────────────────────────────────────────────
|
|
298
|
+
// runWithHealSelector (unchanged from original)
|
|
299
|
+
//
|
|
300
|
+
// Note: broadcast is now handled inside ASTSourceUpdater.update()
|
|
301
|
+
// via broadcastAndReturn(), so there is NO explicit registry call here.
|
|
302
|
+
// The Proxy listener fires automatically before this function returns.
|
|
303
|
+
// ─────────────────────────────────────────────────────────────────
|
|
304
|
+
async function runWithHealSelector(rawPage, prop, args, fullSelector, elementSelectorOnly, stableId, callerInfo, originalError) {
|
|
305
|
+
logger_1.logger.debug(`Healing selector…`);
|
|
306
|
+
const newFullSelector = await singleton_1.sharedEngine.heal(rawPage, fullSelector, elementSelectorOnly, stableId, callerInfo.filePath, callerInfo.line);
|
|
307
|
+
if (!newFullSelector)
|
|
308
|
+
throw originalError;
|
|
309
|
+
// By the time engine.heal() returns, ASTSourceUpdater.update() has
|
|
310
|
+
// already called broadcastAndReturn() → registry.broadcast() →
|
|
311
|
+
// every live Proxy listener has already swapped its activeLocator.
|
|
312
|
+
// We just need to build the locator for THIS call-site's retry.
|
|
313
|
+
const healedLocator = buildLiveLocator(rawPage, newFullSelector);
|
|
314
|
+
try {
|
|
315
|
+
const result = await healedLocator[prop](...buildHealedArgs(prop, args, 8000));
|
|
316
|
+
await singleton_1.sharedEngine.captureSuccessfulElement(rawPage, newFullSelector, stableId);
|
|
317
|
+
return {
|
|
318
|
+
result,
|
|
319
|
+
finalSelector: newFullSelector,
|
|
320
|
+
finalElementSelector: newFullSelector.split(" >> ").pop() ?? newFullSelector,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
catch (retryError) {
|
|
324
|
+
throw retryError;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// ─────────────────────────────────────────────────────────────────
|
|
328
|
+
// runWithHealDragTo (identical to original)
|
|
329
|
+
// ─────────────────────────────────────────────────────────────────
|
|
330
|
+
function extractDragToTargetFromSource(rawFilePath, line) {
|
|
331
|
+
try {
|
|
332
|
+
let filePath = rawFilePath
|
|
333
|
+
.replace(/^.*?at\s+/, "")
|
|
334
|
+
.replace(/:\d+:\d+.*$/, "")
|
|
335
|
+
.trim();
|
|
336
|
+
if (!path.isAbsolute(filePath)) {
|
|
337
|
+
filePath = path.resolve(process.cwd(), filePath);
|
|
338
|
+
}
|
|
339
|
+
if (!fs.existsSync(filePath))
|
|
340
|
+
return null;
|
|
341
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
342
|
+
const lines = content.split(/\r?\n/);
|
|
343
|
+
const block = lines
|
|
344
|
+
.slice(Math.max(0, line - 3), Math.min(lines.length - 1, line + 3) + 1)
|
|
345
|
+
.join("\n");
|
|
346
|
+
const patterns = [
|
|
347
|
+
/\.dragTo\s*\(\s*(?:page|rawPage)\.locator\s*\(\s*"([^"]+)"\s*\)/,
|
|
348
|
+
/\.dragTo\s*\(\s*(?:page|rawPage)\.locator\s*\(\s*'([^']+)'\s*\)/,
|
|
349
|
+
/\.dragTo\s*\(\s*(?:page|rawPage)\.locator\s*\(\s*`([^`]+)`\s*\)/,
|
|
350
|
+
];
|
|
351
|
+
for (const p of patterns) {
|
|
352
|
+
const m = block.match(p);
|
|
353
|
+
if (m?.[1])
|
|
354
|
+
return m[1];
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async function runWithHealDragTo(rawPage, args, fullSelector, elementSelectorOnly, stableId, callerInfo, originalError, buildFreshLocator) {
|
|
363
|
+
logger_1.logger.debug(`dragTo Double Healing initiated`);
|
|
364
|
+
const targetSelectorFromSource = extractDragToTargetFromSource(callerInfo.filePath, callerInfo.line);
|
|
365
|
+
const sourceLocator = buildFreshLocator(fullSelector);
|
|
366
|
+
const isSourceVisible = await sourceLocator
|
|
367
|
+
.isVisible({ timeout: 500 })
|
|
368
|
+
.catch(() => false);
|
|
369
|
+
let healedSourceSelector = fullSelector;
|
|
370
|
+
let healedTargetLocator = args[0];
|
|
371
|
+
if (!isSourceVisible) {
|
|
372
|
+
try {
|
|
373
|
+
healedSourceSelector = await singleton_1.sharedEngine.heal(rawPage, fullSelector, elementSelectorOnly, stableId, callerInfo.filePath, callerInfo.line);
|
|
374
|
+
// broadcast already fired inside engine.heal() via ASTSourceUpdater
|
|
375
|
+
logger_1.logger.debug(`Source healed: "${healedSourceSelector}"`);
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
if (e instanceof Error && e.message.includes("[SafetyGuard]"))
|
|
379
|
+
throw e;
|
|
380
|
+
logger_1.logger.warn(`Source healing failed, using original`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const healedSourceLocator = buildFreshLocator(healedSourceSelector);
|
|
384
|
+
if (targetSelectorFromSource) {
|
|
385
|
+
const tempTarget = buildFreshLocator(targetSelectorFromSource);
|
|
386
|
+
const isTargetVisible = await tempTarget
|
|
387
|
+
.isVisible({ timeout: 500 })
|
|
388
|
+
.catch(() => false);
|
|
389
|
+
if (!isTargetVisible) {
|
|
390
|
+
try {
|
|
391
|
+
const healedTargetSel = await singleton_1.sharedEngine.heal(rawPage, targetSelectorFromSource, targetSelectorFromSource, `${stableId}_target`, callerInfo.filePath, callerInfo.line);
|
|
392
|
+
if (healedTargetSel) {
|
|
393
|
+
// broadcast already fired inside engine.heal() via ASTSourceUpdater
|
|
394
|
+
healedTargetLocator = buildFreshLocator(healedTargetSel);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
logger_1.logger.warn(`Target healing failed: ${e.message}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
healedTargetLocator = tempTarget;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
const targetFromError = originalError.message?.match(/waiting for locator\('([^']+)'\)/)?.[1];
|
|
407
|
+
if (targetFromError && targetFromError !== fullSelector) {
|
|
408
|
+
const tempTarget = buildFreshLocator(targetFromError);
|
|
409
|
+
const isVisible = await tempTarget
|
|
410
|
+
.isVisible({ timeout: 500 })
|
|
411
|
+
.catch(() => false);
|
|
412
|
+
if (!isVisible) {
|
|
413
|
+
try {
|
|
414
|
+
const healedSel = await singleton_1.sharedEngine.heal(rawPage, targetFromError, targetFromError, `${stableId}_target`, callerInfo.filePath, callerInfo.line);
|
|
415
|
+
if (healedSel) {
|
|
416
|
+
healedTargetLocator = buildFreshLocator(healedSel);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (e) {
|
|
420
|
+
if (e instanceof Error && e.message.includes("[SafetyGuard]"))
|
|
421
|
+
throw e;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
healedTargetLocator = tempTarget;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const options = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
430
|
+
try {
|
|
431
|
+
const result = await healedSourceLocator.dragTo(healedTargetLocator, {
|
|
432
|
+
...options,
|
|
433
|
+
timeout: 8000,
|
|
434
|
+
});
|
|
435
|
+
logger_1.logger.debug(`dragTo double heal successful`);
|
|
436
|
+
return {
|
|
437
|
+
result,
|
|
438
|
+
finalSelector: healedSourceSelector,
|
|
439
|
+
finalElementSelector: healedSourceSelector.split(" >> ").pop() ?? healedSourceSelector,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
throw originalError;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// ─────────────────────────────────────────────────────────────────
|
|
447
|
+
// createFrameLocatorProxy (identical to original)
|
|
448
|
+
// ─────────────────────────────────────────────────────────────────
|
|
449
|
+
function createFrameLocatorProxy(rawFrameLocator, frameSelector, rawPage, testTitle, actionCounter, parentChain) {
|
|
450
|
+
const fullFrameChain = parentChain
|
|
451
|
+
? `${parentChain} >> ${frameSelector}`
|
|
452
|
+
: frameSelector;
|
|
453
|
+
return new Proxy(rawFrameLocator, {
|
|
454
|
+
get(fTarget, fProp) {
|
|
455
|
+
// Sela proxy identity - honor before the string-only fast path so
|
|
456
|
+
// symbol probes from tests / debuggers return the kind tag.
|
|
457
|
+
if (fProp === proxyTag_1.SELA_PROXY)
|
|
458
|
+
return "framelocator";
|
|
459
|
+
if (typeof fProp !== "string")
|
|
460
|
+
return Reflect.get(fTarget, fProp);
|
|
461
|
+
if (fProp === "frameLocator") {
|
|
462
|
+
return (innerFrameSel) => createFrameLocatorProxy(fTarget.frameLocator(innerFrameSel), innerFrameSel, rawPage, testTitle, actionCounter, fullFrameChain);
|
|
463
|
+
}
|
|
464
|
+
if (fProp === "locator") {
|
|
465
|
+
return (elementSel) => createLocatorProxy(fTarget.locator(elementSel), `${fullFrameChain} >> ${elementSel}`, rawPage, testTitle, actionCounter, elementSel);
|
|
466
|
+
}
|
|
467
|
+
return Reflect.get(fTarget, fProp);
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
// ─────────────────────────────────────────────────────────────────
|
|
472
|
+
// createLocatorProxy - Runtime Selector Injection added
|
|
473
|
+
//
|
|
474
|
+
// NEW additions vs original:
|
|
475
|
+
// 1. Resolves selector through registry on creation (handles proxies
|
|
476
|
+
// born after a heal has already broadcast).
|
|
477
|
+
// 2. Subscribes to registry so any future broadcast swaps
|
|
478
|
+
// activeLocator on the same tick - before the next await.
|
|
479
|
+
// 3. Exposes _selaUnsubscribe so the fixture can clean up.
|
|
480
|
+
// ─────────────────────────────────────────────────────────────────
|
|
481
|
+
function createLocatorProxy(rawLocator, selector, rawPage, testTitle, actionCounter, elementSelectorOnly) {
|
|
482
|
+
// Defensive: Proxy ctor throws "Cannot create proxy with a non-object as target"
|
|
483
|
+
// when downstream callers (e.g. chained traps) pass a non-Locator return value.
|
|
484
|
+
// Returning the raw value lets the caller see it unchanged instead of crashing.
|
|
485
|
+
if (rawLocator === null ||
|
|
486
|
+
rawLocator === undefined ||
|
|
487
|
+
typeof rawLocator !== "object") {
|
|
488
|
+
return rawLocator;
|
|
489
|
+
}
|
|
490
|
+
// ── Resolve immediately (handles post-heal proxy creation) ────
|
|
491
|
+
const initialResolved = registry.resolveSelector(selector);
|
|
492
|
+
let currentFullSelector = initialResolved;
|
|
493
|
+
let currentElementSelector = elementSelectorOnly ?? initialResolved;
|
|
494
|
+
let activeLocator = initialResolved !== selector
|
|
495
|
+
? buildLiveLocator(rawPage, initialResolved) // already healed
|
|
496
|
+
: rawLocator; // fresh, use as-is
|
|
497
|
+
// ── Subscribe: swap activeLocator when a heal fires ──────────
|
|
498
|
+
// Inside createLocatorProxy - full updated subscribe block
|
|
499
|
+
// בתוך createLocatorProxy - תחליף את הבלוק של ה-subscribe בזה:
|
|
500
|
+
const unsubscribe = registry.subscribe((fp, oldSel, newSel) => {
|
|
501
|
+
// בדיקה אם הסלקטור הנוכחי מכיל את הישן או שהוא הישן בעצמו
|
|
502
|
+
if (currentFullSelector !== oldSel &&
|
|
503
|
+
!currentFullSelector.includes(oldSel)) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
let nextSelector;
|
|
507
|
+
// הגנה חדשה: אם התיקון החדש (newSel) הוא כבר סלקטור מלא ומשורשר (מכיל >>)
|
|
508
|
+
// ואנחנו נמצאים בתוך שרשרת Proxy, אנחנו לא רוצים לשרשר "זבל"
|
|
509
|
+
if (newSel.includes(" >> ") ||
|
|
510
|
+
newSel.startsWith(".") ||
|
|
511
|
+
newSel.startsWith("#")) {
|
|
512
|
+
// אם הישן היה חלק משרשרת מורכבת עם .filter, אנחנו מחליפים את כל השרשרת בתיקון הנקי
|
|
513
|
+
nextSelector = newSel;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
nextSelector = currentFullSelector.replace(oldSel, newSel);
|
|
517
|
+
}
|
|
518
|
+
if (nextSelector === currentFullSelector)
|
|
519
|
+
return;
|
|
520
|
+
logger_1.logger.debug(`proxy swap (Smart Clean): "${currentFullSelector}" → "${nextSelector}"`);
|
|
521
|
+
currentFullSelector = nextSelector;
|
|
522
|
+
currentElementSelector = nextSelector.split(" >> ").pop() ?? nextSelector;
|
|
523
|
+
// בנייה מחדש של הלוקטור האמיתי מהמחרוזת הנקייה
|
|
524
|
+
activeLocator = buildLiveLocator(rawPage, nextSelector);
|
|
525
|
+
});
|
|
526
|
+
const proxy = new Proxy(rawLocator, {
|
|
527
|
+
get(target, prop, receiver) {
|
|
528
|
+
// Sela proxy identity - must be honored before any string-only branch
|
|
529
|
+
// so callers can reliably detect a Sela proxy via Symbol.for("sela.proxy").
|
|
530
|
+
if (prop === proxyTag_1.SELA_PROXY)
|
|
531
|
+
return "locator";
|
|
532
|
+
if (typeof prop !== "string")
|
|
533
|
+
return Reflect.get(target, prop, receiver);
|
|
534
|
+
// ── Internal accessors ───────────────────────────────────
|
|
535
|
+
if (prop === "_selaSelector")
|
|
536
|
+
return currentFullSelector;
|
|
537
|
+
if (prop === "_selaPage")
|
|
538
|
+
return rawPage;
|
|
539
|
+
if (prop === "_selaTestTitle")
|
|
540
|
+
return testTitle;
|
|
541
|
+
if (prop === "_selaActionCounter")
|
|
542
|
+
return actionCounter;
|
|
543
|
+
if (prop === "_selaElementSelector")
|
|
544
|
+
return currentElementSelector;
|
|
545
|
+
if (prop === "_selaUnsubscribe")
|
|
546
|
+
return unsubscribe;
|
|
547
|
+
// ── getBy* ───────────────────────────────────────────────
|
|
548
|
+
if (prop in GET_BY_METHODS) {
|
|
549
|
+
return (...args) => {
|
|
550
|
+
const nextLocator = activeLocator[prop](...args);
|
|
551
|
+
if (nextLocator === null ||
|
|
552
|
+
nextLocator === undefined ||
|
|
553
|
+
typeof nextLocator !== "object") {
|
|
554
|
+
return nextLocator;
|
|
555
|
+
}
|
|
556
|
+
const semanticSelector = GET_BY_METHODS[prop](args);
|
|
557
|
+
const childFullSelector = `${currentFullSelector} >> ${semanticSelector}`;
|
|
558
|
+
return createLocatorProxy(nextLocator, childFullSelector, rawPage, testTitle, actionCounter, semanticSelector);
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
// ── Chainable methods ────────────────────────────────────
|
|
562
|
+
const chainables = ["locator", "filter", "first", "last", "nth"];
|
|
563
|
+
if (chainables.includes(prop)) {
|
|
564
|
+
return (...args) => {
|
|
565
|
+
const nextLocator = activeLocator[prop](...args);
|
|
566
|
+
if (nextLocator === null ||
|
|
567
|
+
nextLocator === undefined ||
|
|
568
|
+
typeof nextLocator !== "object") {
|
|
569
|
+
return nextLocator;
|
|
570
|
+
}
|
|
571
|
+
let nextFull = currentFullSelector;
|
|
572
|
+
let nextElement = currentElementSelector;
|
|
573
|
+
if (prop === "locator") {
|
|
574
|
+
nextFull += ` >> ${args[0]}`;
|
|
575
|
+
nextElement = args[0];
|
|
576
|
+
}
|
|
577
|
+
else if (prop === "filter") {
|
|
578
|
+
const d = args[0]?.hasText
|
|
579
|
+
? `.filter(hasText:"${args[0].hasText}")`
|
|
580
|
+
: `.filter(...)`;
|
|
581
|
+
nextFull += d;
|
|
582
|
+
nextElement += d;
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
const suffix = prop === "first"
|
|
586
|
+
? ":nth-match(1)"
|
|
587
|
+
: prop === "last"
|
|
588
|
+
? ":last"
|
|
589
|
+
: `:nth-match(${args[0] + 1})`;
|
|
590
|
+
nextFull += suffix;
|
|
591
|
+
nextElement += suffix;
|
|
592
|
+
}
|
|
593
|
+
return createLocatorProxy(nextLocator, nextFull, rawPage, testTitle, actionCounter, nextElement);
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
// ── Intercepted actions ──────────────────────────────────
|
|
597
|
+
if (INTERCEPTED_ACTIONS.includes(prop)) {
|
|
598
|
+
return async (...args) => {
|
|
599
|
+
actionCounter.value++;
|
|
600
|
+
const stableId = StackUtils_1.StackUtils.getActionKey(testTitle, prop, actionCounter.value);
|
|
601
|
+
const callerInfo = StackUtils_1.StackUtils.getCallerInfo();
|
|
602
|
+
singleton_1.sharedEngine.preloadTestTitle(stableId, testTitle);
|
|
603
|
+
const { result, finalSelector, finalElementSelector } = await runWithHeal(rawPage, prop, args, currentFullSelector, currentElementSelector, stableId, callerInfo);
|
|
604
|
+
// Post-action desync guard: if runWithHeal found a new selector
|
|
605
|
+
// through engine.heal() and the registry listener fired during
|
|
606
|
+
// that await, currentFullSelector is already updated. This
|
|
607
|
+
// explicit assignment is a safety net for the rare race where
|
|
608
|
+
// the listener callback and the return value disagree.
|
|
609
|
+
if (finalSelector !== currentFullSelector) {
|
|
610
|
+
currentFullSelector = finalSelector;
|
|
611
|
+
currentElementSelector = finalElementSelector;
|
|
612
|
+
activeLocator = buildLiveLocator(rawPage, finalSelector);
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
// ── Passive getters (silent heal on failure) ─────────────
|
|
618
|
+
const passiveGetters = [
|
|
619
|
+
"isVisible",
|
|
620
|
+
"isEnabled",
|
|
621
|
+
"isHidden",
|
|
622
|
+
"isChecked",
|
|
623
|
+
"innerText",
|
|
624
|
+
"innerHTML",
|
|
625
|
+
"textContent",
|
|
626
|
+
"inputValue",
|
|
627
|
+
];
|
|
628
|
+
if (passiveGetters.includes(prop)) {
|
|
629
|
+
return async (...args) => {
|
|
630
|
+
try {
|
|
631
|
+
return await activeLocator[prop](...args);
|
|
632
|
+
}
|
|
633
|
+
catch {
|
|
634
|
+
logger_1.logger.debug(`Silent heal for getter '${prop}'`);
|
|
635
|
+
try {
|
|
636
|
+
actionCounter.value++;
|
|
637
|
+
const stableId = StackUtils_1.StackUtils.getActionKey(testTitle, prop, actionCounter.value);
|
|
638
|
+
const callerInfo = StackUtils_1.StackUtils.getCallerInfo();
|
|
639
|
+
singleton_1.sharedEngine.preloadTestTitle(stableId, testTitle);
|
|
640
|
+
const { finalSelector } = await runWithHeal(rawPage, prop, args, currentFullSelector, currentElementSelector, stableId, callerInfo);
|
|
641
|
+
if (finalSelector !== currentFullSelector) {
|
|
642
|
+
currentFullSelector = finalSelector;
|
|
643
|
+
activeLocator = buildLiveLocator(rawPage, finalSelector);
|
|
644
|
+
}
|
|
645
|
+
return await activeLocator[prop](...args);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return undefined;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
// Reflect on activeLocator (not rawLocator) to stay current
|
|
654
|
+
return Reflect.get(activeLocator, prop, receiver);
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
proxyToActiveLocator.set(proxy, () => activeLocator);
|
|
658
|
+
return proxy;
|
|
659
|
+
}
|
|
660
|
+
// ─────────────────────────────────────────────────────────────────
|
|
661
|
+
// test fixture - teardown added
|
|
662
|
+
// ─────────────────────────────────────────────────────────────────
|
|
663
|
+
// ─── expect - healing proxy replacing the plain baseExpect proxy ──────────────
|
|
664
|
+
exports.test = test_1.test.extend({
|
|
665
|
+
// ── Worker-scoped flush anchor (auto) ───────────────────────────────
|
|
666
|
+
// Setup is a no-op; the teardown after `use()` is the suite-end flush.
|
|
667
|
+
// Aggregation spans the ENTIRE worker run because commitUpdates() is no
|
|
668
|
+
// longer called per test - the report buffer, registry heal-map, and
|
|
669
|
+
// workspace snapshot accumulate across all tests and flush once here.
|
|
670
|
+
_selaWorkerLifecycle: [
|
|
671
|
+
async ({}, use) => {
|
|
672
|
+
await use();
|
|
673
|
+
// Single suite-wide commit: saves DNA snapshots, flushes the Insights
|
|
674
|
+
// report + .sela-history.json ONCE, and writes the DX-review ledger
|
|
675
|
+
// with the full set of healed events from the whole worker run.
|
|
676
|
+
//
|
|
677
|
+
// SELA-10: the commit is a post-run side-effect (report/snapshot/ledger
|
|
678
|
+
// I/O), NEVER a correctness gate for the tests that already ran. A flush
|
|
679
|
+
// failure here must degrade gracefully - if it threw, Playwright would
|
|
680
|
+
// mark the entire worker errored and fail tests that genuinely passed.
|
|
681
|
+
// The reporter's onEnd() retries commitUpdates() as the crash safety net.
|
|
682
|
+
try {
|
|
683
|
+
await singleton_1.sharedEngine.commitUpdates();
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
687
|
+
logger_1.logger.warn(`worker-teardown commitUpdates failed: ${msg}`);
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
{ scope: "worker", auto: true },
|
|
691
|
+
],
|
|
692
|
+
page: async ({ page }, use, testInfo) => {
|
|
693
|
+
page.on("console", (msg) => {
|
|
694
|
+
if (msg.text().includes("[DOM-EXTRACT]")) {
|
|
695
|
+
logger_1.logger.debug(`[Browser Console]: ${msg.text()}`);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
const actionCounter = { value: 0 };
|
|
699
|
+
const rawPage = page;
|
|
700
|
+
const unsubscribers = [];
|
|
701
|
+
const pageProxy = new Proxy(page, {
|
|
702
|
+
get(target, prop) {
|
|
703
|
+
// Sela proxy identity - honored before any other branch so callers
|
|
704
|
+
// can reliably detect a Sela page proxy via Symbol.for("sela.proxy").
|
|
705
|
+
if (prop === proxyTag_1.SELA_PROXY)
|
|
706
|
+
return "page";
|
|
707
|
+
if (prop === "_rawPage")
|
|
708
|
+
return target;
|
|
709
|
+
if (typeof prop !== "string")
|
|
710
|
+
return Reflect.get(target, prop, target);
|
|
711
|
+
if (prop === "locator") {
|
|
712
|
+
return (selector) => {
|
|
713
|
+
const p = createLocatorProxy(target.locator(selector), selector, rawPage, testInfo.title, actionCounter, selector);
|
|
714
|
+
const u = p._selaUnsubscribe;
|
|
715
|
+
if (u)
|
|
716
|
+
unsubscribers.push(u);
|
|
717
|
+
return p;
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
if (prop === "frameLocator") {
|
|
721
|
+
return (frameSel) => createFrameLocatorProxy(target.frameLocator(frameSel), frameSel, rawPage, testInfo.title, actionCounter, "");
|
|
722
|
+
}
|
|
723
|
+
if (prop in GET_BY_METHODS) {
|
|
724
|
+
return (...args) => {
|
|
725
|
+
const semanticSelector = GET_BY_METHODS[prop](args);
|
|
726
|
+
const p = createLocatorProxy(target[prop](...args), semanticSelector, rawPage, testInfo.title, actionCounter, semanticSelector);
|
|
727
|
+
const u = p._selaUnsubscribe;
|
|
728
|
+
if (u)
|
|
729
|
+
unsubscribers.push(u);
|
|
730
|
+
return p;
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
return Reflect.get(target, prop, target);
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
await use(pageProxy);
|
|
737
|
+
// ── Per-test teardown ─────────────────────────────────────────
|
|
738
|
+
// Release this test's proxy registry subscriptions and drop per-test
|
|
739
|
+
// listeners. We deliberately DO NOT call engine.commitUpdates() here -
|
|
740
|
+
// flushing per test is what produced the per-test report writes, the
|
|
741
|
+
// duplicated .sela-history.json entries (each "healed":1), and the
|
|
742
|
+
// last-test-only DX diff. The suite-wide flush lives in the
|
|
743
|
+
// worker-scoped `_selaWorkerLifecycle` teardown above.
|
|
744
|
+
for (const u of unsubscribers)
|
|
745
|
+
u();
|
|
746
|
+
registry.clearForTest();
|
|
747
|
+
},
|
|
748
|
+
expect: async ({ page }, use, testInfo) => {
|
|
749
|
+
const healingExpect = (0, expectProxy_1.createHealingExpect)(singleton_1.sharedEngine, page, testInfo.file, 0,
|
|
750
|
+
// Inject proxy resolver - gives expectProxy.ts access to the live
|
|
751
|
+
// active Locator without exposing the WeakMap directly.
|
|
752
|
+
(value) => {
|
|
753
|
+
if (value && typeof value === "object") {
|
|
754
|
+
const getActive = proxyToActiveLocator.get(value);
|
|
755
|
+
if (getActive)
|
|
756
|
+
return getActive();
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
});
|
|
760
|
+
await use(healingExpect);
|
|
761
|
+
},
|
|
762
|
+
});
|
|
763
|
+
// Re-export expect as a healing expect for bare import usage
|
|
764
|
+
var expectProxy_2 = require("./expectProxy");
|
|
765
|
+
Object.defineProperty(exports, "createHealingExpect", { enumerable: true, get: function () { return expectProxy_2.createHealingExpect; } });
|