sela-core 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/README.md +93 -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 +140 -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 +133 -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/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/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 +123 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +42 -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 +70 -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 +269 -0
- package/dist/config/ConfigLoader.d.ts +15 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +174 -0
- package/dist/config/SelaConfig.d.ts +67 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +57 -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 +98 -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/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +228 -0
- package/dist/fixtures/index.d.ts +6 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +688 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +46 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/services/ASTSourceUpdater.d.ts +79 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3177 -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 +103 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +569 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +134 -0
- package/dist/services/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +350 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +198 -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/HealingAdvisory.d.ts +22 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +42 -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 +37 -0
- package/dist/services/IntentAuditor.d.ts +39 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +302 -0
- package/dist/services/LLMService.d.ts +100 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +439 -0
- package/dist/services/SafetyGuard.d.ts +65 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +524 -0
- package/dist/services/SnapshotService.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +349 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +156 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1021 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +331 -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 +31 -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 +33 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +179 -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/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 +67 -0
|
@@ -0,0 +1,688 @@
|
|
|
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
|
+
const test_1 = require("@playwright/test");
|
|
38
|
+
const StackUtils_1 = require("../utils/StackUtils");
|
|
39
|
+
const HealingRegistry_1 = require("../engine/HealingRegistry");
|
|
40
|
+
const singleton_1 = require("../engine/singleton");
|
|
41
|
+
const expectProxy_1 = require("./expectProxy");
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const registry = HealingRegistry_1.HealingRegistry.getInstance();
|
|
45
|
+
const proxyToActiveLocator = new WeakMap();
|
|
46
|
+
// ─── Action classification (identical to original) ────────────────
|
|
47
|
+
const INTERCEPTED_ACTIONS = [
|
|
48
|
+
"click",
|
|
49
|
+
"fill",
|
|
50
|
+
"check",
|
|
51
|
+
"uncheck",
|
|
52
|
+
"hover",
|
|
53
|
+
"selectOption",
|
|
54
|
+
"innerText",
|
|
55
|
+
"innerHTML",
|
|
56
|
+
"textContent",
|
|
57
|
+
"getAttribute",
|
|
58
|
+
"dblclick",
|
|
59
|
+
"focus",
|
|
60
|
+
"isVisible",
|
|
61
|
+
"isEnabled",
|
|
62
|
+
"isChecked",
|
|
63
|
+
"waitFor",
|
|
64
|
+
"dragTo",
|
|
65
|
+
"pressSequentially",
|
|
66
|
+
"setInputFiles",
|
|
67
|
+
"type",
|
|
68
|
+
"press",
|
|
69
|
+
"clear",
|
|
70
|
+
"tap",
|
|
71
|
+
"count",
|
|
72
|
+
"isHidden",
|
|
73
|
+
"inputValue",
|
|
74
|
+
"dispatchEvent",
|
|
75
|
+
"getProperty",
|
|
76
|
+
"getByRole",
|
|
77
|
+
"getByLabel",
|
|
78
|
+
"getByPlaceholder",
|
|
79
|
+
"getByTestId",
|
|
80
|
+
"getByText",
|
|
81
|
+
"getByAltText",
|
|
82
|
+
"getByTitle",
|
|
83
|
+
"frameLocator",
|
|
84
|
+
"filter",
|
|
85
|
+
"locator",
|
|
86
|
+
"first",
|
|
87
|
+
"last",
|
|
88
|
+
"nth",
|
|
89
|
+
];
|
|
90
|
+
const DIRECT_VALUE_ACTIONS = new Set([
|
|
91
|
+
"fill",
|
|
92
|
+
"selectOption",
|
|
93
|
+
"setInputFiles",
|
|
94
|
+
"type",
|
|
95
|
+
"pressSequentially",
|
|
96
|
+
"dragTo",
|
|
97
|
+
"dispatchEvent",
|
|
98
|
+
]);
|
|
99
|
+
const OPTIONS_ONLY_ACTIONS = new Set([
|
|
100
|
+
"click",
|
|
101
|
+
"hover",
|
|
102
|
+
"dblclick",
|
|
103
|
+
"check",
|
|
104
|
+
"uncheck",
|
|
105
|
+
"tap",
|
|
106
|
+
"focus",
|
|
107
|
+
"press",
|
|
108
|
+
"dispatchEvent",
|
|
109
|
+
"waitFor",
|
|
110
|
+
"isVisible",
|
|
111
|
+
"isEnabled",
|
|
112
|
+
"isChecked",
|
|
113
|
+
"isHidden",
|
|
114
|
+
"dragTo",
|
|
115
|
+
"clear",
|
|
116
|
+
"scrollIntoViewIfNeeded",
|
|
117
|
+
]);
|
|
118
|
+
const NO_ARGS_ACTIONS = new Set([
|
|
119
|
+
"innerText",
|
|
120
|
+
"innerHTML",
|
|
121
|
+
"textContent",
|
|
122
|
+
"inputValue",
|
|
123
|
+
"getAttribute",
|
|
124
|
+
"getProperty",
|
|
125
|
+
"count",
|
|
126
|
+
]);
|
|
127
|
+
// ─── getBy* semantic selector map (identical to original) ─────────
|
|
128
|
+
const GET_BY_METHODS = {
|
|
129
|
+
getByRole: (args) => {
|
|
130
|
+
const role = args[0];
|
|
131
|
+
const opts = args[1] ?? {};
|
|
132
|
+
const name = opts.name ? `[name="${opts.name}"i]` : "";
|
|
133
|
+
return `internal:role=${role}${name}`;
|
|
134
|
+
},
|
|
135
|
+
getByLabel: (args) => `internal:label=${JSON.stringify(args[0])}`,
|
|
136
|
+
getByPlaceholder: (args) => `internal:attr=[placeholder=${JSON.stringify(args[0])}]`,
|
|
137
|
+
getByTestId: (args) => `[data-testid="${args[0]}"]`,
|
|
138
|
+
getByText: (args) => {
|
|
139
|
+
const opts = args[1] ?? {};
|
|
140
|
+
const exact = opts.exact ? "" : "i";
|
|
141
|
+
return `internal:text=${JSON.stringify(args[0])}${exact}`;
|
|
142
|
+
},
|
|
143
|
+
getByAltText: (args) => `internal:attr=[alt=${JSON.stringify(args[0])}]`,
|
|
144
|
+
getByTitle: (args) => `internal:attr=[title=${JSON.stringify(args[0])}]`,
|
|
145
|
+
};
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────
|
|
147
|
+
// buildLiveLocator — registry-aware
|
|
148
|
+
//
|
|
149
|
+
// NEW: resolves the selector through the HealingRegistry before
|
|
150
|
+
// constructing the Playwright Locator. Zero cost when no heal has
|
|
151
|
+
// occurred for this selector.
|
|
152
|
+
// ─────────────────────────────────────────────────────────────────
|
|
153
|
+
function buildLiveLocator(rawPage, fullSelector) {
|
|
154
|
+
// 1. ניקוי אבסולוטי של שאריות Proxy
|
|
155
|
+
const cleanedSelector = sanitizeProxySelector(fullSelector);
|
|
156
|
+
const resolved = registry.resolveSelector(cleanedSelector);
|
|
157
|
+
// 2. פירוק הסלקטור ובנייה חכמה
|
|
158
|
+
const segments = resolved.split(" >> ").map((s) => s.trim());
|
|
159
|
+
let current = rawPage;
|
|
160
|
+
for (const segment of segments) {
|
|
161
|
+
if (!segment)
|
|
162
|
+
continue;
|
|
163
|
+
// טיפול ב-Frames (ללא שינוי)
|
|
164
|
+
if (segment.includes("frame") ||
|
|
165
|
+
segment.startsWith("#outer") ||
|
|
166
|
+
segment.startsWith("#inner")) {
|
|
167
|
+
current = current.frameLocator(segment);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// --- צומת החלטה: האם זה פילטר? (מטפל גם בהזיות AI) ---
|
|
171
|
+
// תבנית א': ה-AI המציא "filter=hasText:..."
|
|
172
|
+
const aiFilterMatch = segment.match(/^filter=hasText:(["']?)(.*?)\1$/);
|
|
173
|
+
// תבנית ב': Playwright Native ":has-text(...)"
|
|
174
|
+
const hasTextMatch = segment.match(/^:has-text\((["'])(.*?)\1\)$/);
|
|
175
|
+
// תבנית ג': ה-AI כתב "text=..." בתוך שרשרת שנועדה להיות פילטר
|
|
176
|
+
const textMatch = segment.match(/^text=(["']?)(.*?)\1$/);
|
|
177
|
+
if (aiFilterMatch || hasTextMatch || textMatch) {
|
|
178
|
+
const textToFilter = (aiFilterMatch?.[2] ||
|
|
179
|
+
hasTextMatch?.[2] ||
|
|
180
|
+
textMatch?.[2]);
|
|
181
|
+
current = current.filter({ hasText: textToFilter });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// אם הגענו לכאן, זה סלקטור רגיל
|
|
185
|
+
current = current.locator(segment);
|
|
186
|
+
}
|
|
187
|
+
return current;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* מנקה בצורה אבסולוטית כל "שארית" של קריאת JS (כמו .filter, .or, .and)
|
|
191
|
+
* הוסף את זה מעל buildLiveLocator אם זה חסר לך.
|
|
192
|
+
*/
|
|
193
|
+
function sanitizeProxySelector(rawSelector) {
|
|
194
|
+
const segments = rawSelector.split(" >> ");
|
|
195
|
+
const cleanedSegments = segments.map((segment) => {
|
|
196
|
+
const jsMethodRegex = /\.[a-zA-Z_$]+\((?:[^"'\)\`]|"[^"]*"|'[^']*'|`[^`]*`)*\)/g;
|
|
197
|
+
return segment.replace(jsMethodRegex, "").trim();
|
|
198
|
+
});
|
|
199
|
+
return cleanedSegments.filter((s) => s.length > 0).join(" >> ");
|
|
200
|
+
}
|
|
201
|
+
// ─── Argument builder (identical to original) ─────────────────────
|
|
202
|
+
function buildHealedArgs(prop, args, timeout = 8000) {
|
|
203
|
+
if (prop === "dragTo") {
|
|
204
|
+
const options = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
205
|
+
return [args[0], { ...options, timeout }];
|
|
206
|
+
}
|
|
207
|
+
if (DIRECT_VALUE_ACTIONS.has(prop)) {
|
|
208
|
+
const opts = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
209
|
+
return [args[0], { ...opts, timeout }];
|
|
210
|
+
}
|
|
211
|
+
if (OPTIONS_ONLY_ACTIONS.has(prop)) {
|
|
212
|
+
const opts = args[0] && typeof args[0] === "object" ? args[0] : {};
|
|
213
|
+
return [{ ...opts, timeout }];
|
|
214
|
+
}
|
|
215
|
+
if (NO_ARGS_ACTIONS.has(prop)) {
|
|
216
|
+
if (args.length > 0 && typeof args[0] === "string") {
|
|
217
|
+
const opts = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
218
|
+
return [args[0], { ...opts, timeout }];
|
|
219
|
+
}
|
|
220
|
+
const opts = args[0] && typeof args[0] === "object" ? args[0] : {};
|
|
221
|
+
return [{ ...opts, timeout }];
|
|
222
|
+
}
|
|
223
|
+
console.warn(`[Fixwright] buildHealedArgs: unknown prop "${prop}", passing as-is`);
|
|
224
|
+
return args;
|
|
225
|
+
}
|
|
226
|
+
// ─────────────────────────────────────────────────────────────────
|
|
227
|
+
// runWithHealArgument (unchanged from original)
|
|
228
|
+
// ─────────────────────────────────────────────────────────────────
|
|
229
|
+
async function runWithHealArgument(rawPage, prop, args, fullSelector, stableId, callerInfo, originalError) {
|
|
230
|
+
console.log(`[Fixwright-Heal] 💡 Element visible, action '${prop}' failed — healing argument…`);
|
|
231
|
+
const oldArgument = typeof args[0] === "string" ? args[0] : JSON.stringify(args[0]);
|
|
232
|
+
const newArgument = await singleton_1.sharedEngine.healArgument(rawPage, fullSelector, prop, oldArgument, stableId, callerInfo.filePath, callerInfo.line);
|
|
233
|
+
if (!newArgument)
|
|
234
|
+
throw originalError;
|
|
235
|
+
const locator = buildLiveLocator(rawPage, fullSelector);
|
|
236
|
+
const healedArgs = buildHealedArgs(prop, [newArgument, ...args.slice(1)], 8000);
|
|
237
|
+
return await locator[prop](...healedArgs);
|
|
238
|
+
}
|
|
239
|
+
// ─────────────────────────────────────────────────────────────────
|
|
240
|
+
// runWithHeal — pre-flight registry check added
|
|
241
|
+
//
|
|
242
|
+
// NEW: Before the first Playwright attempt, resolve the selector
|
|
243
|
+
// through the registry. If a previous heal already mapped it, the
|
|
244
|
+
// first attempt uses the healed selector and succeeds immediately —
|
|
245
|
+
// no timeout, no AI call, no file write.
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────
|
|
247
|
+
async function runWithHeal(rawPage, prop, args, fullSelector, elementSelectorOnly, stableId, callerInfo) {
|
|
248
|
+
// ── Pre-flight: swap to healed selector if one already exists ──
|
|
249
|
+
const preResolved = registry.resolveSelector(fullSelector);
|
|
250
|
+
const effectiveSelector = preResolved !== fullSelector ? preResolved : fullSelector;
|
|
251
|
+
if (effectiveSelector !== fullSelector) {
|
|
252
|
+
console.log(`[Fixwright] ⚡ Pre-flight swap: "${fullSelector}" → "${effectiveSelector}"`);
|
|
253
|
+
}
|
|
254
|
+
const initialLocator = buildLiveLocator(rawPage, effectiveSelector);
|
|
255
|
+
try {
|
|
256
|
+
const result = await initialLocator[prop](...buildHealedArgs(prop, args, 3000));
|
|
257
|
+
await singleton_1.sharedEngine.captureSuccessfulElement(rawPage, effectiveSelector, stableId);
|
|
258
|
+
return {
|
|
259
|
+
result,
|
|
260
|
+
finalSelector: effectiveSelector,
|
|
261
|
+
finalElementSelector: elementSelectorOnly,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
catch (firstError) {
|
|
265
|
+
console.warn(`[Fixwright-Heal] ⚠️ '${prop}' failed on: "${effectiveSelector}"`);
|
|
266
|
+
const isVisible = await initialLocator
|
|
267
|
+
.isVisible({ timeout: 1500 })
|
|
268
|
+
.catch(() => false);
|
|
269
|
+
if (isVisible && (prop === "selectOption" || prop === "fill")) {
|
|
270
|
+
const result = await runWithHealArgument(rawPage, prop, args, effectiveSelector, stableId, callerInfo, firstError);
|
|
271
|
+
return {
|
|
272
|
+
result,
|
|
273
|
+
finalSelector: effectiveSelector,
|
|
274
|
+
finalElementSelector: elementSelectorOnly,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (prop === "dragTo") {
|
|
278
|
+
return await runWithHealDragTo(rawPage, args, effectiveSelector, elementSelectorOnly, stableId, callerInfo, firstError, (s) => buildLiveLocator(rawPage, s));
|
|
279
|
+
}
|
|
280
|
+
return await runWithHealSelector(rawPage, prop, args, effectiveSelector, elementSelectorOnly, stableId, callerInfo, firstError);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// ─────────────────────────────────────────────────────────────────
|
|
284
|
+
// runWithHealSelector (unchanged from original)
|
|
285
|
+
//
|
|
286
|
+
// Note: broadcast is now handled inside ASTSourceUpdater.update()
|
|
287
|
+
// via broadcastAndReturn(), so there is NO explicit registry call here.
|
|
288
|
+
// The Proxy listener fires automatically before this function returns.
|
|
289
|
+
// ─────────────────────────────────────────────────────────────────
|
|
290
|
+
async function runWithHealSelector(rawPage, prop, args, fullSelector, elementSelectorOnly, stableId, callerInfo, originalError) {
|
|
291
|
+
console.log(`[Fixwright-Heal] 🔧 Healing selector…`);
|
|
292
|
+
const newFullSelector = await singleton_1.sharedEngine.heal(rawPage, fullSelector, elementSelectorOnly, stableId, callerInfo.filePath, callerInfo.line);
|
|
293
|
+
if (!newFullSelector)
|
|
294
|
+
throw originalError;
|
|
295
|
+
// By the time engine.heal() returns, ASTSourceUpdater.update() has
|
|
296
|
+
// already called broadcastAndReturn() → registry.broadcast() →
|
|
297
|
+
// every live Proxy listener has already swapped its activeLocator.
|
|
298
|
+
// We just need to build the locator for THIS call-site's retry.
|
|
299
|
+
const healedLocator = buildLiveLocator(rawPage, newFullSelector);
|
|
300
|
+
try {
|
|
301
|
+
const result = await healedLocator[prop](...buildHealedArgs(prop, args, 8000));
|
|
302
|
+
await singleton_1.sharedEngine.captureSuccessfulElement(rawPage, newFullSelector, stableId);
|
|
303
|
+
return {
|
|
304
|
+
result,
|
|
305
|
+
finalSelector: newFullSelector,
|
|
306
|
+
finalElementSelector: newFullSelector.split(" >> ").pop() ?? newFullSelector,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
catch (retryError) {
|
|
310
|
+
throw retryError;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// ─────────────────────────────────────────────────────────────────
|
|
314
|
+
// runWithHealDragTo (identical to original)
|
|
315
|
+
// ─────────────────────────────────────────────────────────────────
|
|
316
|
+
function extractDragToTargetFromSource(rawFilePath, line) {
|
|
317
|
+
try {
|
|
318
|
+
let filePath = rawFilePath
|
|
319
|
+
.replace(/^.*?at\s+/, "")
|
|
320
|
+
.replace(/:\d+:\d+.*$/, "")
|
|
321
|
+
.trim();
|
|
322
|
+
if (!path.isAbsolute(filePath)) {
|
|
323
|
+
filePath = path.resolve(process.cwd(), filePath);
|
|
324
|
+
}
|
|
325
|
+
if (!fs.existsSync(filePath))
|
|
326
|
+
return null;
|
|
327
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
328
|
+
const lines = content.split(/\r?\n/);
|
|
329
|
+
const block = lines
|
|
330
|
+
.slice(Math.max(0, line - 3), Math.min(lines.length - 1, line + 3) + 1)
|
|
331
|
+
.join("\n");
|
|
332
|
+
const patterns = [
|
|
333
|
+
/\.dragTo\s*\(\s*(?:page|rawPage)\.locator\s*\(\s*"([^"]+)"\s*\)/,
|
|
334
|
+
/\.dragTo\s*\(\s*(?:page|rawPage)\.locator\s*\(\s*'([^']+)'\s*\)/,
|
|
335
|
+
/\.dragTo\s*\(\s*(?:page|rawPage)\.locator\s*\(\s*`([^`]+)`\s*\)/,
|
|
336
|
+
];
|
|
337
|
+
for (const p of patterns) {
|
|
338
|
+
const m = block.match(p);
|
|
339
|
+
if (m?.[1])
|
|
340
|
+
return m[1];
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
async function runWithHealDragTo(rawPage, args, fullSelector, elementSelectorOnly, stableId, callerInfo, originalError, buildFreshLocator) {
|
|
349
|
+
console.log(`[Fixwright-Heal] 🎯 dragTo Double Healing initiated`);
|
|
350
|
+
const targetSelectorFromSource = extractDragToTargetFromSource(callerInfo.filePath, callerInfo.line);
|
|
351
|
+
const sourceLocator = buildFreshLocator(fullSelector);
|
|
352
|
+
const isSourceVisible = await sourceLocator
|
|
353
|
+
.isVisible({ timeout: 500 })
|
|
354
|
+
.catch(() => false);
|
|
355
|
+
let healedSourceSelector = fullSelector;
|
|
356
|
+
let healedTargetLocator = args[0];
|
|
357
|
+
if (!isSourceVisible) {
|
|
358
|
+
try {
|
|
359
|
+
healedSourceSelector = await singleton_1.sharedEngine.heal(rawPage, fullSelector, elementSelectorOnly, stableId, callerInfo.filePath, callerInfo.line);
|
|
360
|
+
// broadcast already fired inside engine.heal() via ASTSourceUpdater
|
|
361
|
+
console.log(`[Fixwright-Heal] 💡 Source healed: "${healedSourceSelector}"`);
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
if (e instanceof Error && e.message.includes("[SafetyGuard]"))
|
|
365
|
+
throw e;
|
|
366
|
+
console.warn(`[Fixwright-Heal] ⚠️ Source healing failed, using original`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const healedSourceLocator = buildFreshLocator(healedSourceSelector);
|
|
370
|
+
if (targetSelectorFromSource) {
|
|
371
|
+
const tempTarget = buildFreshLocator(targetSelectorFromSource);
|
|
372
|
+
const isTargetVisible = await tempTarget
|
|
373
|
+
.isVisible({ timeout: 500 })
|
|
374
|
+
.catch(() => false);
|
|
375
|
+
if (!isTargetVisible) {
|
|
376
|
+
try {
|
|
377
|
+
const healedTargetSel = await singleton_1.sharedEngine.heal(rawPage, targetSelectorFromSource, targetSelectorFromSource, `${stableId}_target`, callerInfo.filePath, callerInfo.line);
|
|
378
|
+
if (healedTargetSel) {
|
|
379
|
+
// broadcast already fired inside engine.heal() via ASTSourceUpdater
|
|
380
|
+
healedTargetLocator = buildFreshLocator(healedTargetSel);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (e) {
|
|
384
|
+
console.warn(`[Fixwright-Heal] ⚠️ Target healing failed: ${e.message}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
healedTargetLocator = tempTarget;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const targetFromError = originalError.message?.match(/waiting for locator\('([^']+)'\)/)?.[1];
|
|
393
|
+
if (targetFromError && targetFromError !== fullSelector) {
|
|
394
|
+
const tempTarget = buildFreshLocator(targetFromError);
|
|
395
|
+
const isVisible = await tempTarget
|
|
396
|
+
.isVisible({ timeout: 500 })
|
|
397
|
+
.catch(() => false);
|
|
398
|
+
if (!isVisible) {
|
|
399
|
+
try {
|
|
400
|
+
const healedSel = await singleton_1.sharedEngine.heal(rawPage, targetFromError, targetFromError, `${stableId}_target`, callerInfo.filePath, callerInfo.line);
|
|
401
|
+
if (healedSel) {
|
|
402
|
+
healedTargetLocator = buildFreshLocator(healedSel);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
if (e instanceof Error && e.message.includes("[SafetyGuard]"))
|
|
407
|
+
throw e;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
healedTargetLocator = tempTarget;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const options = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
416
|
+
try {
|
|
417
|
+
const result = await healedSourceLocator.dragTo(healedTargetLocator, {
|
|
418
|
+
...options,
|
|
419
|
+
timeout: 8000,
|
|
420
|
+
});
|
|
421
|
+
console.log(`[Fixwright-Heal] ✅ dragTo double heal successful`);
|
|
422
|
+
return {
|
|
423
|
+
result,
|
|
424
|
+
finalSelector: healedSourceSelector,
|
|
425
|
+
finalElementSelector: healedSourceSelector.split(" >> ").pop() ?? healedSourceSelector,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
throw originalError;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// ─────────────────────────────────────────────────────────────────
|
|
433
|
+
// createFrameLocatorProxy (identical to original)
|
|
434
|
+
// ─────────────────────────────────────────────────────────────────
|
|
435
|
+
function createFrameLocatorProxy(rawFrameLocator, frameSelector, rawPage, testTitle, actionCounter, parentChain) {
|
|
436
|
+
const fullFrameChain = parentChain
|
|
437
|
+
? `${parentChain} >> ${frameSelector}`
|
|
438
|
+
: frameSelector;
|
|
439
|
+
return new Proxy(rawFrameLocator, {
|
|
440
|
+
get(fTarget, fProp) {
|
|
441
|
+
if (typeof fProp !== "string")
|
|
442
|
+
return Reflect.get(fTarget, fProp);
|
|
443
|
+
if (fProp === "frameLocator") {
|
|
444
|
+
return (innerFrameSel) => createFrameLocatorProxy(fTarget.frameLocator(innerFrameSel), innerFrameSel, rawPage, testTitle, actionCounter, fullFrameChain);
|
|
445
|
+
}
|
|
446
|
+
if (fProp === "locator") {
|
|
447
|
+
return (elementSel) => createLocatorProxy(fTarget.locator(elementSel), `${fullFrameChain} >> ${elementSel}`, rawPage, testTitle, actionCounter, elementSel);
|
|
448
|
+
}
|
|
449
|
+
return Reflect.get(fTarget, fProp);
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
// ─────────────────────────────────────────────────────────────────
|
|
454
|
+
// createLocatorProxy — Runtime Selector Injection added
|
|
455
|
+
//
|
|
456
|
+
// NEW additions vs original:
|
|
457
|
+
// 1. Resolves selector through registry on creation (handles proxies
|
|
458
|
+
// born after a heal has already broadcast).
|
|
459
|
+
// 2. Subscribes to registry so any future broadcast swaps
|
|
460
|
+
// activeLocator on the same tick — before the next await.
|
|
461
|
+
// 3. Exposes _fixwrightUnsubscribe so the fixture can clean up.
|
|
462
|
+
// ─────────────────────────────────────────────────────────────────
|
|
463
|
+
function createLocatorProxy(rawLocator, selector, rawPage, testTitle, actionCounter, elementSelectorOnly) {
|
|
464
|
+
// ── Resolve immediately (handles post-heal proxy creation) ────
|
|
465
|
+
const initialResolved = registry.resolveSelector(selector);
|
|
466
|
+
let currentFullSelector = initialResolved;
|
|
467
|
+
let currentElementSelector = elementSelectorOnly ?? initialResolved;
|
|
468
|
+
let activeLocator = initialResolved !== selector
|
|
469
|
+
? buildLiveLocator(rawPage, initialResolved) // already healed
|
|
470
|
+
: rawLocator; // fresh, use as-is
|
|
471
|
+
// ── Subscribe: swap activeLocator when a heal fires ──────────
|
|
472
|
+
// Inside createLocatorProxy — full updated subscribe block
|
|
473
|
+
// בתוך createLocatorProxy - תחליף את הבלוק של ה-subscribe בזה:
|
|
474
|
+
const unsubscribe = registry.subscribe((fp, oldSel, newSel) => {
|
|
475
|
+
// בדיקה אם הסלקטור הנוכחי מכיל את הישן או שהוא הישן בעצמו
|
|
476
|
+
if (currentFullSelector !== oldSel &&
|
|
477
|
+
!currentFullSelector.includes(oldSel)) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
let nextSelector;
|
|
481
|
+
// הגנה חדשה: אם התיקון החדש (newSel) הוא כבר סלקטור מלא ומשורשר (מכיל >>)
|
|
482
|
+
// ואנחנו נמצאים בתוך שרשרת Proxy, אנחנו לא רוצים לשרשר "זבל"
|
|
483
|
+
if (newSel.includes(" >> ") ||
|
|
484
|
+
newSel.startsWith(".") ||
|
|
485
|
+
newSel.startsWith("#")) {
|
|
486
|
+
// אם הישן היה חלק משרשרת מורכבת עם .filter, אנחנו מחליפים את כל השרשרת בתיקון הנקי
|
|
487
|
+
nextSelector = newSel;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
nextSelector = currentFullSelector.replace(oldSel, newSel);
|
|
491
|
+
}
|
|
492
|
+
if (nextSelector === currentFullSelector)
|
|
493
|
+
return;
|
|
494
|
+
console.log(`[Fixwright] ♻️ proxy swap (Smart Clean): "${currentFullSelector}" → "${nextSelector}"`);
|
|
495
|
+
currentFullSelector = nextSelector;
|
|
496
|
+
currentElementSelector = nextSelector.split(" >> ").pop() ?? nextSelector;
|
|
497
|
+
// בנייה מחדש של הלוקטור האמיתי מהמחרוזת הנקייה
|
|
498
|
+
activeLocator = buildLiveLocator(rawPage, nextSelector);
|
|
499
|
+
});
|
|
500
|
+
const proxy = new Proxy(rawLocator, {
|
|
501
|
+
get(target, prop, receiver) {
|
|
502
|
+
if (typeof prop !== "string")
|
|
503
|
+
return Reflect.get(target, prop, receiver);
|
|
504
|
+
// ── Internal accessors ───────────────────────────────────
|
|
505
|
+
if (prop === "_fixwrightSelector")
|
|
506
|
+
return currentFullSelector;
|
|
507
|
+
if (prop === "_fixwrightPage")
|
|
508
|
+
return rawPage;
|
|
509
|
+
if (prop === "_fixwrightTestTitle")
|
|
510
|
+
return testTitle;
|
|
511
|
+
if (prop === "_fixwrightActionCounter")
|
|
512
|
+
return actionCounter;
|
|
513
|
+
if (prop === "_fixwrightElementSelector")
|
|
514
|
+
return currentElementSelector;
|
|
515
|
+
if (prop === "_fixwrightUnsubscribe")
|
|
516
|
+
return unsubscribe;
|
|
517
|
+
// ── getBy* ───────────────────────────────────────────────
|
|
518
|
+
if (prop in GET_BY_METHODS) {
|
|
519
|
+
return (...args) => {
|
|
520
|
+
const semanticSelector = GET_BY_METHODS[prop](args);
|
|
521
|
+
const childFullSelector = `${currentFullSelector} >> ${semanticSelector}`;
|
|
522
|
+
return createLocatorProxy(activeLocator[prop](...args), childFullSelector, rawPage, testTitle, actionCounter, semanticSelector);
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
// ── Chainable methods ────────────────────────────────────
|
|
526
|
+
const chainables = ["locator", "filter", "first", "last", "nth"];
|
|
527
|
+
if (chainables.includes(prop)) {
|
|
528
|
+
return (...args) => {
|
|
529
|
+
const nextLocator = activeLocator[prop](...args);
|
|
530
|
+
let nextFull = currentFullSelector;
|
|
531
|
+
let nextElement = currentElementSelector;
|
|
532
|
+
if (prop === "locator") {
|
|
533
|
+
nextFull += ` >> ${args[0]}`;
|
|
534
|
+
nextElement = args[0];
|
|
535
|
+
}
|
|
536
|
+
else if (prop === "filter") {
|
|
537
|
+
const d = args[0]?.hasText
|
|
538
|
+
? `.filter(hasText:"${args[0].hasText}")`
|
|
539
|
+
: `.filter(...)`;
|
|
540
|
+
nextFull += d;
|
|
541
|
+
nextElement += d;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
const suffix = prop === "first"
|
|
545
|
+
? ":nth-match(1)"
|
|
546
|
+
: prop === "last"
|
|
547
|
+
? ":last"
|
|
548
|
+
: `:nth-match(${args[0] + 1})`;
|
|
549
|
+
nextFull += suffix;
|
|
550
|
+
nextElement += suffix;
|
|
551
|
+
}
|
|
552
|
+
return createLocatorProxy(nextLocator, nextFull, rawPage, testTitle, actionCounter, nextElement);
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
// ── Intercepted actions ──────────────────────────────────
|
|
556
|
+
if (INTERCEPTED_ACTIONS.includes(prop)) {
|
|
557
|
+
return async (...args) => {
|
|
558
|
+
actionCounter.value++;
|
|
559
|
+
const stableId = StackUtils_1.StackUtils.getActionKey(testTitle, prop, actionCounter.value);
|
|
560
|
+
const callerInfo = StackUtils_1.StackUtils.getCallerInfo();
|
|
561
|
+
singleton_1.sharedEngine.preloadTestTitle(stableId, testTitle);
|
|
562
|
+
const { result, finalSelector, finalElementSelector } = await runWithHeal(rawPage, prop, args, currentFullSelector, currentElementSelector, stableId, callerInfo);
|
|
563
|
+
// Post-action desync guard: if runWithHeal found a new selector
|
|
564
|
+
// through engine.heal() and the registry listener fired during
|
|
565
|
+
// that await, currentFullSelector is already updated. This
|
|
566
|
+
// explicit assignment is a safety net for the rare race where
|
|
567
|
+
// the listener callback and the return value disagree.
|
|
568
|
+
if (finalSelector !== currentFullSelector) {
|
|
569
|
+
currentFullSelector = finalSelector;
|
|
570
|
+
currentElementSelector = finalElementSelector;
|
|
571
|
+
activeLocator = buildLiveLocator(rawPage, finalSelector);
|
|
572
|
+
}
|
|
573
|
+
return result;
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
// ── Passive getters (silent heal on failure) ─────────────
|
|
577
|
+
const passiveGetters = [
|
|
578
|
+
"isVisible",
|
|
579
|
+
"isEnabled",
|
|
580
|
+
"isHidden",
|
|
581
|
+
"isChecked",
|
|
582
|
+
"innerText",
|
|
583
|
+
"innerHTML",
|
|
584
|
+
"textContent",
|
|
585
|
+
"inputValue",
|
|
586
|
+
];
|
|
587
|
+
if (passiveGetters.includes(prop)) {
|
|
588
|
+
return async (...args) => {
|
|
589
|
+
try {
|
|
590
|
+
return await activeLocator[prop](...args);
|
|
591
|
+
}
|
|
592
|
+
catch {
|
|
593
|
+
console.debug(`[Fixwright] 🔇 Silent heal for getter '${prop}'`);
|
|
594
|
+
try {
|
|
595
|
+
actionCounter.value++;
|
|
596
|
+
const stableId = StackUtils_1.StackUtils.getActionKey(testTitle, prop, actionCounter.value);
|
|
597
|
+
const callerInfo = StackUtils_1.StackUtils.getCallerInfo();
|
|
598
|
+
singleton_1.sharedEngine.preloadTestTitle(stableId, testTitle);
|
|
599
|
+
const { finalSelector } = await runWithHeal(rawPage, prop, args, currentFullSelector, currentElementSelector, stableId, callerInfo);
|
|
600
|
+
if (finalSelector !== currentFullSelector) {
|
|
601
|
+
currentFullSelector = finalSelector;
|
|
602
|
+
activeLocator = buildLiveLocator(rawPage, finalSelector);
|
|
603
|
+
}
|
|
604
|
+
return await activeLocator[prop](...args);
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
// Reflect on activeLocator (not rawLocator) to stay current
|
|
613
|
+
return Reflect.get(activeLocator, prop, receiver);
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
proxyToActiveLocator.set(proxy, () => activeLocator);
|
|
617
|
+
return proxy;
|
|
618
|
+
}
|
|
619
|
+
// ─────────────────────────────────────────────────────────────────
|
|
620
|
+
// test fixture — teardown added
|
|
621
|
+
// ─────────────────────────────────────────────────────────────────
|
|
622
|
+
// ─── expect — healing proxy replacing the plain baseExpect proxy ──────────────
|
|
623
|
+
exports.test = test_1.test.extend({
|
|
624
|
+
page: async ({ page }, use, testInfo) => {
|
|
625
|
+
page.on("console", (msg) => {
|
|
626
|
+
if (msg.text().includes("[DOM-EXTRACT]")) {
|
|
627
|
+
console.log(`[Browser Console]: ${msg.text()}`);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
const actionCounter = { value: 0 };
|
|
631
|
+
const rawPage = page;
|
|
632
|
+
const unsubscribers = [];
|
|
633
|
+
const pageProxy = new Proxy(page, {
|
|
634
|
+
get(target, prop) {
|
|
635
|
+
if (prop === "_rawPage")
|
|
636
|
+
return target;
|
|
637
|
+
if (typeof prop !== "string")
|
|
638
|
+
return Reflect.get(target, prop, target);
|
|
639
|
+
if (prop === "locator") {
|
|
640
|
+
return (selector) => {
|
|
641
|
+
const p = createLocatorProxy(target.locator(selector), selector, rawPage, testInfo.title, actionCounter, selector);
|
|
642
|
+
const u = p._fixwrightUnsubscribe;
|
|
643
|
+
if (u)
|
|
644
|
+
unsubscribers.push(u);
|
|
645
|
+
return p;
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (prop === "frameLocator") {
|
|
649
|
+
return (frameSel) => createFrameLocatorProxy(target.frameLocator(frameSel), frameSel, rawPage, testInfo.title, actionCounter, "");
|
|
650
|
+
}
|
|
651
|
+
if (prop in GET_BY_METHODS) {
|
|
652
|
+
return (...args) => {
|
|
653
|
+
const semanticSelector = GET_BY_METHODS[prop](args);
|
|
654
|
+
const p = createLocatorProxy(target[prop](...args), semanticSelector, rawPage, testInfo.title, actionCounter, semanticSelector);
|
|
655
|
+
const u = p._fixwrightUnsubscribe;
|
|
656
|
+
if (u)
|
|
657
|
+
unsubscribers.push(u);
|
|
658
|
+
return p;
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
return Reflect.get(target, prop, target);
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
await use(pageProxy);
|
|
665
|
+
// ── Teardown ─────────────────────────────────────────────────
|
|
666
|
+
for (const u of unsubscribers)
|
|
667
|
+
u();
|
|
668
|
+
registry.clearForTest();
|
|
669
|
+
await singleton_1.sharedEngine.commitUpdates();
|
|
670
|
+
},
|
|
671
|
+
expect: async ({ page }, use, testInfo) => {
|
|
672
|
+
const healingExpect = (0, expectProxy_1.createHealingExpect)(singleton_1.sharedEngine, page, testInfo.file, 0,
|
|
673
|
+
// Inject proxy resolver — gives expectProxy.ts access to the live
|
|
674
|
+
// active Locator without exposing the WeakMap directly.
|
|
675
|
+
(value) => {
|
|
676
|
+
if (value && typeof value === "object") {
|
|
677
|
+
const getActive = proxyToActiveLocator.get(value);
|
|
678
|
+
if (getActive)
|
|
679
|
+
return getActive();
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
});
|
|
683
|
+
await use(healingExpect);
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
// Re-export expect as a healing expect for bare import usage
|
|
687
|
+
var expectProxy_2 = require("./expectProxy");
|
|
688
|
+
Object.defineProperty(exports, "createHealingExpect", { enumerable: true, get: function () { return expectProxy_2.createHealingExpect; } });
|