Skip to content

Commit c51f8d0

Browse files
consolidate original and active page to just one page
1 parent c0959a2 commit c51f8d0

File tree

3 files changed

+45
-70
lines changed

3 files changed

+45
-70
lines changed

stagehand/main.py

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,14 @@ def __getattr__(self, name):
5353
"""Delegate all attribute access to the current active page."""
5454
stagehand = object.__getattribute__(self, "_stagehand")
5555

56-
# Get the current active page
57-
if hasattr(stagehand, "_active_page") and stagehand._active_page:
58-
active_page = stagehand._active_page
59-
elif hasattr(stagehand, "_original_page") and stagehand._original_page:
60-
active_page = stagehand._original_page
56+
# Get the current page
57+
if hasattr(stagehand, "_page") and stagehand._page:
58+
page = stagehand._page
6159
else:
6260
raise RuntimeError("No active page available")
6361

6462
# For async operations, make them wait for stability
65-
attr = getattr(active_page, name)
63+
attr = getattr(page, name)
6664
if callable(attr) and asyncio.iscoroutinefunction(attr):
6765
# Don't wait for stability on navigation methods
6866
if name in ["goto", "reload", "go_back", "go_forward"]:
@@ -83,38 +81,32 @@ def __setattr__(self, name, value):
8381
else:
8482
stagehand = object.__getattribute__(self, "_stagehand")
8583

86-
# Get the current active page
87-
if hasattr(stagehand, "_active_page") and stagehand._active_page:
88-
active_page = stagehand._active_page
89-
elif hasattr(stagehand, "_original_page") and stagehand._original_page:
90-
active_page = stagehand._original_page
84+
# Get the current page
85+
if hasattr(stagehand, "_page") and stagehand._page:
86+
page = stagehand._page
9187
else:
9288
raise RuntimeError("No active page available")
9389

94-
# Set the attribute on the active page
95-
setattr(active_page, name, value)
90+
# Set the attribute on the page
91+
setattr(page, name, value)
9692

9793
def __dir__(self):
9894
"""Return attributes of the current active page."""
9995
stagehand = object.__getattribute__(self, "_stagehand")
10096

101-
if hasattr(stagehand, "_active_page") and stagehand._active_page:
102-
active_page = stagehand._active_page
103-
elif hasattr(stagehand, "_original_page") and stagehand._original_page:
104-
active_page = stagehand._original_page
97+
if hasattr(stagehand, "_page") and stagehand._page:
98+
page = stagehand._page
10599
else:
106100
return []
107101

108-
return dir(active_page)
102+
return dir(page)
109103

110104
def __repr__(self):
111105
"""Return representation of the current active page."""
112106
stagehand = object.__getattribute__(self, "_stagehand")
113107

114-
if hasattr(stagehand, "_active_page") and stagehand._active_page:
115-
return f"<LivePageProxy -> {repr(stagehand._active_page)}>"
116-
elif hasattr(stagehand, "_original_page") and stagehand._original_page:
117-
return f"<LivePageProxy -> {repr(stagehand._original_page)}>"
108+
if hasattr(stagehand, "_page") and stagehand._page:
109+
return f"<LivePageProxy -> {repr(stagehand._page)}>"
118110
else:
119111
return "<LivePageProxy -> No active page>"
120112

@@ -252,8 +244,7 @@ def __init__(
252244
self._browser = None
253245
self._context: Optional[BrowserContext] = None
254246
self._playwright_page: Optional[PlaywrightPage] = None
255-
self._original_page: Optional[StagehandPage] = None
256-
self._active_page: Optional[StagehandPage] = None
247+
self._page: Optional[StagehandPage] = None
257248
self.context: Optional[StagehandContext] = None
258249
self.use_api = self.config.use_api
259250
self.experimental = self.config.experimental
@@ -496,16 +487,15 @@ async def init(self):
496487
self._browser,
497488
self._context,
498489
self.context,
499-
self._original_page,
490+
self._page,
500491
) = await connect_browserbase_browser(
501492
self._playwright,
502493
self.session_id,
503494
self.browserbase_api_key,
504495
self,
505496
self.logger,
506497
)
507-
self._playwright_page = self._original_page._page
508-
self._active_page = self._original_page
498+
self._playwright_page = self._page._page
509499
except Exception:
510500
await self.close()
511501
raise
@@ -517,16 +507,15 @@ async def init(self):
517507
self._browser,
518508
self._context,
519509
self.context,
520-
self._original_page,
510+
self._page,
521511
self._local_user_data_dir_temp,
522512
) = await connect_local_browser(
523513
self._playwright,
524514
self.local_browser_launch_options,
525515
self,
526516
self.logger,
527517
)
528-
self._playwright_page = self._original_page._page
529-
self._active_page = self._original_page
518+
self._playwright_page = self._page._page
530519
except Exception:
531520
await self.close()
532521
raise
@@ -713,7 +702,7 @@ def _set_active_page(self, stagehand_page: StagehandPage):
713702
Args:
714703
stagehand_page: The StagehandPage to set as active
715704
"""
716-
self._active_page = stagehand_page
705+
self._page = stagehand_page
717706

718707
@property
719708
def page(self) -> Optional[StagehandPage]:

tests/conftest.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,8 @@ def mock_stagehand_client(mock_stagehand_config):
233233
# Mock the essential components
234234
client.llm = MagicMock()
235235
client.llm.completion = AsyncMock()
236-
# Set internal page properties instead of the read-only page property
237-
client._original_page = MagicMock()
238-
client._active_page = client._original_page
236+
# Set internal page property instead of the read-only page property
237+
client._page = MagicMock()
239238
client.agent = MagicMock()
240239
client._client = MagicMock()
241240
client._execute = AsyncMock()

tests/unit/core/test_live_page_proxy.py

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,49 +13,40 @@ async def test_live_page_proxy_basic_delegation(mock_stagehand_config):
1313
# Create a Stagehand instance
1414
stagehand = Stagehand(config=mock_stagehand_config)
1515

16-
# Mock pages
17-
mock_original_page = MagicMock(spec=StagehandPage)
18-
mock_original_page.url = "https://original.com"
19-
mock_original_page.title = AsyncMock(return_value="Original Page")
20-
21-
mock_active_page = MagicMock(spec=StagehandPage)
22-
mock_active_page.url = "https://active.com"
23-
mock_active_page.title = AsyncMock(return_value="Active Page")
16+
# Mock page
17+
mock_page = MagicMock(spec=StagehandPage)
18+
mock_page.url = "https://active.com"
19+
mock_page.title = AsyncMock(return_value="Active Page")
2420

25-
# Set up the pages
26-
stagehand._original_page = mock_original_page
27-
stagehand._active_page = mock_active_page
21+
# Set up the page
22+
stagehand._page = mock_page
2823
stagehand._initialized = True
2924

3025
# Get the proxy
3126
proxy = stagehand.page
3227

33-
# Test that it delegates to the active page
28+
# Test that it delegates to the page
3429
assert proxy.url == "https://active.com"
3530
title = await proxy.title()
3631
assert title == "Active Page"
3732

3833

3934
@pytest.mark.asyncio
40-
async def test_live_page_proxy_falls_back_to_original(mock_stagehand_config):
41-
"""Test that LivePageProxy falls back to original page when no active page"""
35+
async def test_live_page_proxy_no_page_fallback(mock_stagehand_config):
36+
"""Test that LivePageProxy raises error when no page is set"""
4237
# Create a Stagehand instance
4338
stagehand = Stagehand(config=mock_stagehand_config)
4439

45-
# Mock original page only
46-
mock_original_page = MagicMock(spec=StagehandPage)
47-
mock_original_page.url = "https://original.com"
48-
49-
# Set up the pages
50-
stagehand._original_page = mock_original_page
51-
stagehand._active_page = None
40+
# No page set
41+
stagehand._page = None
5242
stagehand._initialized = True
5343

5444
# Get the proxy
5545
proxy = stagehand.page
5646

57-
# Test that it delegates to the original page
58-
assert proxy.url == "https://original.com"
47+
# Accessing attributes should raise RuntimeError
48+
with pytest.raises(RuntimeError, match="No active page available"):
49+
_ = proxy.url
5950

6051

6152
@pytest.mark.asyncio
@@ -85,9 +76,8 @@ async def __aexit__(self, *args):
8576
mock_page = MagicMock(spec=StagehandPage)
8677
mock_page.click = AsyncMock(return_value=None)
8778

88-
# Set up the pages
89-
stagehand._original_page = mock_page
90-
stagehand._active_page = mock_page
79+
# Set up the page
80+
stagehand._page = mock_page
9181
stagehand._initialized = True
9282

9383
# Get the proxy
@@ -129,9 +119,8 @@ async def __aexit__(self, *args):
129119
mock_page.go_back = AsyncMock(return_value=None)
130120
mock_page.go_forward = AsyncMock(return_value=None)
131121

132-
# Set up the pages
133-
stagehand._original_page = mock_page
134-
stagehand._active_page = mock_page
122+
# Set up the page
123+
stagehand._page = mock_page
135124
stagehand._initialized = True
136125

137126
# Get the proxy
@@ -167,8 +156,7 @@ async def test_live_page_proxy_dynamic_page_switching(mock_stagehand_config):
167156
page2.url = "https://page2.com"
168157

169158
# Set up initial state
170-
stagehand._original_page = page1
171-
stagehand._active_page = page1
159+
stagehand._page = page1
172160
stagehand._initialized = True
173161

174162
# Get the proxy
@@ -177,8 +165,8 @@ async def test_live_page_proxy_dynamic_page_switching(mock_stagehand_config):
177165
# Initially points to page1
178166
assert proxy.url == "https://page1.com"
179167

180-
# Switch active page
181-
stagehand._active_page = page2
168+
# Switch page
169+
stagehand._page = page2
182170

183171
# Now points to page2 without creating a new proxy
184172
assert proxy.url == "https://page2.com"
@@ -189,9 +177,8 @@ def test_live_page_proxy_no_page_error(mock_stagehand_config):
189177
# Create a Stagehand instance
190178
stagehand = Stagehand(config=mock_stagehand_config)
191179

192-
# No pages set
193-
stagehand._original_page = None
194-
stagehand._active_page = None
180+
# No page set
181+
stagehand._page = None
195182
stagehand._initialized = True
196183

197184
# Get the proxy

0 commit comments

Comments
 (0)