@@ -560,33 +560,134 @@ async def signal_camoufox_shutdown() -> None: # pragma: no cover
560560 )
561561
562562
563- async def enable_temporary_chat_mode (page : AsyncPage ) -> None : # pragma: no cover
564- """
565- Check and enable "Temporary chat" mode in the AI Studio interface.
566- This is an independent UI operation and should be called after the page is fully stable.
563+ async def _is_temporary_chat_active (page : AsyncPage ) -> bool :
564+ """Best-effort check for whether Temporary chat is currently enabled.
565+
566+ Supports both the legacy toggle-class signal and the newer UI variants:
567+ - menu item checkmark (`data-test-incognito-checkmark`)
568+ - header indicator (`ms-incognito-mode-indicator`)
567569 """
570+
568571 try :
569- incognito_button_locator = page .locator (
570- 'button[aria-label="Temporary chat toggle"]'
572+ # Newer UI: header indicator appears only when Temporary chat is active.
573+ indicator_locator = page .locator (
574+ "ms-incognito-mode-indicator [data-test-id='main-text'], "
575+ "ms-incognito-mode-indicator .main-text"
571576 )
577+ indicator_count = await indicator_locator .count ()
578+ for i in range (indicator_count ):
579+ try :
580+ indicator_item = indicator_locator .nth (i )
581+ if not await indicator_item .is_visible (timeout = 500 ):
582+ continue
583+ indicator_text = (await indicator_item .inner_text ()).strip ().lower ()
584+ if indicator_text == "temporary chat" :
585+ return True
586+ except Exception :
587+ continue
588+
589+ # Menu button variants (legacy and gray-release UI).
590+ toggle_locator = page .locator (
591+ "button[data-test-incognito-toggle], "
592+ 'button[aria-label="Temporary chat toggle"], '
593+ 'button[aria-label="Toggle temporary chat"]'
594+ )
595+ if await toggle_locator .count () > 0 :
596+ # New UI: active state is represented by a checkmark inside the menu item.
597+ try :
598+ checkmark_locator = page .locator (
599+ "button[data-test-incognito-toggle] [data-test-incognito-checkmark], "
600+ 'button[aria-label="Temporary chat toggle"] [data-test-incognito-checkmark], '
601+ 'button[aria-label="Toggle temporary chat"] [data-test-incognito-checkmark]'
602+ )
603+ if (
604+ await checkmark_locator .count () > 0
605+ ):
606+ return True
607+ except Exception :
608+ pass
609+
610+ # Legacy/alternative signals.
611+ for attr_name in ("aria-pressed" , "aria-checked" ):
612+ try :
613+ attr_value = await toggle_locator .get_attribute (attr_name )
614+ if attr_value and attr_value .lower () == "true" :
615+ return True
616+ except Exception :
617+ pass
618+
619+ try :
620+ button_classes = await toggle_locator .get_attribute ("class" ) or ""
621+ if "ms-button-active" in button_classes :
622+ return True
623+ except Exception :
624+ pass
625+
626+ return False
627+ except Exception :
628+ return False
629+
572630
573- await incognito_button_locator .wait_for (state = "visible" , timeout = 10000 )
631+ async def enable_temporary_chat_mode (page : AsyncPage ) -> bool : # pragma: no cover
632+ """
633+ Check and enable "Temporary chat" mode in the AI Studio interface.
634+ Supports both direct UI visibility and collapsed menu visibility.
635+ """
636+ incognito_selector = (
637+ "button[data-test-incognito-toggle], "
638+ 'button[aria-label="Temporary chat toggle"], '
639+ 'button[aria-label="Toggle temporary chat"]'
640+ )
641+ menu_trigger_selector = 'button[aria-label="View more actions"]'
574642
575- button_classes = await incognito_button_locator .get_attribute ("class" )
643+ incognito_locator = page .locator (incognito_selector )
644+ menu_trigger = page .locator (menu_trigger_selector )
576645
577- if button_classes and "ms-button-active" in button_classes :
646+ async def _close_menu_if_needed () -> None :
647+ """Best-effort menu cleanup that tolerates UI variants without a menu trigger."""
648+ try :
649+ if await menu_trigger .count () == 0 :
650+ return
651+ if await menu_trigger .is_visible ():
652+ if await menu_trigger .get_attribute ("aria-expanded" ) == "true" :
653+ logger .debug ("[UI] Closing menu to restore UI state" )
654+ await page .keyboard .press ("Escape" )
655+ except Exception :
656+ pass
657+
658+ try :
659+ # Fast path
660+ logger .debug ("[UI] Searching for temporary chat button (Fast path)" )
661+ try :
662+ await incognito_locator .wait_for (state = "visible" , timeout = 3000 )
663+ except Exception :
664+ # Fallback
665+ logger .debug ("[UI] Button not visible, attempting to open menu" )
666+ if await menu_trigger .is_visible ():
667+ await menu_trigger .click ()
668+ # Wait for the menu item to appear
669+ await incognito_locator .wait_for (state = "visible" , timeout = 5000 )
670+ else :
671+ logger .warning ("[UI] Neither button nor menu trigger found" )
672+ return False
673+
674+ if await _is_temporary_chat_active (page ):
578675 logger .debug ("[UI] Temporary chat mode already active" )
579676 else :
580- await incognito_button_locator .click (timeout = 5000 , force = True )
677+ logger .debug ("[UI] Enabling temporary chat mode" )
678+ await incognito_locator .click (timeout = 5000 , force = True )
581679 await asyncio .sleep (1 )
582680
583- updated_classes = await incognito_button_locator . get_attribute ( "class" )
584- if updated_classes and "ms-button-active" in updated_classes :
585- logger . debug ( "[UI] Temporary chat mode enabled" )
586- else :
587- logger . warning ( "[UI] Failed to enable temporary chat mode" )
681+ enabled = await _is_temporary_chat_active ( page )
682+
683+ # Recovery: Close menu if still expanded
684+ await _close_menu_if_needed ()
685+ return enabled
588686
589687 except asyncio .CancelledError :
590688 raise
591689 except Exception as e :
592690 logger .warning (f"[UI] Error in temporary chat mode: { e } " )
691+ # Final safety attempt to clear any stuck UI
692+ await _close_menu_if_needed ()
693+ return await _is_temporary_chat_active (page )
0 commit comments