11using System . IO ;
22using System . Net . Http ;
3+ using System . Xml . Linq ;
34
45namespace BotSharp . Plugin . WebDriver . Drivers . PlaywrightDriver ;
56
@@ -8,33 +9,66 @@ public partial class PlaywrightWebDriver
89 public async Task DoAction ( MessageInfo message , ElementActionArgs action , BrowserActionResult result )
910 {
1011 var page = _instance . GetPage ( message . ContextId ) ;
11- if ( string . IsNullOrEmpty ( result . Selector ) )
12+ if ( string . IsNullOrEmpty ( result . Selector ) && action . Position == null )
1213 {
1314 Serilog . Log . Error ( $ "Selector is not set.") ;
1415 return ;
1516 }
1617
17- ILocator locator = page . Locator ( result . Selector ) ;
18- var count = await locator . CountAsync ( ) ;
19-
20- if ( count == 0 )
21- {
22- Serilog . Log . Error ( $ "Element not found: { result . Selector } ") ;
23- return ;
24- }
25- else if ( count > 1 )
18+ ILocator ? locator ;
19+
20+ if ( result . Selector != null )
2621 {
27- if ( ! action . FirstIfMultipleFound )
22+ locator = page . Locator ( result . Selector ) ;
23+
24+ var count = await locator . CountAsync ( ) ;
25+
26+ if ( count == 0 )
2827 {
29- Serilog . Log . Error ( $ "Multiple eElements were found: { result . Selector } ") ;
28+ Serilog . Log . Error ( $ "Element not found: { result . Selector } ") ;
3029 return ;
3130 }
32- else
31+ else if ( count > 1 )
3332 {
34- locator = page . Locator ( result . Selector ) . First ; // 匹配到多个时取第一个,否则当await locator.ClickAsync();匹配到多个就会抛异常。
33+ if ( ! action . FirstIfMultipleFound )
34+ {
35+ Serilog . Log . Error ( $ "Multiple eElements were found: { result . Selector } ") ;
36+ return ;
37+ }
38+ else
39+ {
40+ locator = page . Locator ( result . Selector ) . First ; // 匹配到多个时取第一个,否则当await locator.ClickAsync();匹配到多个就会抛异常。
41+ }
3542 }
43+
44+ await ExecuteAction ( message , page , locator , action ) ;
3645 }
46+ else if ( action . Position != null && action . Position . X != 0 && action . Position . Y != 0 )
47+ {
48+ if ( action . Position != null && action . Position . X != 0 && action . Position . Y != 0 )
49+ {
50+ var elementHandle = await page . EvaluateHandleAsync (
51+ @"(coords) => document.elementFromPoint(coords.x, coords.y)" ,
52+ new { x = ( int ) action . Position . X , y = ( int ) action . Position . Y }
53+ ) ;
3754
55+ await ExecuteAction ( message , page , elementHandle . AsElement ( ) , action ) ;
56+ }
57+ }
58+ else
59+ {
60+ Serilog . Log . Error ( $ "Selector or position is not set.") ;
61+ return ;
62+ }
63+
64+ if ( action . WaitTime > 0 )
65+ {
66+ await Task . Delay ( 1000 * action . WaitTime ) ;
67+ }
68+ }
69+
70+ private async Task ExecuteAction ( MessageInfo message , IPage page , ILocator locator , ElementActionArgs action )
71+ {
3872 if ( action . Action == BroswerActionEnum . Click )
3973 {
4074 if ( action . Position == null )
@@ -201,12 +235,174 @@ await locator.ClickAsync(new LocatorClickOptions
201235 }
202236 }
203237 }
238+ }
204239
205- if ( action . WaitTime > 0 )
240+ private async Task ExecuteAction ( MessageInfo message , IPage page , IElementHandle elementHandle , ElementActionArgs action )
241+ {
242+ var body = page . Locator ( "body" ) ;
243+
244+ if ( action . Action == BroswerActionEnum . Click )
206245 {
246+ await body . ClickAsync ( new LocatorClickOptions
247+ {
248+ Position = new Position
249+ {
250+ X = action . Position . X ,
251+ Y = action . Position . Y
252+ }
253+ } ) ;
254+ }
255+ else if ( action . Action == BroswerActionEnum . DropDown )
256+ {
257+ var tagName = await body . EvaluateAsync < string > ( "el => el.tagName.toLowerCase()" ) ;
258+ if ( tagName == "select" )
259+ {
260+ await HandleSelectDropDownAsync ( page , body , action ) ;
261+ }
262+ else
263+ {
264+ await body . ClickAsync ( ) ;
265+ if ( ! string . IsNullOrWhiteSpace ( action . PressKey ) )
266+ {
267+ await page . Keyboard . PressAsync ( action . PressKey ) ;
268+ await page . Keyboard . PressAsync ( "Enter" ) ;
269+ }
270+ else
271+ {
272+ var optionLocator = page . Locator ( $ "//div[text()='{ action . Content } ']") ;
273+ var optionCount = await optionLocator . CountAsync ( ) ;
274+ if ( optionCount == 0 )
275+ {
276+ Serilog . Log . Error ( $ "Dropdown option not found: { action . Content } ") ;
277+ return ;
278+ }
279+ await optionLocator . First . ClickAsync ( ) ;
280+ }
281+ }
282+ }
283+ else if ( action . Action == BroswerActionEnum . InputText )
284+ {
285+ await elementHandle . FillAsync ( action . Content ) ;
286+
287+ if ( action . PressKey != null )
288+ {
289+ if ( action . DelayBeforePressingKey > 0 )
290+ {
291+ await Task . Delay ( action . DelayBeforePressingKey ) ;
292+ }
293+ await body . PressAsync ( action . PressKey ) ;
294+ }
295+ }
296+ else if ( action . Action == BroswerActionEnum . FileUpload )
297+ {
298+ var _states = _services . GetRequiredService < IConversationStateService > ( ) ;
299+ var files = new List < string > ( ) ;
300+ if ( action . FileUrl != null && action . FileUrl . Length > 0 )
301+ {
302+ files . AddRange ( action . FileUrl ) ;
303+ }
304+ var hooks = _services . GetServices < IWebDriverHook > ( ) ;
305+ foreach ( var hook in hooks )
306+ {
307+ files . AddRange ( await hook . GetUploadFiles ( message ) ) ;
308+ }
309+ if ( files . Count == 0 )
310+ {
311+ Serilog . Log . Warning ( $ "No files found to upload: { action . Content } ") ;
312+ return ;
313+ }
314+ var fileChooser = await page . RunAndWaitForFileChooserAsync ( async ( ) =>
315+ {
316+ await body . ClickAsync ( ) ;
317+ } ) ;
318+ var guid = Guid . NewGuid ( ) . ToString ( ) ;
319+ var directory = Path . Combine ( Path . GetTempPath ( ) , guid ) ;
320+ DeleteDirectory ( directory ) ;
321+ Directory . CreateDirectory ( directory ) ;
322+ var localPaths = new List < string > ( ) ;
323+ var http = _services . GetRequiredService < IHttpClientFactory > ( ) ;
324+ using var httpClient = http . CreateClient ( ) ;
325+ foreach ( var fileUrl in files )
326+ {
327+ try
328+ {
329+ using var fileData = await httpClient . GetAsync ( fileUrl ) ;
330+ var fileName = new Uri ( fileUrl ) . AbsolutePath ;
331+ var localPath = Path . Combine ( directory , Path . GetFileName ( fileName ) ) ;
332+ await using var fs = new FileStream ( localPath , FileMode . Create , FileAccess . Write , FileShare . None ) ;
333+ await fileData . Content . CopyToAsync ( fs ) ;
334+ localPaths . Add ( localPath ) ;
335+ }
336+ catch ( Exception ex )
337+ {
338+ Serilog . Log . Error ( $ "FileUpload failed for { fileUrl } . Message: { ex . Message } ") ;
339+ }
340+ }
341+ await fileChooser . SetFilesAsync ( localPaths ) ;
207342 await Task . Delay ( 1000 * action . WaitTime ) ;
208343 }
344+ else if ( action . Action == BroswerActionEnum . Typing )
345+ {
346+ await body . PressSequentiallyAsync ( action . Content ) ;
347+ if ( action . PressKey != null )
348+ {
349+ if ( action . DelayBeforePressingKey > 0 )
350+ {
351+ await Task . Delay ( action . DelayBeforePressingKey ) ;
352+ }
353+ await body . PressAsync ( action . PressKey ) ;
354+ }
355+ }
356+ else if ( action . Action == BroswerActionEnum . Hover )
357+ {
358+ await body . HoverAsync ( ) ;
359+ }
360+ else if ( action . Action == BroswerActionEnum . DragAndDrop )
361+ {
362+ // Locate the element to drag
363+ var box = await body . BoundingBoxAsync ( ) ;
364+
365+ if ( box != null )
366+ {
367+ // Calculate start position
368+ float startX = box . X + box . Width / 2 ; // Start at the center of the element
369+ float startY = box . Y + box . Height / 2 ;
370+
371+ // Drag offsets
372+ float offsetX = action . Position . X ;
373+ // Move horizontally
374+ if ( action . Position . Y == 0 )
375+ {
376+ // Perform drag-and-move
377+ // Move mouse to the start position
378+ var mouse = page . Mouse ;
379+ await mouse . MoveAsync ( startX , startY ) ;
380+ await mouse . DownAsync ( ) ;
381+
382+ // Move mouse smoothly in increments
383+ var tracks = GetVelocityTrack ( offsetX ) ;
384+ foreach ( var track in tracks )
385+ {
386+ startX += track ;
387+ await page . Mouse . MoveAsync ( startX , 0 , new MouseMoveOptions
388+ {
389+ Steps = 3
390+ } ) ;
391+ }
392+
393+ // Release mouse button
394+ await Task . Delay ( 1000 ) ;
395+ await mouse . UpAsync ( ) ;
396+ }
397+ else
398+ {
399+ throw new NotImplementedException ( ) ;
400+ }
401+ }
402+ }
209403 }
404+
405+
210406 private void DeleteDirectory ( string directory )
211407 {
212408 if ( Directory . Exists ( directory ) )
0 commit comments