mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 15:25:52 +00:00
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:
parent
ef3e805957
commit
255a87c4a6
@ -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',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -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.
|
||||
"""),
|
||||
|
@ -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=(
|
||||
|
@ -9,6 +9,3 @@
|
||||
|
||||
../marionette/client
|
||||
../marionette/harness
|
||||
|
||||
# Allows to use the Puppeteer page object model for Firefox
|
||||
../marionette/puppeteer/firefox/
|
||||
|
@ -11,6 +11,3 @@
|
||||
|
||||
../marionette/client
|
||||
../marionette/harness
|
||||
|
||||
# Allows to use the Puppeteer page object model for Firefox
|
||||
../marionette/puppeteer/firefox/
|
||||
|
@ -1,4 +1,3 @@
|
||||
firefox-puppeteer >= 52.1.0, <53.0.0
|
||||
marionette-harness >= 4.0.0
|
||||
mozfile >= 1.2
|
||||
mozinfo >= 0.8
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
|
@ -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]
|
@ -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()
|
@ -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
|
@ -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')
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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()
|
@ -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)
|
@ -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'))
|
@ -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)
|
@ -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/
|
||||
|
||||
|
@ -1,2 +0,0 @@
|
||||
exclude MANIFEST.in
|
||||
include requirements.txt
|
@ -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."
|
@ -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:
|
@ -1,12 +0,0 @@
|
||||
.. py:currentmodule:: firefox_puppeteer.api.keys
|
||||
|
||||
Keys
|
||||
====
|
||||
|
||||
Keys
|
||||
----
|
||||
|
||||
.. autoclass:: Keys
|
||||
:members:
|
||||
:inherited-members:
|
||||
:undoc-members:
|
@ -1,11 +0,0 @@
|
||||
.. py:currentmodule:: firefox_puppeteer.api.l10n
|
||||
|
||||
Localization
|
||||
============
|
||||
|
||||
Localization
|
||||
------------
|
||||
|
||||
.. autoclass:: L10n
|
||||
:members:
|
||||
:undoc-members:
|
@ -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:
|
@ -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:
|
@ -1,12 +0,0 @@
|
||||
.. py:currentmodule:: firefox_puppeteer.api.utils
|
||||
|
||||
Utils
|
||||
===========
|
||||
|
||||
The Utils class gives access to various helper methods.
|
||||
|
||||
Utils
|
||||
-----
|
||||
|
||||
.. autoclass:: Utils
|
||||
:members:
|
@ -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
|
@ -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`
|
@ -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
|
@ -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:
|
||||
|
@ -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:
|
@ -1,16 +0,0 @@
|
||||
.. py:currentmodule:: firefox_puppeteer.ui.browser.tabbar
|
||||
|
||||
Tabbar
|
||||
======
|
||||
|
||||
TabBar
|
||||
------
|
||||
|
||||
.. autoclass:: TabBar
|
||||
:members:
|
||||
|
||||
Tab
|
||||
---
|
||||
|
||||
.. autoclass:: Tab
|
||||
:members:
|
@ -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:
|
@ -1,11 +0,0 @@
|
||||
.. py:currentmodule:: firefox_puppeteer.ui.browser.window
|
||||
|
||||
BrowserWindow
|
||||
=============
|
||||
|
||||
BrowserWindow
|
||||
-------------
|
||||
|
||||
.. autoclass:: BrowserWindow
|
||||
:members:
|
||||
:inherited-members:
|
@ -1,9 +0,0 @@
|
||||
Deck
|
||||
=====
|
||||
|
||||
Panel
|
||||
------
|
||||
|
||||
.. autoclass:: firefox_puppeteer.ui.deck.Panel
|
||||
:members:
|
||||
:inherited-members:
|
@ -1,10 +0,0 @@
|
||||
.. py:currentmodule:: firefox_puppeteer.ui.menu
|
||||
|
||||
Menu
|
||||
====
|
||||
|
||||
Menu Bar
|
||||
--------
|
||||
|
||||
.. autoclass:: MenuBar
|
||||
:members:
|
@ -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:
|
@ -1,16 +0,0 @@
|
||||
.. py:currentmodule:: firefox_puppeteer.ui.windows
|
||||
|
||||
Windows
|
||||
=======
|
||||
|
||||
Windows
|
||||
-------
|
||||
|
||||
.. autoclass:: Windows
|
||||
:members:
|
||||
|
||||
BaseWindow
|
||||
----------
|
||||
|
||||
.. autoclass:: BaseWindow
|
||||
:members:
|
@ -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'
|
@ -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;
|
||||
""")
|
@ -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
|
@ -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) {
|
||||
}
|
||||
});
|
||||
""")
|
@ -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])
|
@ -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])
|
@ -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
|
@ -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 _
|
@ -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
|
@ -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)
|
@ -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.
|
||||
"""
|
@ -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
|
@ -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)
|
@ -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
|
@ -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/.
|
@ -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
|
@ -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)
|
@ -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')
|
@ -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)
|
@ -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')
|
@ -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()
|
@ -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')
|
@ -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)
|
@ -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
|
@ -1,5 +0,0 @@
|
||||
sphinx
|
||||
sphinx_rtd_theme
|
||||
|
||||
# Required by Readthedocs to install the firefox-puppeteer package
|
||||
-e testing/marionette/puppeteer/firefox
|
@ -1,3 +0,0 @@
|
||||
marionette-driver >= 2.2.0
|
||||
mozinfo >= 0.8
|
||||
six
|
@ -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(),
|
||||
)
|
@ -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'),
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user