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