diff --git a/foxpuppet/windows/browser/notifications/addons.py b/foxpuppet/windows/browser/notifications/addons.py index fe509fa..7ee9aeb 100644 --- a/foxpuppet/windows/browser/notifications/addons.py +++ b/foxpuppet/windows/browser/notifications/addons.py @@ -57,20 +57,6 @@ def close(self) -> None: BaseNotification.close(self) -class AddOnInstallRestart(BaseNotification): - """Add-on install restart notification.""" - - def restart_now(self): - """Restart Firefox immediately to complete the add-on installation.""" - with self.selenium.context(self.selenium.CONTEXT_CHROME): - self.find_primary_button().click() - - def restart_later(self): - """Defer Firefox restart for later.""" - with self.selenium.context(self.selenium.CONTEXT_CHROME): - self.find_secondary_button().click() - - class AddOnInstallFailed(BaseNotification): """Add-on install failed notification.""" @@ -103,14 +89,6 @@ def is_downloading(self): with self.selenium.context(self.selenium.CONTEXT_CHROME): return "Downloading and verifying add-on…" in self.find_description().text - def wait_until_complete(self, timeout=None): - """Wait until the progress notification disappears, indicating completion. - - Args: - timeout (int, optional): Maximum time to wait in seconds. - """ - self.window.wait_for_notification(None, timeout=timeout) - # Clean up of these notifications will happen once Firefox ESR is past version 63 # https://github.com/mozilla/FoxPuppet/issues/212 @@ -119,7 +97,6 @@ def wait_until_complete(self, timeout=None): "addon-install-confirmation-notification": AddOnInstallConfirmation, "addon-install-complete-notification": AddOnInstallComplete, "appMenu-addon-installed-notification": AddOnInstallComplete, - "addon-install-restart-notification": AddOnInstallRestart, "addon-install-failed-notification": AddOnInstallFailed, "addon-installed-notification": AddOnInstallComplete, "addon-progress-notification": AddOnProgress, diff --git a/tests/test_notifications.py b/tests/test_notifications.py index 8697c19..c919feb 100644 --- a/tests/test_notifications.py +++ b/tests/test_notifications.py @@ -5,15 +5,18 @@ import pytest from selenium.common.exceptions import TimeoutException +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By from typing import Any from foxpuppet.windows.browser.notifications import BaseNotification -from selenium.webdriver.common.by import By from foxpuppet.windows.browser.notifications.addons import ( AddOnInstallBlocked, AddOnInstallComplete, AddOnInstallConfirmation, AddOnInstallFailed, + AddOnProgress, ) from selenium.webdriver.remote.webdriver import WebDriver from foxpuppet.windows import BrowserWindow @@ -22,47 +25,67 @@ @pytest.fixture -def firefox_options(firefox_options: FirefoxOptions) -> FirefoxOptions: +def firefox_options(request, firefox_options: FirefoxOptions) -> FirefoxOptions: """Fixture for configuring Firefox.""" # Due to https://bugzilla.mozilla.org/show_bug.cgi?id=1329939 we need the # initial browser window to be in the foreground. Without this, the # notifications will not be displayed. firefox_options.add_argument("-foreground") + if getattr(request, "param", {}).get("page_load_strategy_none", False): + firefox_options.set_capability("pageLoadStrategy", "none") return firefox_options class AddOn: """Class representing an add-on.""" - def __init__(self, name: str, path: str): + def __init__(self, name: str, path_key: str = "default"): self.name = name - self.path = path + self._paths = { + "default": "webextension.xpi", + "corrupt": "corruptwebextension.xpi", + "large": "largewebextension.xpi", + } + if path_key not in self._paths: + raise ValueError(f"Invalid path key: {path_key}") + self._path_key = path_key + + @property + def path(self): + """Returns the current path based on the selected key.""" + return self._paths.get(self._path_key) + + @path.setter + def path(self, ext_path): + """Sets the current path key if it exists in paths.""" + if ext_path in self._paths: + self._path_key = ext_path + else: + raise ValueError(f"Invalid path key: {ext_path}") @pytest.fixture def addon() -> AddOn: - """Fixture for creating an installable add-on. + """Fixture for creating an installable add-on.""" + return AddOn(name="WebExtension") - Returns: - :py:class:`AddOn`: Add-on object containing a name and a path to the - add-on. - """ - - # https://github.com/ambv/black/issues/144#issuecomment-392149599 - class AddOn: - def __init__(self): - self.name = "WebExtension" - self.paths = { - "default": "webextension.xpi", - "corrupt": "corruptwebextension.xpi", - } - - def get_path(self, size="default"): - """Returns web extension path.""" - return self.paths.get(size, self.paths["default"]) +@pytest.fixture +def progress_notification( + addon: AddOn, browser: BrowserWindow, webserver: WebServer, selenium: WebDriver +) -> BaseNotification: + """Fixture that triggers the download progress notification. - return AddOn(name="WebExtension", path="webextension.xpi") + Returns: + :py:class:AddOnProgress: Firefox notification. + """ + addon.path = "large" + selenium.get(webserver.url) + element = WebDriverWait(selenium, 10).until( + EC.element_to_be_clickable((By.LINK_TEXT, addon.path)) + ) + element.click() + return browser.wait_for_notification(AddOnProgress) @pytest.fixture @@ -75,8 +98,8 @@ def blocked_notification( :py:class:`AddOnInstallBlocked`: Firefox notification. """ - selenium.get(webserver.url()) - selenium.find_element(By.LINK_TEXT, addon.get_path()).click() + selenium.get(webserver.url) + selenium.find_element(By.LINK_TEXT, addon.path).click() return browser.wait_for_notification(AddOnInstallBlocked) @@ -109,26 +132,17 @@ def complete_notification( @pytest.fixture -def failed_notification(addon, browser, webserver, selenium): - """Fixture that triggers a failed installation notification. - - Returns: - :py:class:`AddOnInstallFailed`: Firefox notification. - """ - selenium.get(webserver.url()) - selenium.find_element(By.LINK_TEXT, addon.get_path("corrupt")).click() - return browser.wait_for_notification(AddOnInstallFailed) - - -@pytest.fixture -def failed_notification(addon, browser, webserver, selenium): +def failed_notification( + addon: AddOn, browser: BrowserWindow, webserver: WebServer, selenium: WebDriver +): """Fixture that triggers a failed installation notification. Returns: :py:class:`AddOnInstallFailed`: Firefox notification. """ - selenium.get(webserver.url()) - selenium.find_element(By.LINK_TEXT, addon.get_path("corrupt")).click() + addon.path = "corrupt" + selenium.get(webserver.url) + selenium.find_element(By.LINK_TEXT, addon.path).click() return browser.wait_for_notification(AddOnInstallFailed) @@ -172,6 +186,9 @@ def test_notification_with_origin( blocked_notification: AddOnInstallBlocked, ) -> None: """Trigger a notification with an origin.""" + assert ( + blocked_notification.origin is not None + ), "Notification origin should not be None" assert f"{webserver.host}" in blocked_notification.origin assert blocked_notification.label is not None @@ -211,7 +228,20 @@ def test_addon_install_complete( browser.wait_for_notification(None) -def test_failed_installation_notification(failed_notification): +def test_failed_installation_notification( + failed_notification: AddOnInstallFailed, +) -> None: """Test that a failed installation notification is shown for a corrupt add-on.""" error_text = "The add-on downloaded from this site could not be installed because it appears to be corrupt." assert failed_notification.error_message == error_text + + +@pytest.mark.parametrize( + "firefox_options", [{"page_load_strategy_none": True}], indirect=True +) +def test_progress_notification_downloading( + browser: BrowserWindow, progress_notification: AddOnProgress +) -> None: + """Verify downloading status is reported correctly.""" + description = progress_notification.is_downloading + assert description is not None diff --git a/tests/web/largewebextension.xpi b/tests/web/largewebextension.xpi new file mode 100644 index 0000000..824178c Binary files /dev/null and b/tests/web/largewebextension.xpi differ diff --git a/tests/webserver.py b/tests/webserver.py index 53a497a..e80fee1 100644 --- a/tests/webserver.py +++ b/tests/webserver.py @@ -55,7 +55,12 @@ def port(self) -> int: """ return self.server.server_address[1] - def start(self) -> None: + @property + def url(self) -> str: + """Web server URL.""" + return "http://{0.host}:{0.port}/".format(self) + + def start(self): """Start web server.""" self.thread.start() @@ -64,18 +69,6 @@ def stop(self) -> None: self.server.shutdown() self.thread.join() - def url(self, path="/") -> str: - """Web server URL. - - Args: - path (str, optional): Path to append to the web server URL. - - Returns: - str: URL of web server. - - """ - return "http://{0.host}:{0.port}{1}".format(self, path) - @classmethod def get_free_port(cls): """Find and return a free port on the system."""