modules/page_object_prefs.py (379 lines of code) (raw):

import re from time import sleep from typing import List from selenium.webdriver import Firefox from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.select import Select from modules.browser_object import Navigation from modules.classes.autofill_base import AutofillAddressBase from modules.classes.credit_card import CreditCardBase from modules.components.dropdown import Dropdown from modules.page_base import BasePage from modules.util import Utilities class AboutPrefs(BasePage): """ Page Object Model for about:preferences Attributes ---------- driver: selenium.webdriver.Firefox WebDriver object under test """ URL_TEMPLATE = "about:preferences#{category}" iframe = None def __init__(self, driver: Firefox, **kwargs): super().__init__(driver, **kwargs) self.driver = driver # number of tabs to reach the country tab TABS_TO_COUNTRY = 6 TABS_TO_SAVE_CC = 5 class HttpsOnlyStatus: """Fake enum: return a string based on a constant name""" def __init__(self): self.HTTPS_ONLY_ALL = "httpsonly-radio-enabled" self.HTTPS_ONLY_PRIVATE = "httpsonly-radio-enabled-pbm" self.HTTPS_ONLY_DISABLED = "httpsonly-radio-disabled" HTTPS_ONLY_STATUS = HttpsOnlyStatus() # Function Organization ## Search and Settings def search_engine_dropdown(self) -> Dropdown: """Returns the Dropdown region for search engine prefs""" return Dropdown( self, self.driver, root=self.get_element("search-engine-dropdown-root") ) def find_in_settings(self, term: str) -> BasePage: """Search via the Find in Settings bar, return self.""" search_input = self.get_element("find-in-settings-input") search_input.clear() search_input.send_keys(term) return self def set_alternative_language(self, lang_code: str) -> BasePage: """Changes the browser language""" self.get_element("language-set-alternative-button").click() self.driver.switch_to.frame(self.get_iframe()) # Download the language options select_language = self.get_element("language-settings-select") select_language.click() search_languages = self.get_element("language-settings-search") search_languages.click() select_language.click() # Select the language, add, and make sure it appears select_language.click() self.get_element("language-option-by-code", labels=[lang_code]).click() select_language.click() self.get_element("language-settings-add-button").click() self.expect_element_attribute_contains( "language-added-list", "last-selected", f"locale-{lang_code}" ) self.get_element("language-settings-ok").click() return self def select_https_only_setting(self, option_id: HttpsOnlyStatus) -> BasePage: """ Click the HTTPS Only option given """ self.find_in_settings("HTTPS") self.element_clickable(str(option_id)) self.click_on(str(option_id)) self.expect_element_attribute_contains(str(option_id), "checked", "") return self def set_default_zoom_level(self, zoom_percentage: int) -> BasePage: """ Sets the Default Zoom level in about:preferences. """ self.click_on("default-zoom-dropdown") with self.driver.context(self.driver.CONTEXT_CHROME): self.click_on("default-zoom-dropdown-value", labels=[f"{zoom_percentage}"]) self.click_on("default-zoom-dropdown") return self def select_content_and_action(self, content_type: str, action: str) -> BasePage: """ From the applications list that handles how downloaded media is used, select a content type and action """ el = self.get_element("actions-menu", labels=[content_type]) el.click() self.click_on("actions-menu-option", labels=[content_type, action]) self.wait.until(lambda _: el.get_attribute("label") == action) return self def get_history_menulist(self) -> WebElement: """ Gets the webelement for the list of history items that appear in about:preferences """ return self.get_element("history_menulist") ## Payment and Address Management def verify_cc_json( self, cc_info_json: dict, credit_card_fill_obj: CreditCardBase ) -> BasePage: """ Does the assertions that ensure all the extracted information (the cc_info_json) is the same as the generated fake credit_card_fill_obj data. ... Attributes ---------- cc_info_json: dict The dictionary that is the json representation of the extracted information from a web page credit_card_fill_obj: CreditCardBase The object that contains all the generated information """ assert cc_info_json["name"] == credit_card_fill_obj.name assert cc_info_json["number"][-4:] == credit_card_fill_obj.card_number[-4:] assert int(cc_info_json["month"]) == int(credit_card_fill_obj.expiration_month) return self def verify_cc_edit_saved_payments_profile( self, credit_card_fill_obj: CreditCardBase ): """ Verify saved payment profile data is the same as the generated fake credit_card_fill_obj data. Make sure cvv is not displayed. Arguments: credit_card_fill_obj: CreditCardBase The object that contains all the generated information """ self.switch_to_edit_saved_payments_popup_iframe() form_container = self.get_element("form-container") input_elements = form_container.find_elements(By.TAG_NAME, "input") expected_cc_data = [ int(val) if val.isnumeric() else val for val in credit_card_fill_obj.__dict__.values() ] expected_cvv = int(credit_card_fill_obj.cvv) for element in input_elements: field_name = element.get_attribute("id") if field_name.startswith("cc"): field_value = element.get_attribute("value") if field_value.isnumeric(): field_value = int(field_value) assert field_value in expected_cc_data, ( f"{(field_name, field_value)} not found in generated data." ) assert field_value != expected_cvv, "CVV is displayed." select_elements = form_container.find_elements(By.TAG_NAME, "select") for element in select_elements: field_name = element.get_attribute("id") if field_name.startswith("cc"): val = Select(element) # Only get the last two digits field_value = val.first_selected_option.get_attribute("value")[-2:] if field_value.isnumeric(): field_value = int(field_value) assert field_value in expected_cc_data, ( f"{(field_name, field_value)} not found in generated data." ) assert field_value != expected_cvv, "CVV is displayed." return self def get_saved_payments_popup(self) -> WebElement: """ Open saved payments dialog panel """ return self.get_element("prefs-button", labels=["Saved payment methods"]) def click_edit_on_dialog_element(self): """ Click on edit button on dialog panel """ edit_button = self.get_element( "panel-popup-button", labels=["autofill-manage-edit-button"] ) self.expect(EC.element_to_be_clickable(edit_button)) edit_button.click() return self def click_add_on_dialog_element(self): """ Click on add button on dialog panel """ add_button = self.get_element( "panel-popup-button", labels=["autofill-manage-add-button"] ) self.expect(EC.element_to_be_clickable(add_button)) add_button.click() return self def open_and_switch_to_saved_payments_popup(self) -> BasePage: """ Open and Switch to saved payments popup frame. """ saved_payments_iframe = self.get_saved_payments_popup_iframe() self.driver.switch_to.frame(saved_payments_iframe) return self def fill_and_save_cc_panel_information( self, credit_card_fill_information: CreditCardBase ): """ Takes the sample cc object and fills it into the popup panel in the about:prefs section under saved payment methods. Arguments: credit_card_fill_information: The object containing all the sample data """ fields = { "card_number": credit_card_fill_information.card_number, "expiration_month": credit_card_fill_information.expiration_month, "expiration_year": f"20{credit_card_fill_information.expiration_year}", "name": credit_card_fill_information.name, } for field in fields: self.actions.send_keys(fields[field] + Keys.TAB).perform() # Press tab again to navigate to the next field (this accounts for the second tab after the name field) self.actions.send_keys(Keys.TAB).perform() # Finally, press enter self.actions.send_keys(Keys.ENTER).perform() def add_entry_to_saved_payments(self, cc_data: CreditCardBase): """ Takes the sample AutofillAddressBase object and adds an entry to the saved addresses list. Switches the appropriate frames to accommodate the operation. Exits after adding entry Arguments: cc_data: The object containing all the sample data """ self.switch_to_saved_payments_popup_iframe() self.fill_and_save_cc_panel_information(cc_data) self.switch_to_default_frame() self.close_dialog_box() return self def close_dialog_box(self): """Close dialog box for saved addresses or payments.""" self.element_clickable("panel-popup-button", labels=["close-button"]) self.get_element("panel-popup-button", labels=["close-button"]).click() return self def update_cc_field_panel(self, field_name: str, value: str | int) -> BasePage: """ Updates a field in the credit card popup panel in about:prefs Change value of the field_name given """ fields = { "card_number": "cc-number", "expiration_month": "cc-exp-month", "expiration_year": "cc-exp-year", "name": "cc-name", } if field_name not in fields.keys(): raise ValueError( f"{field_name} is not a valid field name for the cc dialog form." ) self.switch_to_edit_saved_payments_popup_iframe() value_field = self.find_element(By.ID, fields[field_name]) if value_field.tag_name != "select": value_field.clear() value_field.send_keys(value) self.get_element("save-button").click() return self def get_saved_addresses_popup(self) -> WebElement: """ Returns saved addresses button element """ return self.get_element("prefs-button", labels=["Saved addresses"]) def open_and_switch_to_saved_addresses_popup(self) -> BasePage: """ Open and Switch to saved addresses popup frame. """ saved_address_iframe = self.get_saved_addresses_popup_iframe() self.driver.switch_to.frame(saved_address_iframe) return self def add_entry_to_saved_addresses(self, address_data: AutofillAddressBase): """ Takes the sample AutofillAddressBase object and adds an entry to the saved addresses list. Switches the appropriate frames to accommodate the operation. Exits after adding entry Arguments: address_data: The object containing all the sample data """ self.switch_to_edit_saved_addresses_popup_iframe() self.fill_and_save_address_panel_information(address_data) self.switch_to_default_frame() self.close_dialog_box() return self def get_all_saved_cc_profiles(self) -> List[WebElement]: """Gets the saved credit card profiles in the cc panel""" self.switch_to_saved_payments_popup_iframe() element = Select(self.get_element("cc-saved-options")) return element.options def get_all_saved_address_profiles(self) -> List[WebElement]: """Gets the saved credit card profiles in the cc panel""" self.switch_to_saved_addresses_popup_iframe() select_el = self.get_element("address-saved-options") if len(select_el.get_attribute("innerHTML")) > 1: return Select(select_el).options return [] def extract_address_data_from_saved_addresses_entry( self, util: Utilities, region: str = "US" ) -> AutofillAddressBase: """ Extracts the data from the saved addresses entry to a AutofillAddressBase object. Arguments: util: Utility instance region: country code in use """ self.switch_to_edit_saved_addresses_popup_iframe() fields = { "name": "", "organization": "", "street-address": "", "address-level2": "", "address-level1": "", "postal-code": "", "tel": "", "country": "", "email": "", } for key in fields.keys(): el = self.find_element(By.ID, key) if el.tag_name == "select": fields[key] = Select(el).first_selected_option.text else: fields[key] = el.get_attribute("value") return AutofillAddressBase( name=fields.get("name"), given_name=fields.get("name", "").split()[0], family_name=fields.get("name", "").split()[1], organization=fields.get("organization"), street_address=fields.get("street-address"), address_level_2=fields.get("address-level2"), address_level_1=fields.get("address-level1"), postal_code=fields.get("postal-code"), country=fields.get("country"), country_code=region, email=fields.get("email"), telephone=util.normalize_regional_phone_numbers(fields.get("tel"), region), ) ## UI Navigation and Iframe Handling def get_saved_payments_popup_iframe(self) -> WebElement: """ Returns the iframe object for the dialog panel in the popup """ self.get_saved_payments_popup().click() iframe = self.get_element("browser-popup") return iframe def switch_to_edit_saved_payments_popup_iframe(self) -> BasePage: """ Switch to form iframe to edit saved payments. """ self.switch_to_default_frame() self.switch_to_iframe(2) return self def press_button_get_popup_dialog_iframe(self, button_label: str) -> WebElement: """ Returns the iframe object for the dialog panel in the popup after pressing some button that triggers a popup """ self.get_element("prefs-button", labels=[button_label]).click() iframe = self.get_element("browser-popup") return iframe def get_saved_addresses_popup_iframe(self) -> WebElement: """ Returns the iframe object for the dialog panel in the popup """ self.get_saved_addresses_popup().click() iframe = self.get_element("browser-popup") return iframe def switch_to_saved_addresses_popup_iframe(self) -> BasePage: """ switch to save addresses popup frame. """ self.switch_to_default_frame() self.switch_to_iframe(1) return self def switch_to_saved_payments_popup_iframe(self) -> BasePage: """ switch to save payments popup frame. """ self.switch_to_default_frame() self.switch_to_iframe(1) return self def switch_to_edit_saved_addresses_popup_iframe(self) -> BasePage: """ Switch to form iframe to edit saved addresses. """ self.switch_to_default_frame() self.switch_to_iframe(2) return self def get_iframe(self) -> WebElement: """ Gets the webelement for the iframe that commonly appears in about:preferences """ return self.get_element("browser-popup") def get_password_exceptions_popup_iframe(self) -> WebElement: """ Returns the iframe object for the Password Exceptions dialog panel in the popup. """ # Click on the "Password Exceptions" button self.get_element("logins-exceptions").click() # Get the iframe element for the popup iframe = self.get_element("browser-popup") return iframe ## Data Extraction and Processing def set_country_autofill_panel(self, country: str) -> BasePage: """Sets the country value in the autofill view""" select_country = Select(self.driver.find_element(By.ID, "country")) select_country.select_by_value(country) return self def fill_and_save_address_panel_information( self, address_data: AutofillAddressBase ) -> BasePage: """ Takes the sample AutofillAddressBase object and fills it into the popup panel in the about:prefs section under saved addresses methods. Arguments: address_data: The object containing all the sample data """ fields = { "name": address_data.name, "organization": address_data.organization, "street-address": address_data.street_address, "address-level2": address_data.address_level_2, "address-level1": address_data.address_level_1, "postal-code": address_data.postal_code, "tel": address_data.telephone, "email": address_data.email, } self.set_country_autofill_panel(address_data.country_code) form_element = self.get_element("form-container") children = [ x.get_attribute("id") for x in form_element.find_elements(By.CSS_SELECTOR, "*") ] for key, val in fields.items(): if key in children: form_element.find_element(By.ID, key).send_keys(val) self.get_element("save-button").click() return self def get_clear_cookie_data_value(self) -> int | None: """ With the 'Clear browsing data and cookies' popup open, returns the <memory used> value of the option for 'Cookies and site data (<memory used>)'. The <memory used> value for no cookies is '0 bytes', otherwise values are '### MB', or '### KB' """ # Find the dialog option elements containing the checkbox label options = self.get_elements("clear-data-dialog-options") # Extract the text from the label the second option second_option = options[1] label_text = second_option.text print(f"The text of the option is: {label_text}") # Use a regular expression to find the memory usage match = re.search(r"\d+", label_text) if match: number_str = match.group() # Extract the matched number as a string number = int(number_str) # Convert the number to an integer print(f"The extracted value is: {number}") return number else: print("No number found in the string") def get_manage_data_site_element(self, site: str) -> WebElement: """ Returns the WebElement for the given site in the manage site data popup """ element = self.get_element("manage-cookies-site", labels=[site]) return element ## Utility Functions def import_bookmarks(self, browser_name: str) -> BasePage: """ Press the import browser data button """ MAX_TRIES = 16 self.click_on("import-browser-data") sleep(2) tries = 0 # Keep cycling through the options until you get it # Using keys for most of this because clicking elements is flaky for some reason while ( browser_name.lower() not in self.get_element("browser-profile-selector").text.lower() and tries < MAX_TRIES ): self.actions.send_keys(" ").perform() for _ in range(tries): self.actions.send_keys(Keys.DOWN).perform() self.actions.send_keys(" ").perform() sleep(1) tries += 1 self.click_on("migration-import-button") # There are two messages that indicate a successful migration self.wait.until( lambda _: self.get_element("migration-progress-header").text in ["Data Imported Successfully", "Data Import Complete"] ) self.actions.send_keys(" ").perform() return self def click_popup_panel_button(self, field: str) -> BasePage: """Clicks the popup panel button for the specified field""" if self.iframe: with self.driver.switch_to.frame(self.iframe): self.get_element("panel-popup-button", labels=[field]).click() else: self.get_element("panel-popup-button", labels=[field]).click() return self class AboutAddons(BasePage): """ The POM for the about:addons page Attributes ---------- driver: selenium.webdriver.Firefox WebDriver object under test """ URL_TEMPLATE = "about:addons" def choose_sidebar_option(self, option: str): """ Clicks the corresponding sidebar option from the about:addons page. """ self.get_element("sidebar-options", labels=[option]).click() def activate_theme( self, nav: Navigation, theme_name: str, intended_color: str, perform_assert=True ): """ Clicks the theme card and presses enable. Then verifies that the theme is the correct color. Attributes ---------- nav: Navigation The navgiation object theme_name: str The name of the theme to press intended_color: str The RGB string that is the intended color of the element """ self.get_element("theme-card", labels=[theme_name]).click() self.get_element("enable-theme").click() self.expect( EC.text_to_be_present_in_element_attribute( self.get_selector("enable-theme"), "innerText", "Disable" ) ) with self.driver.context(self.driver.CONTEXT_CHROME): navigation_component = nav.get_element("navigation-background-component") background_color = navigation_component.value_of_css_property( "background-color" ) if perform_assert: assert background_color == intended_color else: return background_color def is_devedition(self): active_theme_el = self.driver.find_element( By.CSS_SELECTOR, ".card.addon[active] h3.addon-name" ) active_theme_name = active_theme_el.text.lower() return "dark" in active_theme_name or "developer edition" in active_theme_name def enabled_theme_matches(self, expected_theme: str) -> bool: """ Check the enabled theme name against any string. """ enabled_theme = self.get_element("enabled-theme-title").get_attribute( "innerText" ) return enabled_theme == expected_theme def check_theme_has_changed(self, original_theme: str) -> BasePage: """ Ensure that the theme has changed. """ assert not self.enabled_theme_matches(original_theme) return self