Bug 1573383 - [marionette] Remove firefox-puppeteer Python package. r=marionette-reviewers,maja_zf

Differential Revision: https://phabricator.services.mozilla.com/D61687

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Henrik Skupin 2020-02-13 19:50:42 +00:00
parent ef3e805957
commit 255a87c4a6
73 changed files with 0 additions and 5059 deletions

View File

@ -126,14 +126,12 @@ ARCHIVE_FILES = {
'patterns': [
'client/**',
'harness/**',
'puppeteer/**',
'mach_test_package_commands.py',
],
'dest': 'marionette',
'ignore': [
'client/docs',
'harness/marionette_harness/tests',
'puppeteer/firefox/docs',
],
},
{

View File

@ -2017,10 +2017,6 @@ VARIABLES = {
"""List of manifest files defining firefox-ui-functional tests.
"""),
'PUPPETEER_FIREFOX_MANIFESTS': (ManifestparserManifestList, list,
"""List of manifest files defining puppeteer unit tests for Firefox.
"""),
'MARIONETTE_LAYOUT_MANIFESTS': (ManifestparserManifestList, list,
"""List of manifest files defining marionette-layout tests.
"""),

View File

@ -48,7 +48,6 @@ TEST_MANIFESTS = dict(
ANDROID_INSTRUMENTATION=('instrumentation', 'instrumentation', '.', False),
FIREFOX_UI_FUNCTIONAL=('firefox-ui-functional', 'firefox-ui', '.', False),
FIREFOX_UI_UPDATE=('firefox-ui-update', 'firefox-ui', '.', False),
PUPPETEER_FIREFOX=('firefox-ui-functional', 'firefox-ui', '.', False),
PYTHON_UNITTEST=('python', 'python', '.', False),
CRAMTEST=('cram', 'cram', '.', False),
TELEMETRY_TESTS_CLIENT=(

View File

@ -9,6 +9,3 @@
../marionette/client
../marionette/harness
# Allows to use the Puppeteer page object model for Firefox
../marionette/puppeteer/firefox/

View File

@ -11,6 +11,3 @@
../marionette/client
../marionette/harness
# Allows to use the Puppeteer page object model for Firefox
../marionette/puppeteer/firefox/

View File

@ -1,4 +1,3 @@
firefox-puppeteer >= 52.1.0, <53.0.0
marionette-harness >= 4.0.0
mozfile >= 1.2
mozinfo >= 0.8

View File

@ -37,7 +37,6 @@ def run_firefox_ui_test(testtype=None, topsrcdir=None, **kwargs):
test_types = {
'functional': {
'default_tests': [
os.path.join('puppeteer', 'manifest.ini'),
os.path.join('functional', 'manifest.ini'),
],
'cli_module': firefox_ui_harness.cli_functional,

View File

@ -3,8 +3,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
FIREFOX_UI_FUNCTIONAL_MANIFESTS += ["tests/functional/manifest.ini"]
# TODO: Move to testing/marionette/puppeteer/firefox
PUPPETEER_FIREFOX_MANIFESTS += ["tests/puppeteer/manifest.ini"]
with Files("**"):
BUG_COMPONENT = ("Testing", "Firefox UI Tests")

View File

@ -1,21 +0,0 @@
[DEFAULT]
tags = local
# API tests
[test_appinfo.py]
skip-if = artifact # bug 1298233 - build ids are different for artifact builds
[test_places.py]
[test_security.py]
tags = remote
[test_utils.py]
# UI tests
[test_about_window.py]
[test_menubar.py]
[test_notifications.py]
[test_page_info_window.py]
[test_tabbar.py]
[test_toolbars.py]
disabled = Bug 1369556
tags = remote
[test_windows.py]

View File

@ -1,73 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from firefox_puppeteer.ui.deck import Panel
from marionette_harness import MarionetteTestCase
class TestAboutWindow(PuppeteerMixin, MarionetteTestCase):
def setUp(self):
super(TestAboutWindow, self).setUp()
self.about_window = self.browser.open_about_window()
self.deck = self.about_window.deck
def tearDown(self):
try:
self.puppeteer.windows.close_all([self.browser])
finally:
super(TestAboutWindow, self).tearDown()
def test_basic(self):
self.assertEqual(self.about_window.window_type, 'Browser:About')
def test_elements(self):
"""Test correct retrieval of elements."""
self.assertEqual(self.deck.element.get_property('localName'), 'deck')
# apply panel
panel = self.deck.apply
self.assertEqual(panel.element.get_property('localName'), 'hbox')
self.assertEqual(panel.button.get_property('localName'), 'button')
# check_for_updates panel
panel = self.deck.check_for_updates
self.assertEqual(panel.element.get_property('localName'), 'hbox')
self.assertEqual(panel.button.get_property('localName'), 'button')
# checking_for_updates panel
self.assertEqual(self.deck.checking_for_updates.element.get_property('localName'), 'hbox')
# download_and_install panel
panel = self.deck.download_and_install
self.assertEqual(panel.element.get_property('localName'), 'hbox')
self.assertEqual(panel.button.get_property('localName'), 'button')
# download_failed panel
self.assertEqual(self.deck.download_failed.element.get_property('localName'), 'hbox')
# downloading panel
self.assertEqual(self.deck.downloading.element.get_property('localName'), 'hbox')
# check deck attributes
self.assertIsInstance(self.deck.selected_index, int)
self.assertIsInstance(self.deck.selected_panel, Panel)
def test_open_window(self):
"""Test various opening strategies."""
def opener(win):
self.browser.menubar.select_by_id('helpMenu', 'aboutName')
open_strategies = ('menu',
opener,
)
self.about_window.close()
for trigger in open_strategies:
about_window = self.browser.open_about_window(trigger=trigger)
self.assertEquals(about_window, self.puppeteer.windows.current)
about_window.close()

View File

@ -1,30 +0,0 @@
# 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/.
from __future__ import absolute_import
import mozversion
from firefox_puppeteer import PuppeteerMixin
from marionette_harness import MarionetteTestCase
class TestAppInfo(PuppeteerMixin, MarionetteTestCase):
def test_valid_properties(self):
binary = self.marionette.bin
version_info = mozversion.get_version(binary=binary)
self.assertEqual(self.puppeteer.appinfo.ID, version_info['application_id'])
self.assertEqual(self.puppeteer.appinfo.name, version_info['application_name'])
self.assertEqual(self.puppeteer.appinfo.vendor, version_info['application_vendor'])
self.assertEqual(self.puppeteer.appinfo.version, version_info['application_version'])
self.assertEqual(self.puppeteer.appinfo.platformBuildID, version_info['platform_buildid'])
self.assertEqual(self.puppeteer.appinfo.platformVersion, version_info['platform_version'])
self.assertIsNotNone(self.puppeteer.appinfo.locale)
self.assertIsNotNone(self.puppeteer.appinfo.user_agent)
self.assertIsNotNone(self.puppeteer.appinfo.XPCOMABI)
def test_invalid_properties(self):
with self.assertRaises(AttributeError):
self.puppeteer.appinfo.unknown

View File

@ -1,31 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from marionette_driver.errors import NoSuchElementException
from marionette_harness import MarionetteTestCase
class TestMenuBar(PuppeteerMixin, MarionetteTestCase):
def setUp(self):
super(TestMenuBar, self).setUp()
def test_click_item_in_menubar(self):
def opener(_):
self.browser.menubar.select_by_id('file-menu',
'menu_newNavigatorTab')
self.browser.tabbar.open_tab(trigger=opener)
self.browser.tabbar.tabs[-1].close()
def test_click_non_existent_menu_and_item(self):
with self.assertRaises(NoSuchElementException):
self.browser.menubar.select_by_id('foobar-menu',
'menu_newNavigatorTab')
with self.assertRaises(NoSuchElementException):
self.browser.menubar.select_by_id('file-menu', 'menu_foobar')

View File

@ -1,83 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from firefox_puppeteer.ui.browser.notifications import (
AddOnInstallConfirmationNotification,
AddOnInstallFailedNotification,
)
from marionette_driver import By
from marionette_driver.errors import TimeoutException
from marionette_harness import MarionetteTestCase
class TestNotifications(PuppeteerMixin, MarionetteTestCase):
def setUp(self):
super(TestNotifications, self).setUp()
self.marionette.set_pref('extensions.install.requireSecureOrigin', False)
self.addons_url = self.marionette.absolute_url('addons/extensions/')
self.puppeteer.utils.permissions.add(self.marionette.baseurl, 'install')
def tearDown(self):
try:
self.marionette.clear_pref('extensions.install.requireSecureOrigin')
self.marionette.clear_pref('xpinstall.signatures.required')
self.puppeteer.utils.permissions.remove(self.addons_url, 'install')
if self.browser.notification:
self.browser.notification.close(force=True)
finally:
super(TestNotifications, self).tearDown()
def test_open_close_notification(self):
"""Trigger and dismiss a notification"""
self.assertIsNone(self.browser.notification)
self.trigger_addon_notification('webextension-signed.xpi')
self.browser.notification.close()
self.assertIsNone(self.browser.notification)
def test_wait_for_notification_timeout(self):
"""Wait for a notification when one is not shown"""
message = 'No notification was shown'
with self.assertRaisesRegexp(TimeoutException, message):
self.browser.wait_for_notification()
def test_wait_for_specific_notification_timeout(self):
"""Wait for a notification when one is not shown"""
message = 'AddOnInstallFailedNotification was not shown'
with self.assertRaisesRegexp(TimeoutException, message):
self.browser.wait_for_notification(AddOnInstallFailedNotification)
def test_wait_for_no_notification_timeout(self):
"""Wait for no notification when one is shown"""
message = 'Unexpected notification shown'
self.trigger_addon_notification('webextension-signed.xpi')
with self.assertRaisesRegexp(TimeoutException, message):
self.browser.wait_for_notification(None)
def test_notification_with_origin(self):
"""Trigger a notification with an origin"""
self.trigger_addon_notification('webextension-signed.xpi')
self.assertIn(self.browser.notification.origin, self.marionette.baseurl)
self.assertIsNotNone(self.browser.notification.label)
def test_addon_install_failed_notification(self):
"""Trigger add-on blocked notification using an unsigned add-on"""
# Ensure that installing unsigned extensions will fail
self.marionette.set_pref('xpinstall.signatures.required', True)
self.trigger_addon_notification(
'webextension-unsigned.xpi',
notification=AddOnInstallFailedNotification)
def trigger_addon_notification(self, addon, notification=AddOnInstallConfirmationNotification):
with self.marionette.using_context('content'):
self.marionette.navigate(self.addons_url)
self.marionette.find_element(By.LINK_TEXT, addon).click()
self.browser.wait_for_notification(notification)

View File

@ -1,95 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from marionette_harness import MarionetteTestCase
class TestPageInfoWindow(PuppeteerMixin, MarionetteTestCase):
def tearDown(self):
try:
self.puppeteer.windows.close_all([self.browser])
finally:
super(TestPageInfoWindow, self).tearDown()
def test_elements(self):
"""Test correct retrieval of elements."""
page_info = self.browser.open_page_info_window()
self.assertEqual(page_info.deck.element.get_property('localName'), 'deck')
# general panel
self.assertEqual(page_info.deck.general.element.get_property('localName'), 'vbox')
# media panel
self.assertEqual(page_info.deck.media.element.get_property('localName'), 'vbox')
# permissions panel
self.assertEqual(page_info.deck.permissions.element.get_property('localName'), 'vbox')
# security panel
panel = page_info.deck.select(page_info.deck.security)
self.assertEqual(panel.element.get_property('localName'), 'vbox')
self.assertEqual(panel.domain.get_property('localName'), 'input')
self.assertEqual(panel.owner.get_property('localName'), 'input')
self.assertEqual(panel.verifier.get_property('localName'), 'input')
self.assertEqual(panel.view_certificate.get_property('localName'), 'button')
self.assertEqual(panel.view_passwords.get_property('localName'), 'button')
def test_select(self):
"""Test properties and methods for switching between panels."""
page_info = self.browser.open_page_info_window()
deck = page_info.deck
self.assertEqual(deck.selected_panel, deck.general)
self.assertEqual(deck.select(deck.security), deck.security)
self.assertEqual(deck.selected_panel, deck.security)
def test_open_window(self):
"""Test various opening strategies."""
def opener(win):
self.browser.menubar.select_by_id('tools-menu', 'menu_pageInfo')
open_strategies = ('menu',
'shortcut',
opener,
)
platformName = self.marionette.session_capabilities['platformName']
for trigger in open_strategies:
if trigger == 'shortcut' and platformName == 'windows':
# The shortcut for page info window does not exist on windows.
self.assertRaises(ValueError, self.browser.open_page_info_window,
trigger=trigger)
continue
page_info = self.browser.open_page_info_window(trigger=trigger)
self.assertEquals(page_info, self.puppeteer.windows.current)
page_info.close()
def test_close_window(self):
"""Test various closing strategies."""
def closer(win):
win.send_shortcut('w', accel=True)
# Close a tab by each trigger method
close_strategies = ('menu',
'shortcut',
closer,
)
platformName = self.marionette.session_capabilities['platformName']
for trigger in close_strategies:
# menu only works on OS X
if trigger == 'menu' and platformName != 'mac':
continue
page_info = self.browser.open_page_info_window()
page_info.close(trigger=trigger)
self.assertTrue(page_info.closed)

View File

@ -1,86 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from marionette_driver import Wait
from marionette_harness import MarionetteTestCase
class TestPlaces(PuppeteerMixin, MarionetteTestCase):
def setUp(self):
super(TestPlaces, self).setUp()
self.urls = [self.marionette.absolute_url('layout/mozilla_governance.html'),
self.marionette.absolute_url('layout/mozilla_grants.html'),
]
def tearDown(self):
try:
self.puppeteer.places.restore_default_bookmarks()
self.puppeteer.places.remove_all_history()
finally:
super(TestPlaces, self).tearDown()
def get_all_urls_in_history(self):
return self.marionette.execute_script("""
let hs = Components.classes["@mozilla.org/browser/nav-history-service;1"]
.getService(Components.interfaces.nsINavHistoryService);
let urls = [];
let options = hs.getNewQueryOptions();
options.resultType = options.RESULTS_AS_URI;
let root = hs.executeQuery(hs.getNewQuery(), options).root
root.containerOpen = true;
for (let i = 0; i < root.childCount; i++) {
urls.push(root.getChild(i).uri)
}
root.containerOpen = false;
return urls;
""")
def test_plugins(self):
# TODO: Once we use a plugin, add a test case to verify that the data will be removed
self.puppeteer.places.clear_plugin_data()
def test_bookmarks(self):
star_button = self.marionette.execute_script("return BookmarkingUI.star")
# Visit URLs and bookmark them all
for url in self.urls:
with self.marionette.using_context('content'):
self.marionette.navigate(url)
Wait(self.marionette).until(
lambda _: self.puppeteer.places.is_bookmark_star_button_ready())
star_button.click()
Wait(self.marionette).until(lambda _: self.puppeteer.places.is_bookmarked(url))
ids = self.puppeteer.places.get_folder_ids_for_url(url)
self.assertEqual(len(ids), 1)
self.assertEqual(ids[0], self.puppeteer.places.bookmark_folders.unfiled)
# Restore default bookmarks, so the added URLs are gone
self.puppeteer.places.restore_default_bookmarks()
for url in self.urls:
self.assertFalse(self.puppeteer.places.is_bookmarked(url))
def test_history(self):
self.assertEqual(len(self.get_all_urls_in_history()), 0)
# Visit pages and check that they are all present
def load_urls():
with self.marionette.using_context('content'):
for url in self.urls:
self.marionette.navigate(url)
self.puppeteer.places.wait_for_visited(self.urls, load_urls)
self.assertEqual(self.get_all_urls_in_history(), self.urls)
# Check that both pages are no longer in the remove_all_history
self.puppeteer.places.remove_all_history()
self.assertEqual(len(self.get_all_urls_in_history()), 0)

View File

@ -1,44 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from firefox_puppeteer.errors import NoCertificateError
from marionette_harness import MarionetteTestCase
class TestSecurity(PuppeteerMixin, MarionetteTestCase):
def test_get_address_from_certificate(self):
url = 'https://extended-validation.badssl.com'
with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
self.marionette.navigate(url)
cert = self.browser.tabbar.tabs[0].certificate
self.assertIn(cert['commonName'], url)
self.assertEqual(cert['organization'], 'Mozilla Foundation')
self.assertEqual(cert['issuerOrganization'], 'DigiCert Inc')
address = self.puppeteer.security.get_address_from_certificate(cert)
self.assertIsNotNone(address)
self.assertIsNotNone(address['city'])
self.assertIsNotNone(address['country'])
self.assertIsNotNone(address['state'])
def test_get_certificate(self):
url_http = self.marionette.absolute_url('layout/mozilla.html')
url_https = 'https://extended-validation.badssl.com'
# Test EV certificate
with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
self.marionette.navigate(url_https)
cert = self.browser.tabbar.tabs[0].certificate
self.assertIn(cert['commonName'], url_https)
# HTTP connections do not have a SSL certificate
with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
self.marionette.navigate(url_http)
with self.assertRaises(NoCertificateError):
self.browser.tabbar.tabs[0].certificate

View File

@ -1,194 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from firefox_puppeteer.errors import NoCertificateError
from marionette_harness import MarionetteTestCase
class TestTabBar(PuppeteerMixin, MarionetteTestCase):
def tearDown(self):
try:
self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
finally:
super(TestTabBar, self).tearDown()
def test_basics(self):
tabbar = self.browser.tabbar
self.assertEqual(tabbar.window, self.browser)
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
self.assertEqual(tabbar.newtab_button.get_property('localName'), 'toolbarbutton')
self.assertEqual(tabbar.toolbar.get_property('localName'), 'tabs')
def test_open_close(self):
tabbar = self.browser.tabbar
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(tabbar.selected_index, 0)
# Open with default trigger, and force closing the tab
tabbar.open_tab()
tabbar.close_tab(force=True)
# Open a new tab by each trigger method
open_strategies = ('button',
'menu',
'shortcut',
lambda tab: tabbar.newtab_button.click()
)
for trigger in open_strategies:
new_tab = tabbar.open_tab(trigger=trigger)
self.assertEqual(len(tabbar.tabs), 2)
self.assertEqual(new_tab.handle, self.marionette.current_window_handle)
self.assertEqual(new_tab.handle, tabbar.tabs[1].handle)
tabbar.close_tab()
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
self.assertNotEqual(new_tab.handle, tabbar.tabs[0].handle)
# Close a tab by each trigger method
close_strategies = ('button',
'menu',
'shortcut',
lambda tab: tab.close_button.click())
for trigger in close_strategies:
new_tab = tabbar.open_tab()
self.assertEqual(len(tabbar.tabs), 2)
self.assertEqual(new_tab.handle, self.marionette.current_window_handle)
self.assertEqual(new_tab.handle, tabbar.tabs[1].handle)
tabbar.close_tab(trigger=trigger)
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
self.assertNotEqual(new_tab.handle, tabbar.tabs[0].handle)
def test_close_not_selected_tab(self):
tabbar = self.browser.tabbar
new_tab = tabbar.open_tab()
tabbar.close_tab(tabbar.tabs[0], trigger="button")
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(new_tab, tabbar.tabs[0])
def test_close_all_tabs_except_first(self):
tabbar = self.browser.tabbar
orig_tab = tabbar.tabs[0]
for i in range(0, 3):
tabbar.open_tab()
tabbar.close_all_tabs([orig_tab])
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(orig_tab.handle, self.marionette.current_window_handle)
def test_switch_to(self):
tabbar = self.browser.tabbar
# Open a new tab in the foreground (will be auto-selected)
new_tab = tabbar.open_tab()
self.assertEqual(new_tab.handle, self.marionette.current_window_handle)
self.assertEqual(tabbar.selected_index, 1)
self.assertEqual(tabbar.selected_tab, new_tab)
# Switch by index
tabbar.switch_to(0)
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
# Switch by tab
tabbar.switch_to(new_tab)
self.assertEqual(new_tab.handle, self.marionette.current_window_handle)
# Switch by callback
tabbar.switch_to(lambda tab: tab.window.tabbar.selected_tab != tab)
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
tabbar.close_tab(tabbar.tabs[1])
class TestTab(PuppeteerMixin, MarionetteTestCase):
def tearDown(self):
try:
self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
finally:
super(TestTab, self).tearDown()
def test_basic(self):
tab = self.browser.tabbar.tabs[0]
self.assertEqual(tab.window, self.browser)
self.assertEqual(tab.tab_element.get_property('localName'), 'tab')
self.assertEqual(tab.close_button.get_property('localName'), 'image')
def test_certificate(self):
url = self.marionette.absolute_url('layout/mozilla.html')
with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
self.marionette.navigate(url)
with self.assertRaises(NoCertificateError):
self.browser.tabbar.tabs[0].certificate
def test_close(self):
tabbar = self.browser.tabbar
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(tabbar.selected_index, 0)
# Force closing the tab
new_tab = tabbar.open_tab()
new_tab.close(force=True)
# Close a tab by each trigger method
close_strategies = ('button',
'menu',
'shortcut',
lambda tab: tab.close_button.click())
for trigger in close_strategies:
new_tab = tabbar.open_tab()
self.assertEqual(len(tabbar.tabs), 2)
self.assertEqual(len(self.marionette.window_handles), 2)
self.assertEqual(new_tab.handle, self.marionette.current_window_handle)
self.assertEqual(new_tab.handle, tabbar.tabs[1].handle)
new_tab.close(trigger=trigger)
self.assertEqual(len(tabbar.tabs), 1)
self.assertEqual(len(self.marionette.window_handles), 1)
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
self.assertNotEqual(new_tab.handle, tabbar.tabs[0].handle)
def test_location(self):
url = self.marionette.absolute_url('layout/mozilla.html')
with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
self.marionette.navigate(url)
self.assertEqual(self.browser.tabbar.tabs[0].location, url)
def test_switch_to(self):
tabbar = self.browser.tabbar
new_tab = tabbar.open_tab()
# Switch to the first tab, which will not select it
tabbar.tabs[0].switch_to()
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
# Bug 1128656: We cannot test as long as switch_to_window() auto-selects the tab
# self.assertEqual(tabbar.selected_index, 1)
# self.assertEqual(tabbar.selected_tab, new_tab)
# Now select the first tab
tabbar.tabs[0].select()
self.assertEqual(tabbar.tabs[0].handle, self.marionette.current_window_handle)
self.assertTrue(tabbar.tabs[0].selected)
self.assertFalse(tabbar.tabs[1].selected)
new_tab.close()

View File

@ -1,119 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from marionette_driver import expected, By, Wait
from marionette_harness import MarionetteTestCase
class TestNavBar(PuppeteerMixin, MarionetteTestCase):
def setUp(self):
super(TestNavBar, self).setUp()
self.navbar = self.browser.navbar
self.url = self.marionette.absolute_url('layout/mozilla.html')
with self.marionette.using_context('content'):
self.marionette.navigate('about:blank')
# TODO: check why self.puppeteer.places.remove_all_history() does not work here
self.marionette.execute_script("""
let count = gBrowser.sessionHistory.count;
gBrowser.sessionHistory.legacySHistory.PurgeHistory(count);
""")
def test_elements(self):
self.assertEqual(self.navbar.back_button.get_property('localName'), 'toolbarbutton')
self.assertEqual(self.navbar.forward_button.get_property('localName'), 'toolbarbutton')
self.assertEqual(self.navbar.home_button.get_property('localName'), 'toolbarbutton')
self.assertEqual(self.navbar.menu_button.get_property('localName'), 'toolbarbutton')
self.assertEqual(self.navbar.toolbar.get_property('localName'), 'toolbar')
def test_buttons(self):
self.marionette.set_context('content')
# Load initial web page
self.marionette.navigate(self.url)
Wait(self.marionette).until(expected.element_present(lambda m:
m.find_element(By.ID, 'mozilla_logo')))
with self.marionette.using_context('chrome'):
# Both buttons are disabled
self.assertFalse(self.navbar.back_button.is_enabled())
self.assertFalse(self.navbar.forward_button.is_enabled())
# Go to the homepage
self.navbar.home_button.click()
Wait(self.marionette).until(expected.element_not_present(lambda m:
m.find_element(By.ID, 'mozilla_logo')))
self.assertEqual(self.marionette.get_url(), self.browser.default_homepage)
with self.marionette.using_context('chrome'):
# Only back button is enabled
self.assertTrue(self.navbar.back_button.is_enabled())
self.assertFalse(self.navbar.forward_button.is_enabled())
# Navigate back
self.navbar.back_button.click()
Wait(self.marionette).until(expected.element_present(lambda m:
m.find_element(By.ID, 'mozilla_logo')))
self.assertEqual(self.marionette.get_url(), self.url)
with self.marionette.using_context('chrome'):
# Only forward button is enabled
self.assertFalse(self.navbar.back_button.is_enabled())
self.assertTrue(self.navbar.forward_button.is_enabled())
# Navigate forward
self.navbar.forward_button.click()
Wait(self.marionette).until(expected.element_not_present(lambda m:
m.find_element(By.ID, 'mozilla_logo')))
self.assertEqual(self.marionette.get_url(), self.browser.default_homepage)
class TestLocationBar(PuppeteerMixin, MarionetteTestCase):
def setUp(self):
super(TestLocationBar, self).setUp()
self.locationbar = self.browser.navbar.locationbar
def test_elements(self):
self.assertEqual(self.locationbar.urlbar_input.get_property('id'), 'urlbar-input')
self.assertEqual(self.locationbar.reload_button.get_property('localName'),
'toolbarbutton')
self.assertEqual(self.locationbar.stop_button.get_property('localName'),
'toolbarbutton')
def test_reload(self):
event_types = ["shortcut", "shortcut2", "button"]
for event in event_types:
# TODO: Until we have waitForPageLoad, this only tests API
# compatibility.
self.locationbar.reload_url(event, force=True)
self.locationbar.reload_url(event, force=False)
def test_focus_and_clear(self):
self.locationbar.urlbar.send_keys("zyx")
self.locationbar.clear()
self.assertEqual(self.locationbar.value, '')
self.locationbar.urlbar.send_keys("zyx")
self.assertEqual(self.locationbar.value, 'zyx')
self.locationbar.clear()
self.assertEqual(self.locationbar.value, '')
def test_load_url(self):
data_uri = 'data:text/html,<title>Title</title>'
self.locationbar.load_url(data_uri)
with self.marionette.using_context('content'):
Wait(self.marionette).until(lambda mn: mn.get_url() == data_uri)

View File

@ -1,49 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer import PuppeteerMixin
from marionette_harness import MarionetteTestCase
class TestSanitize(PuppeteerMixin, MarionetteTestCase):
def setUp(self):
super(TestSanitize, self).setUp()
# Clear all previous history and cookies.
self.puppeteer.places.remove_all_history()
with self.marionette.using_context('content'):
self.marionette.delete_all_cookies()
self.urls = [
'layout/mozilla_projects.html',
'layout/mozilla.html',
'layout/mozilla_mission.html',
'cookies/cookie_single.html'
]
self.urls = [self.marionette.absolute_url(url) for url in self.urls]
# Open the test urls, including the single cookie setting page.
def load_urls():
with self.marionette.using_context('content'):
for url in self.urls:
self.marionette.navigate(url)
self.puppeteer.places.wait_for_visited(self.urls, load_urls)
def test_sanitize_history(self):
""" Clears history. """
self.assertEqual(self.puppeteer.places.get_all_urls_in_history(), self.urls)
self.puppeteer.utils.sanitize(data_type={"history": True})
self.assertEqual(self.puppeteer.places.get_all_urls_in_history(), [])
def test_sanitize_cookies(self):
""" Clears cookies. """
with self.marionette.using_context('content'):
self.assertIsNotNone(self.marionette.get_cookie('litmus_1'))
self.puppeteer.utils.sanitize(data_type={"cookies": True})
with self.marionette.using_context('content'):
self.assertIsNone(self.marionette.get_cookie('litmus_1'))

View File

@ -1,234 +0,0 @@
# 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/.
from __future__ import absolute_import
import firefox_puppeteer.errors as errors
from firefox_puppeteer import PuppeteerMixin
from firefox_puppeteer.ui.windows import BaseWindow
from marionette_driver import By, Wait
from marionette_driver.errors import NoSuchWindowException
from marionette_harness import MarionetteTestCase
class TestWindows(PuppeteerMixin, MarionetteTestCase):
def tearDown(self):
try:
self.puppeteer.windows.close_all([self.browser])
self.browser.switch_to()
finally:
super(TestWindows, self).tearDown()
def test_switch_to(self):
url = self.marionette.absolute_url('layout/mozilla.html')
# Open two more windows
for index in range(0, 2):
self.marionette.open(type="window")
windows = self.puppeteer.windows.all
self.assertEquals(len(windows), 3)
# Switch to the 2nd window
self.puppeteer.windows.switch_to(windows[1].handle)
self.assertEquals(windows[1].handle, self.marionette.current_chrome_window_handle)
# TODO: Needs updated tabs module for improved navigation
with self.marionette.using_context('content'):
self.marionette.navigate(url)
# Switch to the last window and find 2nd window by URL
self.puppeteer.windows.switch_to(windows[2].handle)
# TODO: A window can have multiple tabs, so this may need an update
# when the tabs module gets implemented
def find_by_url(win):
with win.marionette.using_context('content'):
return win.marionette.get_url() == url
self.puppeteer.windows.switch_to(find_by_url)
self.assertEquals(windows[1].handle, self.marionette.current_chrome_window_handle)
self.puppeteer.windows.switch_to(find_by_url)
# Switching to an unknown handles has to fail
self.assertRaises(NoSuchWindowException,
self.puppeteer.windows.switch_to, "humbug")
self.assertRaises(NoSuchWindowException,
self.puppeteer.windows.switch_to, lambda win: False)
self.puppeteer.windows.close_all([self.browser])
self.browser.switch_to()
self.assertEqual(len(self.puppeteer.windows.all), 1)
def test_switch_to_unknown_window_type(self):
def open_by_js(_):
with self.marionette.using_context('chrome'):
self.marionette.execute_script("""
window.open('chrome://browser/content/safeMode.xhtml', '_blank',
'chrome,centerscreen,resizable=no');
""")
win = self.browser.open_window(callback=open_by_js, expected_window_class=BaseWindow)
win.close()
self.browser.switch_to()
class TestBaseWindow(PuppeteerMixin, MarionetteTestCase):
def tearDown(self):
try:
self.puppeteer.windows.close_all([self.browser])
self.browser.switch_to()
finally:
super(TestBaseWindow, self).tearDown()
def test_basics(self):
# force BaseWindow instance
win1 = BaseWindow(self.marionette, self.browser.handle)
self.assertEquals(win1.handle, self.marionette.current_chrome_window_handle)
self.assertEquals(win1.window_element,
self.marionette.find_element(By.CSS_SELECTOR, ':root'))
self.assertEquals(win1.window_element.get_attribute('windowtype'),
self.marionette.get_window_type())
self.assertFalse(win1.closed)
# Test invalid parameters for BaseWindow constructor
self.assertRaises(errors.UnknownWindowError,
BaseWindow, self.marionette, 10)
# Test invalid shortcuts
self.assertRaises(KeyError,
win1.send_shortcut, 'l', acel=True)
def test_open_close(self):
# force BaseWindow instance
win1 = BaseWindow(self.marionette, self.browser.handle)
# Open a new window (will be focused), and check states
win2 = win1.open_window()
# force BaseWindow instance
win2 = BaseWindow(self.marionette, win2.handle)
self.assertEquals(len(self.marionette.chrome_window_handles), 2)
self.assertNotEquals(win1.handle, win2.handle)
self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
win2.close()
self.assertTrue(win2.closed)
self.assertEquals(len(self.marionette.chrome_window_handles), 1)
with self.assertRaises(NoSuchWindowException):
self.marionette.current_chrome_window_handle
Wait(self.marionette).until(lambda _: win1.focused) # catch the no focused window
win1.focus()
# Open and close a new window by a custom callback
def opener(window):
window.marionette.open(type="window")
def closer(window):
window.marionette.close()
win2 = win1.open_window(callback=opener)
# force BaseWindow instance
win2 = BaseWindow(self.marionette, win2.handle)
self.assertEquals(len(self.marionette.chrome_window_handles), 2)
win2.close(callback=closer)
win1.focus()
# Check for an unexpected window class
self.assertRaises(errors.UnexpectedWindowTypeError,
win1.open_window, expected_window_class=BaseWindow)
self.puppeteer.windows.close_all([win1])
def test_switch_to_and_focus(self):
# force BaseWindow instance
win1 = BaseWindow(self.marionette, self.browser.handle)
# Open a new window (will be focused), and check states
win2 = win1.open_window()
# force BaseWindow instance
win2 = BaseWindow(self.marionette, win2.handle)
self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
self.assertEquals(win2.handle, self.puppeteer.windows.focused_chrome_window_handle)
self.assertFalse(win1.focused)
self.assertTrue(win2.focused)
# Switch back to win1 without moving the focus, but focus separately
win1.switch_to()
self.assertEquals(win1.handle, self.marionette.current_chrome_window_handle)
self.assertTrue(win2.focused)
win1.focus()
self.assertTrue(win1.focused)
# Switch back to win2 by focusing it directly
win2.focus()
self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
self.assertEquals(win2.handle, self.puppeteer.windows.focused_chrome_window_handle)
self.assertTrue(win2.focused)
# Close win2, and check that it keeps active but looses focus
win2.switch_to()
win2.close()
win1.switch_to()
class TestBrowserWindow(PuppeteerMixin, MarionetteTestCase):
def tearDown(self):
try:
self.puppeteer.windows.close_all([self.browser])
self.browser.switch_to()
finally:
super(TestBrowserWindow, self).tearDown()
def test_basic(self):
self.assertFalse(self.browser.is_private)
self.assertIsNotNone(self.browser.menubar)
self.assertIsNotNone(self.browser.navbar)
self.assertIsNotNone(self.browser.tabbar)
def test_open_close(self):
# open and close a new browser windows by menu
win2 = self.browser.open_browser(trigger='menu')
self.assertEquals(win2, self.puppeteer.windows.current)
self.assertFalse(self.browser.is_private)
win2.close(trigger='menu')
# open and close a new browser window by shortcut
win2 = self.browser.open_browser(trigger='shortcut')
self.assertEquals(win2, self.puppeteer.windows.current)
self.assertFalse(self.browser.is_private)
win2.close(trigger='shortcut')
# open and close a new private browsing window
win2 = self.browser.open_browser(is_private=True)
self.assertEquals(win2, self.puppeteer.windows.current)
self.assertTrue(win2.is_private)
win2.close()
# open and close a new private browsing window
win2 = self.browser.open_browser(trigger='shortcut', is_private=True)
self.assertEquals(win2, self.puppeteer.windows.current)
self.assertTrue(win2.is_private)
win2.close()
# force closing a window
win2 = self.browser.open_browser()
self.assertEquals(win2, self.puppeteer.windows.current)
win2.close(force=True)

View File

@ -17,9 +17,6 @@ write different kinds of functional tests:
* The `Marionette Python client`_ is used in the `Mn` job, which
is generally what you want to use for interacting with web documents
* `Firefox Puppeteer`_ is a convenience library for Firefox
frontend UI automation based on the `Marionette Python client`_
Outside the tree, Marionette is used by `geckodriver`_ to implement
`WebDriver`_.
@ -28,7 +25,6 @@ including Firefox, Thunderbird, Fennec, and Fenix.
.. _protocol: Protocol.html
.. _Marionette Python client: /python/marionette_driver.html
.. _Firefox Puppeteer: http://firefox-puppeteer.readthedocs.io
.. _geckodriver: /testing/geckodriver/
.. _WebDriver: https://w3c.github.io/webdriver/

View File

@ -1,2 +0,0 @@
exclude MANIFEST.in
include requirements.txt

View File

@ -1,177 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/FirefoxPuppeteer.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/FirefoxPuppeteer.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/FirefoxPuppeteer"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/FirefoxPuppeteer"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

View File

@ -1,15 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.api.appinfo
AppInfo
=======
The appinfo class is a wrapper around the nsIXULAppInfo_ interface in
Firefox and provides access to a subset of its members.
.. _nsIXULAppInfo: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIXULAppInfo
AppInfo
-------
.. autoclass:: AppInfo
:members:

View File

@ -1,12 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.api.keys
Keys
====
Keys
----
.. autoclass:: Keys
:members:
:inherited-members:
:undoc-members:

View File

@ -1,11 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.api.l10n
Localization
============
Localization
------------
.. autoclass:: L10n
:members:
:undoc-members:

View File

@ -1,13 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.api.places
Places
======
The Places class provides low-level access for several bookmark and history
related methods.
Places
------
.. autoclass:: Places
:members:

View File

@ -1,13 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.api.security
Security
===========
The Security class gives access to various helper methods, which assist in working with
certificates or accessing specific security related information.
Security
--------
.. autoclass:: Security
:members:

View File

@ -1,12 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.api.utils
Utils
===========
The Utils class gives access to various helper methods.
Utils
-----
.. autoclass:: Utils
:members:

View File

@ -1,272 +0,0 @@
# -*- coding: utf-8 -*-
#
# Firefox Puppeteer documentation build configuration file, created by
# sphinx-quickstart on Thu Nov 20 10:35:33 2014.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
from __future__ import absolute_import
import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.todo',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Firefox Puppeteer'
copyright = u'2014-2015, Mozilla'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd:
try:
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
except ImportError:
pass
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
html_show_copyright = False
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'FirefoxPuppeteerdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'FirefoxPuppeteer.tex', u'Firefox Puppeteer Documentation',
u'Mozilla', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'firefoxpuppeteer', u'Firefox Puppeteer Documentation',
[u'Mozilla'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'FirefoxPuppeteer', u'Firefox Puppeteer Documentation',
u'Mozilla', 'FirefoxPuppeteer', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False

View File

@ -1,63 +0,0 @@
.. py:currentmodule:: firefox_puppeteer
Firefox Puppeteer
=================
`Firefox Puppeteer`_ is a library built on top of the `Marionette Python client`_.
It aims to make automation of Firefox's browser UI simpler. It does **not**
make sense to use Firefox Puppeteer if:
* You are manipulating something other than Firefox (like Firefox OS)
* You are only manipulating elements in content scope (like a webpage)
Roughly speaking, Firefox Puppeteer provides a library to manipulate each
visual section of Firefox's browser UI. For example, there are different
libraries for the tab bar, the navigation bar, etc.
.. _Firefox Puppeteer: http://firefox-puppeteer.readthedocs.io/
.. _Marionette Python client: https://firefox-source-docs.mozilla.org/python/marionette_driver.html
Installation
------------
To install the package you have to clone the `mozilla-central`_ repository
and run the following commands::
$ cd testing/marionette/puppeteer/firefox
$ python setup.py develop
In both cases all necessary files including all dependencies will be installed.
.. _mozilla-central: https://hg.mozilla.org/mozilla-central
Libraries
---------
The following libraries are currently implemented. More will be added in the
future. Each library is available from an instance of the FirefoxTestCase class.
.. toctree::
ui/about_window/window
ui/deck
ui/menu
ui/pageinfo/window
ui/browser/notifications
ui/browser/tabbar
ui/browser/toolbars
ui/browser/window
ui/windows
api/appinfo
api/keys
api/l10n
api/places
api/security
api/utils
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,242 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\FirefoxPuppeteer.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\FirefoxPuppeteer.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %BUILDDIR%/..
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %BUILDDIR%/..
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:end

View File

@ -1,59 +0,0 @@
About Window
============
AboutWindow
--------------
.. autoclass:: firefox_puppeteer.ui.about_window.window.AboutWindow
:members:
:inherited-members:
Deck
----
.. autoclass:: firefox_puppeteer.ui.about_window.deck.Deck
:members:
:inherited-members:
ApplyPanel
----------
.. autoclass:: firefox_puppeteer.ui.about_window.deck.ApplyPanel
:members:
:inherited-members:
CheckForUpdatesPanel
--------------------
.. autoclass:: firefox_puppeteer.ui.about_window.deck.CheckForUpdatesPanel
:members:
:inherited-members:
CheckingForUpdatesPanel
-----------------------
.. autoclass:: firefox_puppeteer.ui.about_window.deck.CheckingForUpdatesPanel
:members:
:inherited-members:
DownloadAndInstallPanel
-----------------------
.. autoclass:: firefox_puppeteer.ui.about_window.deck.DownloadAndInstallPanel
:members:
:inherited-members:
DownloadFailedPanel
-------------------
.. autoclass:: firefox_puppeteer.ui.about_window.deck.DownloadFailedPanel
:members:
:inherited-members:
DownloadingPanel
----------------
.. autoclass:: firefox_puppeteer.ui.about_window.deck.DownloadingPanel
:members:
:inherited-members:

View File

@ -1,44 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.ui.browser.notifications
Notifications
=============
AddOnInstallBlockedNotification
-------------------------------
.. autoclass:: AddOnInstallBlockedNotification
:members:
:inherited-members:
:show-inheritance:
AddOnInstallConfirmationNotification
------------------------------------
.. autoclass:: AddOnInstallConfirmationNotification
:members:
:inherited-members:
:show-inheritance:
AddOnInstallCompleteNotification
--------------------------------
.. autoclass:: AddOnInstallCompleteNotification
:members:
:inherited-members:
:show-inheritance:
AddOnInstallFailedNotification
------------------------------
.. autoclass:: AddOnInstallFailedNotification
:members:
:inherited-members:
:show-inheritance:
AddOnProgressNotification
-------------------------
.. autoclass:: AddOnProgressNotification
:members:
:inherited-members:
:show-inheritance:

View File

@ -1,16 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.ui.browser.tabbar
Tabbar
======
TabBar
------
.. autoclass:: TabBar
:members:
Tab
---
.. autoclass:: Tab
:members:

View File

@ -1,28 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.ui.browser.toolbars
Toolbars
========
NavBar
------
.. autoclass:: NavBar
:members:
LocationBar
-----------
.. autoclass:: LocationBar
:members:
AutocompleteResults
-------------------
.. autoclass:: AutocompleteResults
:members:
IdentityPopup
-------------
.. autoclass:: IdentityPopup
:members:

View File

@ -1,11 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.ui.browser.window
BrowserWindow
=============
BrowserWindow
-------------
.. autoclass:: BrowserWindow
:members:
:inherited-members:

View File

@ -1,9 +0,0 @@
Deck
=====
Panel
------
.. autoclass:: firefox_puppeteer.ui.deck.Panel
:members:
:inherited-members:

View File

@ -1,10 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.ui.menu
Menu
====
Menu Bar
--------
.. autoclass:: MenuBar
:members:

View File

@ -1,30 +0,0 @@
Page Info Window
================
PageInfoWindow
--------------
.. autoclass:: firefox_puppeteer.ui.pageinfo.window.PageInfoWindow
:members:
:inherited-members:
Deck
----
.. autoclass:: firefox_puppeteer.ui.pageinfo.deck.Deck
:members:
:inherited-members:
PageInfoPanel
-------------
.. autoclass:: firefox_puppeteer.ui.pageinfo.deck.PageInfoPanel
:members:
:inherited-members:
SecurityPanel
-------------
.. autoclass:: firefox_puppeteer.ui.pageinfo.deck.SecurityPanel
:members:
:inherited-members:

View File

@ -1,16 +0,0 @@
.. py:currentmodule:: firefox_puppeteer.ui.windows
Windows
=======
Windows
-------
.. autoclass:: Windows
:members:
BaseWindow
----------
.. autoclass:: BaseWindow
:members:

View File

@ -1,11 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer.mixins import PuppeteerMixin
from firefox_puppeteer.puppeteer import Puppeteer
__version__ = '52.1.0'

View File

@ -1,57 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer.base import BaseLib
class AppInfo(BaseLib):
"""This class provides access to various attributes of AppInfo.
For more details on AppInfo, visit:
https://developer.mozilla.org/en-US/docs/Mozilla/QA/Mozmill_tests/Shared_Modules/UtilsAPI/appInfo
"""
def __getattr__(self, attr):
with self.marionette.using_context('chrome'):
value = self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
return Services.appinfo[arguments[0]];
""", script_args=[attr])
if value is not None:
return value
else:
raise AttributeError('{} has no attribute {}'.format(self.__class__.__name__,
attr))
@property
def locale(self):
with self.marionette.using_context('chrome'):
return self.marionette.execute_script("""
// LocaleService has been added in 55, use ChromeRegistry for
// earlier releases.
// The ChromeRegistry path can be removed when 52esr is not longer
// supported.
try {
return Components.classes["@mozilla.org/intl/localeservice;1"]
.getService(Components.interfaces.mozILocaleService)
.getAppLocaleAsBCP47();
} catch (e) {
return Components.classes["@mozilla.org/chrome/chrome-registry;1"]
.getService(Components.interfaces.nsIXULChromeRegistry)
.getSelectedLocale("global");
}
""")
@property
def user_agent(self):
with self.marionette.using_context('chrome'):
return self.marionette.execute_script("""
return Components.classes["@mozilla.org/network/protocol;1?name=http"]
.getService(Components.interfaces.nsIHttpProtocolHandler)
.userAgent;
""")

View File

@ -1,21 +0,0 @@
# 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/.
from __future__ import absolute_import
import marionette_driver
class Keys(marionette_driver.keys.Keys):
"""
Proxy to Marionette's keys with an "accel" provided for convenience
testing across platforms.
"""
def __init__(self, marionette):
self.is_mac = marionette.session_capabilities["platformName"] == "mac"
@property
def ACCEL(self):
return self.META if self.is_mac else self.CONTROL

View File

@ -1,156 +0,0 @@
# 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/.
from __future__ import absolute_import
from collections import namedtuple
from time import sleep
from marionette_driver.errors import MarionetteException
from firefox_puppeteer.base import BaseLib
class Places(BaseLib):
"""Low-level access to several bookmark and history related actions."""
BookmarkFolders = namedtuple('bookmark_folders',
['root', 'menu', 'toolbar', 'unfiled'])
bookmark_folders = BookmarkFolders('root________', 'menu________',
'toolbar_____', 'unfiled_____')
# Bookmark related helpers #
def is_bookmarked(self, url):
"""Check if the given URL is bookmarked.
:param url: The URL to Check
:returns: True, if the URL is a bookmark
"""
return self.marionette.execute_async_script("""
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
let [url, resolve] = arguments;
PlacesUtils.bookmarks.fetch({url}).then(bm => {
resolve(bm != null);
});
""", script_args=[url])
def get_folder_ids_for_url(self, url):
"""Retrieve the folder ids where the given URL has been bookmarked in.
:param url: URL of the bookmark
:returns: List of folder ids
"""
return self.marionette.execute_async_script("""
let [url, resolve] = arguments;
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
let folderGuids = [];
function onResult(bm) {
folderGuids.push(bm.parentGuid);
}
PlacesUtils.bookmarks.fetch({url}, onResult).then(() => {
resolve(folderGuids);
});
""", script_args=[url])
def is_bookmark_star_button_ready(self):
"""Check if the status of the star-button is not updating.
:returns: True, if the button is ready
"""
return self.marionette.execute_script("""
let button = window.BookmarkingUI;
return button.status !== button.STATUS_UPDATING;
""")
def restore_default_bookmarks(self):
"""Restore the default bookmarks for the current profile."""
retval = self.marionette.execute_async_script("""
let [resolve] = arguments;
Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
// Default bookmarks.html file is stored inside omni.jar,
// so get it via a resource URI
let defaultBookmarks = 'chrome://browser/locale/bookmarks.html';
// Trigger the import of the default bookmarks
BookmarkHTMLUtils.importFromURL(defaultBookmarks, { replace: true })
.then(() => resolve(true))
.catch(() => resolve(false));
""", script_timeout=10000)
if not retval:
raise MarionetteException("Restore Default Bookmarks failed")
# Browser history related helpers #
def get_all_urls_in_history(self):
"""Retrieve any URLs which have been stored in the history."""
return self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
let options = PlacesUtils.history.getNewQueryOptions();
let root = PlacesUtils.history.executeQuery(
PlacesUtils.history.getNewQuery(), options).root;
let urls = [];
root.containerOpen = true;
for (let i = 0; i < root.childCount; i++) {
urls.push(root.getChild(i).uri)
}
root.containerOpen = false;
return urls;
""")
def remove_all_history(self):
"""Remove all history items."""
retval = self.marionette.execute_async_script("""
let [resolve] = arguments;
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
PlacesUtils.history.clear()
.then(() => resolve(true))
.catch(() => resolve(false));
""", script_timeout=10000)
if not retval:
raise MarionetteException("Removing all history failed")
def wait_for_visited(self, urls, callback):
"""Wait until all passed-in urls have been visited.
:param urls: List of URLs which need to be visited and indexed
:param callback: Method to execute which triggers loading of the URLs
"""
# Bug 1121691: Needs observer handling support with callback first
# Until then we have to wait about 4s to ensure the page has been indexed
callback()
sleep(4)
# Plugin related helpers #
def clear_plugin_data(self):
"""Clear any kind of locally stored data from plugins."""
self.marionette.execute_script("""
let host = Components.classes["@mozilla.org/plugin/host;1"]
.getService(Components.interfaces.nsIPluginHost);
let tags = host.getPluginTags();
tags.forEach(aTag => {
try {
host.clearSiteData(aTag, null, Components.interfaces.nsIPluginHost
.FLAG_CLEAR_ALL, -1);
} catch (ex) {
}
});
""")

View File

@ -1,66 +0,0 @@
# 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/.
from __future__ import absolute_import
import re
from firefox_puppeteer.base import BaseLib
from firefox_puppeteer.errors import NoCertificateError
class Security(BaseLib):
"""Low-level access to security (SSL, TLS) related information."""
# Security related helpers #
def get_address_from_certificate(self, certificate):
"""Retrieves the address of the organization from the certificate information.
The returned address may be `None` in case of no address found, or a dictionary
with the following entries: `city`, `country`, `postal_code`, `state`, `street`.
:param certificate: A JSON object representing the certificate, which can usually be
retrieved via the current tab: `self.browser.tabbar.selected_tab.certificate`.
:returns: Address details as dictionary
"""
regex = re.compile('.*?L=(?P<city>.+?),ST=(?P<state>.+?),C=(?P<country>.+?),')
results = regex.search(certificate['subjectName'])
return results.groupdict() if results else results
def get_certificate_for_page(self, tab_element):
"""The SSL certificate assiciated with the loaded web page in the given tab.
:param tab_element: The inner tab DOM element.
:returns: Certificate details as JSON object.
"""
cert = self.marionette.execute_script("""
var secInfo = arguments[0].linkedBrowser.securityUI.secInfo;
return secInfo ? secInfo.serverCert : null;
""", script_args=[tab_element])
uri = self.marionette.execute_script("""
return arguments[0].linkedBrowser.currentURI.spec;
""", script_args=[tab_element])
if cert is None:
raise NoCertificateError('No certificate found for "{}"'.format(uri))
return cert
def get_domain_from_common_name(self, common_name):
"""Retrieves the domain associated with a page's security certificate from the common name.
:param certificate: A string containing the certificate's common name, which can usually
be retrieved like so: `certificate['commonName']`.
:returns: Domain as string
"""
return self.marionette.execute_script("""
return Services.eTLD.getBaseDomainFromHost(arguments[0]);
""", script_args=[common_name])

View File

@ -1,118 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver.errors import MarionetteException
from firefox_puppeteer.base import BaseLib
class Utils(BaseLib):
"""Low-level access to utility actions."""
def __init__(self, *args, **kwargs):
super(Utils, self).__init__(*args, **kwargs)
self._permissions = Permissions(self.marionette)
@property
def permissions(self):
"""Handling of various permissions for hosts.
:returns: Instance of the Permissions class.
"""
return self._permissions
def compare_version(self, a, b):
"""Compare two version strings.
:param a: The first version.
:param b: The second version.
:returns: -1 if a is smaller than b, 0 if equal, and 1 if larger.
"""
return self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
return Services.vc.compare(arguments[0], arguments[1]);
""", script_args=[a, b])
def sanitize(self, data_type):
"""Sanitize user data, including cache, cookies, offlineApps, history, formdata,
downloads, passwords, sessions, siteSettings.
Usage:
sanitize(): Clears all user data.
sanitize({ "sessions": True }): Clears only session user data.
more: https://dxr.mozilla.org/mozilla-central/source/browser/modules/Sanitizer.jsm
:param data_type: optional, Information specifying data to be sanitized
"""
with self.marionette.using_context('chrome'):
result = self.marionette.execute_async_script("""
let resolve = arguments[arguments.length - 1];
var {Sanitizer} = Components.utils.import("resource:///modules/Sanitizer.jsm", {});
var data_type = arguments[0];
// Apply options for what to sanitize
var itemsToClear = [];
for (var pref of Object.keys(data_type)) {
if (data_type[pref]) {
itemsToClear.push(pref);
}
};
// Sanitize and wait for the promise to resolve
Sanitizer.sanitize(itemsToClear).then(() => {
resolve(true);
}, aError => {
resolve(false);
});
""", script_args=[data_type])
if not result:
raise MarionetteException('Sanitizing of profile data failed.')
class Permissions(BaseLib):
def add(self, host, permission):
"""Add a permission for web host.
Permissions include safe-browsing, install, geolocation, and others described here:
https://dxr.mozilla.org/mozilla-central/source/browser/modules/SitePermissions.jsm#144
and elsewhere.
:param host: The web host whose permission will be added.
:param permission: The type of permission to be added.
"""
with self.marionette.using_context('chrome'):
self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
let uri = Services.io.newURI(arguments[0], null, null);
let principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
Services.perms.addFromPrincipal(principal, arguments[1],
Components.interfaces.nsIPermissionManager.ALLOW_ACTION);
""", script_args=[host, permission])
def remove(self, host, permission):
"""Remove a permission for web host.
Permissions include safe-browsing, install, geolocation, and others described here:
https://dxr.mozilla.org/mozilla-central/source/browser/modules/SitePermissions.jsm#144
and elsewhere.
:param host: The web host whose permission will be removed.
:param permission: The type of permission to be removed.
"""
with self.marionette.using_context('chrome'):
self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
let uri = Services.io.newURI(arguments[0], null, null);
let principal = Services.scriptSecurityManager.createContentPrincipal(uri, {});
Services.perms.removeFromPrincipal(principal, arguments[1]);
""", script_args=[host, permission])

View File

@ -1,16 +0,0 @@
# 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/.
from __future__ import absolute_import
class BaseLib(object):
"""A base class that handles lazily setting the "client" class attribute."""
def __init__(self, marionette):
self._marionette = marionette
@property
def marionette(self):
return self._marionette

View File

@ -1,37 +0,0 @@
# 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/.
from __future__ import absolute_import
from functools import wraps
from importlib import import_module
class use_class_as_property(object):
"""
This decorator imports a library module and sets an instance
of the associated class as an attribute on the Puppeteer
object and returns it.
Note: return value of the wrapped function is ignored.
"""
def __init__(self, lib):
self.lib = lib
self.mod_name, self.cls_name = self.lib.rsplit('.', 1)
def __call__(self, func):
@property
@wraps(func)
def _(cls, *args, **kwargs):
tag = '_{}_{}'.format(self.mod_name, self.cls_name)
prop = getattr(cls, tag, None)
if not prop:
module = import_module('.{}'.format(self.mod_name),
'firefox_puppeteer')
prop = getattr(module, self.cls_name)(cls.marionette)
setattr(cls, tag, prop)
func(cls, *args, **kwargs)
return prop
return _

View File

@ -1,23 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver.errors import MarionetteException
class NoCertificateError(MarionetteException):
pass
class UnexpectedWindowTypeError(MarionetteException):
pass
class UnknownTabError(MarionetteException):
pass
class UnknownWindowError(MarionetteException):
pass

View File

@ -1,105 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer.puppeteer import Puppeteer
from firefox_puppeteer.ui.browser.window import BrowserWindow
class PuppeteerMixin(object):
"""Mix-in class for Firefox specific API modules exposed to test scope.
It also provides common set-up and tear-down code for Firefox tests.
Child test case classes are expected to also subclass MarionetteTestCase such
that PuppeteerMixin is followed by MarionetteTestCase. This will insert the
Puppeteer mixin before the MarionetteTestCase into the MRO.
example:
`class MyTestCase(PuppeteerMixin, MarionetteTestCase)`
The key role of MarionetteTestCase is to set self.marionette appropriately
in `setUp()`. Any TestCase class that satisfies this requirement is
compatible with this class.
If you're extending the inheritance tree further to make specialized
TestCases, favour the use of super() as opposed to explicit calls to a
parent class.
"""
def _check_and_fix_leaked_handles(self):
handle_count = len(self.marionette.window_handles)
url = []
try:
# Verify the existence of leaked tabs and print their URLs.
if self._start_handle_count < handle_count:
message = ('A test must not leak window handles. This test started with '
'%s open top level browsing contexts, but ended with %s.'
' Remaining Tabs URLs:') % (self._start_handle_count, handle_count)
with self.marionette.using_context('content'):
for tab in self.marionette.window_handles:
if tab not in self._init_tab_handles:
url.append(' %s' % self.marionette.get_url())
self.assertListEqual(self._init_tab_handles, self.marionette.window_handles,
message + ','.join(url))
finally:
# For clean-up make sure we work on a proper browser window
if not self.browser or self.browser.closed:
# Find a proper replacement browser window
# TODO: We have to make this less error prone in case no browser is open.
self.browser = self.puppeteer.windows.switch_to(
lambda win: type(win) is BrowserWindow)
# Ensure to close all the remaining chrome windows to give following
# tests a proper start condition and make them not fail.
self.puppeteer.windows.close_all([self.browser])
self.browser.focus()
# Also close all remaining tabs
self.browser.tabbar.close_all_tabs([self.browser.tabbar.tabs[0]])
self.browser.tabbar.tabs[0].switch_to()
def restart(self, *args, **kwargs):
"""Restart Firefox and re-initialize data.
:param flags: Specific restart flags for Firefox
"""
# If no clean restart is requested, always use an in_app one
if not kwargs.get('clean'):
kwargs.update({"in_app": True})
self.marionette.restart(*args, **kwargs)
# Ensure that we always have a valid browser instance available
self.browser = self.puppeteer.windows.switch_to(lambda win: type(win) is BrowserWindow)
def setUp(self, *args, **kwargs):
super(PuppeteerMixin, self).setUp(*args, **kwargs)
self._start_handle_count = len(self.marionette.window_handles)
self._init_tab_handles = self.marionette.window_handles
self.marionette.set_context('chrome')
self.puppeteer = Puppeteer(self.marionette)
self.browser = self.puppeteer.windows.current
self.browser.focus()
with self.marionette.using_context(self.marionette.CONTEXT_CONTENT):
# Bug 1312674 - Navigating to about:blank twice can cause a hang in
# Marionette. So try to always have a known default page loaded.
# Bug 1418979 - Update this to a test-framework-specific file.
self.marionette.navigate('about:about')
def tearDown(self, *args, **kwargs):
self.marionette.set_context('chrome')
try:
# This code should be run after all other tearDown code
# so that in case of a failure, further tests will not run
# in a state that is more inconsistent than necessary.
self._check_and_fix_leaked_handles()
finally:
super(PuppeteerMixin, self).tearDown(*args, **kwargs)

View File

@ -1,78 +0,0 @@
# 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/.
from __future__ import absolute_import
from .decorators import use_class_as_property
class Puppeteer(object):
"""The puppeteer class is used to expose additional API and UI libraries.
Each library can be referenced by its puppeteer name as a member of a
Puppeteer instance. For example, the `current_window` member of the
`Browser` class can be accessed via `puppeteer.browser.current_window`.
"""
def __init__(self, marionette):
self._marionette = marionette
@property
def marionette(self):
return self._marionette
@use_class_as_property('api.appinfo.AppInfo')
def appinfo(self):
"""
Provides access to members of the appinfo api.
See the :class:`~api.appinfo.AppInfo` reference.
"""
@use_class_as_property('api.keys.Keys')
def keys(self):
"""
Provides a definition of control keys to use with keyboard shortcuts.
For example, keys.CONTROL or keys.ALT.
See the :class:`~api.keys.Keys` reference.
"""
@use_class_as_property('api.places.Places')
def places(self):
"""Provides low-level access to several bookmark and history related actions.
See the :class:`~api.places.Places` reference.
"""
@use_class_as_property('api.utils.Utils')
def utils(self):
"""Provides an api for interacting with utility actions.
See the :class:`~api.utils.Utils` reference.
"""
@use_class_as_property('api.prefs.Preferences')
def prefs(self):
"""
Provides an api for setting and inspecting preferences, as see in
about:config.
See the :class:`~api.prefs.Preferences` reference.
"""
@use_class_as_property('api.security.Security')
def security(self):
"""
Provides an api for accessing security related properties and helpers.
See the :class:`~api.security.Security` reference.
"""
@use_class_as_property('ui.windows.Windows')
def windows(self):
"""
Provides shortcuts to the top-level windows.
See the :class:`~ui.window.Windows` reference.
"""

View File

@ -1,176 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By
from firefox_puppeteer.ui.base import UIBaseLib
from firefox_puppeteer.ui.deck import Panel
class Deck(UIBaseLib):
def _create_panel_for_id(self, panel_id):
"""Creates an instance of :class:`Panel` for the specified panel id.
:param panel_id: The ID of the panel to create an instance of.
:returns: :class:`Panel` instance
"""
mapping = {'apply': ApplyPanel,
'checkForUpdates': CheckForUpdatesPanel,
'checkingForUpdates': CheckingForUpdatesPanel,
'downloadAndInstall': DownloadAndInstallPanel,
'downloadFailed': DownloadFailedPanel,
'downloading': DownloadingPanel,
'noUpdatesFound': NoUpdatesFoundPanel,
}
panel = self.element.find_element(By.ID, panel_id)
return mapping.get(panel_id, Panel)(self.marionette, self.window, panel)
# Properties for visual elements of the deck #
@property
def apply(self):
"""The :class:`ApplyPanel` instance for the apply panel.
:returns: :class:`ApplyPanel` instance.
"""
return self._create_panel_for_id('apply')
@property
def check_for_updates(self):
"""The :class:`CheckForUpdatesPanel` instance for the check for updates panel.
:returns: :class:`CheckForUpdatesPanel` instance.
"""
return self._create_panel_for_id('checkForUpdates')
@property
def checking_for_updates(self):
"""The :class:`CheckingForUpdatesPanel` instance for the checking for updates panel.
:returns: :class:`CheckingForUpdatesPanel` instance.
"""
return self._create_panel_for_id('checkingForUpdates')
@property
def download_and_install(self):
"""The :class:`DownloadAndInstallPanel` instance for the download and install panel.
:returns: :class:`DownloadAndInstallPanel` instance.
"""
return self._create_panel_for_id('downloadAndInstall')
@property
def download_failed(self):
"""The :class:`DownloadFailedPanel` instance for the download failed panel.
:returns: :class:`DownloadFailedPanel` instance.
"""
return self._create_panel_for_id('downloadFailed')
@property
def downloading(self):
"""The :class:`DownloadingPanel` instance for the downloading panel.
:returns: :class:`DownloadingPanel` instance.
"""
return self._create_panel_for_id('downloading')
@property
def no_updates_found(self):
"""The :class:`NoUpdatesFoundPanel` instance for the no updates found panel.
:returns: :class:`NoUpdatesFoundPanel` instance.
"""
return self._create_panel_for_id('noUpdatesFound')
@property
def panels(self):
"""List of all the :class:`Panel` instances of the current deck.
:returns: List of :class:`Panel` instances.
"""
panels = self.marionette.execute_script("""
let deck = arguments[0];
let panels = [];
for (let index = 0; index < deck.children.length; index++) {
if (deck.children[index].id) {
panels.push(deck.children[index].id);
}
}
return panels;
""", script_args=[self.element])
return [self._create_panel_for_id(panel) for panel in panels]
@property
def selected_index(self):
"""The index of the currently selected panel.
:return: Index of the selected panel.
"""
return int(self.element.get_property('selectedIndex'))
@property
def selected_panel(self):
"""A :class:`Panel` instance of the currently selected panel.
:returns: :class:`Panel` instance.
"""
return self.panels[self.selected_index]
class ApplyPanel(Panel):
@property
def button(self):
"""The DOM element which represents the Update button.
:returns: Reference to the button element.
"""
return self.element.find_element(By.ID, 'updateButton')
class CheckForUpdatesPanel(Panel):
@property
def button(self):
"""The DOM element which represents the Check for Updates button.
:returns: Reference to the button element.
"""
return self.element.find_element(By.ID, 'checkForUpdatesButton')
class CheckingForUpdatesPanel(Panel):
pass
class DownloadAndInstallPanel(Panel):
@property
def button(self):
"""The DOM element which represents the Download button.
:returns: Reference to the button element.
"""
return self.element.find_element(By.ID, 'downloadAndInstallButton')
class DownloadFailedPanel(Panel):
pass
class DownloadingPanel(Panel):
pass
class NoUpdatesFoundPanel(Panel):
pass

View File

@ -1,29 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By
from firefox_puppeteer.ui.about_window.deck import Deck
from firefox_puppeteer.ui.windows import BaseWindow, Windows
class AboutWindow(BaseWindow):
"""Representation of the About window."""
window_type = 'Browser:About'
@property
def deck(self):
"""The :class:`Deck` instance which represents the deck.
:returns: Reference to the deck.
"""
self.switch_to()
deck = self.window_element.find_element(By.ID, 'updateDeck')
return Deck(self.marionette, self, deck)
Windows.register_window(AboutWindow.window_type, AboutWindow)

View File

@ -1,56 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver.marionette import HTMLElement
from firefox_puppeteer.base import BaseLib
from firefox_puppeteer.ui.windows import BaseWindow
class UIBaseLib(BaseLib):
"""A base class for all UI element wrapper classes inside a chrome window."""
def __init__(self, marionette, window, element):
super(UIBaseLib, self).__init__(marionette)
assert isinstance(window, BaseWindow)
assert isinstance(element, HTMLElement)
self._window = window
self._element = element
@property
def element(self):
"""Returns the reference to the underlying DOM element.
:returns: Reference to the DOM element
"""
return self._element
@property
def window(self):
"""Returns the reference to the chrome window.
:returns: :class:`BaseWindow` instance of the chrome window.
"""
return self._window
class DOMElement(HTMLElement):
"""
Class that inherits from HTMLElement and provides a way for subclasses to
expose new api's.
"""
def __new__(cls, element):
instance = object.__new__(cls)
instance.__dict__ = element.__dict__.copy()
setattr(instance, 'inner', element)
return instance
def __init__(self, element):
pass

View File

@ -1,3 +0,0 @@
# 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/.

View File

@ -1,92 +0,0 @@
# 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/.
from __future__ import absolute_import
from abc import ABCMeta
from marionette_driver import By
from firefox_puppeteer.ui.base import UIBaseLib
class BaseNotification(UIBaseLib):
"""Abstract base class for any kind of notification."""
__metaclass__ = ABCMeta
@property
def close_button(self):
"""Provide access to the close button.
:returns: The close button.
"""
return self.element.find_element(By.CSS_SELECTOR, ".popup-notification-closebutton")
@property
def label(self):
"""Provide access to the notification label.
:returns: The notification label.
"""
return self.element.get_attribute('label')
@property
def origin(self):
"""Provide access to the notification origin.
:returns: The notification origin.
"""
return self.element.get_attribute('origin')
def close(self, force=False):
"""Close the notification.
:param force: Optional, if True force close the notification.
Defaults to False.
"""
if force:
self.marionette.execute_script('arguments[0].click()',
script_args=[self.close_button])
else:
self.close_button.click()
self.window.wait_for_notification(None)
class AddOnInstallBlockedNotification(BaseNotification):
"""Add-on install blocked notification."""
@property
def allow_button(self):
"""Provide access to the allow button.
:returns: The allow button.
"""
return self.element.find_element(
By.CLASS, "popup-notification-primary-button")
class AddOnInstallConfirmationNotification(BaseNotification):
"""Add-on install confirmation notification."""
pass
class AddOnInstallCompleteNotification(BaseNotification):
"""Add-on install complete notification."""
pass
class AddOnInstallFailedNotification(BaseNotification):
"""Add-on install failed notification."""
pass
class AddOnProgressNotification(BaseNotification):
"""Add-on progress notification."""
pass

View File

@ -1,350 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import (
By, Wait
)
import firefox_puppeteer.errors as errors
from firefox_puppeteer.api.security import Security
from firefox_puppeteer.ui.base import UIBaseLib
class TabBar(UIBaseLib):
"""Wraps the tabs toolbar DOM element inside a browser window."""
# Properties for visual elements of the tabs toolbar #
@property
def newtab_button(self):
"""The DOM element which represents the new tab button.
:returns: Reference to the new tab button.
"""
return self.toolbar.find_element(By.ID, 'tabs-newtab-button')
@property
def tabs(self):
"""List of all the :class:`Tab` instances of the current browser window.
:returns: List of :class:`Tab` instances.
"""
tabs = self.toolbar.find_elements(By.TAG_NAME, 'tab')
return [Tab(self.marionette, self.window, tab) for tab in tabs]
@property
def toolbar(self):
"""The DOM element which represents the tab toolbar.
:returns: Reference to the tabs toolbar.
"""
return self.element
# Properties for helpers when working with the tabs toolbar #
@property
def selected_index(self):
"""The index of the currently selected tab.
:return: Index of the selected tab.
"""
return int(self.toolbar.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]
# Methods for helpers when working with the tabs toolbar #
def close_all_tabs(self, exceptions=None):
"""Forces closing of all open tabs.
There is an optional `exceptions` list, which can be used to exclude
specific tabs from being closed.
:param exceptions: Optional, list of :class:`Tab` instances not to close.
"""
if exceptions is None:
exceptions = []
# Get handles from tab exceptions, and find those which can be closed
for tab in self.tabs:
if tab not in exceptions:
tab.close(force=True)
def close_tab(self, tab=None, trigger='menu', force=False):
"""Closes the tab by using the specified trigger.
By default the currently selected tab will be closed. If another :class:`Tab`
is specified, that one will be closed instead. Also when the tab is closed, a
:func:`switch_to` call is automatically performed, so that the new selected
tab becomes active.
:param tab: Optional, the :class:`Tab` instance to close. Defaults to
the currently selected tab.
:param trigger: Optional, method to close the current tab. This can
be a string with one of `menu` or `shortcut`, or a callback which gets triggered
with the :class:`Tab` as parameter. Defaults to `menu`.
:param force: Optional, forces the closing of the window by using the Gecko API.
Defaults to `False`.
"""
tab = tab or self.selected_tab
tab.close(trigger, force)
def open_tab(self, trigger='menu'):
"""Opens a new tab in the current browser window.
If the tab opens in the foreground, a call to :func:`switch_to` will
automatically be performed. But if it opens in the background, the current
tab will keep its focus.
It will first verify that a new `<tab>` element has been
introduced in the tabbar, and then that a new content
browser has been created.
:param trigger: Optional, method to open the new tab. This can
be a string with one of `menu`, `button` or `shortcut`, or a callback
which gets triggered with the current :class:`Tab` as parameter.
Defaults to `menu`.
:returns: :class:`Tab` instance for the opened tab.
"""
start_handles = self.marionette.window_handles
start_tabs = self.window.tabbar.tabs
# Prepare action which triggers the opening of the browser window
if callable(trigger):
trigger(self.selected_tab)
elif trigger == 'button':
self.window.tabbar.newtab_button.click()
elif trigger == 'menu':
self.window.menubar.select_by_id('file-menu', 'menu_newNavigatorTab')
elif trigger == 'shortcut':
self.window.send_shortcut('t', accel=True)
# elif - need to add other cases
else:
raise ValueError('Unknown opening method: "%s"' % trigger)
Wait(self.marionette).until(
lambda _: len(self.window.tabbar.tabs) == len(start_tabs) + 1,
message='No new tab present in tabbar')
Wait(self.marionette).until(
lambda mn: len(mn.window_handles) == len(start_handles) + 1,
message='No new content browser created')
handles = self.marionette.window_handles
[new_handle] = list(set(handles) - set(start_handles))
[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.switch_to()
return new_tab
def switch_to(self, target):
"""Switches the context to the specified tab.
:param target: The tab to switch to. `target` can be an index, a :class:`Tab`
instance, or a callback that returns True in the context of the desired tab.
:returns: Instance of the selected :class:`Tab`.
"""
start_handle = self.marionette.current_window_handle
if isinstance(target, int):
return self.tabs[target].switch_to()
elif isinstance(target, Tab):
return target.switch_to()
if callable(target):
for tab in self.tabs:
tab.switch_to()
if target(tab):
return tab
self.marionette.switch_to_window(start_handle)
raise errors.UnknownTabError("No tab found for '{}'".format(target))
raise ValueError("The 'target' parameter must either be an index or a callable")
@staticmethod
def get_handle_for_tab(marionette, tab_element):
"""Retrieves the marionette handle for the given :class:`Tab` instance.
:param marionette: An instance of the Marionette client.
:param tab_element: The DOM element corresponding to a tab inside the tabs toolbar.
:returns: `handle` of the tab.
"""
# TODO: This introduces coupling with marionette's window handles
# implementation. To avoid this, the capacity to get the XUL
# element corresponding to the active window according to
# marionette or a similar ability should be added to marionette.
handle = marionette.execute_script("""
let browser = arguments[0].linkedBrowser;
if (!browser || browser.outerWindowID == null) {
return null;
}
return browser.outerWindowID.toString();
""", script_args=[tab_element])
return handle
class Tab(UIBaseLib):
"""Wraps a tab DOM element."""
def __init__(self, marionette, window, element):
super(Tab, self).__init__(marionette, window, element)
self._security = Security(self.marionette)
self._handle = None
# Properties for visual elements of tabs #
@property
def close_button(self):
"""The DOM element which represents the tab close button.
:returns: Reference to the tab close button.
"""
return self.tab_element.find_element(By.CSS_SELECTOR, '.tab-close-button')
@property
def tab_element(self):
"""The inner tab DOM element.
:returns: Tab DOM element.
"""
return self.element
# Properties for backend values
@property
def location(self):
"""Returns the current URL
:returns: Current URL
"""
self.switch_to()
return self.marionette.execute_script("""
return arguments[0].linkedBrowser.currentURI.spec;
""", script_args=[self.tab_element])
@property
def certificate(self):
"""The SSL certificate assiciated with the loaded web page.
:returns: Certificate details as JSON blob.
"""
self.switch_to()
return self._security.get_certificate_for_page(self.tab_element)
# Properties for helpers when working with tabs #
@property
def handle(self):
"""The `handle` of the content window.
:returns: content window `handle`.
"""
# If no handle has been set yet, wait until it is available
if not self._handle:
self._handle = Wait(self.marionette).until(
lambda mn: TabBar.get_handle_for_tab(mn, self.element),
message='Tab handle could not be found.')
return self._handle
@property
def selected(self):
"""Checks if the tab is selected.
:return: `True` if the tab is selected.
"""
return self.marionette.execute_script("""
return arguments[0].hasAttribute('selected');
""", script_args=[self.tab_element])
# Methods for helpers when working with tabs #
def __eq__(self, other):
return self.handle == other.handle
def close(self, trigger='menu', force=False):
"""Closes the tab by using the specified trigger.
To ensure the tab was closed, it will first ensure the
`<tab>` element is removed from the tabbar, and then confirm
that the content browser was discarded.
When the tab is closed a :func:`switch_to` call is automatically performed, so that
the new selected tab becomes active.
:param trigger: Optional, method in how to close the tab. This can
be a string with one of `button`, `menu` or `shortcut`, or a callback which
gets triggered with the current :class:`Tab` as parameter. Defaults to `menu`.
:param force: Optional, forces the closing of the window by using the Gecko API.
Defaults to `False`.
"""
handle = self.handle
start_handles = self.marionette.window_handles
start_tabs = self.window.tabbar.tabs
self.switch_to()
if force:
self.marionette.close()
elif callable(trigger):
trigger(self)
elif trigger == 'button':
self.close_button.click()
elif trigger == 'menu':
self.window.menubar.select_by_id('file-menu', 'menu_close')
elif trigger == 'shortcut':
self.window.send_shortcut('w', accel=True)
else:
raise ValueError('Unknown closing method: "%s"' % trigger)
Wait(self.marionette).until(
lambda _: len(self.window.tabbar.tabs) == len(start_tabs) - 1,
message='Tab"%s" has not been closed' % handle)
Wait(self.marionette).until(
lambda mn: len(mn.window_handles) == len(start_handles) - 1,
message='Content browser "%s" has not been closed' % handle)
# Ensure to switch to the window handle which represents the new selected tab
self.window.tabbar.selected_tab.switch_to()
def select(self):
"""Selects the tab and sets the focus to it."""
self.tab_element.click()
self.switch_to()
# Bug 1121705: Maybe we have to wait for TabSelect event
Wait(self.marionette).until(
lambda _: self.selected,
message='Tab with handle "%s" could not be selected.' % self.handle)
def switch_to(self):
"""Switches the context of Marionette to this tab.
Please keep in mind that calling this method will not select the tab.
Use the :func:`~Tab.select` method instead.
"""
self.marionette.switch_to_window(self.handle)

View File

@ -1,475 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By, keys, Wait
from firefox_puppeteer.ui.base import UIBaseLib
class NavBar(UIBaseLib):
"""Provides access to the DOM elements contained in the
navigation bar as well as the location bar."""
def __init__(self, *args, **kwargs):
super(NavBar, self).__init__(*args, **kwargs)
self._locationbar = None
@property
def back_button(self):
"""Provides access to the DOM element back button in the navbar.
:returns: Reference to the back button.
"""
return self.marionette.find_element(By.ID, 'back-button')
@property
def forward_button(self):
"""Provides access to the DOM element forward button in the navbar.
:returns: Reference to the forward button.
"""
return self.marionette.find_element(By.ID, 'forward-button')
@property
def home_button(self):
"""Provides access to the DOM element home button in the navbar.
:returns: Reference to the home button element
"""
return self.marionette.find_element(By.ID, 'home-button')
@property
def locationbar(self):
"""Provides access to the DOM elements contained in the
locationbar.
See the :class:`LocationBar` reference.
"""
if not self._locationbar:
urlbar = self.marionette.find_element(By.ID, 'urlbar')
self._locationbar = LocationBar(self.marionette, self.window, urlbar)
return self._locationbar
@property
def menu_button(self):
"""Provides access to the DOM element menu button in the navbar.
:returns: Reference to the menu button element.
"""
return self.marionette.find_element(By.ID, 'PanelUI-menu-button')
@property
def toolbar(self):
"""The DOM element which represents the navigation toolbar.
:returns: Reference to the navigation toolbar.
"""
return self.element
class LocationBar(UIBaseLib):
"""Provides access to and methods for the DOM elements contained in the
locationbar (the text area of the ui that typically displays the current url)."""
def __init__(self, *args, **kwargs):
super(LocationBar, self).__init__(*args, **kwargs)
self._identity_popup = None
def clear(self):
"""Clears the contents of the url bar (via the DELETE shortcut)."""
self.focus('shortcut')
self.urlbar.send_keys(keys.Keys.DELETE)
Wait(self.marionette).until(
lambda _: self.value == '',
message='Contents of location bar could not be cleared.')
@property
def focused(self):
"""Checks the focus state of the location bar.
:returns: `True` if focused, otherwise `False`
"""
return self.urlbar.get_attribute('focused') == 'true'
@property
def identity_icon(self):
""" Provides access to the urlbar identity icon.
:returns: Reference to the identity icon element.
"""
return self.marionette.find_element(By.ID, 'identity-icon')
def focus(self, event='click'):
"""Focus the location bar according to the provided event.
:param eventt: The event to synthesize in order to focus the urlbar
(one of `click` or `shortcut`).
"""
if event == 'click':
self.urlbar.click()
elif event == 'shortcut':
self.window.send_shortcut('l', accel=True)
else:
raise ValueError("An unknown event type was passed: %s" % event)
Wait(self.marionette).until(
lambda _: self.focused,
message='Location bar has not be focused.')
@property
def identity_box(self):
"""The DOM element which represents the identity box.
:returns: Reference to the identity box.
"""
return self.marionette.find_element(By.ID, 'identity-box')
@property
def identity_country_label(self):
"""The DOM element which represents the identity icon country label.
:returns: Reference to the identity icon country label.
"""
return self.marionette.find_element(By.ID, 'identity-icon-country-label')
@property
def identity_organization_label(self):
"""The DOM element which represents the identity icon label.
:returns: Reference to the identity icon label.
"""
return self.marionette.find_element(By.ID, 'identity-icon-label')
@property
def identity_popup(self):
"""Provides utility members for accessing and manipulating the
identity popup.
See the :class:`IdentityPopup` reference.
"""
if not self._identity_popup:
popup = self.marionette.find_element(By.ID, 'identity-popup')
self._identity_popup = IdentityPopup(self.marionette,
self.window, popup)
return self._identity_popup
def load_url(self, url):
"""Load the specified url in the location bar by synthesized
keystrokes.
:param url: The url to load.
"""
self.clear()
self.focus('shortcut')
self.urlbar.send_keys(url + keys.Keys.ENTER)
@property
def notification_popup(self):
"""Provides access to the DOM element notification popup.
:returns: Reference to the notification popup.
"""
return self.marionette.find_element(By.ID, "notification-popup")
def open_identity_popup(self):
"""Open the identity popup."""
self.identity_box.click()
Wait(self.marionette).until(
lambda _: self.identity_popup.is_open,
message='Identity popup has not been opened.')
@property
def reload_button(self):
"""Provides access to the DOM element reload button.
:returns: Reference to the reload button.
"""
return self.marionette.find_element(By.ID, 'reload-button')
def reload_url(self, trigger='button', force=False):
"""Reload the currently open page.
:param trigger: The event type to use to cause the reload (one of
`shortcut`, `shortcut2`, or `button`).
:param force: Whether to cause a forced reload.
"""
# TODO: The force parameter is ignored for the moment. Use
# mouse event modifiers or actions when they're ready.
# Bug 1097705 tracks this feature in marionette.
if trigger == 'button':
self.reload_button.click()
elif trigger == 'shortcut':
self.window.send_shortcut('r')
elif trigger == 'shortcut2':
self.window.send_shortcut(keys.Keys.F5)
@property
def stop_button(self):
"""Provides access to the DOM element stop button.
:returns: Reference to the stop button.
"""
return self.marionette.find_element(By.ID, 'stop-button')
@property
def urlbar(self):
"""Provides access to the DOM element urlbar.
:returns: Reference to the url bar.
"""
return self.marionette.find_element(By.ID, 'urlbar')
@property
def urlbar_input(self):
"""Provides access to the urlbar input element.
:returns: Reference to the urlbar input.
"""
return self.marionette.find_element(By.ID, 'urlbar-input')
@property
def value(self):
"""Provides access to the currently displayed value of the urlbar.
:returns: The urlbar value.
"""
return self.urlbar_input.get_property('value')
class IdentityPopup(UIBaseLib):
"""Wraps DOM elements and methods for interacting with the identity popup."""
def __init__(self, *args, **kwargs):
super(IdentityPopup, self).__init__(*args, **kwargs)
self._view = None
@property
def is_open(self):
"""Returns whether this popup is currently open.
:returns: True when the popup is open, otherwise false.
"""
return self.element.get_property('state') == 'open'
def close(self, force=False):
"""Closes the identity popup by hitting the escape key.
:param force: Optional, If `True` force close the popup.
Defaults to `False`
"""
if not self.is_open:
return
if force:
self.marionette.execute_script("""
arguments[0].hidePopup();
""", script_args=[self.element])
else:
self.element.send_keys(keys.Keys.ESCAPE)
Wait(self.marionette).until(
lambda _: not self.is_open,
message='Identity popup has not been closed.')
@property
def view(self):
"""Provides utility members for accessing and manipulating the
identity popup's multi view.
See the :class:`IdentityPopupMultiView` reference.
"""
if not self._view:
view = self.marionette.find_element(By.ID, 'identity-popup-multiView')
self._view = IdentityPopupMultiView(self.marionette, self.window, view)
return self._view
class IdentityPopupMultiView(UIBaseLib):
def _create_view_for_id(self, view_id):
"""Creates an instance of :class:`IdentityPopupView` for the specified view id.
:param view_id: The ID of the view to create an instance of.
:returns: :class:`IdentityPopupView` instance
"""
mapping = {'identity-popup-mainView': IdentityPopupMainView,
'identity-popup-securityView': IdentityPopupSecurityView,
}
view = self.marionette.find_element(By.ID, view_id)
return mapping.get(view_id, IdentityPopupView)(self.marionette, self.window, view)
@property
def main(self):
"""The DOM element which represents the main view.
:returns: Reference to the main view.
"""
return self._create_view_for_id('identity-popup-mainView')
@property
def security(self):
"""The DOM element which represents the security view.
:returns: Reference to the security view.
"""
return self._create_view_for_id('identity-popup-securityView')
class IdentityPopupView(UIBaseLib):
@property
def selected(self):
"""Checks if the view is selected.
:return: `True` if the view is selected.
"""
return self.element.get_attribute('visible') == 'true'
class IdentityPopupMainView(IdentityPopupView):
@property
def selected(self):
"""Checks if the view is selected.
:return: `True` if the view is selected.
"""
return self.marionette.execute_script("""
return arguments[0].panelMultiView.getAttribute('viewtype') == 'main';
""", script_args=[self.element])
@property
def expander(self):
"""The DOM element which represents the expander button for the security content.
:returns: Reference to the identity popup expander button.
"""
return self.element.find_element(By.CLASS_NAME, 'identity-popup-expander')
@property
def header(self):
"""The DOM element which represents the identity-popup header.
:returns: Reference to the identity-popup header.
"""
return self.element.find_element(By.ID, 'identity-popup-mainView-panel-header-span')
@property
def insecure_connection_label(self):
"""The DOM element which represents the identity popup insecure connection label.
:returns: Reference to the identity-popup insecure connection label.
"""
return self.element.find_element(By.CLASS_NAME, 'identity-popup-connection-not-secure')
@property
def internal_connection_label(self):
"""The DOM element which represents the identity popup internal connection label.
:returns: Reference to the identity-popup internal connection label.
"""
return self.element.find_element(By.CSS_SELECTOR, 'description[when-connection=chrome]')
@property
def permissions(self):
"""The DOM element which represents the identity-popup permissions content.
:returns: Reference to the identity-popup permissions.
"""
return self.element.find_element(By.ID, 'identity-popup-permissions-content')
@property
def secure_connection_label(self):
"""The DOM element which represents the identity popup secure connection label.
:returns: Reference to the identity-popup secure connection label.
"""
return self.element.find_element(By.CLASS_NAME, 'identity-popup-connection-secure')
class IdentityPopupSecurityView(IdentityPopupView):
@property
def disable_mixed_content_blocking_button(self):
"""The DOM element which represents the disable mixed content blocking button.
:returns: Reference to the disable mixed content blocking button.
"""
return self.element.find_element(By.CSS_SELECTOR,
'button[when-mixedcontent=active-blocked]')
@property
def enable_mixed_content_blocking_button(self):
"""The DOM element which represents the enable mixed content blocking button.
:returns: Reference to the enable mixed content blocking button.
"""
return self.element.find_element(By.CSS_SELECTOR,
'button[when-mixedcontent=active-loaded]')
@property
def host(self):
"""The DOM element which represents the identity-popup content host.
:returns: Reference to the identity-popup content host.
"""
return self.element.find_element(By.ID, 'identity-popup-host')
@property
def insecure_connection_label(self):
"""The DOM element which represents the identity popup insecure connection label.
:returns: Reference to the identity-popup insecure connection label.
"""
return self.element.find_element(By.CLASS_NAME, 'identity-popup-connection-not-secure')
@property
def more_info_button(self):
"""The DOM element which represents the identity-popup more info button.
:returns: Reference to the identity-popup more info button.
"""
return self.element.find_element(By.ID, 'identity-popup-more-info')
@property
def owner(self):
"""The DOM element which represents the identity-popup content owner.
:returns: Reference to the identity-popup content owner.
"""
return self.element.find_element(By.ID, 'identity-popup-content-owner')
@property
def owner_location(self):
"""The DOM element which represents the identity-popup content supplemental.
:returns: Reference to the identity-popup content supplemental.
"""
return self.element.find_element(By.ID, 'identity-popup-content-supplemental')
@property
def secure_connection_label(self):
"""The DOM element which represents the identity popup secure connection label.
:returns: Reference to the identity-popup secure connection label.
"""
return self.element.find_element(By.CLASS_NAME, 'identity-popup-connection-secure')
@property
def verifier(self):
"""The DOM element which represents the identity-popup content verifier.
:returns: Reference to the identity-popup content verifier.
"""
return self.element.find_element(By.ID, 'identity-popup-content-verifier')

View File

@ -1,243 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By, Wait
from marionette_driver.errors import NoSuchElementException
from firefox_puppeteer.ui.about_window.window import AboutWindow
from firefox_puppeteer.ui.browser.notifications import (
AddOnInstallBlockedNotification,
AddOnInstallConfirmationNotification,
AddOnInstallCompleteNotification,
AddOnInstallFailedNotification,
AddOnProgressNotification,
BaseNotification)
from firefox_puppeteer.ui.browser.tabbar import TabBar
from firefox_puppeteer.ui.browser.toolbars import NavBar
from firefox_puppeteer.ui.pageinfo.window import PageInfoWindow
from firefox_puppeteer.ui.windows import BaseWindow, Windows
class BrowserWindow(BaseWindow):
"""Representation of a browser window."""
window_type = 'navigator:browser'
def __init__(self, *args, **kwargs):
super(BrowserWindow, self).__init__(*args, **kwargs)
self._navbar = None
self._tabbar = None
@property
def default_homepage(self):
"""The default homepage as used by the current locale.
:returns: The default homepage for the current locale.
"""
return self.marionette.get_pref('browser.startup.homepage')
@property
def is_private(self):
"""Returns True if this is a Private Browsing window."""
self.switch_to()
with self.marionette.using_context('chrome'):
return self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
let chromeWindow = arguments[0].ownerDocument.defaultView;
return PrivateBrowsingUtils.isWindowPrivate(chromeWindow);
""", script_args=[self.window_element])
@property
def navbar(self):
"""Provides access to the navigation bar. This is the toolbar containing
the back, forward and home buttons. It also contains the location bar.
See the :class:`~ui.browser.toolbars.NavBar` reference.
"""
self.switch_to()
if not self._navbar:
navbar = self.window_element.find_element(By.ID, 'nav-bar')
self._navbar = NavBar(self.marionette, self, navbar)
return self._navbar
@property
def notification(self):
"""Provides access to the currently displayed notification."""
notifications_map = {
'addon-install-blocked-notification': AddOnInstallBlockedNotification,
'addon-webext-permissions-notification': AddOnInstallConfirmationNotification,
'addon-install-complete-notification': AddOnInstallCompleteNotification,
'addon-install-failed-notification': AddOnInstallFailedNotification,
'addon-progress-notification': AddOnProgressNotification,
}
try:
notification = self.window_element.find_element(
By.CSS_SELECTOR, '#notification-popup popupnotification')
notification_id = notification.get_attribute('id')
return notifications_map.get(notification_id, BaseNotification)(
self.marionette, self, notification)
except NoSuchElementException:
return None # no notification is displayed
def wait_for_notification(self, notification_class=BaseNotification,
timeout=5):
"""Waits for the specified notification to be displayed.
:param notification_class: Optional, the notification class to wait for.
If `None` is specified it will wait for any notification to be closed.
Defaults to `BaseNotification`.
:param timeout: Optional, how long to wait for the expected notification.
Defaults to 5 seconds.
"""
wait = Wait(self.marionette, timeout=timeout)
if notification_class:
if notification_class is BaseNotification:
message = 'No notification was shown.'
else:
message = '{0} was not shown.'.format(notification_class.__name__)
wait.until(
lambda _: isinstance(self.notification, notification_class),
message=message)
else:
message = 'Unexpected notification shown.'
wait.until(
lambda _: self.notification is None,
message='Unexpected notification shown.')
@property
def tabbar(self):
"""Provides access to the tab bar.
See the :class:`~ui.browser.tabbar.TabBar` reference.
"""
self.switch_to()
if not self._tabbar:
tabbrowser = self.window_element.find_element(By.ID, 'tabbrowser-tabs')
self._tabbar = TabBar(self.marionette, self, tabbrowser)
return self._tabbar
def close(self, trigger='menu', force=False):
"""Closes the current browser window by using the specified trigger.
:param trigger: Optional, method to close the current browser window. This can
be a string with one of `menu` or `shortcut`, or a callback which gets triggered
with the current :class:`BrowserWindow` as parameter. Defaults to `menu`.
:param force: Optional, forces the closing of the window by using the Gecko API.
Defaults to `False`.
"""
def callback(win):
# Prepare action which triggers the opening of the browser window
if callable(trigger):
trigger(win)
elif trigger == 'menu':
self.menubar.select_by_id('file-menu', 'menu_closeWindow')
elif trigger == 'shortcut':
win.send_shortcut('w', accel=True, shift=True)
else:
raise ValueError('Unknown closing method: "%s"' % trigger)
BaseWindow.close(self, callback, force)
def get_final_url(self, url):
"""Loads the page at `url` and returns the resulting url.
This function enables testing redirects.
:param url: The url to test.
:returns: The resulting loaded url.
"""
with self.marionette.using_context('content'):
self.marionette.navigate(url)
return self.marionette.get_url()
def open_browser(self, trigger='menu', is_private=False):
"""Opens a new browser window by using the specified trigger.
:param trigger: Optional, method in how to open the new browser window. This can
be a string with one of `menu` or `shortcut`, or a callback which gets triggered
with the current :class:`BrowserWindow` as parameter. Defaults to `menu`.
:param is_private: Optional, if True the new window will be a private browsing one.
:returns: :class:`BrowserWindow` instance for the new browser window.
"""
def callback(win):
# Prepare action which triggers the opening of the browser window
if callable(trigger):
trigger(win)
elif trigger == 'menu':
menu_id = 'menu_newPrivateWindow' if is_private else 'menu_newNavigator'
self.menubar.select_by_id('file-menu', menu_id)
elif trigger == 'shortcut':
win.send_shortcut('p' if is_private else 'n',
accel=True, shift=is_private)
else:
raise ValueError('Unknown opening method: "%s"' % trigger)
return BaseWindow.open_window(self, callback, BrowserWindow)
def open_about_window(self, trigger='menu'):
"""Opens the about window by using the specified trigger.
:param trigger: Optional, method in how to open the new browser window. This can
either the string `menu` or a callback which gets triggered
with the current :class:`BrowserWindow` as parameter. Defaults to `menu`.
:returns: :class:`AboutWindow` instance of the opened window.
"""
def callback(win):
# Prepare action which triggers the opening of the browser window
if callable(trigger):
trigger(win)
elif trigger == 'menu':
self.menubar.select_by_id('helpMenu', 'aboutName')
else:
raise ValueError('Unknown opening method: "%s"' % trigger)
return BaseWindow.open_window(self, callback, AboutWindow)
def open_page_info_window(self, trigger='menu'):
"""Opens the page info window by using the specified trigger.
:param trigger: Optional, method in how to open the new browser window. This can
be a string with one of `menu` or `shortcut`, or a callback which gets triggered
with the current :class:`BrowserWindow` as parameter. Defaults to `menu`.
:returns: :class:`PageInfoWindow` instance of the opened window.
"""
def callback(win):
# Prepare action which triggers the opening of the browser window
if callable(trigger):
trigger(win)
elif trigger == 'menu':
self.menubar.select_by_id('tools-menu', 'menu_pageInfo')
elif trigger == 'shortcut':
if win.marionette.session_capabilities['platformName'] == 'windows':
raise ValueError('Page info shortcut not available on Windows.')
win.send_shortcut('i', accel=True)
elif trigger == 'context_menu':
# TODO: Add once we can do right clicks
pass
else:
raise ValueError('Unknown opening method: "%s"' % trigger)
return BaseWindow.open_window(self, callback, PageInfoWindow)
Windows.register_window(BrowserWindow.window_type, BrowserWindow)

View File

@ -1,19 +0,0 @@
# 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/.
from __future__ import absolute_import
from firefox_puppeteer.ui.base import UIBaseLib
class Panel(UIBaseLib):
def __eq__(self, other):
return self.element.get_attribute('id') == other.element.get_attribute('id')
def __ne__(self, other):
return self.element.get_attribute('id') != other.element.get_attribute('id')
def __str__(self):
return self.element.get_attribute('id')

View File

@ -1,112 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By
from marionette_driver.errors import NoSuchElementException
from firefox_puppeteer.base import BaseLib
from firefox_puppeteer.ui.base import DOMElement
class MenuBar(BaseLib):
"""Wraps the menubar DOM element inside a browser window."""
@property
def menus(self):
"""A list of :class:`MenuElement` instances corresponding to
the top level menus in the menubar.
:returns: A list of :class:`MenuElement` instances.
"""
menus = (self.marionette.find_element(By.ID, 'main-menubar')
.find_elements(By.TAG_NAME, 'menu'))
return [self.MenuElement(menu) for menu in menus]
def get_menu(self, label):
"""Get a :class:`MenuElement` instance corresponding to the specified label.
:param label: The label of the menu, e.g., **File** or **View**.
:returns: A :class:`MenuElement` instance.
"""
menu = [m for m in self.menus if m.get_attribute('label') == label]
if not menu:
raise NoSuchElementException('Could not find a menu with '
'label "{}"'.format(label))
return menu[0]
def get_menu_by_id(self, menu_id):
"""Get a :class:`MenuElement` instance corresponding to the specified
ID.
:param menu_id: The ID of the menu, e.g., **file-menu** or **view-menu**.
:returns: A :class:`MenuElement` instance.
"""
menu = [m for m in self.menus if m.get_attribute('id') == menu_id]
if not menu:
raise NoSuchElementException('Could not find a menu with '
'id "{}"'.format(menu_id))
return menu[0]
def select(self, label, item):
"""Select an item in a menu.
:param label: The label of the menu, e.g., **File** or **View**.
:param item: The label of the item in the menu, e.g., **New Tab**.
"""
return self.get_menu(label).select(item)
def select_by_id(self, menu_id, item_id):
"""Select an item in a menu.
:param menu_id: The ID of the menu, e.g. **file-menu** or **view-menu**.
:param item_id: The ID of the item in the menu, e.g. **menu_newNavigatorTab**.
"""
return self.get_menu_by_id(menu_id).select_by_id(item_id)
class MenuElement(DOMElement):
"""Wraps a menu element DOM element."""
@property
def items(self):
"""A list of menuitem DOM elements within this :class:`MenuElement` instance.
:returns: A list of items in the menu.
"""
return (self.find_element(By.TAG_NAME, 'menupopup')
.find_elements(By.TAG_NAME, 'menuitem'))
def select(self, label):
"""Click on a menu item within this menu.
:param label: The label of the menu item, e.g., **New Tab**.
"""
item = [l for l in self.items if l.get_attribute('label') == label]
if not item:
message = ("Item labeled '{}' not found in the '{}' menu"
.format(label, self.get_attribute('label')))
raise NoSuchElementException(message)
return item[0].click()
def select_by_id(self, menu_item_id):
"""Click on a menu item within this menu.
:param menu_item_id: The ID of the menu item, e.g. **menu_newNavigatorTab**.
"""
item = [l for l in self.items if l.get_attribute('id') ==
menu_item_id]
if not item:
message = ("Item with ID '{}' not found in the '{}' menu"
.format(menu_item_id, self.get_attribute('id')))
raise NoSuchElementException(message)
return item[0].click()

View File

@ -1,185 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By, Wait
from firefox_puppeteer.ui.base import UIBaseLib
from firefox_puppeteer.ui.deck import Panel
class Deck(UIBaseLib):
def _create_panel_for_id(self, panel_id):
"""Creates an instance of :class:`Panel` for the specified panel id.
:param panel_id: The ID of the panel to create an instance of.
:returns: :class:`Panel` instance
"""
mapping = {'generalPanel': GeneralPanel,
'mediaPanel': MediaPanel,
'permPanel': PermissionsPanel,
'securityPanel': SecurityPanel
}
panel = self.element.find_element(By.ID, panel_id)
return mapping.get(panel_id, Panel)(self.marionette, self.window, panel)
# Properties for visual elements of the deck #
@property
def general(self):
"""The :class:`GeneralPanel` instance for the general panel.
:returns: :class:`GeneralPanel` instance.
"""
return self._create_panel_for_id('generalPanel')
@property
def media(self):
"""The :class:`MediaPanel` instance for the media panel.
:returns: :class:`MediaPanel` instance.
"""
return self._create_panel_for_id('mediaPanel')
@property
def panels(self):
"""List of all the :class:`Panel` instances of the current deck.
:returns: List of :class:`Panel` instances.
"""
panels = self.marionette.execute_script("""
let deck = arguments[0];
let panels = [];
for (let index = 0; index < deck.children.length; index++) {
if (deck.children[index].id) {
panels.push(deck.children[index].id);
}
}
return panels;
""", script_args=[self.element])
return [self._create_panel_for_id(panel) for panel in panels]
@property
def permissions(self):
"""The :class:`PermissionsPanel` instance for the permissions panel.
:returns: :class:`PermissionsPanel` instance.
"""
return self._create_panel_for_id('permPanel')
@property
def security(self):
"""The :class:`SecurityPanel` instance for the security panel.
:returns: :class:`SecurityPanel` instance.
"""
return self._create_panel_for_id('securityPanel')
# Properties for helpers when working with the deck #
@property
def selected_index(self):
"""The index of the currently selected panel.
:return: Index of the selected panel.
"""
return int(self.element.get_property('selectedIndex'))
@property
def selected_panel(self):
"""A :class:`Panel` instance of the currently selected panel.
:returns: :class:`Panel` instance.
"""
return self.panels[self.selected_index]
# Methods for helpers when working with the deck #
def select(self, panel):
"""Selects the specified panel via the tab element.
:param panel: The panel to select.
:returns: :class:`Panel` instance of the selected panel.
"""
panel.tab.click()
Wait(self.marionette).until(
lambda _: self.selected_panel == panel,
message='Panel with ID "%s" could not be selected.' % panel)
return panel
class PageInfoPanel(Panel):
@property
def tab(self):
"""The DOM element which represents the corresponding tab element at the top.
:returns: Reference to the tab element.
"""
name = self.element.get_property('id').split('Panel')[0]
return self.window.window_element.find_element(By.ID, name + 'Tab')
class GeneralPanel(PageInfoPanel):
pass
class MediaPanel(PageInfoPanel):
pass
class PermissionsPanel(PageInfoPanel):
pass
class SecurityPanel(PageInfoPanel):
@property
def domain(self):
"""The DOM element which represents the domain textbox.
:returns: Reference to the textbox element.
"""
return self.element.find_element(By.ID, 'security-identity-domain-value')
@property
def owner(self):
"""The DOM element which represents the owner textbox.
:returns: Reference to the textbox element.
"""
return self.element.find_element(By.ID, 'security-identity-owner-value')
@property
def verifier(self):
"""The DOM element which represents the verifier textbox.
:returns: Reference to the textbox element.
"""
return self.element.find_element(By.ID, 'security-identity-verifier-value')
@property
def view_certificate(self):
"""The DOM element which represents the view certificate button.
:returns: Reference to the button element.
"""
return self.element.find_element(By.ID, 'security-view-cert')
@property
def view_passwords(self):
"""The DOM element which represents the view passwords button.
:returns: Reference to the button element.
"""
return self.element.find_element(By.ID, 'security-view-password')

View File

@ -1,52 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By
from firefox_puppeteer.ui.pageinfo.deck import Deck
from firefox_puppeteer.ui.windows import BaseWindow, Windows
class PageInfoWindow(BaseWindow):
"""Representation of a page info window."""
window_type = 'Browser:page-info'
@property
def deck(self):
"""The :class:`Deck` instance which represents the deck.
:returns: Reference to the deck.
"""
deck = self.window_element.find_element(By.ID, 'mainDeck')
return Deck(self.marionette, self, deck)
def close(self, trigger='shortcut', force=False):
"""Closes the current page info window by using the specified trigger.
:param trigger: Optional, method to close the current window. This can
be a string with one of `menu` (OS X only) or `shortcut`, or a callback
which gets triggered with the current :class:`PageInfoWindow` as parameter.
Defaults to `shortcut`.
:param force: Optional, forces the closing of the window by using the Gecko API.
Defaults to `False`.
"""
def callback(win):
# Prepare action which triggers the opening of the browser window
if callable(trigger):
trigger(win)
elif trigger == 'menu':
self.menubar.select_by_id('file-menu', 'menu_close')
elif trigger == 'shortcut':
win.send_shortcut('w', accel=True)
else:
raise ValueError('Unknown closing method: "%s"' % trigger)
super(PageInfoWindow, self).close(callback, force)
Windows.register_window(PageInfoWindow.window_type, PageInfoWindow)

View File

@ -1,395 +0,0 @@
# 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/.
from __future__ import absolute_import
from marionette_driver import By, Wait
from marionette_driver.errors import NoSuchWindowException
from marionette_driver.keys import Keys
import firefox_puppeteer.errors as errors
from firefox_puppeteer.base import BaseLib
from firefox_puppeteer.decorators import use_class_as_property
class Windows(BaseLib):
# Used for registering the different windows with this class to avoid
# circular dependencies with BaseWindow
windows_map = {}
@property
def all(self):
"""Retrieves a list of all open chrome windows.
:returns: List of :class:`BaseWindow` instances corresponding to the
windows in `marionette.chrome_window_handles`.
"""
return [self.create_window_instance(handle) for handle in
self.marionette.chrome_window_handles]
@property
def current(self):
"""Retrieves the currently selected chrome window.
:returns: The :class:`BaseWindow` for the currently active window.
"""
return self.create_window_instance(self.marionette.current_chrome_window_handle)
@property
def focused_chrome_window_handle(self):
"""Returns the currently focused chrome window handle.
:returns: The `window handle` of the focused chrome window.
"""
with self.marionette.using_context('chrome'):
return self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
let win = Services.focus.activeWindow;
if (win) {
return win.windowUtils.outerWindowID.toString();
}
return null;
""")
def close(self, handle):
"""Closes the chrome window with the given handle.
:param handle: The handle of the chrome window.
"""
self.switch_to(handle)
# TODO: Maybe needs to wait as handled via an observer
return self.marionette.close_chrome_window()
def close_all(self, exceptions=None):
"""Closes all open chrome windows.
There is an optional `exceptions` list, which can be used to exclude
specific chrome windows from being closed.
:param exceptions: Optional, list of :class:`BaseWindow` instances not to close.
"""
windows_to_keep = exceptions or []
# Get handles of windows_to_keep
handles_to_keep = [entry.handle for entry in windows_to_keep]
# Find handles to close and close them all
handles_to_close = set(self.marionette.chrome_window_handles) - set(handles_to_keep)
for handle in handles_to_close:
self.close(handle)
def create_window_instance(self, handle, expected_class=None):
"""Creates a :class:`BaseWindow` instance for the given chrome window.
:param handle: The handle of the chrome window.
:param expected_class: Optional, check for the correct window class.
"""
current_handle = self.marionette.current_chrome_window_handle
window = None
with self.marionette.using_context('chrome'):
try:
# Retrieve window type to determine the type of chrome window
if handle != self.marionette.current_chrome_window_handle:
self.switch_to(handle)
window_type = self.marionette.get_window_type()
finally:
# Ensure to switch back to the original window
if handle != current_handle:
self.switch_to(current_handle)
if window_type in self.windows_map:
window = self.windows_map[window_type](self.marionette, handle)
else:
window = BaseWindow(self.marionette, handle)
if expected_class is not None and type(window) is not expected_class:
raise errors.UnexpectedWindowTypeError('Expected window "%s" but got "%s"' %
(expected_class, type(window)))
# Before continuing ensure the chrome window has been completed loading
Wait(self.marionette).until(
lambda _: self.loaded(handle),
message='Chrome window with handle "%s" did not finish loading.' % handle)
return window
def focus(self, handle):
"""Focuses the chrome window with the given handle.
:param handle: The handle of the chrome window.
"""
self.switch_to(handle, focus=True)
def loaded(self, handle):
"""Check if the chrome window with the given handle has been completed loading.
:param handle: The handle of the chrome window.
:returns: True, if the chrome window has been loaded.
"""
with self.marionette.using_context('chrome'):
return self.marionette.execute_script("""
Components.utils.import("resource://gre/modules/Services.jsm");
let win = Services.wm.getOuterWindowWithId(Number(arguments[0]));
return win.document.readyState == 'complete';
""", script_args=[handle])
def switch_to(self, target, focus=False):
"""Switches context to the specified chrome window.
:param target: The window to switch to. `target` can be a `handle` or a
callback that returns True in the context of the desired
window.
:returns: Instance of the selected :class:`BaseWindow`.
"""
target_handle = None
if target in self.marionette.chrome_window_handles:
target_handle = target
elif callable(target):
current_handle = self.marionette.current_chrome_window_handle
# switches context if callback for a chrome window returns `True`.
for handle in self.marionette.chrome_window_handles:
self.marionette.switch_to_window(handle)
window = self.create_window_instance(handle)
if target(window):
target_handle = handle
break
# if no handle has been found switch back to original window
if not target_handle:
self.marionette.switch_to_window(current_handle)
if target_handle is None:
raise NoSuchWindowException("No window found for '{}'"
.format(target))
self.marionette.switch_to_window(target_handle, focus=focus)
return self.create_window_instance(target_handle)
@classmethod
def register_window(cls, window_type, window_class):
"""Registers a chrome window with this class so that this class may in
turn create the appropriate window instance later on.
:param window_type: The type of window.
:param window_class: The constructor of the window
"""
cls.windows_map[window_type] = window_class
class BaseWindow(BaseLib):
"""Base class for any kind of chrome window."""
def __init__(self, marionette, window_handle):
super(BaseWindow, self).__init__(marionette)
self._windows = Windows(self.marionette)
if window_handle not in self.marionette.chrome_window_handles:
raise errors.UnknownWindowError('Window with handle "%s" does not exist' %
window_handle)
self._handle = window_handle
def __eq__(self, other):
return self.handle == other.handle
@property
def closed(self):
"""Returns closed state of the chrome window.
:returns: True if the window has been closed.
"""
return self.handle not in self.marionette.chrome_window_handles
@property
def focused(self):
"""Returns `True` if the chrome window is focused.
:returns: True if the window is focused.
"""
self.switch_to()
return self.handle == self._windows.focused_chrome_window_handle
@property
def handle(self):
"""Returns the `window handle` of the chrome window.
:returns: `window handle`.
"""
return self._handle
@property
def loaded(self):
"""Checks if the window has been fully loaded.
:returns: True, if the window is loaded.
"""
self._windows.loaded(self.handle)
@use_class_as_property('ui.menu.MenuBar')
def menubar(self):
"""Provides access to the menu bar, for example, the **File** menu.
See the :class:`~ui.menu.MenuBar` reference.
"""
@property
def window_element(self):
"""Returns the inner DOM window element.
:returns: DOM window element.
"""
self.switch_to()
return self.marionette.find_element(By.CSS_SELECTOR, ':root')
def close(self, callback=None, force=False):
"""Closes the current chrome window.
If this is the last remaining window, the Marionette session is ended.
:param callback: Optional, function to trigger the window to open. It is
triggered with the current :class:`BaseWindow` as parameter.
Defaults to `OpenBrowserWindow()` (from browser.js).
:param force: Optional, forces the closing of the window by using the Gecko API.
Defaults to `False`.
"""
self.switch_to()
# Bug 1121698
# For more stable tests register an observer topic first
prev_win_count = len(self.marionette.chrome_window_handles)
handle = self.handle
if force or callback is None:
self._windows.close(handle)
else:
callback(self)
# Bug 1121698
# Observer code should let us ditch this wait code
Wait(self.marionette).until(
lambda m: len(m.chrome_window_handles) == prev_win_count - 1,
message='Chrome window with handle "%s" has not been closed.' % handle)
def focus(self):
"""Sets the focus to the current chrome window."""
return self._windows.focus(self.handle)
def open_window(self, callback=None, expected_window_class=None, focus=True):
"""Opens a new top-level chrome window.
:param callback: Optional, function to trigger the window to open. It is
triggered with the current :class:`BaseWindow` as parameter.
Defaults to `OpenBrowserWindow()` (from browser.js).
:param expected_class: Optional, check for the correct window class.
:param focus: Optional, if true, focus the new window.
Defaults to `True`.
"""
# Bug 1121698
# For more stable tests register an observer topic first
start_handles = self.marionette.chrome_window_handles
self.switch_to()
if callable(callback):
with self.marionette.using_context('chrome'):
callback(self)
else:
result = self.marionette.open(type="window", focus=focus)
if result["type"] != "window":
raise Exception(
"Newly opened browsing context is of type {} and not window.".format(
result["type"]))
# TODO: Needs to be replaced with observer handling code (bug 1121698)
Wait(self.marionette).until(
lambda mn: len(mn.chrome_window_handles) == len(start_handles) + 1,
message="No new chrome window has been opened"
)
handles = self.marionette.chrome_window_handles
[new_handle] = list(set(handles) - set(start_handles))
assert new_handle is not None
window = self._windows.create_window_instance(new_handle, expected_window_class)
if focus:
window.focus()
return window
def send_shortcut(self, command_key, **kwargs):
"""Sends a keyboard shortcut to the window.
:param command_key: The key (usually a letter) to be pressed.
:param accel: Optional, If `True`, the `Accel` modifier key is pressed.
This key differs between OS X (`Meta`) and Linux/Windows (`Ctrl`). Defaults to `False`.
:param alt: Optional, If `True`, the `Alt` modifier key is pressed. Defaults to `False`.
:param ctrl: Optional, If `True`, the `Ctrl` modifier key is pressed. Defaults to `False`.
:param meta: Optional, If `True`, the `Meta` modifier key is pressed. Defaults to `False`.
:param shift: Optional, If `True`, the `Shift` modifier key is pressed.
Defaults to `False`.
"""
platform = self.marionette.session_capabilities['platformName']
keymap = {
'accel': Keys.META if platform == "mac" else Keys.CONTROL,
'alt': Keys.ALT,
'cmd': Keys.COMMAND,
'ctrl': Keys.CONTROL,
'meta': Keys.META,
'shift': Keys.SHIFT,
}
# Append all to press modifier keys
keys = []
for modifier in kwargs:
if modifier not in keymap:
raise KeyError('"%s" is not a known modifier' % modifier)
if kwargs[modifier] is True:
keys.append(keymap[modifier])
# Bug 1125209 - Only lower-case command keys should be sent
keys.append(command_key.lower())
self.switch_to()
with self.marionette.using_context("chrome"):
self.window_element.send_keys(*keys)
def switch_to(self, focus=False):
"""Switches the context to this chrome window.
By default it will not focus the window. If that behavior is wanted, the
`focus` parameter can be used.
:param focus: If `True`, the chrome window will be focused.
:returns: Current window as :class:`BaseWindow` instance.
"""
self._windows.switch_to(self.handle, focus=focus)
return self

View File

@ -1,5 +0,0 @@
sphinx
sphinx_rtd_theme
# Required by Readthedocs to install the firefox-puppeteer package
-e testing/marionette/puppeteer/firefox

View File

@ -1,3 +0,0 @@
marionette-driver >= 2.2.0
mozinfo >= 0.8
six

View File

@ -1,40 +0,0 @@
# 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/.
from __future__ import absolute_import
import os
import re
from setuptools import setup, find_packages
THIS_DIR = os.path.dirname(os.path.realpath(__name__))
def read(*parts):
with open(os.path.join(THIS_DIR, *parts)) as f:
return f.read()
def get_version():
return re.findall("__version__ = '([\d\.]+)'",
read('firefox_puppeteer', '__init__.py'), re.M)[0]
setup(name='firefox-puppeteer',
version=get_version(),
description="Firefox Puppeteer",
long_description='See http://firefox-puppeteer.readthedocs.org/',
classifiers=['Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 2 :: Only'],
# Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
keywords='mozilla',
author='Auto-tools',
author_email='tools-marionette@lists.mozilla.org',
url='https://wiki.mozilla.org/Auto-tools/Projects/Marionette/Puppeteer/Firefox',
license='MPL',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=read('requirements.txt').splitlines(),
)

View File

@ -310,7 +310,6 @@ class FirefoxUIFunctionalTests(FirefoxUITests):
cli_script = 'cli_functional.py'
default_tests = [
os.path.join('puppeteer', 'manifest.ini'),
os.path.join('functional', 'manifest.ini'),
]