From 5404bb7cdbfd25152f4468a68370444765cdae70 Mon Sep 17 00:00:00 2001 From: Michael Rowlinson Date: Fri, 20 Feb 2026 19:30:34 -0800 Subject: [PATCH 1/2] fix: update UI selectors and submission for current AI Studio Google AI Studio updated its Angular UI, breaking the existing selectors and submission flow. This patch fixes three issues: 1. Submit button selector: Added 'button.ctrl-enter-submits' to match the current AI Studio submit button class. 2. Text input method: Replaced JavaScript element.value assignment with Playwright's native fill() method. The JS approach bypassed Angular's reactive forms change detection, leaving the internal model empty despite text being visually present. 3. Submission method: Replaced the submit button click + tooltip dismissal flow with Meta+Enter keyboard shortcut. The tooltip dismissal (_dismiss_tooltip_overlays) was hanging indefinitely on the current UI, and Meta+Enter is more reliable as it bypasses button state checks entirely. Tested on macOS with Camoufox v135.0.1-beta.24. --- browser_utils/page_controller.py | 74 +++----------------------------- config/selectors.py | 8 ++++ 2 files changed, 14 insertions(+), 68 deletions(-) diff --git a/browser_utils/page_controller.py b/browser_utils/page_controller.py index 38c3dda0..d4b4e47b 100644 --- a/browser_utils/page_controller.py +++ b/browser_utils/page_controller.py @@ -153,76 +153,14 @@ async def submit_prompt( ) # Fill textarea using centralized logic (inherited from InputController if possible, or direct) - await textarea.evaluate( - "(el, t) => { el.value = t; el.dispatchEvent(new Event('input', {bubbles:true})); el.dispatchEvent(new Event('change', {bubbles:true})); }", - prompt, - ) - await self._check_disconnect( - check_client_disconnected, "After Input Fill" - ) - - if image_list: - await self._open_upload_menu_and_choose_file(image_list) - - # Wait for submit button to be enabled - submit = self.page.locator(SUBMIT_BUTTON_SELECTOR) - button_clicked = False - is_btn_enabled = False - try: - await expect_async(submit).to_be_enabled(timeout=10000) - is_btn_enabled = True - except Exception: - self.logger.warning( - f"[{self.req_id}] Submit button not enabled within timeout, trying keyboard fallback." - ) - + # Playwright fill + keyboard submit (patched) + await textarea.click() + await textarea.fill(prompt) + await asyncio.sleep(0.3) + await textarea.press("Meta+Enter") await self._check_disconnect( - check_client_disconnected, "After Submit Button Check" + check_client_disconnected, "After Submit" ) - - if is_btn_enabled: - try: - # Defensive workarounds before click: handle dialogs, backdrops and tooltips - await self._handle_post_upload_dialog() - await self._dismiss_backdrops() - if hasattr(self, "_dismiss_tooltip_overlays"): - await self._dismiss_tooltip_overlays() - - await submit.click(timeout=5000) - button_clicked = True - self.logger.info(f"[{self.req_id}] Submit button clicked.") - await check_quota_limit(self.page, self.req_id) - except QuotaExceededError: - raise - except Exception as click_err: - self.logger.warning( - f"[{self.req_id}] Button click failed: {click_err}. Trying keyboard fallback." - ) - - if not button_clicked: - # Keyboard fallbacks (using logic inherited from InputController) - self.logger.info( - f"[{self.req_id}] Attempting Enter key submission..." - ) - if await self._try_enter_submit( - textarea, check_client_disconnected - ): - button_clicked = True - else: - self.logger.info( - f"[{self.req_id}] Attempting Combo key submission..." - ) - if await self._try_combo_submit( - textarea, check_client_disconnected - ): - button_clicked = True - - if not button_clicked: - raise Exception( - "Failed to submit prompt via button or keyboard shortcuts." - ) - - await self._check_disconnect(check_client_disconnected, "After Submit") return except QuotaExceededError: raise diff --git a/config/selectors.py b/config/selectors.py index 1d90d33d..21bd7936 100644 --- a/config/selectors.py +++ b/config/selectors.py @@ -296,3 +296,11 @@ 'mat-dialog-container button:has-text("Cancel"), ' 'mat-mdc-dialog-container button:has-text("Cancel")' ) + +# Patched: add new AI Studio submit button class +SUBMIT_BUTTON_SELECTOR = ( + "button.ctrl-enter-submits, " + "ms-run-button button[type=\"submit\"].ms-button-primary, " + "ms-run-button button[type=\"submit\"], " + 'button[aria-label="Run"][type="submit"]' +) From 070ae46c10e7a493dd4d30fed8deba00f4026a4b Mon Sep 17 00:00:00 2001 From: MasuRii Date: Thu, 26 Feb 2026 02:06:03 +0800 Subject: [PATCH 2/2] fix: preserve robust submit flow while updating AI Studio input/selector --- browser_utils/page_controller.py | 71 +++++++++++++++++++++++++++++--- config/selectors.py | 8 +--- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/browser_utils/page_controller.py b/browser_utils/page_controller.py index d4b4e47b..fa682dda 100644 --- a/browser_utils/page_controller.py +++ b/browser_utils/page_controller.py @@ -152,15 +152,74 @@ async def submit_prompt( check_client_disconnected, "After Input Visible" ) - # Fill textarea using centralized logic (inherited from InputController if possible, or direct) - # Playwright fill + keyboard submit (patched) - await textarea.click() + # Use Playwright-native fill so Angular/React/Vue input handlers receive proper events. await textarea.fill(prompt) - await asyncio.sleep(0.3) - await textarea.press("Meta+Enter") await self._check_disconnect( - check_client_disconnected, "After Submit" + check_client_disconnected, "After Input Fill" ) + + if image_list: + await self._open_upload_menu_and_choose_file(image_list) + + # Wait for submit button to be enabled + submit = self.page.locator(SUBMIT_BUTTON_SELECTOR) + button_clicked = False + is_btn_enabled = False + try: + await expect_async(submit).to_be_enabled(timeout=10000) + is_btn_enabled = True + except Exception: + self.logger.warning( + f"[{self.req_id}] Submit button not enabled within timeout, trying keyboard fallback." + ) + + await self._check_disconnect( + check_client_disconnected, "After Submit Button Check" + ) + + if is_btn_enabled: + try: + # Defensive workarounds before click: handle dialogs, backdrops and tooltips + await self._handle_post_upload_dialog() + await self._dismiss_backdrops() + if hasattr(self, "_dismiss_tooltip_overlays"): + await self._dismiss_tooltip_overlays() + + await submit.click(timeout=5000) + button_clicked = True + self.logger.info(f"[{self.req_id}] Submit button clicked.") + await check_quota_limit(self.page, self.req_id) + except QuotaExceededError: + raise + except Exception as click_err: + self.logger.warning( + f"[{self.req_id}] Button click failed: {click_err}. Trying keyboard fallback." + ) + + if not button_clicked: + # Keyboard fallbacks (using logic inherited from InputController) + self.logger.info( + f"[{self.req_id}] Attempting Enter key submission..." + ) + if await self._try_enter_submit( + textarea, check_client_disconnected + ): + button_clicked = True + else: + self.logger.info( + f"[{self.req_id}] Attempting Combo key submission..." + ) + if await self._try_combo_submit( + textarea, check_client_disconnected + ): + button_clicked = True + + if not button_clicked: + raise Exception( + "Failed to submit prompt via button or keyboard shortcuts." + ) + + await self._check_disconnect(check_client_disconnected, "After Submit") return except QuotaExceededError: raise diff --git a/config/selectors.py b/config/selectors.py index 21bd7936..3c57d431 100644 --- a/config/selectors.py +++ b/config/selectors.py @@ -23,6 +23,7 @@ # Submit button: prioritize primary submit button in prompt area SUBMIT_BUTTON_SELECTOR = ( # Current UI structure + "button.ctrl-enter-submits, " 'ms-run-button button[type="submit"].ms-button-primary, ' 'ms-run-button button[type="submit"], ' # Legacy selectors @@ -297,10 +298,3 @@ 'mat-mdc-dialog-container button:has-text("Cancel")' ) -# Patched: add new AI Studio submit button class -SUBMIT_BUTTON_SELECTOR = ( - "button.ctrl-enter-submits, " - "ms-run-button button[type=\"submit\"].ms-button-primary, " - "ms-run-button button[type=\"submit\"], " - 'button[aria-label="Run"][type="submit"]' -)