code-puppy 0.0.177__py3-none-any.whl → 0.0.179__py3-none-any.whl

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.
@@ -1,642 +0,0 @@
1
- """Browser element discovery tools using semantic locators and XPath."""
2
-
3
- from typing import Any, Dict, Optional
4
-
5
- from pydantic_ai import RunContext
6
-
7
- from code_puppy.messaging import emit_info
8
- from code_puppy.tools.common import generate_group_id
9
-
10
- from .camoufox_manager import get_camoufox_manager
11
-
12
-
13
- async def find_by_role(
14
- role: str,
15
- name: Optional[str] = None,
16
- exact: bool = False,
17
- timeout: int = 10000,
18
- ) -> Dict[str, Any]:
19
- """Find elements by ARIA role."""
20
- group_id = generate_group_id("browser_find_by_role", f"{role}_{name or 'any'}")
21
- emit_info(
22
- f"[bold white on blue] BROWSER FIND BY ROLE [/bold white on blue] 🎨 role={role} name={name}",
23
- message_group=group_id,
24
- )
25
- try:
26
- browser_manager = get_camoufox_manager()
27
- page = await browser_manager.get_current_page()
28
-
29
- if not page:
30
- return {"success": False, "error": "No active browser page available"}
31
-
32
- # Build locator
33
- locator = page.get_by_role(role, name=name, exact=exact)
34
-
35
- # Wait for at least one element
36
- await locator.first.wait_for(state="visible", timeout=timeout)
37
-
38
- # Count elements
39
- count = await locator.count()
40
-
41
- # Get element info
42
- elements = []
43
- for i in range(min(count, 10)): # Limit to first 10 elements
44
- element = locator.nth(i)
45
- if await element.is_visible():
46
- text = await element.text_content()
47
- elements.append({"index": i, "text": text, "visible": True})
48
-
49
- emit_info(
50
- f"[green]Found {count} elements with role '{role}'[/green]",
51
- message_group=group_id,
52
- )
53
-
54
- return {
55
- "success": True,
56
- "role": role,
57
- "name": name,
58
- "count": count,
59
- "elements": elements,
60
- }
61
-
62
- except Exception as e:
63
- return {"success": False, "error": str(e), "role": role, "name": name}
64
-
65
-
66
- async def find_by_text(
67
- text: str,
68
- exact: bool = False,
69
- timeout: int = 10000,
70
- ) -> Dict[str, Any]:
71
- """Find elements containing specific text."""
72
- group_id = generate_group_id("browser_find_by_text", text[:50])
73
- emit_info(
74
- f"[bold white on blue] BROWSER FIND BY TEXT [/bold white on blue] 🔍 text='{text}' exact={exact}",
75
- message_group=group_id,
76
- )
77
- try:
78
- browser_manager = get_camoufox_manager()
79
- page = await browser_manager.get_current_page()
80
-
81
- if not page:
82
- return {"success": False, "error": "No active browser page available"}
83
-
84
- locator = page.get_by_text(text, exact=exact)
85
-
86
- # Wait for at least one element
87
- await locator.first.wait_for(state="visible", timeout=timeout)
88
-
89
- count = await locator.count()
90
-
91
- elements = []
92
- for i in range(min(count, 10)):
93
- element = locator.nth(i)
94
- if await element.is_visible():
95
- tag_name = await element.evaluate("el => el.tagName.toLowerCase()")
96
- full_text = await element.text_content()
97
- elements.append(
98
- {"index": i, "tag": tag_name, "text": full_text, "visible": True}
99
- )
100
-
101
- emit_info(
102
- f"[green]Found {count} elements containing text '{text}'[/green]",
103
- message_group=group_id,
104
- )
105
-
106
- return {
107
- "success": True,
108
- "search_text": text,
109
- "exact": exact,
110
- "count": count,
111
- "elements": elements,
112
- }
113
-
114
- except Exception as e:
115
- return {"success": False, "error": str(e), "search_text": text}
116
-
117
-
118
- async def find_by_label(
119
- text: str,
120
- exact: bool = False,
121
- timeout: int = 10000,
122
- ) -> Dict[str, Any]:
123
- """Find form elements by their associated label text."""
124
- group_id = generate_group_id("browser_find_by_label", text[:50])
125
- emit_info(
126
- f"[bold white on blue] BROWSER FIND BY LABEL [/bold white on blue] 🏷️ label='{text}' exact={exact}",
127
- message_group=group_id,
128
- )
129
- try:
130
- browser_manager = get_camoufox_manager()
131
- page = await browser_manager.get_current_page()
132
-
133
- if not page:
134
- return {"success": False, "error": "No active browser page available"}
135
-
136
- locator = page.get_by_label(text, exact=exact)
137
-
138
- await locator.first.wait_for(state="visible", timeout=timeout)
139
-
140
- count = await locator.count()
141
-
142
- elements = []
143
- for i in range(min(count, 10)):
144
- element = locator.nth(i)
145
- if await element.is_visible():
146
- tag_name = await element.evaluate("el => el.tagName.toLowerCase()")
147
- input_type = await element.get_attribute("type")
148
- value = (
149
- await element.input_value()
150
- if tag_name in ["input", "textarea"]
151
- else None
152
- )
153
-
154
- elements.append(
155
- {
156
- "index": i,
157
- "tag": tag_name,
158
- "type": input_type,
159
- "value": value,
160
- "visible": True,
161
- }
162
- )
163
-
164
- emit_info(
165
- f"[green]Found {count} elements with label '{text}'[/green]",
166
- message_group=group_id,
167
- )
168
-
169
- return {
170
- "success": True,
171
- "label_text": text,
172
- "exact": exact,
173
- "count": count,
174
- "elements": elements,
175
- }
176
-
177
- except Exception as e:
178
- return {"success": False, "error": str(e), "label_text": text}
179
-
180
-
181
- async def find_by_placeholder(
182
- text: str,
183
- exact: bool = False,
184
- timeout: int = 10000,
185
- ) -> Dict[str, Any]:
186
- """Find elements by placeholder text."""
187
- group_id = generate_group_id("browser_find_by_placeholder", text[:50])
188
- emit_info(
189
- f"[bold white on blue] BROWSER FIND BY PLACEHOLDER [/bold white on blue] 📝 placeholder='{text}' exact={exact}",
190
- message_group=group_id,
191
- )
192
- try:
193
- browser_manager = get_camoufox_manager()
194
- page = await browser_manager.get_current_page()
195
-
196
- if not page:
197
- return {"success": False, "error": "No active browser page available"}
198
-
199
- locator = page.get_by_placeholder(text, exact=exact)
200
-
201
- await locator.first.wait_for(state="visible", timeout=timeout)
202
-
203
- count = await locator.count()
204
-
205
- elements = []
206
- for i in range(min(count, 10)):
207
- element = locator.nth(i)
208
- if await element.is_visible():
209
- tag_name = await element.evaluate("el => el.tagName.toLowerCase()")
210
- placeholder = await element.get_attribute("placeholder")
211
- value = await element.input_value()
212
-
213
- elements.append(
214
- {
215
- "index": i,
216
- "tag": tag_name,
217
- "placeholder": placeholder,
218
- "value": value,
219
- "visible": True,
220
- }
221
- )
222
-
223
- emit_info(
224
- f"[green]Found {count} elements with placeholder '{text}'[/green]",
225
- message_group=group_id,
226
- )
227
-
228
- return {
229
- "success": True,
230
- "placeholder_text": text,
231
- "exact": exact,
232
- "count": count,
233
- "elements": elements,
234
- }
235
-
236
- except Exception as e:
237
- return {"success": False, "error": str(e), "placeholder_text": text}
238
-
239
-
240
- async def find_by_test_id(
241
- test_id: str,
242
- timeout: int = 10000,
243
- ) -> Dict[str, Any]:
244
- """Find elements by test ID attribute."""
245
- group_id = generate_group_id("browser_find_by_test_id", test_id)
246
- emit_info(
247
- f"[bold white on blue] BROWSER FIND BY TEST ID [/bold white on blue] 🧪 test_id='{test_id}'",
248
- message_group=group_id,
249
- )
250
- try:
251
- browser_manager = get_camoufox_manager()
252
- page = await browser_manager.get_current_page()
253
-
254
- if not page:
255
- return {"success": False, "error": "No active browser page available"}
256
-
257
- locator = page.get_by_test_id(test_id)
258
-
259
- await locator.first.wait_for(state="visible", timeout=timeout)
260
-
261
- count = await locator.count()
262
-
263
- elements = []
264
- for i in range(min(count, 10)):
265
- element = locator.nth(i)
266
- if await element.is_visible():
267
- tag_name = await element.evaluate("el => el.tagName.toLowerCase()")
268
- text = await element.text_content()
269
-
270
- elements.append(
271
- {
272
- "index": i,
273
- "tag": tag_name,
274
- "text": text,
275
- "test_id": test_id,
276
- "visible": True,
277
- }
278
- )
279
-
280
- emit_info(
281
- f"[green]Found {count} elements with test-id '{test_id}'[/green]",
282
- message_group=group_id,
283
- )
284
-
285
- return {
286
- "success": True,
287
- "test_id": test_id,
288
- "count": count,
289
- "elements": elements,
290
- }
291
-
292
- except Exception as e:
293
- return {"success": False, "error": str(e), "test_id": test_id}
294
-
295
-
296
- async def run_xpath_query(
297
- xpath: str,
298
- timeout: int = 10000,
299
- ) -> Dict[str, Any]:
300
- """Find elements using XPath selector."""
301
- group_id = generate_group_id("browser_xpath_query", xpath[:100])
302
- emit_info(
303
- f"[bold white on blue] BROWSER XPATH QUERY [/bold white on blue] 🔍 xpath='{xpath}'",
304
- message_group=group_id,
305
- )
306
- try:
307
- browser_manager = get_camoufox_manager()
308
- page = await browser_manager.get_current_page()
309
-
310
- if not page:
311
- return {"success": False, "error": "No active browser page available"}
312
-
313
- # Use page.locator with xpath
314
- locator = page.locator(f"xpath={xpath}")
315
-
316
- # Wait for at least one element
317
- await locator.first.wait_for(state="visible", timeout=timeout)
318
-
319
- count = await locator.count()
320
-
321
- elements = []
322
- for i in range(min(count, 10)):
323
- element = locator.nth(i)
324
- if await element.is_visible():
325
- tag_name = await element.evaluate("el => el.tagName.toLowerCase()")
326
- text = await element.text_content()
327
- class_name = await element.get_attribute("class")
328
- element_id = await element.get_attribute("id")
329
-
330
- elements.append(
331
- {
332
- "index": i,
333
- "tag": tag_name,
334
- "text": text[:100] if text else None, # Truncate long text
335
- "class": class_name,
336
- "id": element_id,
337
- "visible": True,
338
- }
339
- )
340
-
341
- emit_info(
342
- f"[green]Found {count} elements with XPath '{xpath}'[/green]",
343
- message_group=group_id,
344
- )
345
-
346
- return {"success": True, "xpath": xpath, "count": count, "elements": elements}
347
-
348
- except Exception as e:
349
- return {"success": False, "error": str(e), "xpath": xpath}
350
-
351
-
352
- async def find_buttons(
353
- text_filter: Optional[str] = None, timeout: int = 10000
354
- ) -> Dict[str, Any]:
355
- """Find all button elements on the page."""
356
- group_id = generate_group_id("browser_find_buttons", text_filter or "all")
357
- emit_info(
358
- f"[bold white on blue] BROWSER FIND BUTTONS [/bold white on blue] 🔘 filter='{text_filter or 'none'}'",
359
- message_group=group_id,
360
- )
361
- try:
362
- browser_manager = get_camoufox_manager()
363
- page = await browser_manager.get_current_page()
364
-
365
- if not page:
366
- return {"success": False, "error": "No active browser page available"}
367
-
368
- # Find buttons by role
369
- locator = page.get_by_role("button")
370
-
371
- count = await locator.count()
372
-
373
- buttons = []
374
- for i in range(min(count, 20)): # Limit to 20 buttons
375
- button = locator.nth(i)
376
- if await button.is_visible():
377
- text = await button.text_content()
378
- if text_filter and text_filter.lower() not in text.lower():
379
- continue
380
-
381
- buttons.append({"index": i, "text": text, "visible": True})
382
-
383
- filtered_count = len(buttons)
384
-
385
- emit_info(
386
- f"[green]Found {filtered_count} buttons"
387
- + (f" containing '{text_filter}'" if text_filter else "")
388
- + "[/green]",
389
- message_group=group_id,
390
- )
391
-
392
- return {
393
- "success": True,
394
- "text_filter": text_filter,
395
- "total_count": count,
396
- "filtered_count": filtered_count,
397
- "buttons": buttons,
398
- }
399
-
400
- except Exception as e:
401
- return {"success": False, "error": str(e), "text_filter": text_filter}
402
-
403
-
404
- async def find_links(
405
- text_filter: Optional[str] = None, timeout: int = 10000
406
- ) -> Dict[str, Any]:
407
- """Find all link elements on the page."""
408
- group_id = generate_group_id("browser_find_links", text_filter or "all")
409
- emit_info(
410
- f"[bold white on blue] BROWSER FIND LINKS [/bold white on blue] 🔗 filter='{text_filter or 'none'}'",
411
- message_group=group_id,
412
- )
413
- try:
414
- browser_manager = get_camoufox_manager()
415
- page = await browser_manager.get_current_page()
416
-
417
- if not page:
418
- return {"success": False, "error": "No active browser page available"}
419
-
420
- # Find links by role
421
- locator = page.get_by_role("link")
422
-
423
- count = await locator.count()
424
-
425
- links = []
426
- for i in range(min(count, 20)): # Limit to 20 links
427
- link = locator.nth(i)
428
- if await link.is_visible():
429
- text = await link.text_content()
430
- href = await link.get_attribute("href")
431
-
432
- if text_filter and text_filter.lower() not in text.lower():
433
- continue
434
-
435
- links.append({"index": i, "text": text, "href": href, "visible": True})
436
-
437
- filtered_count = len(links)
438
-
439
- emit_info(
440
- f"[green]Found {filtered_count} links"
441
- + (f" containing '{text_filter}'" if text_filter else "")
442
- + "[/green]",
443
- message_group=group_id,
444
- )
445
-
446
- return {
447
- "success": True,
448
- "text_filter": text_filter,
449
- "total_count": count,
450
- "filtered_count": filtered_count,
451
- "links": links,
452
- }
453
-
454
- except Exception as e:
455
- return {"success": False, "error": str(e), "text_filter": text_filter}
456
-
457
-
458
- # Tool registration functions
459
- def register_find_by_role(agent):
460
- """Register the find by role tool."""
461
-
462
- @agent.tool
463
- async def browser_find_by_role(
464
- context: RunContext,
465
- role: str,
466
- name: Optional[str] = None,
467
- exact: bool = False,
468
- timeout: int = 10000,
469
- ) -> Dict[str, Any]:
470
- """
471
- Find elements by ARIA role (recommended for accessibility).
472
-
473
- Args:
474
- role: ARIA role (button, link, textbox, heading, etc.)
475
- name: Optional accessible name to filter by
476
- exact: Whether to match name exactly
477
- timeout: Timeout in milliseconds
478
-
479
- Returns:
480
- Dict with found elements and their properties
481
- """
482
- return await find_by_role(role, name, exact, timeout)
483
-
484
-
485
- def register_find_by_text(agent):
486
- """Register the find by text tool."""
487
-
488
- @agent.tool
489
- async def browser_find_by_text(
490
- context: RunContext,
491
- text: str,
492
- exact: bool = False,
493
- timeout: int = 10000,
494
- ) -> Dict[str, Any]:
495
- """
496
- Find elements containing specific text content.
497
-
498
- Args:
499
- text: Text to search for
500
- exact: Whether to match text exactly
501
- timeout: Timeout in milliseconds
502
-
503
- Returns:
504
- Dict with found elements and their properties
505
- """
506
- return await find_by_text(text, exact, timeout)
507
-
508
-
509
- def register_find_by_label(agent):
510
- """Register the find by label tool."""
511
-
512
- @agent.tool
513
- async def browser_find_by_label(
514
- context: RunContext,
515
- text: str,
516
- exact: bool = False,
517
- timeout: int = 10000,
518
- ) -> Dict[str, Any]:
519
- """
520
- Find form elements by their associated label text.
521
-
522
- Args:
523
- text: Label text to search for
524
- exact: Whether to match label exactly
525
- timeout: Timeout in milliseconds
526
-
527
- Returns:
528
- Dict with found form elements and their properties
529
- """
530
- return await find_by_label(text, exact, timeout)
531
-
532
-
533
- def register_find_by_placeholder(agent):
534
- """Register the find by placeholder tool."""
535
-
536
- @agent.tool
537
- async def browser_find_by_placeholder(
538
- context: RunContext,
539
- text: str,
540
- exact: bool = False,
541
- timeout: int = 10000,
542
- ) -> Dict[str, Any]:
543
- """
544
- Find elements by placeholder text.
545
-
546
- Args:
547
- text: Placeholder text to search for
548
- exact: Whether to match placeholder exactly
549
- timeout: Timeout in milliseconds
550
-
551
- Returns:
552
- Dict with found elements and their properties
553
- """
554
- return await find_by_placeholder(text, exact, timeout)
555
-
556
-
557
- def register_find_by_test_id(agent):
558
- """Register the find by test ID tool."""
559
-
560
- @agent.tool
561
- async def browser_find_by_test_id(
562
- context: RunContext,
563
- test_id: str,
564
- timeout: int = 10000,
565
- ) -> Dict[str, Any]:
566
- """
567
- Find elements by test ID attribute (data-testid).
568
-
569
- Args:
570
- test_id: Test ID to search for
571
- timeout: Timeout in milliseconds
572
-
573
- Returns:
574
- Dict with found elements and their properties
575
- """
576
- return await find_by_test_id(test_id, timeout)
577
-
578
-
579
- def register_run_xpath_query(agent):
580
- """Register the XPath query tool."""
581
-
582
- @agent.tool
583
- async def browser_xpath_query(
584
- context: RunContext,
585
- xpath: str,
586
- timeout: int = 10000,
587
- ) -> Dict[str, Any]:
588
- """
589
- Find elements using XPath selector (fallback when semantic locators fail).
590
-
591
- Args:
592
- xpath: XPath expression
593
- timeout: Timeout in milliseconds
594
-
595
- Returns:
596
- Dict with found elements and their properties
597
- """
598
- return await run_xpath_query(xpath, timeout)
599
-
600
-
601
- def register_find_buttons(agent):
602
- """Register the find buttons tool."""
603
-
604
- @agent.tool
605
- async def browser_find_buttons(
606
- context: RunContext,
607
- text_filter: Optional[str] = None,
608
- timeout: int = 10000,
609
- ) -> Dict[str, Any]:
610
- """
611
- Find all button elements on the page.
612
-
613
- Args:
614
- text_filter: Optional text to filter buttons by
615
- timeout: Timeout in milliseconds
616
-
617
- Returns:
618
- Dict with found buttons and their properties
619
- """
620
- return await find_buttons(text_filter, timeout)
621
-
622
-
623
- def register_find_links(agent):
624
- """Register the find links tool."""
625
-
626
- @agent.tool
627
- async def browser_find_links(
628
- context: RunContext,
629
- text_filter: Optional[str] = None,
630
- timeout: int = 10000,
631
- ) -> Dict[str, Any]:
632
- """
633
- Find all link elements on the page.
634
-
635
- Args:
636
- text_filter: Optional text to filter links by
637
- timeout: Timeout in milliseconds
638
-
639
- Returns:
640
- Dict with found links and their properties
641
- """
642
- return await find_links(text_filter, timeout)