modules/browser_object_tabbar.py (168 lines of code) (raw):

import logging from typing import Union from selenium.common.exceptions import NoSuchElementException from selenium.webdriver import Keys from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support import expected_conditions as EC from modules.page_base import BasePage class TabBar(BasePage): """Page Object Model for tab navigation""" URL_TEMPLATE = "about:blank" class MediaStatus: """Fake enum: just return a string based on a constant name""" def __init__(self): self.PLAYING = "soundplaying" self.MUTED = "muted" self.AUTOPLAY_BLOCKED = "blocked" self.PIP = "pictureinpicture" MEDIA_STATUS = MediaStatus() class ScrollDirection: """Fake enum: Which way are we scrolling tabs""" def __init__(self): self.LEFT = "left" self.RIGHT = "right" SCROLL_DIRECTION = ScrollDirection() @BasePage.context_chrome def new_tab_by_button(self) -> BasePage: """Use the New Tab button (+) to open a new tab""" self.get_element("newtab-button").click() return self def new_tab_by_keys(self, sys_platform: str) -> BasePage: """Use keyboard shortcut to open a new tab""" if sys_platform == "Darwin": self.actions.key_down(Keys.COMMAND).send_keys("t").key_up( Keys.COMMAND ).perform() else: self.actions.key_down(Keys.CONTROL).send_keys("t").key_up( Keys.CONTROL ).perform() return self def new_window_by_keys(self, sys_platform: str) -> BasePage: """Use keyboard shortcut to open a new tab""" if sys_platform == "Darwin": self.actions.key_down(Keys.COMMAND).send_keys("n").key_up( Keys.COMMAND ).perform() else: self.actions.key_down(Keys.CONTROL).send_keys("n").key_up( Keys.CONTROL ).perform() return self def reopen_closed_tab_by_keys(self, sys_platform: str) -> BasePage: """Use keyboard shortcut to reopen a last closed tab""" if sys_platform == "Darwin": self.actions.key_down(Keys.COMMAND).key_down(Keys.SHIFT).send_keys( "t" ).key_up(Keys.SHIFT).key_up(Keys.COMMAND).perform() else: self.actions.key_down(Keys.CONTROL).key_down(Keys.SHIFT).send_keys( "t" ).key_up(Keys.SHIFT).key_up(Keys.CONTROL).perform() return self @BasePage.context_chrome def click_tab_by_title(self, title: str) -> BasePage: """Given a full page title, click the corresponding tab""" self.get_element("tab-by-title", labels=[title]).click() return self @BasePage.context_chrome def get_tab_by_title(self, title: str) -> WebElement: """Given a full page title, return the corresponding tab""" return self.get_element("tab-by-title", labels=[title]) @BasePage.context_chrome def click_tab_by_index(self, index: int) -> BasePage: """Given a tab index (int), click the corresponding tab""" self.get_element("tab-by-index", labels=[str(index)]).click() return self @BasePage.context_chrome def get_tab(self, identifier: Union[str, int]) -> Union[WebElement, None]: """Return a tab root based on either a title or an index""" if isinstance(identifier, int): tab = self.get_element("tab-by-index", labels=[str(identifier)]) elif isinstance(identifier, str): tab = self.get_element("tab-by-title", labels=[identifier]) else: # if we get an unexpected type, we shouldn't assume that the user wants sys exit, # but we have to cause problems for them nonetheless assert False, "Error getting tab root" return tab @BasePage.context_chrome def is_pinned(self, tab_root: WebElement) -> bool: """Is this tab pinned?""" pinned = tab_root.get_attribute("pinned") if pinned in ["true", "false"]: return pinned == "true" else: assert False, "Error checking tab pinned status" @BasePage.context_chrome def click_tab_mute_button(self, identifier: Union[str, int]) -> BasePage: """Click the tab icon overlay, no matter what's happening with media""" logging.info(f"toggling tab mute for {identifier}") tab = self.get_tab(identifier) self.actions.move_to_element(tab).perform() self.get_element("tab-icon-overlay").click() return self @BasePage.context_chrome def get_tab_title(self, tab_element: WebElement) -> str: """Given a tab root element, get the title text of the tab""" tab_label = tab_element.find_element(*self.get_selector("tab-title")) return tab_label.text def expect_tab_sound_status( self, identifier: Union[str, int], status: MediaStatus ) -> BasePage: """Check to see if the tab has an expected MediaStatus""" tab = self.get_tab(identifier) self.wait.until(lambda _: tab.get_attribute(status) is not None) return self def expect_title_contains(self, text: str) -> BasePage: """ Check if the page title contains given text """ self.expect(EC.title_contains(text)) return self @BasePage.context_chrome def open_all_tabs_list(self) -> BasePage: """Click the Tab Visibility / List All Tabs button""" self.get_element("list-all-tabs-button").click() self.expect( EC.text_to_be_present_in_element_attribute( self.get_selector("list-all-tabs-button"), "open", "true" ) ) return self @BasePage.context_chrome def count_tabs_in_all_tabs_menu(self) -> int: """Return the number of entries in the all tabs menu""" all_tabs_menu = self.get_element("all-tabs-menu") all_tabs_entries = all_tabs_menu.find_elements( self.get_selector("all-tabs-entry") ) return len(all_tabs_entries) @BasePage.context_chrome def scroll_tabs(self, direction: ScrollDirection) -> BasePage: """Scroll tabs in tab bar using the < and > scroll buttons""" logging.info(f"Scrolling tabs {direction}") try: scroll_button = self.get_element(f"tab-scrollbox-{direction}-button") scroll_button.click() except NoSuchElementException: logging.info("Could not scroll any further!") return self def get_text_of_all_tabs_entry(self, selected=False, index=0) -> str: """ Given an index or a True for the selected attr, get the text in the corresponding entry in the all tabs menu. ... Parameters ---------- selected: bool Get the selected tab's text? Overrides index. index: int Index of List All Tabs menu entry to get text from Returns ------- str: Text of List All Tabs menu entry. """ entry = None if selected: entry = self.get_element("all-tabs-entry-selected") else: entries = self.get_elements("all-tabs-entry") entry = entries[index] return entry.find_element(By.CLASS_NAME, "all-tabs-button").get_attribute( "label" ) def get_location_of_all_tabs_entry(self, selected=False, index=0) -> dict: """ Given an index or a True for the selected attr, get the location of the entry in the all tabs menu. ... Parameters ---------- selected: bool Get the selected tab's location? Overrides index. index: int Index of List All Tabs menu entry whose location we want. Returns ------- dict: location of entry, keys are 'x' and 'y'. """ entry = None if selected: entry = self.get_element("all-tabs-entry-selected") else: entries = self.get_elements("all-tabs-entry") entry = entries[index] return entry.find_element(By.CLASS_NAME, "all-tabs-button").location @BasePage.context_chrome def scroll_on_all_tabs_menu(self, down=True, pixels=200) -> BasePage: """ Scroll the List All Tabs menu down or up. ... Parameters ---------- down: bool Should we scroll down? A value of False scrolls up. pixels: int The number of pixels to scroll the bar """ menu = self.get_element("all-tabs-menu") logging.info(f"menu location: {menu.location}") logging.info(f"menu size: {menu.size}") # HACK: Can't figure out what the scrollbox selector is, but it's ~4 pixels # off the edge of the menu. x_start = (menu.size["width"] / 2.0) - 4.0 # +Y is down, -Y is up sign = 1 if down else -1 self.actions.move_to_element_with_offset(menu, x_start, 0) self.actions.click_and_hold() self.actions.move_by_offset(0, (sign * pixels)) self.actions.release() self.actions.perform() @BasePage.context_chrome def close_tab(self, tab: WebElement) -> BasePage: """ Given the index of the tab, it closes that tab. """ # cur_tab = self.click_tab_by_index(index) self.get_element("tab-x-icon", parent_element=tab).click() return self def open_web_page_in_new_tab(self, web_page: BasePage, num_tabs: int) -> BasePage: """ Opens a new tab, switches the driver context to the new tab, and opens the given webpage """ self.new_tab_by_button() self.wait_for_num_tabs(num_tabs) self.driver.switch_to.window(self.driver.window_handles[-1]) web_page.open() return self