Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions foxpuppet/windows/browser/tab_bar.py
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):
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we assume that a user testing this will only open one window?

I wouldn't make this assumption. We're providing them with an API for opening new windows and now tabs.

Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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 aka chrome_window_handle. Also it doesn't matter for the test in which window this tab is located in.

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
18 changes: 16 additions & 2 deletions foxpuppet/windows/browser/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from foxpuppet.windows import BaseWindow
from foxpuppet.windows.browser.navbar import NavBar
from foxpuppet.windows.browser.notifications import BaseNotification
from foxpuppet.windows.browser.tab_bar import TabBar


class BrowserWindow(BaseWindow):
Expand All @@ -21,10 +22,23 @@ class BrowserWindow(BaseWindow):
_nav_bar_locator = (By.ID, 'nav-bar')
_notification_locator = (
By.CSS_SELECTOR, '#notification-popup popupnotification')
_tab_browser_locator = (By.ID, 'tabbrowser-tabs')
_tab_bar_locator = (By.ID, 'tabbrowser-tabs')

@property
def navbar(self):
def tab_bar(self):
"""Provide access to the Tab Bar.

Returns:
:py:class:`Tab_Bar`: FoxPuppet Tab Bar object.

"""
window = BaseWindow(self.selenium, self.selenium.current_window_handle)
with self.selenium.context(self.selenium.CONTEXT_CHROME):
el = self.selenium.find_element(*self._tab_bar_locator)
return TabBar(window, el)

@property
def nav_bar(self):
"""Provide access to the Navigation Bar.

Returns:
Expand Down
24 changes: 24 additions & 0 deletions tests/test_browser_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If open_new_tab returned the tab region, then we could simplify this to:

tab = browser.tab_bar.open_new_tab()
tab.close()

assert len(browser.tab_bar.tabs) == 1
Copy link
Member

Choose a reason for hiding this comment

The 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()
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? Selenium has to switch_to that tab even though firefox switches to it automatically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean like browser.tab_bar.current_tab or an attribute on the tab to indicate if it's the currently selected tab. This would allow you to store the currently selected tab, open a tab, close the new tab, and assert that we're back to the original tab.

assert 'https://www.mozilla.org/' in selenium.current_url