Skip to content

feat: don't close new opened tabs (#161) #169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft

Conversation

miguelg719
Copy link
Collaborator

  • feat: port new page handling from JS Stagehand PR #844
  • Add live page proxy that dynamically tracks the focused page
  • Implement context event listener to initialize new pages automatically
  • Remove automatic tab closing behavior in act_handler_utils and cua_handler
  • Keep both original and new tabs open when new pages are created
  • Ensure stagehand.page always references the current active page

This implementation matches the behavior of browserbase/stagehand#844

  • Update stagehand/handlers/cua_handler.py

  • Update stagehand/handlers/act_handler_utils.py


why

what changed

test plan

arunpatro and others added 3 commits July 26, 2025 09:32
* feat: port new page handling from JS Stagehand PR #844

- Add live page proxy that dynamically tracks the focused page
- Implement context event listener to initialize new pages automatically
- Remove automatic tab closing behavior in act_handler_utils and cua_handler
- Keep both original and new tabs open when new pages are created
- Ensure stagehand.page always references the current active page

This implementation matches the behavior of browserbase/stagehand#844

* Update stagehand/handlers/cua_handler.py

* Update stagehand/handlers/act_handler_utils.py

---------

Co-authored-by: Arun Patro <apatro@ramp.com>
Co-authored-by: Miguel <36487034+miguelg719@users.noreply.github.com>
return self.page_map[pw_page]
stagehand_page = self.page_map[pw_page]
# Update active page when getting a page
# self.set_active_page(stagehand_page)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we either want this so uncomment or we don't want this so get rid of it? we shouldnt have setter behaviour in a getter method imo

)

# Schedule the async work
import asyncio
Copy link
Collaborator

@filip-michalsky filip-michalsky Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets discuss the event lifecycle here.

User clicks on a link which opens a new page - this method docstring says it happens synchronously in the event lifecycle, but the code suggest its an async side event.

we have no guarantee that stagehand operations will be continuing on the new page the moment a new page is requested in this code - or am I missing something? There could be a couple ms or hundreds of ms gap where new stagehand page has not been swapped to but stagehand commands continue to start firing - unless we make this a truly execution blokcing and not a side async event loop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to make it blocking/sync @filip-michalsky
This covers updates happening api-side, which are bound to race conditions anyways but blocking/awaiting will reduce its effect

…ties

The page property is now read-only (returns LivePageProxy), so tests need to set the internal _original_page and _active_page properties instead
Ensures async operations wait for any pending page switches to complete
# Use object.__setattr__ to avoid infinite recursion
object.__setattr__(self, "_stagehand", stagehand_instance)

async def _ensure_page_stability(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prevent race condition:

  1. User clicks link → Browser opens new tab
  2. Browser fires "page" event → StagehandContext._handle_new_page() called
  3. _handle_new_page() schedules async work with create_task() (NON-BLOCKING)
  4. Stagehand operations continue immediately on OLD page
  5. (Meanwhile) Async task eventually completes and sets new page as active

else:
raise RuntimeError("No active page available")

# For async operations, make them wait for stability
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

block everything until page is set

- Initialize _page_switch_lock in Stagehand constructor
- Skip page stability check for navigation methods (goto, reload, go_back, go_forward)
- Use lock when switching active pages in context
- Add comprehensive tests for LivePageProxy functionality
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants