-
Notifications
You must be signed in to change notification settings - Fork 109
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
Changes from 3 commits
78525a7
b2ee152
535339d
a474768
bbd8289
55800e4
29e34e2
c0959a2
c51f8d0
d96a372
15719dc
a274c02
6f35684
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "stagehand": patch | ||
| --- | ||
|
|
||
| Don't close new opened tabs | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -40,7 +40,10 @@ async def inject_custom_scripts(self, pw_page: Page): | |||||
| async def get_stagehand_page(self, pw_page: Page) -> StagehandPage: | ||||||
| if pw_page not in self.page_map: | ||||||
| return await self.create_stagehand_page(pw_page) | ||||||
| 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) | ||||||
|
miguelg719 marked this conversation as resolved.
Outdated
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
miguelg719 marked this conversation as resolved.
Outdated
|
||||||
| return stagehand_page | ||||||
|
|
||||||
| async def get_stagehand_pages(self) -> list: | ||||||
| # Return a list of StagehandPage wrappers for all pages in the context | ||||||
|
|
@@ -53,25 +56,82 @@ async def get_stagehand_pages(self) -> list: | |||||
|
|
||||||
| def set_active_page(self, stagehand_page: StagehandPage): | ||||||
| self.active_stagehand_page = stagehand_page | ||||||
| # Optionally update the active page in the stagehand client if needed | ||||||
| # Update the active page in the stagehand client | ||||||
| if hasattr(self.stagehand, "_set_active_page"): | ||||||
| self.stagehand._set_active_page(stagehand_page) | ||||||
| self.stagehand.logger.debug( | ||||||
| f"Set active page to: {stagehand_page.url}", category="context" | ||||||
| ) | ||||||
| else: | ||||||
| self.stagehand.logger.debug( | ||||||
| "Stagehand does not have _set_active_page method", category="context" | ||||||
| ) | ||||||
|
|
||||||
| def get_active_page(self) -> StagehandPage: | ||||||
| return self.active_stagehand_page | ||||||
|
|
||||||
| @classmethod | ||||||
| async def init(cls, context: BrowserContext, stagehand): | ||||||
| stagehand.logger.debug("StagehandContext.init() called", category="context") | ||||||
| instance = cls(context, stagehand) | ||||||
| # Pre-initialize StagehandPages for any existing pages | ||||||
| stagehand.logger.debug( | ||||||
| f"Found {len(instance._context.pages)} existing pages", category="context" | ||||||
| ) | ||||||
| for pw_page in instance._context.pages: | ||||||
| await instance.create_stagehand_page(pw_page) | ||||||
| if instance._context.pages: | ||||||
| first_page = instance._context.pages[0] | ||||||
| stagehand_page = await instance.get_stagehand_page(first_page) | ||||||
| instance.set_active_page(stagehand_page) | ||||||
|
|
||||||
| # Add event listener for new pages (popups, new tabs from window.open, etc.) | ||||||
| def handle_page_event(pw_page): | ||||||
| instance._handle_new_page(pw_page) | ||||||
|
|
||||||
| context.on("page", handle_page_event) | ||||||
|
|
||||||
| return instance | ||||||
|
|
||||||
| def _handle_new_page(self, pw_page: Page): | ||||||
| """ | ||||||
| Handle new pages created by the browser (popups, window.open, etc.). | ||||||
| This runs synchronously in the event handler context. | ||||||
| """ | ||||||
|
|
||||||
| async def _async_handle(): | ||||||
| try: | ||||||
| self.stagehand.logger.debug( | ||||||
| f"Creating StagehandPage for new page with URL: {pw_page.url}", | ||||||
| category="context", | ||||||
| ) | ||||||
| stagehand_page = await self.create_stagehand_page(pw_page) | ||||||
| self.set_active_page(stagehand_page) | ||||||
| self.stagehand.logger.debug( | ||||||
| "New page detected and initialized", category="context" | ||||||
| ) | ||||||
| except Exception as e: | ||||||
| self.stagehand.logger.error( | ||||||
| f"Failed to initialize new page: {str(e)}", category="context" | ||||||
| ) | ||||||
| import traceback | ||||||
|
|
||||||
| self.stagehand.logger.error( | ||||||
| f"Traceback: {traceback.format_exc()}", category="context" | ||||||
| ) | ||||||
|
|
||||||
| # Schedule the async work | ||||||
| import asyncio | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need to make it blocking/sync @filip-michalsky |
||||||
|
|
||||||
| try: | ||||||
| loop = asyncio.get_running_loop() | ||||||
| loop.create_task(_async_handle()) | ||||||
| except RuntimeError: | ||||||
| # No event loop running, which shouldn't happen in normal operation | ||||||
| self.stagehand.logger.error( | ||||||
| "No event loop available to handle new page", category="context" | ||||||
| ) | ||||||
|
|
||||||
| def __getattr__(self, name): | ||||||
| # Forward attribute lookups to the underlying BrowserContext | ||||||
| attr = getattr(self._context, name) | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.