-
Notifications
You must be signed in to change notification settings - Fork 15
type(feat): Implemented PanelUI Classes and Tests #338
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 all commits
23bcee4
c45b7db
5008d76
dbab333
b963ae2
fe817cd
098d505
db54174
e2ae56e
0f8a715
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,4 @@ | ||
| # 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 the Panel UI API and supporting files.""" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| # 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", | ||
| ) | ||
|
Comment on lines
+98
to
+102
Contributor
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. Is this needed?
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. Yes, this more accurately addresses the root issue compared to |
||
|
|
||
| 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() | ||
Temidayo32 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | ||
b4handjr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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. | ||
| """ | ||
|
Comment on lines
+157
to
+160
Contributor
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. I wonder if we can confirm the history is cleared some way. What do you think?
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. Yes, the |
||
| 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, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # 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/. | ||
| """Creates Navbar object to interact with Firefox URL Bar.""" | ||
|
|
||
| from selenium.webdriver.common.by import By | ||
| from selenium.webdriver.remote.webdriver import WebDriver | ||
| from selenium.webdriver.support.wait import WebDriverWait | ||
| from foxpuppet.region import Region | ||
|
|
||
|
|
||
| class UrlBar(Region): | ||
| def suggestions(self, url: str) -> list[str]: | ||
| """ | ||
| Get all URL suggestions shown in the URL bar. | ||
|
|
||
| Args: | ||
| url (str): The URL to type into the URL bar | ||
|
|
||
| Returns: | ||
| list[str]: List of suggested URLs that appear in the URL bar | ||
| """ | ||
| with self.selenium.context(self.selenium.CONTEXT_CHROME): | ||
| url_bar = self.selenium.find_element(*URLBarLocators.INPUT_FIELD) | ||
| url_bar.clear() | ||
| url_bar.send_keys(url) | ||
|
|
||
| self.wait.until( | ||
| lambda _: self.selenium.find_elements(*URLBarLocators.SEARCH_RESULTS) | ||
| ) | ||
|
|
||
| search_results = self.selenium.find_elements( | ||
| *URLBarLocators.SEARCH_RESULT_ITEMS | ||
| ) | ||
|
|
||
| suggested_urls = [ | ||
| result.find_element(*URLBarLocators.SEARCH_RESULT_ITEM).text | ||
| for result in search_results | ||
| if result.find_element(*URLBarLocators.SEARCH_RESULT_ITEM).text | ||
| ] | ||
|
|
||
| return suggested_urls | ||
|
|
||
|
|
||
| class URLBarLocators: | ||
| INPUT_FIELD = (By.ID, "urlbar-input") | ||
| SEARCH_RESULTS = (By.ID, "urlbar-results") | ||
| SEARCH_RESULT_ITEM = (By.CSS_SELECTOR, "span.urlbarView-url") | ||
| SEARCH_RESULT_ITEMS = (By.CSS_SELECTOR, "div.urlbarView-row[role='presentation']") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [pytest] | ||
| addopts = -vvv --driver Firefox --cov --cov-fail-under=95 --html=results/report.html --self-contained-html | ||
| testpaths = tests | ||
|
Comment on lines
+2
to
+3
Contributor
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. Please add a newline |
||
Uh oh!
There was an error while loading. Please reload this page.