modules/browser_object_navigation.py (249 lines of code) (raw):

import logging from selenium.common.exceptions import TimeoutException from selenium.webdriver import ActionChains, 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 modules.classes.bookmark import Bookmark from modules.page_base import BasePage from modules.util import BrowserActions class Navigation(BasePage): """Page Object Model for nav buttons, AwesomeBar and toolbar""" URL_TEMPLATE = "about:blank" BROWSER_MODES = { "Bookmarks": "*", "Tabs": "%", "History": "^", "Actions": ">", } VALID_SEARCH_MODES = { "Google", "eBay", "Amazon.com", "Bing", "DuckDuckGo", "Wikipedia (en)", } def __init__(self, driver: Firefox, **kwargs): super().__init__(driver, **kwargs) self.search_bar = None self.awesome_bar = None self.change_search_settings_button = None @BasePage.context_content def expect_in_content(self, condition) -> BasePage: """Like BasePage.expect, but guarantee we're looking at CONTEXT_CONTENT""" self.wait.until(condition) return self @BasePage.context_chrome def set_awesome_bar(self) -> BasePage: """Set the awesome_bar attribute of the Navigation object""" self.awesome_bar = self.get_element("awesome-bar") return self @BasePage.context_chrome def get_awesome_bar(self) -> WebElement: """Get the Awesome Bar. Prefer this over get_element.""" self.set_awesome_bar() return self.awesome_bar @BasePage.context_chrome def get_awesome_bar_text(self): """ Get the text directly from the awesome bar. This is different from 'driver.current_url' which pulls from href """ awesome_bar = self.get_element("awesome-bar").get_attribute("value") return awesome_bar @BasePage.context_chrome def clear_awesome_bar(self) -> BasePage: """Clear the Awesome Bar. Prefer this over get_element("awesome-bar").clear()""" self.set_awesome_bar() self.awesome_bar.clear() return self @BasePage.context_chrome def type_in_awesome_bar(self, term: str) -> BasePage: """Enter text into the Awesome Bar. You probably want self.search()""" self.set_awesome_bar() self.awesome_bar.click() self.awesome_bar.send_keys(term) return self def set_search_mode_via_awesome_bar(self, mode: str) -> BasePage: """ Given a `mode`, set the Awesome Bar search mode. Returns self. Parameter --------- mode: str The name of the search mode, or the keystroke shortcut (e.g. ^ for History) """ if mode in self.BROWSER_MODES: abbr = self.BROWSER_MODES[mode] else: abbr = mode.lower()[:2] self.type_in_awesome_bar(abbr) self.wait.until( EC.visibility_of_element_located( self.get_selector("tab-to-search-text-span") ) ) self.awesome_bar.send_keys(Keys.TAB) self.wait.until( EC.text_to_be_present_in_element( self.get_selector("search-mode-span"), mode ) ) return self @BasePage.context_chrome def search(self, term: str, mode=None) -> BasePage: """ Search using the Awesome Bar, optionally setting the search mode first. Returns self. Attributes ---------- term : str The search term mode : str | None If set, the name or keystroke shortcut of the search mode """ if mode: self.set_search_mode_via_awesome_bar(mode).type_in_awesome_bar( term + Keys.ENTER ) else: self.type_in_awesome_bar(term + Keys.ENTER) return self @BasePage.context_chrome def set_search_bar(self) -> BasePage: """Set the search_bar attribute of the Navigation object""" self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox") return self @BasePage.context_chrome def search_bar_search(self, term: str) -> BasePage: """ Search using the *Old* Search Bar. Returns self. Attributes ---------- term : str The search term """ self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox") self.search_bar.click() self.search_bar.send_keys(term + Keys.ENTER) return self @BasePage.context_chrome def type_in_search_bar(self, term: str) -> BasePage: """ Type in the *Old* Search Bar. Returns self. Attributes ---------- term : str The search term """ self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox") self.search_bar.click() self.search_bar.send_keys(term) return self def open_awesome_bar_settings(self): """Open search settings from the awesome bar""" self.click_on("search-settings") return self @BasePage.context_chrome def click_on_change_search_settings_button(self) -> BasePage: self.search_bar = self.find_element(By.CLASS_NAME, "searchbar-textbox") self.search_bar.click() self.change_search_settings_button = self.find_element( By.ID, "searchbar-anon-search-settings" ) self.change_search_settings_button.click() return self @BasePage.context_chrome def click_in_awesome_bar(self) -> BasePage: self.set_awesome_bar() self.awesome_bar.click() return self @BasePage.context_chrome def click_search_mode_switcher(self) -> BasePage: """ click search mode switcher """ self.search_mode_switcher = self.get_element("searchmode-switcher") self.search_mode_switcher.click() return self @BasePage.context_chrome def set_search_mode(self, search_mode: str) -> BasePage: """ set new search location if search_mode in VALID_SEARCH_MODES Parameter: search_mode (str): search mode to be selected Raises: StopIteration: if a valid search mode is not found in the list of valid elements. """ # check if search_mode is valid, otherwise raise error. if search_mode not in self.VALID_SEARCH_MODES: raise ValueError("search location is not valid.") # switch to chrome context # get list of all valid search modes and filter by label self.get_element("search-mode-switcher-option", labels=[search_mode]).click() return self def context_click_in_awesome_bar(self) -> BasePage: self.set_awesome_bar() actions = ActionChains(self.driver) actions.context_click(self.awesome_bar).perform() return self @BasePage.context_chrome def get_download_button(self) -> WebElement: """ Gets the download button WebElement """ downloads_button = self.get_element("downloads-button") return downloads_button def click_download_button(self) -> BasePage: self.get_download_button().click() return self @BasePage.context_chrome def wait_for_download_animation_finish( self, downloads_button: WebElement ) -> BasePage: """ Waits for the download button to finish playing the animation for downloading to local computer """ try: self.wait.until( lambda _: downloads_button.get_attribute("notification") == "finish" ) except TimeoutException: logging.warning("Animation did not finish or did not play.") return self @BasePage.context_chrome def open_tracker_panel(self) -> BasePage: """ Clicks the shield icon and opens the panel associated with it """ self.get_element("shield-icon").click() return self def wait_for_item_to_download(self, filename: str) -> BasePage: """ Check the downloads tool in the toolbar to wait for a given file to download """ original_timeout = self.driver.timeouts.implicit_wait try: # Whatever our timeout, we want to lengthen it because downloads self.driver.implicitly_wait(original_timeout * 2) self.element_visible("downloads-item-by-file", labels=[filename]) self.expect_not( EC.element_attribute_to_include( self.get_selector("downloads-button"), "animate" ) ) with self.driver.context(self.context_id): self.driver.execute_script( "arguments[0].setAttribute('hidden', true)", self.get_element("downloads-button"), ) finally: self.driver.implicitly_wait(original_timeout) return self @BasePage.context_chrome def refresh_page(self) -> BasePage: """ Refreshes the current page by clicking the refresh button in the browser. """ self.get_element("refresh-button").click() self.wait_for_page_to_load() return self def handle_geolocation_prompt(self, button_type="primary"): """ Handles geolocation prompt by clicking either the 'Allow' or 'Block' button based on the button_type provided """ button_selector = f"popup-notification-{button_type}-button" self.element_clickable(button_selector) self.click_on(button_selector) def open_searchmode_switcher_settings(self): """Open search settings from searchmode switcher in awesome bar""" self.click_on("searchmode-switcher") self.click_on("searchmode-switcher-settings") return self @BasePage.context_chrome def select_element_in_nav(self, element: str) -> BasePage: self.get_element(element).click() return self # Bookmark @BasePage.context_chrome def add_bookmark_via_star_icon(self) -> BasePage: """ Bookmark a site via star button and click save on the bookmark panel """ self.get_element("star-button").click() self.get_element("save-bookmark-button").click() return self @BasePage.context_chrome def verify_star_button_is_blue(self) -> BasePage: """ Verifies that the star button is blue (indicating a bookmarked page) """ self.element_visible("blue-star-button") return self @BasePage.context_chrome def bookmark_page_other(self) -> BasePage: self.get_element("star-button").click() dropdown = self.get_element("bookmarks-type-dropdown") dropdown.click() self.get_element("bookmarks-type-dropdown-other").click() dropdown.click() self.get_element("save-bookmark-button").click() return self @BasePage.context_chrome def add_bookmark_advanced( self, bookmark_data: Bookmark, ba: BrowserActions ) -> BasePage: iframe = self.get_element("bookmark-iframe") ba.switch_to_iframe_context(iframe) # fill name if bookmark_data.name is not None: self.actions.send_keys(bookmark_data.name).perform() self.actions.send_keys(Keys.TAB).perform() # fill url self.actions.send_keys(bookmark_data.url + Keys.TAB).perform() # fill tags if bookmark_data.tags is not None: self.actions.send_keys(bookmark_data.tags).perform() self.actions.send_keys(Keys.TAB).perform() # fill keywords if bookmark_data.keyword is not None: self.actions.send_keys(bookmark_data.keyword).perform() self.actions.send_keys(Keys.TAB, Keys.TAB, Keys.TAB, Keys.ENTER).perform() ba.switch_to_content_context() return self @BasePage.context_chrome def toggle_bookmarks_toolbar_with_key_combo(self) -> BasePage: """Use Cmd/Ctrl + B to open the Print Preview, wait for load""" if self.sys_platform() == "Darwin": mod_key = Keys.COMMAND else: mod_key = Keys.CONTROL self.perform_key_combo(mod_key, Keys.SHIFT, "b") return self @BasePage.context_chrome def confirm_bookmark_exists(self, match_string: str) -> BasePage: """ For a given string, return self if it exists in the label of a bookmark, else assert False. """ bookmarks = self.get_elements("bookmark-in-bar") logging.info(f"Found {len(bookmarks)} bookmarks.") for el in bookmarks: logging.info(el.get_attribute("label")) matches_short_string = any( [match_string in el.get_attribute("label") for el in bookmarks] ) matches_long_string = any( [el.get_attribute("label") in match_string for el in bookmarks] ) assert matches_short_string or matches_long_string return self