1717using Bellatrix . LLM ;
1818using Bellatrix . LLM . Plugins ;
1919using Bellatrix . Playwright . LLM . Plugins ;
20- using Bellatrix . Playwright . Locators ;
21- using Microsoft . Identity . Client ;
2220using Microsoft . SemanticKernel ;
23- using Pipelines . Sockets . Unofficial . Arenas ;
2421using System . Text . RegularExpressions ;
2522using System . Threading ;
2623
2724namespace Bellatrix . Playwright . LLM ;
2825
2926public class FindByPrompt : FindStrategy
3027{
31- private bool _tryResolveFromPages = true ;
28+ private readonly bool _tryResolveFromPages ;
3229 /// <summary>
3330 /// Initializes a new instance of the <see cref="FindByPrompt"/> class with the specified prompt value.
3431 /// </summary>
@@ -58,51 +55,80 @@ private FindStrategy TryResolveLocator(string location, IViewSnapshotProvider sn
5855 {
5956 if ( _tryResolveFromPages )
6057 {
61- // Try from memory
62- var match = SemanticKernelService . Memory
63- . SearchAsync ( Value , index : "PageObjects" , limit : 1 )
64- . Result . Results . FirstOrDefault ( ) ;
65-
66- if ( match != null )
58+ var ragLocator = TryResolveFromPageObjectMemory ( Value ) ;
59+ if ( ragLocator != null && ragLocator . Resolve ( WrappedBrowser . CurrentPage ) . IsPresent )
6760 {
68- var pageSummary = match . Partitions . FirstOrDefault ( ) ? . Text ?? "" ;
69- var mappedPrompt = SemanticKernelService . Kernel
70- . InvokeAsync ( nameof ( LocatorMapperSkill ) , nameof ( LocatorMapperSkill . MatchPromptToKnownLocator ) , new ( )
71- {
72- [ "pageSummary" ] = pageSummary ,
73- [ "instruction" ] = Value
74- } ) . Result . GetValue < string > ( ) ;
75-
76- var result = SemanticKernelService . Kernel . InvokePromptAsync ( mappedPrompt ) . Result ;
77- var rawLocator = result ? . GetValue < string > ( ) ? . Trim ( ) ;
78- var ragLocator = new FindXpathStrategy ( rawLocator ) ;
79- if ( ragLocator != null )
80- {
81- Logger . LogInformation ( $ "✅ Using RAG-located element '{ ragLocator } ' For '${ Value } '") ;
82- return ragLocator ;
83- }
61+ Logger . LogInformation ( $ "✅ Using RAG-located element '{ ragLocator } ' For '${ Value } '") ;
62+ return ragLocator ;
8463 }
8564 }
8665
87- // Try cache
88- var cached = LocatorCacheService . TryGetCached ( location , Value ) ;
89- if ( ! string . IsNullOrEmpty ( cached ) )
66+ // Step 2: Try local persistent cache
67+ Logger . LogInformation ( "⚠️ RAG-located element not present. Trying cached selectors..." ) ;
68+ var cached = LocatorCacheService . TryGetCached ( WrappedBrowser . CurrentPage . Url , Value ) ;
69+
70+ var strategy = new FindXpathStrategy ( cached ) ;
71+ if ( ! string . IsNullOrEmpty ( cached ) && strategy . Resolve ( WrappedBrowser . CurrentPage ) . IsPresent )
9072 {
91- return new FindXpathStrategy ( cached ) ;
73+ Logger . LogInformation ( "✅ Using cached selector." ) ;
74+ return strategy ;
9275 }
9376
94- // Remove broken and fall back
95- LocatorCacheService . Remove ( location , Value ) ;
77+ // Step 3: Fall back to AI + prompt regeneration
78+ Logger . LogInformation ( "⚠️ Cached selector failed or not found. Re-querying AI..." ) ;
79+ LocatorCacheService . Remove ( WrappedBrowser . CurrentPage . Url , Value ) ;
80+
9681 return ResolveViaPromptFallback ( location , snapshotProvider ) ;
9782 }
9883
84+ private static FindXpathStrategy TryResolveFromPageObjectMemory ( string instruction )
85+ {
86+ var match = SemanticKernelService . Memory
87+ . SearchAsync ( instruction , index : "PageObjects" , limit : 1 )
88+ . Result . Results . FirstOrDefault ( ) ;
89+
90+ if ( match == null ) return null ;
91+
92+ var pageSummary = match . Partitions . FirstOrDefault ( ) ? . Text ?? string . Empty ;
93+ var mappedPrompt = SemanticKernelService . Kernel
94+ . InvokeAsync ( nameof ( LocatorMapperSkill ) , nameof ( LocatorMapperSkill . MatchPromptToKnownLocator ) ,
95+ new ( )
96+ {
97+ [ "pageSummary" ] = pageSummary ,
98+ [ "instruction" ] = instruction
99+ } ) . Result . GetValue < string > ( ) ;
100+
101+ var locatorResult = SemanticKernelService . Kernel
102+ . InvokePromptAsync ( mappedPrompt ) . Result . GetValue < string > ( ) ;
103+
104+ return ParsePromptLocatorToStrategy ( locatorResult ) ;
105+ }
106+
107+ private static FindXpathStrategy ParsePromptLocatorToStrategy ( string promptResult )
108+ {
109+ if ( promptResult == "Unknown" )
110+ {
111+ return null ;
112+ }
113+
114+ var parts = Regex . Match ( promptResult , @"^\s*xpath\s*=\s*(//.+)$" , RegexOptions . IgnoreCase ) ;
115+ if ( ! parts . Success )
116+ {
117+ throw new ArgumentException ( $ "❌ Invalid format. Expected: xpath=//... but received '{ promptResult } '") ;
118+ }
119+
120+ var xpath = parts . Groups [ 1 ] . Value . Trim ( ) ;
121+
122+ return new FindXpathStrategy ( xpath ) ;
123+ }
124+
99125 private FindStrategy ResolveViaPromptFallback ( string location , IViewSnapshotProvider snapshotProvider , int maxAttempts = 3 )
100126 {
101- var summaryJson = snapshotProvider . GetCurrentViewSnapshot ( ) ;
102127 var failedSelectors = new List < string > ( ) ;
103128
104129 for ( var attempt = 1 ; attempt <= maxAttempts ; attempt ++ )
105130 {
131+ var summaryJson = snapshotProvider . GetCurrentViewSnapshot ( ) ;
106132 var prompt = SemanticKernelService . Kernel
107133 . InvokeAsync ( nameof ( LocatorSkill ) , nameof ( LocatorSkill . BuildLocatorPrompt ) ,
108134 new ( )
@@ -113,25 +139,22 @@ private FindStrategy ResolveViaPromptFallback(string location, IViewSnapshotProv
113139 } ) . Result . GetValue < string > ( ) ;
114140
115141 var result = SemanticKernelService . Kernel . InvokePromptAsync ( prompt ) . Result ;
116- var raw = result ? . GetValue < string > ( ) ? . Trim ( ) ;
142+ var rawSelector = result ? . GetValue < string > ( ) ? . Trim ( ) ;
117143
118- if ( ! string . IsNullOrWhiteSpace ( raw ) )
144+ var strategy = new FindXpathStrategy ( rawSelector ) ;
145+ if ( ! string . IsNullOrWhiteSpace ( rawSelector ) && strategy . Resolve ( WrappedBrowser . CurrentPage ) . IsPresent )
119146 {
120- var locator = new FindXpathStrategy ( raw ) ;
121- if ( locator != null )
122- {
123- LocatorCacheService . Update ( location , Value , locator . Value ) ;
124- return locator ;
125- }
126-
127- failedSelectors . Add ( raw ) ;
147+ LocatorCacheService . Update ( location , Value , strategy . Value ) ;
148+ return strategy ;
128149 }
129150
151+ failedSelectors . Add ( rawSelector ) ;
152+ Logger . LogInformation ( $ "[Attempt { attempt } ] Selector failed: { rawSelector } ") ;
130153 Thread . Sleep ( 300 ) ;
131154 }
132155
133156 throw new ArgumentException ( $ "❌ No valid locator found for: { Value } ") ;
134157 }
135158
136159 public override string ToString ( ) => $ "Prompt = { Value } ";
137- }
160+ }
0 commit comments