Skip to content

Commit 35447aa

Browse files
committed
flexible wait
1 parent 24bf8f1 commit 35447aa

File tree

3 files changed

+213
-5
lines changed

3 files changed

+213
-5
lines changed

examples/click_rect_demo.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Example: Using click_rect for coordinate-based clicking with visual feedback
3+
"""
4+
5+
from sentience import SentienceBrowser, snapshot, find, click_rect
6+
import os
7+
8+
9+
def main():
10+
# Get API key from environment variable (optional - uses free tier if not set)
11+
api_key = os.environ.get("SENTIENCE_API_KEY")
12+
13+
with SentienceBrowser(api_key=api_key, headless=False) as browser:
14+
# Navigate to example.com
15+
browser.page.goto("https://example.com", wait_until="domcontentloaded")
16+
17+
print("=== click_rect Demo ===\n")
18+
19+
# Example 1: Click using rect dictionary
20+
print("1. Clicking at specific coordinates (100, 100) with size 50x30")
21+
print(" (You should see a red border highlight for 2 seconds)")
22+
result = click_rect(browser, {"x": 100, "y": 100, "w": 50, "h": 30})
23+
print(f" Result: success={result.success}, outcome={result.outcome}")
24+
print(f" Duration: {result.duration_ms}ms\n")
25+
26+
# Wait a bit
27+
browser.page.wait_for_timeout(1000)
28+
29+
# Example 2: Click using element's bbox
30+
print("2. Clicking using element's bounding box")
31+
snap = snapshot(browser)
32+
link = find(snap, "role=link")
33+
34+
if link:
35+
print(f" Found link: '{link.text}' at ({link.bbox.x}, {link.bbox.y})")
36+
print(" Clicking at center of element's bbox...")
37+
result = click_rect(browser, {
38+
"x": link.bbox.x,
39+
"y": link.bbox.y,
40+
"w": link.bbox.width,
41+
"h": link.bbox.height
42+
})
43+
print(f" Result: success={result.success}, outcome={result.outcome}")
44+
print(f" URL changed: {result.url_changed}\n")
45+
46+
# Navigate back if needed
47+
if result.url_changed:
48+
browser.page.goto("https://example.com", wait_until="domcontentloaded")
49+
browser.page.wait_for_load_state("networkidle")
50+
51+
# Example 3: Click without highlight (for headless/CI)
52+
print("3. Clicking without visual highlight")
53+
result = click_rect(browser, {"x": 200, "y": 200, "w": 40, "h": 20}, highlight=False)
54+
print(f" Result: success={result.success}\n")
55+
56+
# Example 4: Custom highlight duration
57+
print("4. Clicking with custom highlight duration (3 seconds)")
58+
result = click_rect(browser, {"x": 300, "y": 300, "w": 60, "h": 40}, highlight_duration=3.0)
59+
print(f" Result: success={result.success}")
60+
print(" (Red border should stay visible for 3 seconds)\n")
61+
62+
# Example 5: Click with snapshot capture
63+
print("5. Clicking and capturing snapshot after action")
64+
result = click_rect(
65+
browser,
66+
{"x": 150, "y": 150, "w": 50, "h": 30},
67+
take_snapshot=True
68+
)
69+
if result.snapshot_after:
70+
print(f" Snapshot captured: {len(result.snapshot_after.elements)} elements found")
71+
print(f" URL: {result.snapshot_after.url}\n")
72+
73+
print("✅ click_rect demo complete!")
74+
print("\nNote: click_rect uses Playwright's native mouse.click() for realistic")
75+
print("event simulation, triggering hover, focus, mousedown, mouseup sequences.")
76+
77+
78+
if __name__ == "__main__":
79+
main()
80+

examples/semantic_wait_demo.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Example: Semantic wait_for using query DSL
3+
Demonstrates waiting for elements using semantic selectors
4+
"""
5+
6+
from sentience import SentienceBrowser, wait_for, click
7+
import os
8+
9+
10+
def main():
11+
# Get API key from environment variable (optional - uses free tier if not set)
12+
api_key = os.environ.get("SENTIENCE_API_KEY")
13+
14+
with SentienceBrowser(api_key=api_key, headless=False) as browser:
15+
# Navigate to example.com
16+
browser.page.goto("https://example.com", wait_until="domcontentloaded")
17+
18+
print("=== Semantic wait_for Demo ===\n")
19+
20+
# Example 1: Wait for element by role
21+
print("1. Waiting for link element (role=link)")
22+
wait_result = wait_for(browser, "role=link", timeout=5.0)
23+
if wait_result.found:
24+
print(f" ✅ Found after {wait_result.duration_ms}ms")
25+
print(f" Element: '{wait_result.element.text}' (id: {wait_result.element.id})")
26+
else:
27+
print(f" ❌ Not found (timeout: {wait_result.timeout})")
28+
print()
29+
30+
# Example 2: Wait for element by role and text
31+
print("2. Waiting for link with specific text")
32+
wait_result = wait_for(browser, "role=link text~'Example'", timeout=5.0)
33+
if wait_result.found:
34+
print(f" ✅ Found after {wait_result.duration_ms}ms")
35+
print(f" Element text: '{wait_result.element.text}'")
36+
else:
37+
print(" ❌ Not found")
38+
print()
39+
40+
# Example 3: Wait for clickable element
41+
print("3. Waiting for clickable element")
42+
wait_result = wait_for(browser, "clickable=true", timeout=5.0)
43+
if wait_result.found:
44+
print(f" ✅ Found clickable element after {wait_result.duration_ms}ms")
45+
print(f" Role: {wait_result.element.role}")
46+
print(f" Text: '{wait_result.element.text}'")
47+
print(f" Is clickable: {wait_result.element.visual_cues.is_clickable}")
48+
else:
49+
print(" ❌ Not found")
50+
print()
51+
52+
# Example 4: Wait for element with importance threshold
53+
print("4. Waiting for important element (importance > 100)")
54+
wait_result = wait_for(browser, "importance>100", timeout=5.0)
55+
if wait_result.found:
56+
print(f" ✅ Found important element after {wait_result.duration_ms}ms")
57+
print(f" Importance: {wait_result.element.importance}")
58+
print(f" Role: {wait_result.element.role}")
59+
else:
60+
print(" ❌ Not found")
61+
print()
62+
63+
# Example 5: Wait and then click
64+
print("5. Wait for element, then click it")
65+
wait_result = wait_for(browser, "role=link", timeout=5.0)
66+
if wait_result.found:
67+
print(" ✅ Found element, clicking...")
68+
click_result = click(browser, wait_result.element.id)
69+
print(f" Click result: success={click_result.success}, outcome={click_result.outcome}")
70+
if click_result.url_changed:
71+
print(f" ✅ Navigation occurred: {browser.page.url}")
72+
else:
73+
print(" ❌ Element not found, cannot click")
74+
print()
75+
76+
# Example 6: Using local extension (fast polling)
77+
print("6. Using local extension with auto-optimized interval")
78+
print(" When use_api=False, interval auto-adjusts to 0.25s (fast)")
79+
wait_result = wait_for(browser, "role=link", timeout=5.0, use_api=False)
80+
if wait_result.found:
81+
print(f" ✅ Found after {wait_result.duration_ms}ms")
82+
print(" (Used local extension, polled every 0.25 seconds)")
83+
print()
84+
85+
# Example 7: Using remote API (slower polling)
86+
print("7. Using remote API with auto-optimized interval")
87+
print(" When use_api=True, interval auto-adjusts to 1.5s (network-friendly)")
88+
if api_key:
89+
wait_result = wait_for(browser, "role=link", timeout=5.0, use_api=True)
90+
if wait_result.found:
91+
print(f" ✅ Found after {wait_result.duration_ms}ms")
92+
print(" (Used remote API, polled every 1.5 seconds)")
93+
else:
94+
print(" ⚠️ Skipped (no API key set)")
95+
print()
96+
97+
# Example 8: Custom interval override
98+
print("8. Custom interval override (manual control)")
99+
print(" You can still specify custom interval if needed")
100+
wait_result = wait_for(browser, "role=link", timeout=5.0, interval=0.5, use_api=False)
101+
if wait_result.found:
102+
print(f" ✅ Found after {wait_result.duration_ms}ms")
103+
print(" (Custom interval: 0.5 seconds)")
104+
print()
105+
106+
print("✅ Semantic wait_for demo complete!")
107+
print("\nNote: wait_for uses the semantic query DSL to find elements.")
108+
print("This is more robust than CSS selectors because it understands")
109+
print("the semantic meaning of elements (role, text, clickability, etc.).")
110+
111+
112+
if __name__ == "__main__":
113+
main()
114+

sentience/wait.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import time
66
from typing import Union, Optional
77
from .browser import SentienceBrowser
8-
from .models import WaitResult, Element
8+
from .models import WaitResult
99
from .snapshot import snapshot
1010
from .query import find
1111

@@ -14,7 +14,8 @@ def wait_for(
1414
browser: SentienceBrowser,
1515
selector: Union[str, dict],
1616
timeout: float = 10.0,
17-
interval: float = 0.25,
17+
interval: Optional[float] = None,
18+
use_api: Optional[bool] = None,
1819
) -> WaitResult:
1920
"""
2021
Wait for element matching selector to appear
@@ -23,16 +24,29 @@ def wait_for(
2324
browser: SentienceBrowser instance
2425
selector: String DSL or dict query
2526
timeout: Maximum time to wait (seconds)
26-
interval: Polling interval (seconds)
27+
interval: Polling interval (seconds). If None, auto-detects:
28+
- 0.25s for local extension (use_api=False, fast)
29+
- 1.5s for remote API (use_api=True or default, network latency)
30+
use_api: Force use of server-side API if True, local extension if False.
31+
If None, uses API if api_key is set, otherwise uses local extension.
2732
2833
Returns:
2934
WaitResult
3035
"""
36+
# Auto-detect optimal interval based on API usage
37+
if interval is None:
38+
# Determine if using API
39+
will_use_api = use_api if use_api is not None else (browser.api_key is not None)
40+
if will_use_api:
41+
interval = 1.5 # Longer interval for API calls (network latency)
42+
else:
43+
interval = 0.25 # Shorter interval for local extension (fast)
44+
3145
start_time = time.time()
3246

3347
while time.time() - start_time < timeout:
34-
# Take snapshot
35-
snap = snapshot(browser)
48+
# Take snapshot (may be local extension or remote API)
49+
snap = snapshot(browser, use_api=use_api)
3650

3751
# Try to find element
3852
element = find(snap, selector)

0 commit comments

Comments
 (0)