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.
Files changed (267) hide show
  1. package/README.md +103 -0
  2. package/bin/sela.js +3 -0
  3. package/dist/cli/ErrorHandler.d.ts +10 -0
  4. package/dist/cli/ErrorHandler.d.ts.map +1 -0
  5. package/dist/cli/ErrorHandler.js +70 -0
  6. package/dist/cli/commands/bulk.d.ts +3 -0
  7. package/dist/cli/commands/bulk.d.ts.map +1 -0
  8. package/dist/cli/commands/bulk.js +158 -0
  9. package/dist/cli/commands/find.d.ts +3 -0
  10. package/dist/cli/commands/find.d.ts.map +1 -0
  11. package/dist/cli/commands/find.js +51 -0
  12. package/dist/cli/commands/init.d.ts +3 -0
  13. package/dist/cli/commands/init.d.ts.map +1 -0
  14. package/dist/cli/commands/init.js +166 -0
  15. package/dist/cli/commands/list.d.ts +3 -0
  16. package/dist/cli/commands/list.d.ts.map +1 -0
  17. package/dist/cli/commands/list.js +56 -0
  18. package/dist/cli/commands/merge.d.ts +13 -0
  19. package/dist/cli/commands/merge.d.ts.map +1 -0
  20. package/dist/cli/commands/merge.js +119 -0
  21. package/dist/cli/commands/refactor.d.ts +3 -0
  22. package/dist/cli/commands/refactor.d.ts.map +1 -0
  23. package/dist/cli/commands/refactor.js +30 -0
  24. package/dist/cli/commands/showReport.d.ts +3 -0
  25. package/dist/cli/commands/showReport.d.ts.map +1 -0
  26. package/dist/cli/commands/showReport.js +82 -0
  27. package/dist/cli/commands/status.d.ts +3 -0
  28. package/dist/cli/commands/status.d.ts.map +1 -0
  29. package/dist/cli/commands/status.js +51 -0
  30. package/dist/cli/commands/sync.d.ts +3 -0
  31. package/dist/cli/commands/sync.d.ts.map +1 -0
  32. package/dist/cli/commands/sync.js +131 -0
  33. package/dist/cli/index.d.ts +2 -0
  34. package/dist/cli/index.d.ts.map +1 -0
  35. package/dist/cli/index.js +94 -0
  36. package/dist/cli/ui/DnaTable.d.ts +3 -0
  37. package/dist/cli/ui/DnaTable.d.ts.map +1 -0
  38. package/dist/cli/ui/DnaTable.js +77 -0
  39. package/dist/cli/ui/LocatorPicker.d.ts +8 -0
  40. package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
  41. package/dist/cli/ui/LocatorPicker.js +33 -0
  42. package/dist/cli/ui/ProgressReporter.d.ts +11 -0
  43. package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
  44. package/dist/cli/ui/ProgressReporter.js +33 -0
  45. package/dist/cli/ui/RefactorWizard.d.ts +26 -0
  46. package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
  47. package/dist/cli/ui/RefactorWizard.js +295 -0
  48. package/dist/config/ConfigLoader.d.ts +16 -0
  49. package/dist/config/ConfigLoader.d.ts.map +1 -0
  50. package/dist/config/ConfigLoader.js +185 -0
  51. package/dist/config/DryRunGuard.d.ts +14 -0
  52. package/dist/config/DryRunGuard.d.ts.map +1 -0
  53. package/dist/config/DryRunGuard.js +80 -0
  54. package/dist/config/PathScopeGuard.d.ts +42 -0
  55. package/dist/config/PathScopeGuard.d.ts.map +1 -0
  56. package/dist/config/PathScopeGuard.js +192 -0
  57. package/dist/config/SelaConfig.d.ts +178 -0
  58. package/dist/config/SelaConfig.d.ts.map +1 -0
  59. package/dist/config/SelaConfig.js +141 -0
  60. package/dist/config/defineConfig.d.ts +3 -0
  61. package/dist/config/defineConfig.d.ts.map +1 -0
  62. package/dist/config/defineConfig.js +9 -0
  63. package/dist/engine/FixwrightEngine.d.ts +24 -0
  64. package/dist/engine/FixwrightEngine.d.ts.map +1 -0
  65. package/dist/engine/FixwrightEngine.js +403 -0
  66. package/dist/engine/HealingRegistry.d.ts +40 -0
  67. package/dist/engine/HealingRegistry.d.ts.map +1 -0
  68. package/dist/engine/HealingRegistry.js +99 -0
  69. package/dist/engine/SelaEngine.d.ts +57 -0
  70. package/dist/engine/SelaEngine.d.ts.map +1 -0
  71. package/dist/engine/SelaEngine.js +1146 -0
  72. package/dist/engine/singleton.d.ts +3 -0
  73. package/dist/engine/singleton.d.ts.map +1 -0
  74. package/dist/engine/singleton.js +5 -0
  75. package/dist/errors/SelaError.d.ts +146 -0
  76. package/dist/errors/SelaError.d.ts.map +1 -0
  77. package/dist/errors/SelaError.js +168 -0
  78. package/dist/fixtures/expectProxy.d.ts +12 -0
  79. package/dist/fixtures/expectProxy.d.ts.map +1 -0
  80. package/dist/fixtures/expectProxy.js +246 -0
  81. package/dist/fixtures/index.d.ts +30 -0
  82. package/dist/fixtures/index.d.ts.map +1 -0
  83. package/dist/fixtures/index.js +765 -0
  84. package/dist/fixtures/moduleExpect.d.ts +2 -0
  85. package/dist/fixtures/moduleExpect.d.ts.map +1 -0
  86. package/dist/fixtures/moduleExpect.js +54 -0
  87. package/dist/fixtures/proxyTag.d.ts +12 -0
  88. package/dist/fixtures/proxyTag.d.ts.map +1 -0
  89. package/dist/fixtures/proxyTag.js +45 -0
  90. package/dist/index.d.ts +9 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +31 -0
  93. package/dist/reporter/SelaReporter.d.ts +165 -0
  94. package/dist/reporter/SelaReporter.d.ts.map +1 -0
  95. package/dist/reporter/SelaReporter.js +517 -0
  96. package/dist/services/ASTSourceUpdater.d.ts +143 -0
  97. package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
  98. package/dist/services/ASTSourceUpdater.js +3514 -0
  99. package/dist/services/AnchorResolver.d.ts +157 -0
  100. package/dist/services/AnchorResolver.d.ts.map +1 -0
  101. package/dist/services/AnchorResolver.js +289 -0
  102. package/dist/services/ArgumentTypeAnalyzer.d.ts +26 -0
  103. package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
  104. package/dist/services/ArgumentTypeAnalyzer.js +92 -0
  105. package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
  106. package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
  107. package/dist/services/BlastRadiusAnalyzer.js +107 -0
  108. package/dist/services/ChainValidator.d.ts +76 -0
  109. package/dist/services/ChainValidator.d.ts.map +1 -0
  110. package/dist/services/ChainValidator.js +570 -0
  111. package/dist/services/CrossFileHealer.d.ts +6 -0
  112. package/dist/services/CrossFileHealer.d.ts.map +1 -0
  113. package/dist/services/CrossFileHealer.js +135 -0
  114. package/dist/services/DecisionEngine.d.ts +51 -0
  115. package/dist/services/DecisionEngine.d.ts.map +1 -0
  116. package/dist/services/DecisionEngine.js +260 -0
  117. package/dist/services/DefinitionTracer.d.ts +41 -0
  118. package/dist/services/DefinitionTracer.d.ts.map +1 -0
  119. package/dist/services/DefinitionTracer.js +366 -0
  120. package/dist/services/DnaEditorService.d.ts +31 -0
  121. package/dist/services/DnaEditorService.d.ts.map +1 -0
  122. package/dist/services/DnaEditorService.js +217 -0
  123. package/dist/services/DnaIndexService.d.ts +24 -0
  124. package/dist/services/DnaIndexService.d.ts.map +1 -0
  125. package/dist/services/DnaIndexService.js +131 -0
  126. package/dist/services/HealBudget.d.ts +32 -0
  127. package/dist/services/HealBudget.d.ts.map +1 -0
  128. package/dist/services/HealBudget.js +85 -0
  129. package/dist/services/HealReportService.d.ts +161 -0
  130. package/dist/services/HealReportService.d.ts.map +1 -0
  131. package/dist/services/HealReportService.js +1039 -0
  132. package/dist/services/HealingAdvisory.d.ts +30 -0
  133. package/dist/services/HealingAdvisory.d.ts.map +1 -0
  134. package/dist/services/HealingAdvisory.js +51 -0
  135. package/dist/services/HealingCacheService.d.ts +135 -0
  136. package/dist/services/HealingCacheService.d.ts.map +1 -0
  137. package/dist/services/HealingCacheService.js +404 -0
  138. package/dist/services/HealthReportService.d.ts +10 -0
  139. package/dist/services/HealthReportService.d.ts.map +1 -0
  140. package/dist/services/HealthReportService.js +84 -0
  141. package/dist/services/InitializerUpdater.d.ts +16 -0
  142. package/dist/services/InitializerUpdater.d.ts.map +1 -0
  143. package/dist/services/InitializerUpdater.js +73 -0
  144. package/dist/services/IntentAuditor.d.ts +56 -0
  145. package/dist/services/IntentAuditor.d.ts.map +1 -0
  146. package/dist/services/IntentAuditor.js +326 -0
  147. package/dist/services/InteractiveReview.d.ts +15 -0
  148. package/dist/services/InteractiveReview.d.ts.map +1 -0
  149. package/dist/services/InteractiveReview.js +128 -0
  150. package/dist/services/LLMService.d.ts +121 -0
  151. package/dist/services/LLMService.d.ts.map +1 -0
  152. package/dist/services/LLMService.js +475 -0
  153. package/dist/services/MutationApplier.d.ts +69 -0
  154. package/dist/services/MutationApplier.d.ts.map +1 -0
  155. package/dist/services/MutationApplier.js +360 -0
  156. package/dist/services/PRAutomationService.d.ts +50 -0
  157. package/dist/services/PRAutomationService.d.ts.map +1 -0
  158. package/dist/services/PRAutomationService.js +760 -0
  159. package/dist/services/PendingPromptLedger.d.ts +51 -0
  160. package/dist/services/PendingPromptLedger.d.ts.map +1 -0
  161. package/dist/services/PendingPromptLedger.js +205 -0
  162. package/dist/services/ReportGenerator.d.ts +156 -0
  163. package/dist/services/ReportGenerator.d.ts.map +1 -0
  164. package/dist/services/ReportGenerator.js +278 -0
  165. package/dist/services/ReportMergeService.d.ts +95 -0
  166. package/dist/services/ReportMergeService.d.ts.map +1 -0
  167. package/dist/services/ReportMergeService.js +0 -0
  168. package/dist/services/SafetyGuard.d.ts +112 -0
  169. package/dist/services/SafetyGuard.d.ts.map +1 -0
  170. package/dist/services/SafetyGuard.js +87 -0
  171. package/dist/services/Sanitizer.d.ts +28 -0
  172. package/dist/services/Sanitizer.d.ts.map +1 -0
  173. package/dist/services/Sanitizer.js +142 -0
  174. package/dist/services/SanitizingAnthropic.d.ts +19 -0
  175. package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
  176. package/dist/services/SanitizingAnthropic.js +84 -0
  177. package/dist/services/SelectorSanitizer.d.ts +52 -0
  178. package/dist/services/SelectorSanitizer.d.ts.map +1 -0
  179. package/dist/services/SelectorSanitizer.js +318 -0
  180. package/dist/services/SnapshotService.d.ts +11 -0
  181. package/dist/services/SnapshotService.d.ts.map +1 -0
  182. package/dist/services/SnapshotService.js +352 -0
  183. package/dist/services/SourceLinkService.d.ts +26 -0
  184. package/dist/services/SourceLinkService.d.ts.map +1 -0
  185. package/dist/services/SourceLinkService.js +165 -0
  186. package/dist/services/SourceUpdater.d.ts +45 -0
  187. package/dist/services/SourceUpdater.d.ts.map +1 -0
  188. package/dist/services/SourceUpdater.js +1091 -0
  189. package/dist/services/TemplateDiffService.d.ts +25 -0
  190. package/dist/services/TemplateDiffService.d.ts.map +1 -0
  191. package/dist/services/TemplateDiffService.js +334 -0
  192. package/dist/services/TraceBackEngine.d.ts +67 -0
  193. package/dist/services/TraceBackEngine.d.ts.map +1 -0
  194. package/dist/services/TraceBackEngine.js +672 -0
  195. package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
  196. package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
  197. package/dist/services/WorkspaceSnapshotService.js +200 -0
  198. package/dist/services/safety/PipelineContext.d.ts +63 -0
  199. package/dist/services/safety/PipelineContext.d.ts.map +1 -0
  200. package/dist/services/safety/PipelineContext.js +34 -0
  201. package/dist/services/safety/roleClassifier.d.ts +4 -0
  202. package/dist/services/safety/roleClassifier.d.ts.map +1 -0
  203. package/dist/services/safety/roleClassifier.js +233 -0
  204. package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
  205. package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
  206. package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
  207. package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
  208. package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
  209. package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
  210. package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
  211. package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
  212. package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
  213. package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
  214. package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
  215. package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
  216. package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
  217. package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
  218. package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
  219. package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
  220. package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
  221. package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
  222. package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
  223. package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
  224. package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
  225. package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
  226. package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
  227. package/dist/services/safety/rules/VisibilityRule.js +53 -0
  228. package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
  229. package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
  230. package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
  231. package/dist/services/safety/rules/index.d.ts +3 -0
  232. package/dist/services/safety/rules/index.d.ts.map +1 -0
  233. package/dist/services/safety/rules/index.js +34 -0
  234. package/dist/services/types.d.ts +59 -0
  235. package/dist/services/types.d.ts.map +1 -0
  236. package/dist/services/types.js +2 -0
  237. package/dist/storage/SnapshotManager.d.ts +5 -0
  238. package/dist/storage/SnapshotManager.d.ts.map +1 -0
  239. package/dist/storage/SnapshotManager.js +32 -0
  240. package/dist/types/index.d.ts +95 -0
  241. package/dist/types/index.d.ts.map +1 -0
  242. package/dist/types/index.js +7 -0
  243. package/dist/utils/DOMUtils.d.ts +49 -0
  244. package/dist/utils/DOMUtils.d.ts.map +1 -0
  245. package/dist/utils/DOMUtils.js +466 -0
  246. package/dist/utils/FileLock.d.ts +44 -0
  247. package/dist/utils/FileLock.d.ts.map +1 -0
  248. package/dist/utils/FileLock.js +189 -0
  249. package/dist/utils/IsolatedDiff.d.ts +45 -0
  250. package/dist/utils/IsolatedDiff.d.ts.map +1 -0
  251. package/dist/utils/IsolatedDiff.js +382 -0
  252. package/dist/utils/StackUtils.d.ts +11 -0
  253. package/dist/utils/StackUtils.d.ts.map +1 -0
  254. package/dist/utils/StackUtils.js +120 -0
  255. package/dist/utils/SummaryTable.d.ts +17 -0
  256. package/dist/utils/SummaryTable.d.ts.map +1 -0
  257. package/dist/utils/SummaryTable.js +37 -0
  258. package/dist/utils/atomicWrite.d.ts +9 -0
  259. package/dist/utils/atomicWrite.d.ts.map +1 -0
  260. package/dist/utils/atomicWrite.js +83 -0
  261. package/dist/utils/logger.d.ts +59 -0
  262. package/dist/utils/logger.d.ts.map +1 -0
  263. package/dist/utils/logger.js +123 -0
  264. package/dist/vendor/enquirer.d.ts +33 -0
  265. package/dist/vendor/enquirer.d.ts.map +1 -0
  266. package/dist/vendor/enquirer.js +11 -0
  267. 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; } });