-
Notifications
You must be signed in to change notification settings - Fork 15
Added tabbar and tab representation #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||
# You can obtain one at http://mozilla.org/MPL/2.0/ | ||
"""Creates tab_bar object to interact with the Firefox tabs and tabbar.""" | ||
|
||
from selenium.webdriver.common.by import By | ||
|
||
from foxpuppet.region import Region | ||
|
||
|
||
class TabBar(Region): | ||
"""Representation of the tab bar which contains the tabs. | ||
|
||
Args: | ||
window (:py:class:`BaseWindow`): Window object this region appears in. | ||
root | ||
(:py:class:`~selenium.webdriver.remote.webelement.WebElement`): | ||
WebDriver element object that serves as the root for the | ||
region. | ||
|
||
""" | ||
|
||
_new_tab_button_locator = (By.ID, 'new-tab-button') | ||
_tabs_locator = (By.TAG_NAME, 'tab') | ||
|
||
@property | ||
def tabs(self): | ||
"""Return a list of tabs. | ||
|
||
Returns: :py:class:`~foxpuppet.window.browser.tab_bar.Tab` | ||
|
||
""" | ||
with self.selenium.context(self.selenium.CONTEXT_CHROME): | ||
tabs = [self.Tab(self, el) for el in | ||
self.selenium.find_elements(*self._tabs_locator)] | ||
# Assign handles | ||
for tab, handle in zip(tabs, self.selenium.window_handles): | ||
tab.handle = handle | ||
return tabs | ||
|
||
@property | ||
def selected_index(self): | ||
"""The index of the currently selected tab. | ||
|
||
:return: Index of the selected tab. | ||
""" | ||
with self.selenium.context(self.selenium.CONTEXT_CHROME): | ||
tab_bar = self.selenium.find_element(By.ID, 'tabbrowser-tabs') | ||
return int(tab_bar.get_property('selectedIndex')) | ||
|
||
@property | ||
def selected_tab(self): | ||
"""A :class:`Tab` instance of the currently selected tab. | ||
|
||
:returns: :class:`Tab` instance. | ||
""" | ||
return self.tabs[self.selected_index] | ||
|
||
def open_new_tab(self): | ||
"""Open a new tab in the current window. | ||
|
||
Returns: list of :py:class:`Tab`. | ||
|
||
""" | ||
starting_tabs = self.selenium.window_handles | ||
with self.selenium.context(self.selenium.CONTEXT_CHROME): | ||
self.selenium.find_element(*self._new_tab_button_locator).click() | ||
# Wait for tab to open | ||
self.wait.until( | ||
lambda s: len(s.window_handles) == len(starting_tabs) + 1) | ||
|
||
current_tabs = self.selenium.window_handles | ||
[new_handle] = list(set(current_tabs) - set(starting_tabs)) | ||
[new_tab] = [tab for tab in self.tabs if tab.handle == new_handle] | ||
|
||
# if the new tab is the currently selected tab, switch to it | ||
if new_tab == self.selected_tab: | ||
new_tab.focus() | ||
|
||
return new_tab | ||
|
||
class Tab(Region): | ||
"""Representaion of the Tab. | ||
|
||
Args: | ||
window (:py:class:`BaseWindow`): Window object this region appears | ||
in. | ||
root | ||
(:py:class:`~selenium.webdriver.remote.webelement.WebElement`): | ||
WebDriver element object that serves as the root for the | ||
region. | ||
|
||
""" | ||
|
||
def close(self): | ||
"""Close the selected tab.""" | ||
tab_closed = self.handle | ||
with self.selenium.context(self.selenium.CONTEXT_CHROME): | ||
button = self.root.find_anonymous_element_by_attribute( | ||
'anonid', 'close-button') | ||
button.click() | ||
self.wait.until( | ||
lambda s: tab_closed not in s.window_handles, | ||
message='No new tab has been opened.') | ||
# Switch to last available tab | ||
self.selenium.switch_to.window(self.selenium.window_handles[-1]) | ||
|
||
@property | ||
def selected(self): | ||
"""Checks if the tab is selected. | ||
|
||
:return: `True` if the tab is selected. | ||
""" | ||
with self.selenium.context(self.selenium.CONTEXT_CHROME): | ||
return self.selenium.execute_script(""" | ||
return arguments[0].hasAttribute('selected'); | ||
""", self.root) | ||
|
||
def focus(self): | ||
"""Focus the tab. | ||
|
||
Returns: :py:class:`Tab`. | ||
|
||
""" | ||
with self.selenium.context(self.selenium.CONTEXT_CHROME): | ||
self.root.click() | ||
self.wait.until( | ||
lambda _: self.selected, | ||
message='Tab with handle "{0}" could not be selected.'.format( | ||
self.handle)) | ||
self.selenium.switch_to.window(self.handle) | ||
|
||
@property | ||
def handle(self): | ||
"""Return the handle of the tab. | ||
|
||
Returns: Selenium Firefox window handle. | ||
|
||
""" | ||
return self._handle | ||
|
||
@handle.setter | ||
def handle(self, value): | ||
"""Handle setter.""" | ||
self._handle = value |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,3 +54,27 @@ def test_tracking_protection_shield(foxpuppet, selenium): | |
selenium.get('https://www.washingtonpost.com/') | ||
WebDriverWait(selenium, timeout=5).until( | ||
lambda _: browser.navbar.is_tracking_shield_displayed) | ||
|
||
|
||
def test_open_new_tab(browser, selenium): | ||
"""Test Open new Tab.""" | ||
assert len(browser.tab_bar.tabs) == 1 | ||
browser.tab_bar.open_new_tab() | ||
assert len(browser.tab_bar.tabs) == 2 | ||
|
||
|
||
def test_close_tab(browser, selenium): | ||
"""Test close Tab.""" | ||
browser.tab_bar.open_new_tab() | ||
browser.tab_bar.tabs[1].close() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If tab = browser.tab_bar.open_new_tab()
tab.close() |
||
assert len(browser.tab_bar.tabs) == 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could also ensure the handle for the new tab is not in the list of handles. |
||
|
||
|
||
def test_select_tab(browser, selenium): | ||
"""Test selecting a tab.""" | ||
selenium.get('https://www.mozilla.org/') | ||
tabs = browser.tab_bar.open_new_tab() | ||
tabs[-1].select() | ||
assert 'about:newtab' in selenium.current_url | ||
tabs[0].select() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes me feel like we should have a way to access the currently displayed tab, but that can be a later enhancement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? Selenium has to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean like |
||
assert 'https://www.mozilla.org/' in selenium.current_url |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we be sure that the tab elements and window handles will be returned in the same order? What if there are two windows, each with multiple tabs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll look into this. I was under the impression that it will be in order of tab opened since it is a list.
Now thinking about the multiple window situation...technically, geckodriver would see it as the same session since you can have multiple windows per profile already. I don't know if there is a way to differentiate tabs from windows as far as
window_handles
goes. I think they are all in one.Its a little tricky because do we assume that a user testing this will only open one window? If there is no way to tell a window and a tab apart, unless we create a foxpuppet tracking object that has a
dict
object and can tell the user which is which depending on when they opened it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't make this assumption. We're providing them with an API for opening new windows and now tabs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no order of window handles at all. Also note that window handles can change right now given if the tab is moved to a different content process (like remoteness change).
Chrome windows of Firefox are handled via
chrome_window_handles
but not sure if we pass those through geckodriver yet.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going along with whimboo, it seems as if it doesn't matter if you open a window or a tab, selenium sees that as a
window_handle
.We could either keep track of each event with a dict but would that be necessary? It seems as if we need an easy way to differentiate between a
chrome
window handle and a "Normal" window_handle.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are going to open a new tab, you are interested in that tab aka
window_handle
and not the window akachrome_window_handle
. Also it doesn't matter for the test in which window this tab is located in.