Skip to content

Commit d61e118

Browse files
authored
fix: active page context (#251)
### Why When a page closes (e.g., splash screen during auth), `get_active_page()` still returns the closed page instead of switching to a remaining open page. ### What Changed Added `_handle_page_close()` in `context.py` to switch active page to a remaining open page when the current active page closes. ### Test Plan Verified with multi-page script: open two pages, close active page, confirm `get_active_page()` returns remaining open page. Existing unit tests pass. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes stale active page after a page closes. When the active page closes (e.g., splash during auth), we now switch to a remaining open page or clear the active page. - **Bug Fixes** - Added _handle_page_close to update the active page on close events. - Uses the page switch lock to avoid race conditions; 30s timeout with error logging. - Wired to Playwright's page "close" event; also unregisters frame IDs. - get_active_page() now returns the correct remaining page (or None if none remain). <sup>Written for commit a7cb745. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
1 parent 69289b9 commit d61e118

File tree

1 file changed

+47
-1
lines changed

1 file changed

+47
-1
lines changed

stagehand/context.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,50 @@ async def handle_with_lock():
141141
f"Failed to initialize new page: {str(e)}", category="context"
142142
)
143143

144+
async def _handle_page_close(self, closing_page: StagehandPage):
145+
"""
146+
Handle page close events and update the active page if needed.
147+
Uses the page switch lock to prevent race conditions with ongoing operations.
148+
"""
149+
try:
150+
async def handle_with_lock():
151+
async with self.stagehand._page_switch_lock:
152+
if self.active_stagehand_page is not closing_page:
153+
return
154+
155+
remaining_pages = self._context.pages
156+
if remaining_pages:
157+
first_remaining = remaining_pages[0]
158+
new_active = self.page_map.get(first_remaining)
159+
if new_active:
160+
self.set_active_page(new_active)
161+
self.stagehand.logger.debug(
162+
f"Active page closed, switching to: {first_remaining.url}",
163+
category="context",
164+
)
165+
else:
166+
self.stagehand.logger.warning(
167+
"Could not find StagehandPage wrapper for remaining page",
168+
category="context",
169+
)
170+
else:
171+
self.active_stagehand_page = None
172+
self.stagehand.logger.debug(
173+
"Active page closed and no pages remaining",
174+
category="context",
175+
)
176+
177+
await asyncio.wait_for(handle_with_lock(), timeout=30)
178+
except asyncio.TimeoutError:
179+
self.stagehand.logger.error(
180+
"Timeout waiting for page switch lock when handling page close",
181+
category="context",
182+
)
183+
except Exception as e:
184+
self.stagehand.logger.error(
185+
f"Failed to handle page close: {str(e)}", category="context"
186+
)
187+
144188
def __getattr__(self, name):
145189
# Forward attribute lookups to the underlying BrowserContext
146190
attr = getattr(self._context, name)
@@ -220,11 +264,13 @@ def on_frame_navigated(params):
220264
# Register the event listener
221265
cdp_session.on("Page.frameNavigated", on_frame_navigated)
222266

223-
# Clean up frame ID when page closes
224267
def on_page_close():
225268
if stagehand_page.frame_id:
226269
self.unregister_frame_id(stagehand_page.frame_id)
227270

271+
if self.active_stagehand_page is stagehand_page:
272+
asyncio.create_task(self._handle_page_close(stagehand_page))
273+
228274
pw_page.once("close", on_page_close)
229275

230276
except Exception as e:

0 commit comments

Comments
 (0)