|
| 1 | +""" |
| 2 | +Text search utilities - find text and get pixel coordinates |
| 3 | +""" |
| 4 | + |
| 5 | +from .browser import SentienceBrowser |
| 6 | +from .models import TextRectSearchResult |
| 7 | + |
| 8 | + |
| 9 | +def find_text_rect( |
| 10 | + browser: SentienceBrowser, |
| 11 | + text: str, |
| 12 | + case_sensitive: bool = False, |
| 13 | + whole_word: bool = False, |
| 14 | + max_results: int = 10, |
| 15 | +) -> TextRectSearchResult: |
| 16 | + """ |
| 17 | + Find all occurrences of text on the page and get their exact pixel coordinates. |
| 18 | +
|
| 19 | + This function searches for text in all visible text nodes on the page and returns |
| 20 | + the bounding rectangles for each match. Useful for: |
| 21 | + - Finding specific UI elements by their text content |
| 22 | + - Locating buttons, links, or labels without element IDs |
| 23 | + - Getting exact coordinates for click automation |
| 24 | + - Highlighting search results visually |
| 25 | +
|
| 26 | + Args: |
| 27 | + browser: SentienceBrowser instance |
| 28 | + text: Text to search for (required) |
| 29 | + case_sensitive: If True, search is case-sensitive (default: False) |
| 30 | + whole_word: If True, only match whole words surrounded by whitespace (default: False) |
| 31 | + max_results: Maximum number of matches to return (default: 10, max: 100) |
| 32 | +
|
| 33 | + Returns: |
| 34 | + TextRectSearchResult with: |
| 35 | + - status: "success" or "error" |
| 36 | + - query: The search text |
| 37 | + - case_sensitive: Whether search was case-sensitive |
| 38 | + - whole_word: Whether whole-word matching was used |
| 39 | + - matches: Number of matches found |
| 40 | + - results: List of TextMatch objects, each containing: |
| 41 | + - text: The matched text |
| 42 | + - rect: Absolute rectangle (with scroll offset) |
| 43 | + - viewport_rect: Viewport-relative rectangle |
| 44 | + - context: Surrounding text (before/after) |
| 45 | + - in_viewport: Whether visible in current viewport |
| 46 | + - viewport: Current viewport dimensions and scroll position |
| 47 | + - error: Error message if status is "error" |
| 48 | +
|
| 49 | + Examples: |
| 50 | + # Find "Sign In" button |
| 51 | + result = find_text_rect(browser, "Sign In") |
| 52 | + if result.status == "success" and result.results: |
| 53 | + first_match = result.results[0] |
| 54 | + print(f"Found at: ({first_match.rect.x}, {first_match.rect.y})") |
| 55 | + print(f"Size: {first_match.rect.width}x{first_match.rect.height}") |
| 56 | + print(f"In viewport: {first_match.in_viewport}") |
| 57 | +
|
| 58 | + # Case-sensitive search |
| 59 | + result = find_text_rect(browser, "LOGIN", case_sensitive=True) |
| 60 | +
|
| 61 | + # Whole word only |
| 62 | + result = find_text_rect(browser, "log", whole_word=True) # Won't match "login" |
| 63 | +
|
| 64 | + # Find all matches and click the first visible one |
| 65 | + result = find_text_rect(browser, "Buy Now", max_results=5) |
| 66 | + if result.status == "success" and result.results: |
| 67 | + for match in result.results: |
| 68 | + if match.in_viewport: |
| 69 | + # Use click_rect from actions module |
| 70 | + from sentience import click_rect |
| 71 | + click_result = click_rect(browser, { |
| 72 | + "x": match.rect.x, |
| 73 | + "y": match.rect.y, |
| 74 | + "w": match.rect.width, |
| 75 | + "h": match.rect.height |
| 76 | + }) |
| 77 | + break |
| 78 | + """ |
| 79 | + if not browser.page: |
| 80 | + raise RuntimeError("Browser not started. Call browser.start() first.") |
| 81 | + |
| 82 | + if not text or not text.strip(): |
| 83 | + return TextRectSearchResult( |
| 84 | + status="error", |
| 85 | + error="Text parameter is required and cannot be empty", |
| 86 | + ) |
| 87 | + |
| 88 | + # Limit max_results to prevent performance issues |
| 89 | + max_results = min(max_results, 100) |
| 90 | + |
| 91 | + # Call the extension's findTextRect method |
| 92 | + result_dict = browser.page.evaluate( |
| 93 | + """ |
| 94 | + (options) => { |
| 95 | + return window.sentience.findTextRect(options); |
| 96 | + } |
| 97 | + """, |
| 98 | + { |
| 99 | + "text": text, |
| 100 | + "caseSensitive": case_sensitive, |
| 101 | + "wholeWord": whole_word, |
| 102 | + "maxResults": max_results, |
| 103 | + }, |
| 104 | + ) |
| 105 | + |
| 106 | + # Parse and validate with Pydantic |
| 107 | + return TextRectSearchResult(**result_dict) |
0 commit comments