1
1
using System . IO ;
2
2
using System . Net . Http ;
3
+ using System . Xml . Linq ;
3
4
4
5
namespace BotSharp . Plugin . WebDriver . Drivers . PlaywrightDriver ;
5
6
@@ -8,33 +9,66 @@ public partial class PlaywrightWebDriver
8
9
public async Task DoAction ( MessageInfo message , ElementActionArgs action , BrowserActionResult result )
9
10
{
10
11
var page = _instance . GetPage ( message . ContextId ) ;
11
- if ( string . IsNullOrEmpty ( result . Selector ) )
12
+ if ( string . IsNullOrEmpty ( result . Selector ) && action . Position == null )
12
13
{
13
14
Serilog . Log . Error ( $ "Selector is not set.") ;
14
15
return ;
15
16
}
16
17
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 )
26
21
{
27
- if ( ! action . FirstIfMultipleFound )
22
+ locator = page . Locator ( result . Selector ) ;
23
+
24
+ var count = await locator . CountAsync ( ) ;
25
+
26
+ if ( count == 0 )
28
27
{
29
- Serilog . Log . Error ( $ "Multiple eElements were found: { result . Selector } ") ;
28
+ Serilog . Log . Error ( $ "Element not found: { result . Selector } ") ;
30
29
return ;
31
30
}
32
- else
31
+ else if ( count > 1 )
33
32
{
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
+ }
35
42
}
43
+
44
+ await ExecuteAction ( message , page , locator , action ) ;
36
45
}
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
+ ) ;
37
54
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
+ {
38
72
if ( action . Action == BroswerActionEnum . Click )
39
73
{
40
74
if ( action . Position == null )
@@ -201,12 +235,174 @@ await locator.ClickAsync(new LocatorClickOptions
201
235
}
202
236
}
203
237
}
238
+ }
204
239
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 )
206
245
{
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 ) ;
207
342
await Task . Delay ( 1000 * action . WaitTime ) ;
208
343
}
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
+ }
209
403
}
404
+
405
+
210
406
private void DeleteDirectory ( string directory )
211
407
{
212
408
if ( Directory . Exists ( directory ) )
0 commit comments