foxpuppet/windows/browser/panel_ui/panel_ui.py (131 lines of code) (raw):

# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. """Contains classes for handling Firefox Panel UI (Hamburger menu).""" from selenium.webdriver.common.by import By import time from foxpuppet.windows.browser.navbar import NavBar from selenium.webdriver.remote.webelement import WebElement from typing import Type, Any, TYPE_CHECKING, Optional from selenium.webdriver.support import expected_conditions as EC class PanelUI(NavBar): """Handles interaction with Panel UI.""" if TYPE_CHECKING: from foxpuppet.windows import BrowserWindow @staticmethod def create( window: Optional["BrowserWindow"], root: WebElement ) -> Type["PanelUI"] | Any: """Create a Panel UI object. Args: window (:py:class:`BrowserWindow`): Window object this region appears in. root (:py:class:`~selenium.webdriver.remote.webelement.WebElement`): WebDriver element object that serves as the root for the Panel UI. Returns: :py:class:`PanelUI`: Firefox Panel UI. """ panel_items: dict = {} _id: str | bool | WebElement | dict = root.get_property("id") panel_items.update(PANEL_ITEMS) return panel_items.get(_id, PanelUI)(window, root) @property def is_update_available(self) -> bool: """ Checks if the Panel UI button indicates a pending Firefox update. Returns: bool: True if an update notification (barge) is present, False otherwise. """ with self.selenium.context(self.selenium.CONTEXT_CHROME): update_status = self.selenium.find_element( *PanelUILocators.PANEL_UI_BUTTON ).get_attribute("barged") return update_status == "true" def open_panel_menu(self) -> None: """ Opens the Panel UI menu. """ with self.selenium.context(self.selenium.CONTEXT_CHROME): self.selenium.find_element(*PanelUILocators.PANEL_UI_BUTTON).click() self.wait.until( EC.presence_of_element_located(*PanelUILocators.PANEL_POPUP), message="Panel UI menu did not open", ) def open_new_tab(self) -> None: """ Opens a new tab using the Panel UI menu. """ initial_handles = set(self.selenium.window_handles) self.open_panel_menu() with self.selenium.context(self.selenium.CONTEXT_CHROME): self.selenium.find_element(*PanelUILocators.NEW_TAB).click() self.wait.until( lambda _: set(self.selenium.window_handles) - initial_handles, message="New Tab did not open", ) new_tab = (set(self.selenium.window_handles) - initial_handles).pop() self.selenium.switch_to.window(new_tab) def open_new_window(self) -> None: """ Opens a new window using the Panel UI menu. """ initial_handles = set(self.selenium.window_handles) self.open_panel_menu() with self.selenium.context(self.selenium.CONTEXT_CHROME): self.selenium.find_element(*PanelUILocators.NEW_WINDOW).click() self.wait.until( lambda _: set(self.selenium.window_handles) - initial_handles, message="New window did not open", ) new_window = (set(self.selenium.window_handles) - initial_handles).pop() self.selenium.switch_to.window(new_window) self.wait.until( lambda _: self.selenium.execute_script("return document.readyState") == "complete", message="New window document not fully loaded", ) def open_private_window(self) -> None: """ Opens a new window in private browsing mode using the Panel UI menu. """ initial_handles = set(self.selenium.window_handles) self.open_panel_menu() with self.selenium.context(self.selenium.CONTEXT_CHROME): self.selenium.find_element(*PanelUILocators.PRIVATE_WINDOW).click() self.wait.until( lambda _: set(self.selenium.window_handles) - initial_handles, message="Private window did not open", ) from foxpuppet.windows.browser.window import BrowserWindow new_private_window = self.selenium.window_handles[-1] try: private_window = BrowserWindow( self.selenium, new_private_window ).is_private if private_window: self.selenium.switch_to.window(new_private_window) except Exception as e: raise Exception(f"The new window is not private: {str(e)}") def open_history_menu(self) -> None: """ Opens the History in Panel UI Menu """ with self.selenium.context(self.selenium.CONTEXT_CHROME): self.selenium.find_element(*PanelUILocators.HISTORY).click() self.wait.until( lambda _: self.selenium.find_element( *PanelUILocators.PANEL_HISTORY ).is_displayed(), message="History menu did not open", ) class History(PanelUI): def history_items(self) -> list[WebElement]: """ Retrieves all history items from the Panel UI history menu. Returns: list[WebElement]: List of WebElement objects representing history items. Returns an empty list if no history items are found. """ with self.selenium.context(self.selenium.CONTEXT_CHROME): history_items = self.selenium.find_elements( *PanelUILocators.RECENT_HISTORY_ITEMS ) return history_items def clear_history(self): """ Clears the browsing history. """ with self.selenium.context(self.selenium.CONTEXT_CHROME): self.selenium.find_element(*PanelUILocators.CLEAR_RECENT_HISTORY).click() self.selenium.switch_to.frame( self.selenium.find_element(*PanelUILocators.HISTORY_IFRAME) ) with self.selenium.context(self.selenium.CONTEXT_CONTENT): self.selenium.find_element(*PanelUILocators.DROPDOWN_HISTORY).click() self.selenium.find_element( *PanelUILocators.CLEAR_HISTORY_EVERYTHING ).click() self.selenium.execute_script( """ const shadowHost = arguments[0]; const shadowRoot = shadowHost.shadowRoot; const clearRecentHistoryButton = shadowRoot.querySelector('button[dlgtype="accept"]'); clearRecentHistoryButton.click(); """, self.selenium.find_element(*PanelUILocators.HISTORY_DIALOG_BUTTON), ) time.sleep(1) class PanelUILocators: CLEAR_HISTORY_EVERYTHING = (By.CSS_SELECTOR, "menuitem[value='0']") CLEAR_RECENT_HISTORY = (By.ID, "appMenuClearRecentHistory") CLEAR_RECENT_HISTORY_BUTTON = (By.CSS_SELECTOR, "button[dlgtype='accept']") DROPDOWN_HISTORY = (By.ID, "sanitizeDurationChoice") HISTORY = (By.ID, "appMenu-history-button") HISTORY_DIALOG_BUTTON = (By.CSS_SELECTOR, "dialog[defaultButton='accept']") HISTORY_IFRAME = (By.CSS_SELECTOR, "browser.dialogFrame") NEW_TAB = (By.ID, "appMenu-new-tab-button2") NEW_WINDOW = (By.ID, "appMenu-new-window-button2") PANEL_HISTORY = (By.ID, "PanelUI-history") PANEL_POPUP = ((By.ID, "appMenu-popup"),) PANEL_UI_BUTTON = (By.ID, "PanelUI-menu-button") PRIVATE_WINDOW = (By.ID, "appMenu-new-private-window-button2") RECENT_HISTORY_ITEMS = ( By.CSS_SELECTOR, "#appMenu_historyMenu toolbarbutton.subviewbutton", ) PANEL_ITEMS = { "PanelUI-menu-button": PanelUI, "appMenu-history-button": History, }