Skip to content

Commit b0788bc

Browse files
author
SentienceDEV
committed
Phase 5: BrowserProtocol PageProtocl for mocking mor unit tests
1 parent 981e82e commit b0788bc

File tree

7 files changed

+37
-31
lines changed

7 files changed

+37
-31
lines changed

sentience/action_executor.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ class ActionExecutor:
2424
- Handle action parsing errors consistently
2525
"""
2626

27-
def __init__(self, browser: Union[SentienceBrowser, AsyncSentienceBrowser, BrowserProtocol, AsyncBrowserProtocol]):
27+
def __init__(
28+
self,
29+
browser: (
30+
SentienceBrowser | AsyncSentienceBrowser | BrowserProtocol | AsyncBrowserProtocol
31+
),
32+
):
2833
"""
2934
Initialize action executor.
3035

sentience/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from .element_filter import ElementFilter
1616
from .llm_interaction_handler import LLMInteractionHandler
1717
from .llm_provider import LLMProvider, LLMResponse
18-
from .protocols import AsyncBrowserProtocol, BrowserProtocol
1918
from .models import (
2019
ActionHistory,
2120
ActionTokenUsage,
@@ -26,6 +25,7 @@
2625
SnapshotOptions,
2726
TokenStats,
2827
)
28+
from .protocols import AsyncBrowserProtocol, BrowserProtocol
2929
from .snapshot import snapshot, snapshot_async
3030
from .trace_event_builder import TraceEventBuilder
3131

@@ -59,7 +59,7 @@ class SentienceAgent(BaseAgent):
5959

6060
def __init__(
6161
self,
62-
browser: Union[SentienceBrowser, BrowserProtocol],
62+
browser: SentienceBrowser | BrowserProtocol,
6363
llm: LLMProvider,
6464
default_snapshot_limit: int = 50,
6565
verbose: bool = True,

sentience/conversational_agent.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55

66
import json
77
import time
8-
from typing import Any
9-
10-
from typing import Union
8+
from typing import Any, Union
119

1210
from .agent import SentienceAgent
1311
from .browser import SentienceBrowser
1412
from .llm_provider import LLMProvider
15-
from .protocols import BrowserProtocol
1613
from .models import ExtractionResult, Snapshot, SnapshotOptions, StepExecutionResult
14+
from .protocols import BrowserProtocol
1715
from .snapshot import snapshot
1816

1917

@@ -33,7 +31,10 @@ class ConversationalAgent:
3331
"""
3432

3533
def __init__(
36-
self, browser: Union[SentienceBrowser, BrowserProtocol], llm: LLMProvider, verbose: bool = True
34+
self,
35+
browser: SentienceBrowser | BrowserProtocol,
36+
llm: LLMProvider,
37+
verbose: bool = True,
3738
):
3839
"""
3940
Initialize conversational agent

sentience/protocols.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def evaluate(self, script: str, *args: Any, **kwargs: Any) -> Any:
4242
"""
4343
...
4444

45-
def goto(self, url: str, **kwargs: Any) -> Optional[Any]:
45+
def goto(self, url: str, **kwargs: Any) -> Any | None:
4646
"""
4747
Navigate to a URL.
4848
@@ -64,7 +64,7 @@ def wait_for_timeout(self, timeout: int) -> None:
6464
"""
6565
...
6666

67-
def wait_for_load_state(self, state: str = "load", timeout: Optional[int] = None) -> None:
67+
def wait_for_load_state(self, state: str = "load", timeout: int | None = None) -> None:
6868
"""
6969
Wait for page load state.
7070
@@ -89,7 +89,7 @@ class BrowserProtocol(Protocol):
8989
"""
9090

9191
@property
92-
def page(self) -> Optional[PageProtocol]:
92+
def page(self) -> PageProtocol | None:
9393
"""
9494
Current Playwright Page object.
9595
@@ -102,7 +102,7 @@ def start(self) -> None:
102102
"""Start the browser session."""
103103
...
104104

105-
def close(self, output_path: Optional[str] = None) -> Optional[str]:
105+
def close(self, output_path: str | None = None) -> str | None:
106106
"""
107107
Close the browser session.
108108
@@ -151,7 +151,7 @@ async def evaluate(self, script: str, *args: Any, **kwargs: Any) -> Any:
151151
"""
152152
...
153153

154-
async def goto(self, url: str, **kwargs: Any) -> Optional[Any]:
154+
async def goto(self, url: str, **kwargs: Any) -> Any | None:
155155
"""
156156
Navigate to a URL (async).
157157
@@ -173,9 +173,7 @@ async def wait_for_timeout(self, timeout: int) -> None:
173173
"""
174174
...
175175

176-
async def wait_for_load_state(
177-
self, state: str = "load", timeout: Optional[int] = None
178-
) -> None:
176+
async def wait_for_load_state(self, state: str = "load", timeout: int | None = None) -> None:
179177
"""
180178
Wait for page load state (async).
181179
@@ -195,7 +193,7 @@ class AsyncBrowserProtocol(Protocol):
195193
"""
196194

197195
@property
198-
def page(self) -> Optional[AsyncPageProtocol]:
196+
def page(self) -> AsyncPageProtocol | None:
199197
"""
200198
Current Playwright AsyncPage object.
201199
@@ -208,7 +206,7 @@ async def start(self) -> None:
208206
"""Start the browser session (async)."""
209207
...
210208

211-
async def close(self, output_path: Optional[str] = None) -> Optional[str]:
209+
async def close(self, output_path: str | None = None) -> str | None:
212210
"""
213211
Close the browser session (async).
214212
@@ -228,4 +226,3 @@ async def goto(self, url: str) -> None:
228226
url: URL to navigate to
229227
"""
230228
...
231-

tests/integration/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
These tests use real browser instances to test end-to-end functionality
55
and catch real-world bugs that mocks might miss.
66
"""
7-

tests/unit/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
These tests use mocks and protocols to test logic in isolation,
55
without requiring real browser instances.
66
"""
7-

tests/unit/test_agent_errors.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@ def url(self) -> str:
5959
def evaluate(self, script: str, *args: Any, **kwargs: Any) -> Any:
6060
# Return proper snapshot structure when snapshot is called
6161
# The script is a function that calls window.sentience.snapshot(options)
62-
if "window.sentience.snapshot" in script or ("snapshot" in script.lower() and "options" in script):
62+
if "window.sentience.snapshot" in script or (
63+
"snapshot" in script.lower() and "options" in script
64+
):
6365
# Check if args contain options (for empty snapshot tests)
6466
options = kwargs.get("options") or (args[0] if args else {})
6567
limit = options.get("limit", 50) if isinstance(options, dict) else 50
66-
68+
6769
# Return elements based on limit (0 for empty snapshot tests)
6870
elements = []
6971
if limit > 0:
@@ -84,7 +86,7 @@ def evaluate(self, script: str, *args: Any, **kwargs: Any) -> Any:
8486
"z_index": 10,
8587
}
8688
]
87-
89+
8890
# Snapshot model expects 'elements' not 'raw_elements'
8991
return {
9092
"status": "success",
@@ -236,9 +238,7 @@ def test_agent_handles_malformed_llm_response(self):
236238
llm = MockLLMProvider(responses=["INVALID_RESPONSE_FORMAT"])
237239
agent = SentienceAgent(browser, llm, verbose=False)
238240

239-
with (
240-
patch("sentience.snapshot.snapshot") as mock_snapshot,
241-
):
241+
with (patch("sentience.snapshot.snapshot") as mock_snapshot,):
242242
mock_snapshot.return_value = create_mock_snapshot()
243243

244244
# Action executor should raise ValueError for invalid format
@@ -365,7 +365,9 @@ def test_agent_handles_unicode_in_actions(self):
365365
from sentience.models import ActionResult
366366

367367
mock_snapshot.return_value = create_mock_snapshot()
368-
mock_type.return_value = ActionResult(success=True, duration_ms=200, outcome="dom_updated")
368+
mock_type.return_value = ActionResult(
369+
success=True, duration_ms=200, outcome="dom_updated"
370+
)
369371

370372
result = agent.act("Type 你好世界", max_retries=0)
371373
assert result.success is True
@@ -385,7 +387,9 @@ def test_agent_handles_special_characters_in_goal(self):
385387
from sentience.models import ActionResult
386388

387389
mock_snapshot.return_value = create_mock_snapshot()
388-
mock_click.return_value = ActionResult(success=True, duration_ms=150, outcome="dom_updated")
390+
mock_click.return_value = ActionResult(
391+
success=True, duration_ms=150, outcome="dom_updated"
392+
)
389393

390394
# Test with special characters
391395
result = agent.act('Click the "Submit" button (with quotes)', max_retries=0)
@@ -435,9 +439,10 @@ def test_agent_handles_tracer_errors_gracefully(self):
435439
from sentience.models import ActionResult
436440

437441
mock_snapshot.return_value = create_mock_snapshot()
438-
mock_click.return_value = ActionResult(success=True, duration_ms=150, outcome="dom_updated")
442+
mock_click.return_value = ActionResult(
443+
success=True, duration_ms=150, outcome="dom_updated"
444+
)
439445

440446
# Agent should still complete action despite tracer error
441447
result = agent.act("Click the button", max_retries=0)
442448
assert result.success is True
443-

0 commit comments

Comments
 (0)