PK7EpHڂfirefox_puppeteer/errors.py# 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 marionette_driver.errors import MarionetteException class NoCertificateError(MarionetteException): pass class UnexpectedWindowTypeError(MarionetteException): pass class UnknownTabError(MarionetteException): pass class UnknownWindowError(MarionetteException): pass PKxHdiA A firefox_puppeteer/__init__.py# 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/. import os from marionette_driver.marionette import HTMLElement from decorators import use_class_as_property __version__ = '4.0.0' class Puppeteer(object): """The puppeteer class is used to expose libraries to test cases. example: `class MyTestCase(MarionetteTestCase, Puppeteer)` Each library can be referenced by its puppeteer name as a member of a the TestCase instance. For example, from within a test method, the "current_window" member of the "Browser" class can be accessed via "self.browser.current_window". """ def __init__(self): self.marionette = None def get_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. """ @property def platform(self): """Returns the lowercased platform name. :returns: Platform name """ return self.marionette.session_capabilities['platformName'].lower() @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. """ 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 def get_marionette(self): return self.marionette PK7EpH<󑻹firefox_puppeteer/decorators.py# 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 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.get_marionette) setattr(cls, tag, prop) func(cls, *args, **kwargs) return prop return _ PK7EpH,##firefox_puppeteer/base.py# 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/. class BaseLib(object): """A base class that handles lazily setting the "client" class attribute.""" def __init__(self, marionette_getter): if not callable(marionette_getter): raise TypeError('Invalid callback for "marionette_getter": %s' % marionette_getter) self._marionette = None self._marionette_getter = marionette_getter @property def marionette(self): if self._marionette is None: self._marionette = self._marionette_getter() return self._marionette def get_marionette(self): return self.marionette PK7EpH6aa firefox_puppeteer/ui_base_lib.py# 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 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_getter, window, element): assert isinstance(window, BaseWindow) assert isinstance(element, HTMLElement) BaseLib.__init__(self, marionette_getter) 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 PKxH~  'firefox_puppeteer/testcases/__init__.py# 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 firefox_puppeteer.testcases.base import BaseFirefoxTestCase PKxHK),,#firefox_puppeteer/testcases/base.py# 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/. import unittest from firefox_puppeteer import Puppeteer from firefox_puppeteer.ui.browser.window import BrowserWindow class BaseFirefoxTestCase(unittest.TestCase, Puppeteer): """Base TestCase class for Firefox Desktop tests. This is designed to enhance MarionetteTestCase by inserting the Puppeteer mixin class (so Firefox specific API modules are exposed to test scope) and providing common set-up and tear-down code for Firefox tests. Child classes are expected to also subclass MarionetteTestCase such that MarionetteTestCase is inserted into the MRO after FirefoxTestCase but before unittest.TestCase. example: `class AwesomeTestCase(FirefoxTestCase, MarionetteTestCase)` The key role of MarionetteTestCase is to set self.marionette appropriately in `__init__`. 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 __init__(self, *args, **kwargs): super(BaseFirefoxTestCase, self).__init__(*args, **kwargs) def _check_and_fix_leaked_handles(self): handle_count = len(self.marionette.window_handles) try: self.assertEqual(handle_count, self._start_handle_count, 'A test must not leak window handles. This test started with ' '%s open top level browsing contexts, but ended with %s.' % (self._start_handle_count, handle_count)) 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.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.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, flags=None): """Restart Firefox and re-initialize data. :param flags: Specific restart flags for Firefox """ # TODO: Bug 1148220 Marionette's in_app restart has to send 'quit-application-requested' # observer notification before an in_app restart self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] .createInstance(Components.interfaces.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); """) self.marionette.restart(in_app=True) # Marionette doesn't keep the former context, so restore to chrome self.marionette.set_context('chrome') # Ensure that we always have a valid browser instance available self.browser = self.windows.switch_to(lambda win: type(win) is BrowserWindow) def setUp(self, *args, **kwargs): super(BaseFirefoxTestCase, self).setUp(*args, **kwargs) self._start_handle_count = len(self.marionette.window_handles) self.marionette.set_context('chrome') self.browser = self.windows.current self.browser.focus() with self.marionette.using_context(self.marionette.CONTEXT_CONTENT): # Ensure that we have a default page opened self.marionette.navigate(self.prefs.get_pref('browser.newtab.url')) def tearDown(self, *args, **kwargs): self.marionette.set_context('chrome') try: self.prefs.restore_all_prefs() # 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(BaseFirefoxTestCase, self).tearDown(*args, **kwargs) PK7EpHs*firefox_puppeteer/api/keys.py# 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/. 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_getter): self.marionette_getter = marionette_getter caps = self.marionette_getter().session_capabilities self.isDarwin = caps['platformName'] == 'DARWIN' @property def ACCEL(self): return self.META if self.isDarwin else self.CONTROL PK›xHW=firefox_puppeteer/api/places.py# 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 collections import namedtuple from time import sleep from marionette_driver.errors import MarionetteException, TimeoutException 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', 'tags', 'unfiled']) bookmark_folders = BookmarkFolders(1, 2, 3, 4, 5) # Bookmark related helpers # def is_bookmarked(self, url): """Checks if the given URL is bookmarked. :param url: The URL to Check :returns: True, if the URL is a bookmark """ return self.marionette.execute_script(""" let url = arguments[0]; let bs = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"] .getService(Components.interfaces.nsINavBookmarksService); let ios = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); let uri = ios.newURI(url, null, null); let results = bs.getBookmarkIdsForURI(uri, {}); return results.length == 1; """, script_args=[url]) def get_folder_ids_for_url(self, url): """Retrieves 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_script(""" let url = arguments[0]; let bs = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"] .getService(Components.interfaces.nsINavBookmarksService); let ios = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); let bookmarkIds = bs.getBookmarkIdsForURI(ios.newURI(url, null, null), {}); let folderIds = []; for (let i = 0; i < bookmarkIds.length; i++) { folderIds.push(bs.getFolderIdForItem(bookmarkIds[i])); } return folderIds; """, script_args=[url]) def is_bookmark_star_button_ready(self): """Checks 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): """Restores the default bookmarks for the current profile.""" retval = self.marionette.execute_async_script(""" Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); // Default bookmarks.html file is stored inside omni.jar, // so get it via a resource URI let defaultBookmarks = 'chrome://browser/locale/bookmarks.html'; let observer = { observe: function (aSubject, aTopic, aData) { Services.obs.removeObserver(observer, "bookmarks-restore-success"); Services.obs.removeObserver(observer, "bookmarks-restore-failed"); marionetteScriptFinished(aTopic == "bookmarks-restore-success"); } }; // Trigger the import of the default bookmarks Services.obs.addObserver(observer, "bookmarks-restore-success", false); Services.obs.addObserver(observer, "bookmarks-restore-failed", false); BookmarkHTMLUtils.importFromURL(defaultBookmarks, true); """, script_timeout=10000) if not retval: raise MarionetteException("Restore Default Bookmarks failed") # Browser history related helpers # def get_all_urls_in_history(self): return self.marionette.execute_script(""" let hs = Cc["@mozilla.org/browser/nav-history-service;1"] .getService(Ci.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 remove_all_history(self): """Removes all history items.""" with self.marionette.using_context('chrome'): try: self.marionette.execute_async_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let hs = Components.classes["@mozilla.org/browser/nav-history-service;1"] .getService(Components.interfaces.nsIBrowserHistory); let observer = { observe: function (aSubject, aTopic, aData) { Services.obs.removeObserver(observer, 'places-expiration-finished'); marionetteScriptFinished(true); } }; // Remove the pages, then block until we're done or until timeout is reached Services.obs.addObserver(observer, 'places-expiration-finished', false); hs.removeAllPages(); """, script_timeout=10000) except TimeoutException: # TODO: In case of a timeout clean-up the registered topic pass def wait_for_visited(self, urls, callback): """Waits 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): """Clears 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) { } }); """) PK7EpH!firefox_puppeteer/api/__init__.pyPK7EpH#Η firefox_puppeteer/api/l10n.py# 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/. import copy from marionette_driver.errors import MarionetteException from firefox_puppeteer.base import BaseLib class L10n(BaseLib): def get_entity(self, dtd_urls, entity_id): """Returns the localized string for the specified DTD entity id. To find the entity all given DTD files will be searched for the id. :param dtd_urls: A list of dtd files to search. :param entity_id: The id to retrieve the value from. :returns: The localized string for the requested entity. :raises MarionetteException: When entity id is not found in dtd_urls. """ # Add xhtml11.dtd to prevent missing entity errors with XHTML files dtds = copy.copy(dtd_urls) dtds.append("resource:///res/dtd/xhtml11.dtd") dtd_refs = '' for index, item in enumerate(dtds): dtd_id = 'dtd_%s' % index dtd_refs += '%%%s;' % \ (dtd_id, item, dtd_id) contents = """ &%s;""" % (dtd_refs, entity_id) with self.marionette.using_context('chrome'): value = self.marionette.execute_script(""" var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] .createInstance(Components.interfaces.nsIDOMParser); var doc = parser.parseFromString(arguments[0], "text/xml"); var node = doc.querySelector("elem[id='entity']"); return node ? node.textContent : null; """, script_args=[contents]) if not value: raise MarionetteException('DTD Entity not found: %s' % entity_id) return value def get_property(self, property_urls, property_id): """Returns the localized string for the specified property id. To find the property all given property files will be searched for the id. :param property_urls: A list of property files to search. :param property_id: The id to retrieve the value from. :returns: The localized string for the requested entity. :raises MarionetteException: When property id is not found in property_urls. """ with self.marionette.using_context('chrome'): value = self.marionette.execute_script(""" let property = null; let property_id = arguments[1]; arguments[0].some(aUrl => { let bundle = Services.strings.createBundle(aUrl); try { property = bundle.GetStringFromName(property_id); return true; } catch (ex) { } }); return property; """, script_args=[property_urls, property_id]) if not value: raise MarionetteException('Property not found: %s' % property_id) return value PK7EpH  firefox_puppeteer/api/utils.py# 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 marionette_driver.errors import MarionetteException from firefox_puppeteer.base import BaseLib class Utils(BaseLib): """Low-level access to utility actions.""" def remove_perms(self, host, permission): """Remove 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); Services.perms.remove(uri, arguments[1]); """, script_args=[host, permission]) 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/base/content/sanitize.js :param data_type: optional, Information specifying data to be sanitized """ with self.marionette.using_context('chrome'): result = self.marionette.execute_async_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); var data_type = arguments[0]; var data_type = (typeof data_type === "undefined") ? {} : { cache: data_type.cache || false, cookies: data_type.cookies || false, downloads: data_type.downloads || false, formdata: data_type.formdata || false, history: data_type.history || false, offlineApps: data_type.offlineApps || false, passwords: data_type.passwords || false, sessions: data_type.sessions || false, siteSettings: data_type.siteSettings || false }; // Load the sanitize script var tempScope = {}; Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader) .loadSubScript("chrome://browser/content/sanitize.js", tempScope); // Instantiate the Sanitizer var s = new tempScope.Sanitizer(); s.prefDomain = "privacy.cpd."; var itemPrefs = Services.prefs.getBranch(s.prefDomain); // Apply options for what to sanitize for (var pref in data_type) { itemPrefs.setBoolPref(pref, data_type[pref]); }; // Sanitize and wait for the promise to resolve var finished = false; s.sanitize().then(() => { for (let pref in data_type) { itemPrefs.clearUserPref(pref); }; marionetteScriptFinished(true); }, aError => { for (let pref in data_type) { itemPrefs.clearUserPref(pref); }; marionetteScriptFinished(false); }); """, script_args=[data_type]) if not result: raise MarionetteException('Sanitizing of profile data failed.') PK7EpHbp_<_<(firefox_puppeteer/api/software_update.py# 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/. import os import re import mozinfo from firefox_puppeteer.base import BaseLib from firefox_puppeteer.api.appinfo import AppInfo from firefox_puppeteer.api.prefs import Preferences class ActiveUpdate(BaseLib): def __getattr__(self, attr): value = self.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate[arguments[0]]; """, script_args=[attr]) if value: return value else: raise AttributeError('{} has no attribute {}'.format(self.__class__.__name__, attr)) @property def exists(self): """Checks if there is an active update. :returns: True if there is an active update """ active_update = self.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate; """) return bool(active_update) def get_patch_at(self, patch_index): """Use nsIUpdate.getPatchAt to return a patch from an update. :returns: JSON data for an nsIUpdatePatch object """ return self.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate.getPatchAt(arguments[0]); """, script_args=[patch_index]) @property def patch_count(self): """Get the patchCount from the active update. :returns: The patch count """ return self.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate.patchCount; """) @property def selected_patch(self): """Get the selected patch for the active update. :returns: JSON data for the selected patch """ return self.marionette.execute_script(""" let ums = Components.classes['@mozilla.org/updates/update-manager;1'] .getService(Components.interfaces.nsIUpdateManager); return ums.activeUpdate.selectedPatch; """) class MARChannels(BaseLib): """Class to handle the allowed MAR channels as listed in update-settings.ini.""" INI_SECTION = 'Settings' INI_OPTION = 'ACCEPTED_MAR_CHANNEL_IDS' def __init__(self, marionette_getter): BaseLib.__init__(self, marionette_getter) self._ini_file_path = self.marionette.execute_script(""" Components.utils.import('resource://gre/modules/Services.jsm'); let file = Services.dirsvc.get('GreD', Components.interfaces.nsIFile); file.append('update-settings.ini'); return file.path; """) @property def config_file_path(self): """The path to the update-settings.ini file.""" return self._ini_file_path @property def config_file_contents(self): """The contents of the update-settings.ini file.""" with open(self.config_file_path) as f: return f.read() @property def channels(self): """The channels as found in the ACCEPTED_MAR_CHANNEL_IDS option of the update-settings.ini file. :returns: A set of channel names """ channels = self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/FileUtils.jsm"); let iniFactory = Components.classes['@mozilla.org/xpcom/ini-processor-factory;1'] .getService(Components.interfaces.nsIINIParserFactory); let file = new FileUtils.File(arguments[0]); let parser = iniFactory.createINIParser(file); return parser.getString(arguments[1], arguments[2]); """, script_args=[self.config_file_path, self.INI_SECTION, self.INI_OPTION]) return set(channels.split(',')) @channels.setter def channels(self, channels): """Set the channels in the update-settings.ini file. :param channels: A set of channel names """ new_channels = ','.join(channels) self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/FileUtils.jsm"); let iniFactory = Components.classes['@mozilla.org/xpcom/ini-processor-factory;1'] .getService(Components.interfaces.nsIINIParserFactory); let file = new FileUtils.File(arguments[0]); let writer = iniFactory.createINIParser(file) .QueryInterface(Components.interfaces.nsIINIParserWriter); writer.setString(arguments[1], arguments[2], arguments[3]); writer.writeFile(null, Components.interfaces.nsIINIParserWriter.WRITE_UTF16); """, script_args=[self.config_file_path, self.INI_SECTION, self.INI_OPTION, new_channels]) def add_channels(self, channels): """Add channels to the update-settings.ini file. :param channels: A set of channel names to add """ self.channels = self.channels | set(channels) def remove_channels(self, channels): """Remove channels from the update-settings.ini file. :param channels: A set of channel names to remove """ self.channels = self.channels - set(channels) class SoftwareUpdate(BaseLib): """The SoftwareUpdate API adds support for an easy access to the update process.""" PREF_APP_DISTRIBUTION = 'distribution.id' PREF_APP_DISTRIBUTION_VERSION = 'distribution.version' PREF_APP_UPDATE_URL = 'app.update.url' PREF_APP_UPDATE_URL_OVERRIDE = 'app.update.url.override' PREF_DISABLED_ADDONS = 'extensions.disabledAddons' def __init__(self, marionette_getter): BaseLib.__init__(self, marionette_getter) self.app_info = AppInfo(marionette_getter) self.prefs = Preferences(marionette_getter) self._update_channel = UpdateChannel(marionette_getter) self._mar_channels = MARChannels(marionette_getter) self._active_update = ActiveUpdate(marionette_getter) @property def ABI(self): """Get the customized ABI for the update service. :returns: ABI version """ abi = self.app_info.XPCOMABI if mozinfo.isMac: abi += self.marionette.execute_script(""" let macutils = Components.classes['@mozilla.org/xpcom/mac-utils;1'] .getService(Components.interfaces.nsIMacUtils); if (macutils.isUniversalBinary) { return '-u-' + macutils.architecturesInBinary; } return ''; """) return abi @property def active_update(self): """ Holds a reference to an :class:`ActiveUpdate` object.""" return self._active_update @property def allowed(self): """Check if the user has permissions to run the software update :returns: Status if the user has the permissions """ return self.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.canCheckForUpdates && aus.canApplyUpdates; """) @property def build_info(self): """Return information of the current build version :returns: A dictionary of build information """ return { 'buildid': self.app_info.appBuildID, 'channel': self.update_channel.channel, 'disabled_addons': self.prefs.get_pref(self.PREF_DISABLED_ADDONS), 'locale': self.app_info.locale, 'mar_channels': self.mar_channels.channels, 'url_aus': self.get_update_url(True), 'user_agent': self.app_info.user_agent, 'version': self.app_info.version } @property def is_complete_update(self): """Return true if the offered update is a complete update :returns: True if the offered update is a complete update """ # Throw when isCompleteUpdate is called without an update. This should # never happen except if the test is incorrectly written. assert self.active_update.exists, 'An active update has been found' patch_count = self.active_update.patch_count assert patch_count == 1 or patch_count == 2,\ 'An update must have one or two patches included' # Ensure Partial and Complete patches produced have unique urls if patch_count == 2: patch0_url = self.active_update.get_patch_at(0)['URL'] patch1_url = self.active_update.get_patch_at(1)['URL'] assert patch0_url != patch1_url,\ 'Partial and Complete download URLs are different' return self.active_update.selected_patch['type'] == 'complete' @property def mar_channels(self): """ Holds a reference to a :class:`MARChannels` object.""" return self._mar_channels @property def os_version(self): """Returns information about the OS version :returns: The OS version """ return self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let osVersion; try { osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version"); } catch (ex) { } if (osVersion) { try { osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")"; } catch (e) { // Not all platforms have a secondary widget library, // so an error is nothing to worry about. } osVersion = encodeURIComponent(osVersion); } return osVersion; """) @property def patch_info(self): """ Returns information of the active update in the queue.""" info = {'channel': self.update_channel.channel} if (self.active_update.exists): info['buildid'] = self.active_update.buildID info['is_complete'] = self.is_complete_update info['size'] = self.active_update.selected_patch['size'] info['type'] = self.update_type info['url_mirror'] = \ self.active_update.selected_patch['finalURL'] or 'n/a' info['version'] = self.active_update.appVersion return info @property def staging_directory(self): """ Returns the path to the updates staging directory.""" return self.marionette.execute_script(""" let aus = Components.classes['@mozilla.org/updates/update-service;1'] .getService(Components.interfaces.nsIApplicationUpdateService); return aus.getUpdatesDirectory().path; """) @property def update_channel(self): """ Holds a reference to an :class:`UpdateChannel` object.""" return self._update_channel @property def update_type(self): """Returns the type of the active update.""" return self.active_update.type def force_fallback(self): """Update the update.status file and set the status to 'failed:6'""" with open(os.path.join(self.staging_directory, 'update.status'), 'w') as f: f.write('failed: 6\n') def get_update_url(self, force=False): """Retrieve the AUS update URL the update snippet is retrieved from :param force: Boolean flag to force an update check :returns: The URL of the update snippet """ url = self.prefs.get_pref(self.PREF_APP_UPDATE_URL_OVERRIDE) if not url: url = self.prefs.get_pref(self.PREF_APP_UPDATE_URL) # get the next two prefs from the default branch dist = self.prefs.get_pref(self.PREF_APP_DISTRIBUTION, True) or 'default' dist_version = self.prefs.get_pref(self.PREF_APP_DISTRIBUTION_VERSION, True) or 'default' # Not all placeholders are getting replaced correctly by formatURL url = url.replace('%PRODUCT%', self.app_info.name) url = url.replace('%BUILD_ID%', self.app_info.appBuildID) url = url.replace('%BUILD_TARGET%', self.app_info.OS + '_' + self.ABI) url = url.replace('%OS_VERSION%', self.os_version) url = url.replace('%CHANNEL%', self.update_channel.channel) url = url.replace('%DISTRIBUTION%', dist) url = url.replace('%DISTRIBUTION_VERSION%', dist_version) url = self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); return Services.urlFormatter.formatURL(arguments[0]); """, script_args=[url]) if force: if '?' in url: url += '&' else: url += '?' url += 'force=1' return url class UpdateChannel(BaseLib): """Class to handle the update channel as listed in channel-prefs.js""" REGEX_UPDATE_CHANNEL = re.compile(r'("app\.update\.channel", ")([^"].*)(?=")') def __init__(self, marionette_getter): BaseLib.__init__(self, marionette_getter) self.prefs = Preferences(marionette_getter) self.file_path = self.marionette.execute_script(""" Components.utils.import('resource://gre/modules/Services.jsm'); let file = Services.dirsvc.get('PrfDef', Components.interfaces.nsIFile); file.append('channel-prefs.js'); return file.path; """) @property def file_contents(self): """The contents of the channel-prefs.js file.""" with open(self.file_path) as f: return f.read() @property def channel(self): """The name of the update channel as stored in the app.update.channel pref.""" return self.prefs.get_pref('app.update.channel', True) @property def default_channel(self): """Get the default update channel :returns: Current default update channel """ matches = re.search(self.REGEX_UPDATE_CHANNEL, self.file_contents).groups() assert len(matches) == 2, 'Update channel value has been found' return matches[1] @default_channel.setter def default_channel(self, channel): """Set default update channel. :param channel: New default update channel """ assert channel, 'Update channel has been specified' new_content = re.sub( self.REGEX_UPDATE_CHANNEL, r'\g<1>' + channel, self.file_contents) with open(self.file_path, 'w') as f: f.write(new_content) PK7EpH_~홹 !firefox_puppeteer/api/security.py# 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/. 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.+?),ST=(?P.+?),C=(?P.+?)' ',postalCode=(?P.+?),STREET=(?P.+?)' ',serial') 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 securityUI = arguments[0].linkedBrowser.securityUI; var status = securityUI.QueryInterface(Components.interfaces.nsISSLStatusProvider) .SSLStatus; return status ? status.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]) PK7EpH}  firefox_puppeteer/api/prefs.py# 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 marionette_driver.errors import MarionetteException from firefox_puppeteer.base import BaseLib class Preferences(BaseLib): archive = {} def get_pref(self, pref_name, default_branch=False, interface=None): """Retrieves the value of a preference. To retrieve the value of a preference its name has to be specified. By default it will be read from the `user` branch, and returns the current value. If the default value is wanted instead, ensure to flag that by passing `True` via default_branch. It is also possible to retrieve the value of complex preferences, which do not represent an atomic type like `basestring`, `int`, or `boolean`. Specify the interface of the represented XPCOM object via `interface`. :param pref_name: The preference name :param default_branch: Optional, flag to use the default branch, default to `False` :param interface: Optional, interface of the complex preference, default to `None`. Possible values are: `nsILocalFile`, `nsISupportsString`, and `nsIPrefLocalizedString` :returns: The preference value. """ assert pref_name is not None with self.marionette.using_context('chrome'): return self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let pref_name = arguments[0]; let default_branch = arguments[1]; let interface = arguments[2]; let prefBranch; if (default_branch) { prefBranch = Services.prefs.getDefaultBranch(""); } else { prefBranch = Services.prefs; } // If an interface has been set, handle it differently if (interface !== null) { return prefBranch.getComplexValue(pref_name, Components.interfaces[interface]).data; } let type = prefBranch.getPrefType(pref_name); switch (type) { case prefBranch.PREF_STRING: return prefBranch.getCharPref(pref_name); case prefBranch.PREF_BOOL: return prefBranch.getBoolPref(pref_name); case prefBranch.PREF_INT: return prefBranch.getIntPref(pref_name); case prefBranch.PREF_INVALID: return null; } """, script_args=[pref_name, default_branch, interface]) def reset_pref(self, pref_name): """Resets a user set preference. Every modification of a preference will turn its status into a user-set preference. To restore the default value of the preference, call this function once. Further calls have no effect as long as the value hasn't changed again. In case the preference is not present on the default branch, it will be completely removed. :param pref_name: The preference to reset :returns: `True` if a user preference has been removed """ assert pref_name is not None with self.marionette.using_context('chrome'): return self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let prefBranch = Services.prefs; let pref_name = arguments[0]; if (prefBranch.prefHasUserValue(pref_name)) { prefBranch.clearUserPref(pref_name); return true; } else { return false; } """, script_args=[pref_name]) def restore_all_prefs(self): """Restores all previously modified preferences to their former values. Please see :func:`~Preferences.restore_pref` for details. """ while len(self.archive): self.restore_pref(self.archive.keys()[0]) def restore_pref(self, pref_name): """Restores a previously set preference to its former value. The first time a preference gets modified a backup of its value is made. By calling this method, exactly this value will be restored, whether how often the preference has been modified again afterward. If the preference did not exist before and has been newly created, it will be reset to its original value. Please see :func:`~Preferences.reset_pref` for details. :param pref_name: The preference to restore """ assert pref_name is not None try: # in case it is a newly set preference, reset it. Otherwise restore # its original value. if self.archive[pref_name] is None: self.reset_pref(pref_name) else: self.set_pref(pref_name, self.archive[pref_name]) del self.archive[pref_name] except KeyError: raise MarionetteException('Nothing to restore for preference "%s"', pref_name) def set_pref(self, pref_name, value): """Sets a preference to a specified value. To set the value of a preference its name has to be specified. The first time a new value for a preference is set, its value will be automatically archived. It allows to restore the original value by calling :func:`~Preferences.restore_pref`. :param pref_name: The preference to set :param value: The value to set the preference to """ assert pref_name is not None assert value is not None with self.marionette.using_context('chrome'): # Backup original value only once if pref_name not in self.archive: self.archive[pref_name] = self.get_pref(pref_name) retval = self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let prefBranch = Services.prefs; let pref_name = arguments[0]; let value = arguments[1]; let type = prefBranch.getPrefType(pref_name); // If the pref does not exist yet, get the type from the value if (type == prefBranch.PREF_INVALID) { switch (typeof value) { case "boolean": type = prefBranch.PREF_BOOL; break; case "number": type = prefBranch.PREF_INT; break; case "string": type = prefBranch.PREF_STRING; break; default: type = prefBranch.PREF_INVALID; } } switch (type) { case prefBranch.PREF_BOOL: prefBranch.setBoolPref(pref_name, value); break; case prefBranch.PREF_STRING: prefBranch.setCharPref(pref_name, value); break; case prefBranch.PREF_INT: prefBranch.setIntPref(pref_name, value); break; default: return false; } return true; """, script_args=[pref_name, value]) assert retval PK7EpH]H"" firefox_puppeteer/api/appinfo.py# 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 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(""" 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; """) PK7EpH firefox_puppeteer/ui/__init__.pyPK›xH/TA`?`?firefox_puppeteer/ui/windows.py# 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 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.api.l10n import L10n from firefox_puppeteer.base import BaseLib from firefox_puppeteer.decorators import use_class_as_property from firefox_puppeteer.api.prefs import Preferences 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. """ def get_active_handle(mn): 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindowUtils) .outerWindowID.toString(); } return null; """) # In case of `None` being returned no window is currently active. This can happen # when a focus change action is currently happening. So lets wait until it is done. return Wait(self.marionette).until(get_active_handle, message='No focused window has been found.') 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 = Wait(self.marionette).until( lambda mn: mn.get_window_type(), message='Cannot get window type for chrome window handle "%s"' % handle ) 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](lambda: self.marionette, handle) else: raise errors.UnknownWindowError('Unknown window type "%s" for handle: "%s"' % (window_type, 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) with self.marionette.using_context('chrome'): self.marionette.execute_script(""" window.focus(); """) Wait(self.marionette).until( lambda _: handle == self.focused_chrome_window_handle, message='Focus has not been set to chrome window handle "%s".' % handle) 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): """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)) # only switch if necessary if target_handle != self.marionette.current_chrome_window_handle: self.marionette.switch_to_window(target_handle) 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.""" # l10n class attributes will be set by each window class individually dtds = [] properties = [] def __init__(self, marionette_getter, window_handle): BaseLib.__init__(self, marionette_getter) self._l10n = L10n(self.get_marionette) self._prefs = Preferences(self.get_marionette) self._windows = Windows(self.get_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 `window.open()`. :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 get_entity(self, entity_id): """Returns the localized string for the specified DTD entity id. :param entity_id: The id to retrieve the value from. :returns: The localized string for the requested entity. :raises MarionetteException: When entity id is not found. """ return self._l10n.get_entity(self.dtds, entity_id) def get_property(self, property_id): """Returns the localized string for the specified property id. :param property_id: The id to retrieve the value from. :returns: The localized string for the requested property. :raises MarionetteException: When property id is not found. """ return self._l10n.get_property(self.properties, property_id) 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 `window.open()`. :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() with self.marionette.using_context('chrome'): if callback is not None: callback(self) else: self.marionette.execute_script(""" window.open(); """) # TODO: Needs to be replaced with observer handling code (bug 1121698) def window_opened(mn): return len(mn.chrome_window_handles) == len(start_handles) + 1 Wait(self.marionette).until( window_opened, 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'].lower() keymap = { 'accel': Keys.META if platform == 'darwin' 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() 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. """ if focus: self._windows.focus(self.handle) else: self._windows.switch_to(self.handle) return self PK7EpHrDDfirefox_puppeteer/ui/deck.py# 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 firefox_puppeteer.ui_base_lib 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') PK7EpH) firefox_puppeteer/ui/menu.py# 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 marionette_driver import By from marionette_driver.errors import NoSuchElementException from firefox_puppeteer import DOMElement from firefox_puppeteer.base import BaseLib 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() PK7EpH-firefox_puppeteer/ui/about_window/__init__.pyPK7EpH2+firefox_puppeteer/ui/about_window/window.py# 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 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' dtds = [ 'chrome://branding/locale/brand.dtd', 'chrome://browser/locale/aboutDialog.dtd', ] def __init__(self, *args, **kwargs): BaseWindow.__init__(self, *args, **kwargs) @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(lambda: self.marionette, self, deck) Windows.register_window(AboutWindow.window_type, AboutWindow) PK7EpH9w<<)firefox_puppeteer/ui/about_window/deck.py# 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 marionette_driver import By from firefox_puppeteer.ui_base_lib 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, 'applyBillboard': ApplyBillboardPanel, '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)(lambda: 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 apply_billboard(self): """The :class:`ApplyBillboardPanel` instance for the apply billboard panel. :returns: :class:`ApplyBillboardPanel` instance. """ return self._create_panel_for_id('applyBillboard') @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_attribute('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 ApplyBillboardPanel(Panel): @property def button(self): """The DOM element which represents the Apply Billboard button. :returns: Reference to the button element. """ return self.element.find_element(By.ID, 'applyButtonBillboard') 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 PK7EpHZ~(firefox_puppeteer/ui/browser/__init__.py# 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/. PK7EpHd2!!&firefox_puppeteer/ui/browser/window.py# 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/. import firefox_puppeteer.errors as errors from marionette_driver import By, Wait from marionette_driver.errors import NoSuchWindowException from marionette_driver.keys import Keys from firefox_puppeteer.api.l10n import L10n from firefox_puppeteer.api.prefs import Preferences from firefox_puppeteer.decorators import use_class_as_property from firefox_puppeteer.ui.about_window.window import AboutWindow 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 import firefox_puppeteer.errors as errors class BrowserWindow(BaseWindow): """Representation of a browser window.""" window_type = 'navigator:browser' dtds = [ 'chrome://branding/locale/brand.dtd', 'chrome://browser/locale/aboutPrivateBrowsing.dtd', 'chrome://browser/locale/browser.dtd', 'chrome://browser/locale/netError.dtd', ] properties = [ 'chrome://branding/locale/brand.properties', 'chrome://branding/locale/browserconfig.properties', 'chrome://browser/locale/browser.properties', 'chrome://browser/locale/preferences/preferences.properties', 'chrome://global/locale/browser.properties', ] def __init__(self, *args, **kwargs): BaseWindow.__init__(self, *args, **kwargs) self._navbar = None self._tabbar = None # Timeout for loading remote web pages self.timeout_page_load = 30 @property def default_homepage(self): """The default homepage as used by the current locale. :returns: The default homepage for the current locale. """ return self._prefs.get_pref('browser.startup.homepage', interface='nsIPrefLocalizedString') @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(lambda: self.marionette, self, navbar) return self._navbar @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(lambda: 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(win.get_entity('closeCmd.key'), 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': cmd_key = 'privateBrowsingCmd.commandkey' if is_private else 'newNavigatorCmd.key' win.send_shortcut(win.get_entity(cmd_key), 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['platform'] == 'WINDOWS_NT': raise ValueError('Page info shortcut not available on Windows.') win.send_shortcut(win.get_entity('pageInfoCmd.commandkey'), 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) PK›xHʓL44&firefox_puppeteer/ui/browser/tabbar.py# 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 marionette_driver import ( By, Wait ) from marionette_driver.errors import NoSuchElementException import firefox_puppeteer.errors as errors from firefox_puppeteer import DOMElement from firefox_puppeteer.api.security import Security from firefox_puppeteer.ui_base_lib 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 menupanel(self): """A :class:`MenuPanel` instance which represents the menu panel at the far right side of the tabs toolbar. :returns: :class:`MenuPanel` instance. """ return MenuPanel(lambda: self.marionette, self.window) @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.ANON_ATTRIBUTE, {'anonid': '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(lambda: 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_attribute('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. """ # 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. :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 # 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(self.window.get_entity('tabCmd.commandkey'), accel=True) # elif - need to add other cases else: raise ValueError('Unknown opening method: "%s"' % trigger) # TODO: Needs to be replaced with event handling code (bug 1121705) Wait(self.marionette).until( lambda mn: len(mn.window_handles) == len(start_handles) + 1, message='No new tab has been opened.') 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 win = arguments[0].linkedBrowser; if (!win) { return null; } return win.outerWindowID.toString(); """, script_args=[tab_element]) return handle class Tab(UIBaseLib): """Wraps a tab DOM element.""" def __init__(self, marionette_getter, window, element): UIBaseLib.__init__(self, marionette_getter, window, element) self._security = Security(lambda: 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.ANON_ATTRIBUTE, {'anonid': '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. 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 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(self.window.get_entity('closeCmd.key'), accel=True) else: raise ValueError('Unknown closing method: "%s"' % trigger) Wait(self.marionette).until( lambda _: len(self.window.tabbar.tabs) == len(start_handles) - 1, message='Tab with handle "%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) class MenuPanel(UIBaseLib): @property def popup(self): """ :returns: The :class:`MenuPanelElement`. """ popup = self.marionette.find_element(By.ID, 'PanelUI-popup') return self.MenuPanelElement(popup) class MenuPanelElement(DOMElement): """Wraps the menu panel.""" _buttons = None @property def buttons(self): """ :returns: A list of all the clickable buttons in the menu panel. """ if not self._buttons: self._buttons = (self.find_element(By.ID, 'PanelUI-multiView') .find_element(By.ANON_ATTRIBUTE, {'anonid': 'viewContainer'}) .find_elements(By.TAG_NAME, 'toolbarbutton')) return self._buttons def click(self, target=None): """ Overrides HTMLElement.click to provide a target to click. :param target: The label associated with the button to click on, e.g., `New Private Window`. """ if not target: return DOMElement.click(self) for button in self.buttons: if button.get_attribute('label') == target: return button.click() raise NoSuchElementException('Could not find "{}"" in the ' 'menu panel UI'.format(target)) PK›xH仧UU(firefox_puppeteer/ui/browser/toolbars.py# 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 marionette_driver import By, keys, Wait from firefox_puppeteer.ui_base_lib 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): UIBaseLib.__init__(self, *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(lambda: 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): UIBaseLib.__init__(self, *args, **kwargs) self._autocomplete_results = None self._identity_popup = None @property def autocomplete_results(self): """Provides access to and methods for the location bar autocomplete results. See the :class:`AutocompleteResults` reference.""" if not self._autocomplete_results: popup = self.marionette.find_element(By.ID, 'PopupAutoCompleteRichResult') self._autocomplete_results = AutocompleteResults(lambda: self.marionette, self.window, popup) return self._autocomplete_results 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.urlbar.get_attribute('value') == '', message='Contents of location bar could not be cleared.') def close_context_menu(self): """Closes the Location Bar context menu by a key event.""" # TODO: This method should be implemented via the menu API. self.contextmenu.send_keys(keys.Keys.ESCAPE) @property def connection_icon(self): """ Provides access to the urlbar connection icon. :returns: Reference to the connection icon element. """ return self.marionette.find_element(By.ID, 'connection-icon') @property def contextmenu(self): """Provides access to the urlbar context menu. :returns: Reference to the urlbar context menu. """ # TODO: This method should be implemented via the menu API. parent = self.urlbar.find_element(By.ANON_ATTRIBUTE, {'anonid': 'textbox-input-box'}) return parent.find_element(By.ANON_ATTRIBUTE, {'anonid': 'input-box-contextmenu'}) @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': cmd_key = self.window.get_entity('openCmd.commandkey') self.window.send_shortcut(cmd_key, 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.') def get_contextmenu_entry(self, action): """Retrieves the urlbar context menu entry corresponding to the given action. :param action: The action corresponding to the retrieved value. :returns: Reference to the urlbar contextmenu entry. """ # TODO: This method should be implemented via the menu API. entries = self.contextmenu.find_elements(By.CSS_SELECTOR, 'menuitem') filter_on = 'cmd_%s' % action found = [e for e in entries if e.get_attribute('cmd') == filter_on] return found[0] if len(found) else None @property def history_drop_marker(self): """Provides access to the history drop marker. :returns: Reference to the history drop marker. """ return self.urlbar.find_element(By.ANON_ATTRIBUTE, {'anonid': 'historydropmarker'}) @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(lambda: 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, 'urlbar-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': cmd_key = self.window.get_entity('reloadCmd.commandkey') self.window.send_shortcut(cmd_key) 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, 'urlbar-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.urlbar.find_element(By.ANON_ATTRIBUTE, {'anonid': 'input'}) @property def value(self): """Provides access to the currently displayed value of the urlbar. :returns: The urlbar value. """ return self.urlbar.get_attribute('value') class AutocompleteResults(UIBaseLib): """Wraps DOM elements and methods for interacting with autocomplete results.""" def close(self, force=False): """Closes the urlbar autocomplete popup. :param force: If true, the popup is closed by its own hide function, otherwise a key event is sent to close the popup. """ 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='Autocomplete popup has not been closed.') def get_matching_text(self, result, match_type): """Returns an array of strings of the matching text within an autocomplete result in the urlbar. :param result: The result to inspect for matches. :param match_type: The type of match to search for (one of `title` or `url`). """ if match_type == 'title': descnode = self.marionette.execute_script(""" return arguments[0].boxObject.firstChild.childNodes[1].childNodes[0]; """, script_args=[result]) elif match_type == 'url': descnode = self.marionette.execute_script(""" return arguments[0].boxObject.lastChild.childNodes[2].childNodes[0]; """, script_args=[result]) else: raise ValueError('match_type provided must be one of' '"title" or "url", not %s' % match_type) return self.marionette.execute_script(""" let rv = []; for (let node of arguments[0].childNodes) { if (node.nodeName == 'span') { rv.push(node.innerHTML); } } return rv; """, script_args=[descnode]) @property def visible_results(self): """Supplies the list of visible autocomplete result nodes. :returns: The list of visible results. """ return self.marionette.execute_script(""" let rv = []; let node = arguments[0]; for (let i = 0; i < node.itemCount; ++i) { let item = node.getItemAtIndex(i); if (!item.hasAttribute("collapsed")) { rv.push(item); } } return rv; """, script_args=[self.results]) @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_attribute('state') == 'open' @property def is_complete(self): """Returns when this popup is open and autocomplete results are complete. :returns: True, when autocomplete results have been populated. """ return self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let win = Services.focus.activeWindow; if (win) { return win.gURLBar.controller.searchStatus >= Components.interfaces.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH; } return null; """) @property def results(self): """ :returns: The autocomplete result container node. """ return self.element.find_element(By.ANON_ATTRIBUTE, {'anonid': 'richlistbox'}) @property def selected_index(self): """Provides the index of the selected item in the autocomplete list. :returns: The index. """ return self.results.get_attribute('selectedIndex') class IdentityPopup(UIBaseLib): """Wraps DOM elements and methods for interacting with the identity popup.""" def __init__(self, *args, **kwargs): UIBaseLib.__init__(self, *args, **kwargs) self._view = None @property def host(self): """The DOM element which represents the identity-popup content host. :returns: Reference to the identity-popup content host. """ return self.marionette.find_element(By.ID, 'identity-popup-content-host') @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_attribute('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(lambda: 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)(lambda: 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('current') == '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 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 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. """ label = self.window.get_entity('identity.moreInfoLinkText2') return self.element.find_element(By.CSS_SELECTOR, u'button[label="{}"]'.format(label)) @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') PK7EpH)firefox_puppeteer/ui/pageinfo/__init__.pyPK7EpH xz'firefox_puppeteer/ui/pageinfo/window.py# 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 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' dtds = [ 'chrome://browser/locale/pageInfo.dtd', ] properties = [ 'chrome://browser/locale/browser.properties', 'chrome://browser/locale/pageInfo.properties', 'chrome://pippki/locale/pippki.properties', ] def __init__(self, *args, **kwargs): BaseWindow.__init__(self, *args, **kwargs) @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(lambda: 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_closeWindow') elif trigger == 'shortcut': win.send_shortcut(win.get_entity('closeWindow.key'), accel=True) else: raise ValueError('Unknown closing method: "%s"' % trigger) BaseWindow.close(self, callback, force) Windows.register_window(PageInfoWindow.window_type, PageInfoWindow) PK›xH=3%firefox_puppeteer/ui/pageinfo/deck.py# 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 marionette_driver import By, Wait from firefox_puppeteer.ui_base_lib 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 = {'feedPanel': FeedPanel, 'generalPanel': GeneralPanel, 'mediaPanel': MediaPanel, 'permPanel': PermissionsPanel, 'securityPanel': SecurityPanel } panel = self.element.find_element(By.ID, panel_id) return mapping.get(panel_id, Panel)(lambda: self.marionette, self.window, panel) # Properties for visual elements of the deck # @property def feed(self): """The :class:`FeedPanel` instance for the feed panel. :returns: :class:`FeedPanel` instance. """ return self._create_panel_for_id('feedPanel') @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_attribute('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_attribute('id').split('Panel')[0] return self.window.window_element.find_element(By.ID, name + 'Tab') class FeedPanel(PageInfoPanel): pass 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_cookies(self): """The DOM element which represents the view cookies button. :returns: Reference to the button element. """ return self.element.find_element(By.ID, 'security-view-cookies') @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') PK›xHq##,firefox_puppeteer/ui/update_wizard/wizard.py# 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 marionette_driver import By, Wait from firefox_puppeteer.ui_base_lib import UIBaseLib from firefox_puppeteer.ui.deck import Panel class Wizard(UIBaseLib): def __init__(self, *args, **kwargs): UIBaseLib.__init__(self, *args, **kwargs) Wait(self.marionette).until( lambda _: self.selected_panel, message='No panel has been selected by default.') 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 = {'checking': CheckingPanel, 'downloading': DownloadingPanel, 'dummy': DummyPanel, 'errorpatching': ErrorPatchingPanel, 'errors': ErrorPanel, 'errorextra': ErrorExtraPanel, 'finished': FinishedPanel, 'finishedBackground': FinishedBackgroundPanel, 'incompatibleCheck': IncompatibleCheckPanel, 'incompatibleList': IncompatibleListPanel, 'installed': InstalledPanel, 'license': LicensePanel, 'manualUpdate': ManualUpdatePanel, 'noupdatesfound': NoUpdatesFoundPanel, 'pluginupdatesfound': PluginUpdatesFoundPanel, 'updatesfoundbasic': UpdatesFoundBasicPanel, 'updatesfoundbillboard': UpdatesFoundBillboardPanel, } panel = self.element.find_element(By.ID, panel_id) return mapping.get(panel_id, Panel)(lambda: self.marionette, self.window, panel) # Properties for visual buttons of the wizard # @property def _buttons(self): return self.element.find_element(By.ANON_ATTRIBUTE, {'anonid': 'Buttons'}) @property def cancel_button(self): return self._buttons.find_element(By.ANON_ATTRIBUTE, {'dlgtype': 'cancel'}) @property def extra1_button(self): return self._buttons.find_element(By.ANON_ATTRIBUTE, {'dlgtype': 'extra1'}) @property def extra2_button(self): return self._buttons.find_element(By.ANON_ATTRIBUTE, {'dlgtype': 'extra2'}) @property def previous_button(self): return self._buttons.find_element(By.ANON_ATTRIBUTE, {'dlgtype': 'back'}) @property def finish_button(self): return self._buttons.find_element(By.ANON_ATTRIBUTE, {'dlgtype': 'finish'}) @property def next_button(self): return self._buttons.find_element(By.ANON_ATTRIBUTE, {'dlgtype': 'next'}) # Properties for visual panels of the wizard # @property def checking(self): """The checking for updates panel. :returns: :class:`CheckingPanel` instance. """ return self._create_panel_for_id('checking') @property def downloading(self): """The downloading panel. :returns: :class:`DownloadingPanel` instance. """ return self._create_panel_for_id('downloading') @property def dummy(self): """The dummy panel. :returns: :class:`DummyPanel` instance. """ return self._create_panel_for_id('dummy') @property def error_patching(self): """The error patching panel. :returns: :class:`ErrorPatchingPanel` instance. """ return self._create_panel_for_id('errorpatching') @property def error(self): """The errors panel. :returns: :class:`ErrorPanel` instance. """ return self._create_panel_for_id('errors') @property def error_extra(self): """The error extra panel. :returns: :class:`ErrorExtraPanel` instance. """ return self._create_panel_for_id('errorextra') @property def finished(self): """The finished panel. :returns: :class:`FinishedPanel` instance. """ return self._create_panel_for_id('finished') @property def finished_background(self): """The finished background panel. :returns: :class:`FinishedBackgroundPanel` instance. """ return self._create_panel_for_id('finishedBackground') @property def incompatible_check(self): """The incompatible check panel. :returns: :class:`IncompatibleCheckPanel` instance. """ return self._create_panel_for_id('incompatibleCheck') @property def incompatible_list(self): """The incompatible list panel. :returns: :class:`IncompatibleListPanel` instance. """ return self._create_panel_for_id('incompatibleList') @property def installed(self): """The installed panel. :returns: :class:`InstalledPanel` instance. """ return self._create_panel_for_id('installed') @property def license(self): """The license panel. :returns: :class:`LicensePanel` instance. """ return self._create_panel_for_id('license') @property def manual_update(self): """The manual update panel. :returns: :class:`ManualUpdatePanel` instance. """ return self._create_panel_for_id('manualUpdate') @property def no_updates_found(self): """The no updates found panel. :returns: :class:`NoUpdatesFoundPanel` instance. """ return self._create_panel_for_id('noupdatesfound') @property def plugin_updates_found(self): """The plugin updates found panel. :returns: :class:`PluginUpdatesFoundPanel` instance. """ return self._create_panel_for_id('pluginupdatesfound') @property def updates_found_basic(self): """The updates found panel. :returns: :class:`UpdatesFoundPanel` instance. """ return self._create_panel_for_id('updatesfoundbasic') @property def updates_found_billboard(self): """The billboard panel shown if an update has been found. :returns: :class:`UpdatesFoundBillboardPanel` instance. """ return self._create_panel_for_id('updatesfoundbillboard') @property def panels(self): """List of all the available :class:`Panel` instances. :returns: List of :class:`Panel` instances. """ panels = self.marionette.execute_script(""" let wizard = arguments[0]; let panels = []; for (let index = 0; index < wizard.children.length; index++) { if (wizard.children[index].id) { panels.push(wizard.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_attribute('pageIndex')) @property def selected_panel(self): """A :class:`Panel` instance of the currently selected panel. :returns: :class:`Panel` instance. """ return self._create_panel_for_id(self.element.get_attribute('currentpageid')) class CheckingPanel(Panel): @property def progress(self): """The DOM element which represents the progress meter. :returns: Reference to the progress element. """ return self.element.find_element(By.ID, 'checkingProgress') class DownloadingPanel(Panel): @property def progress(self): """The DOM element which represents the progress meter. :returns: Reference to the progress element. """ return self.element.find_element(By.ID, 'downloadProgress') class DummyPanel(Panel): pass class ErrorPatchingPanel(Panel): pass class ErrorPanel(Panel): pass class ErrorExtraPanel(Panel): pass class FinishedPanel(Panel): pass class FinishedBackgroundPanel(Panel): pass class IncompatibleCheckPanel(Panel): @property def progress(self): """The DOM element which represents the progress meter. :returns: Reference to the progress element. """ return self.element.find_element(By.ID, 'incompatibleCheckProgress') class IncompatibleListPanel(Panel): pass class InstalledPanel(Panel): pass class LicensePanel(Panel): pass class ManualUpdatePanel(Panel): pass class NoUpdatesFoundPanel(Panel): pass class PluginUpdatesFoundPanel(Panel): pass class UpdatesFoundBasicPanel(Panel): pass class UpdatesFoundBillboardPanel(Panel): pass PK7EpH@a.firefox_puppeteer/ui/update_wizard/__init__.py# 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 dialog import UpdateWizardDialog PK›xH,firefox_puppeteer/ui/update_wizard/dialog.py# 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 marionette_driver import By, Wait from firefox_puppeteer.ui.update_wizard.wizard import Wizard from firefox_puppeteer.ui.windows import BaseWindow, Windows # Bug 1143020 - Subclass from BaseDialog ui class with possible wizard mixin class UpdateWizardDialog(BaseWindow): """Representation of the old Software Update Wizard Dialog.""" window_type = 'Update:Wizard' dtds = [ 'chrome://branding/locale/brand.dtd', 'chrome://mozapps/locale/update/updates.dtd', ] properties = [ 'chrome://branding/locale/brand.properties', 'chrome://mozapps/locale/update/updates.properties', ] def __init__(self, *args, **kwargs): BaseWindow.__init__(self, *args, **kwargs) @property def wizard(self): """The :class:`Wizard` instance which represents the wizard. :returns: Reference to the wizard. """ # The deck is also the root element wizard = self.marionette.find_element(By.ID, 'updates') return Wizard(lambda: self.marionette, self, wizard) def select_next_page(self): """Clicks on "Next" button, and waits for the next page to show up.""" current_panel = self.wizard.selected_panel self.wizard.next_button.click() Wait(self.marionette).until( lambda _: self.wizard.selected_panel != current_panel, message='Next panel has not been selected.') Windows.register_window(UpdateWizardDialog.window_type, UpdateWizardDialog) PKxH)E001firefox_puppeteer-4.0.0.dist-info/DESCRIPTION.rstSee http://firefox-puppeteer.readthedocs.org/ PKxHFF/firefox_puppeteer-4.0.0.dist-info/metadata.json{"extensions": {"python.details": {"contacts": [{"email": "tools-marionette@lists.mozilla.org", "name": "Auto-tools", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://wiki.mozilla.org/Auto-tools/Projects/Marionette/Puppeteer/Firefox"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["mozilla"], "license": "MPL", "metadata_version": "2.0", "name": "firefox-puppeteer", "run_requires": [{"requires": ["marionette-driver (>=1.3.0)", "mozinfo (>=0.8)"]}], "summary": "Firefox Puppeteer", "version": "4.0.0"}PKxHv /firefox_puppeteer-4.0.0.dist-info/top_level.txtfirefox_puppeteer PKxH''\\'firefox_puppeteer-4.0.0.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKxH*firefox_puppeteer-4.0.0.dist-info/METADATAMetadata-Version: 2.0 Name: firefox-puppeteer Version: 4.0.0 Summary: Firefox Puppeteer Home-page: https://wiki.mozilla.org/Auto-tools/Projects/Marionette/Puppeteer/Firefox Author: Auto-tools Author-email: tools-marionette@lists.mozilla.org License: MPL Keywords: mozilla Platform: UNKNOWN Requires-Dist: marionette-driver (>=1.3.0) Requires-Dist: mozinfo (>=0.8) See http://firefox-puppeteer.readthedocs.org/ PKxHL%7(firefox_puppeteer-4.0.0.dist-info/RECORDfirefox_puppeteer/__init__.py,sha256=FweqeT6NoJlfmjBZskDHzXOex6gegJ_95SFoB5IMvw4,3137 firefox_puppeteer/base.py,sha256=UzC67K-0OjjfG64bL2IBNRjeS9hnHFH-xYL81Yc7mWM,803 firefox_puppeteer/decorators.py,sha256=p5p8OlJ0k0A6pQHrImxk4fA2IXid1fHxQToYgnGCe8A,1209 firefox_puppeteer/errors.py,sha256=qbD-0JxBHKAV4WY8ruGuBxm7lIoXsAQWaf67o4PwBOY,493 firefox_puppeteer/ui_base_lib.py,sha256=qTNMH83m7XU9jAW-i0ZHS6sipxCxDzujlfnut1dnFRk,1121 firefox_puppeteer/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 firefox_puppeteer/api/appinfo.py,sha256=jVfkNm1yIkUXzjVQGwMp3sA1KwlpvM0xbcheYl2s6a4,1826 firefox_puppeteer/api/keys.py,sha256=wZWtkuElAHiPYYok_6eI9RzWNRNwll1DRk021lJd4F0,686 firefox_puppeteer/api/l10n.py,sha256=CwX-itM3-Ogav2bTpso--3JixOKiCH-957AH-T--V6s,3223 firefox_puppeteer/api/places.py,sha256=Mzi_f75sEoM3ugz2oZnj3DV4IJ9XdINje3ZmD093DmU,6941 firefox_puppeteer/api/prefs.py,sha256=51PdeTGIY8YiSJJDvzG6BzOkQQnIr8qe1siGALdEgRg,7689 firefox_puppeteer/api/security.py,sha256=7-8X-xUcVMw50rWCgIpnJ7ezuy6rKFY17NRqjE7_i1M,2745 firefox_puppeteer/api/software_update.py,sha256=77E8BbLr8_sdKNRJltOy1G9Ly06bdOBROOE42D-Pqow,15455 firefox_puppeteer/api/utils.py,sha256=xfhREDMo1EvJaMnL0olIFrJl3844ZwCvFKspktMK1zQ,3852 firefox_puppeteer/testcases/__init__.py,sha256=k1j680hJBtVOgWKG5N_iUtRsf9pZ1jCsfT_2P_5noqM,265 firefox_puppeteer/testcases/base.py,sha256=jKfJBlxj1uoHWXKKESD9As_5_jsV2RpllCG3P2iibS0,4652 firefox_puppeteer/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 firefox_puppeteer/ui/deck.py,sha256=ksUyi1TsrDduiZyhKtaCBaim0Ta_EaRAwSnl1MGLhFw,580 firefox_puppeteer/ui/menu.py,sha256=bC8VTM6LUTETog-yeDCkeS7RmpMZ7wrTvYvsMdbHwwo,4096 firefox_puppeteer/ui/windows.py,sha256=uwvxrgX7Oi4lAkGrKAE22kUKYIHVRX8y7MZvd1aWLuQ,16224 firefox_puppeteer/ui/about_window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 firefox_puppeteer/ui/about_window/deck.py,sha256=51GDKCG_6Pz06i6dOlW3UlM1jHjeWRDXyNc-ZQbJLWM,5692 firefox_puppeteer/ui/about_window/window.py,sha256=QzLoTQs-sicULoOyl0wjKxZpomNhsznQy4koV8CHHlI,1044 firefox_puppeteer/ui/browser/__init__.py,sha256=8h-0SpFwwM1LZ1--FxKXmbCP84W0wtizlWFt9HuKhcs,199 firefox_puppeteer/ui/browser/tabbar.py,sha256=-2HvUq8PgG7l5BCwFap4j3cBUOIgT9bZdhlKaCYGJko,13546 firefox_puppeteer/ui/browser/toolbars.py,sha256=9lO2bVaXv0hcfzpCOd7r7gr8BJL2r9fuU5L9klfNEFs,21962 firefox_puppeteer/ui/browser/window.py,sha256=BULY34Z9kOTzeyW7ym2Hr4gYV3EWx2-K2nkeJBdrpfw,8662 firefox_puppeteer/ui/pageinfo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 firefox_puppeteer/ui/pageinfo/deck.py,sha256=Hk389ZkrMqsbXA8ARQDNKtk3T-KP9aPR_n01xpHYzrc,5875 firefox_puppeteer/ui/pageinfo/window.py,sha256=zVby7g1KA5tCZQjUqY0sZ1-iKIh7VoF0HRYBXYsA5OI,2286 firefox_puppeteer/ui/update_wizard/__init__.py,sha256=xKeUivYf_k82Ih01vyFbeBPUXLKrDEDdEA9PX17YO8k,238 firefox_puppeteer/ui/update_wizard/dialog.py,sha256=MXJDAHzB1B114BoRU5bkrx5F-8vl-BsdHkD_uX0GYWw,1707 firefox_puppeteer/ui/update_wizard/wizard.py,sha256=UWNv_yzRDbUAu1H01ohVoXEaQXL3Lhah9_4rnqZ9cUk,8983 firefox_puppeteer-4.0.0.dist-info/DESCRIPTION.rst,sha256=CAa9cOpGk6pv76-1LEYt1MRV23bnP3G8m_s61ulNu3A,48 firefox_puppeteer-4.0.0.dist-info/METADATA,sha256=pf9w-R97TAeSFZlW6yTl0rbzFyX5Ngv5_u1IT0Z65zo,413 firefox_puppeteer-4.0.0.dist-info/RECORD,, firefox_puppeteer-4.0.0.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 firefox_puppeteer-4.0.0.dist-info/metadata.json,sha256=Y99fzWlCGpWIQHm-Jk23b97uOr4kXyK_7r1lMsn42z8,582 firefox_puppeteer-4.0.0.dist-info/top_level.txt,sha256=sS0CtEryJEJQ9OR9vMy2Cmmp2QDAfEiB1u9O6Ac8Qk4,18 PK7EpHڂfirefox_puppeteer/errors.pyPKxHdiA A &firefox_puppeteer/__init__.pyPK7EpH<󑻹firefox_puppeteer/decorators.pyPK7EpH,##firefox_puppeteer/base.pyPK7EpH6aa firefox_puppeteer/ui_base_lib.pyPKxH~  'firefox_puppeteer/testcases/__init__.pyPKxHK),,#firefox_puppeteer/testcases/base.pyPK7EpHs*L/firefox_puppeteer/api/keys.pyPK›xHW=52firefox_puppeteer/api/places.pyPK7EpH!Mfirefox_puppeteer/api/__init__.pyPK7EpH#Η Mfirefox_puppeteer/api/l10n.pyPK7EpH  Zfirefox_puppeteer/api/utils.pyPK7EpHbp_<_<(ifirefox_puppeteer/api/software_update.pyPK7EpH_~홹 !firefox_puppeteer/api/security.pyPK7EpH}  firefox_puppeteer/api/prefs.pyPK7EpH]H"" firefox_puppeteer/api/appinfo.pyPK7EpH *firefox_puppeteer/ui/__init__.pyPK›xH/TA`?`?hfirefox_puppeteer/ui/windows.pyPK7EpHrDDfirefox_puppeteer/ui/deck.pyPK7EpH) firefox_puppeteer/ui/menu.pyPK7EpH-)firefox_puppeteer/ui/about_window/__init__.pyPK7EpH2+*firefox_puppeteer/ui/about_window/window.pyPK7EpH9w<<)e.firefox_puppeteer/ui/about_window/deck.pyPK7EpHZ~(Dfirefox_puppeteer/ui/browser/__init__.pyPK7EpHd2!!&Efirefox_puppeteer/ui/browser/window.pyPK›xHʓL44&hfirefox_puppeteer/ui/browser/tabbar.pyPK›xH仧UU(=firefox_puppeteer/ui/browser/toolbars.pyPK7EpH)Mfirefox_puppeteer/ui/pageinfo/__init__.pyPK7EpH xz'firefox_puppeteer/ui/pageinfo/window.pyPK›xH=3%firefox_puppeteer/ui/pageinfo/deck.pyPK›xHq##,firefox_puppeteer/ui/update_wizard/wizard.pyPK7EpH@a.^7firefox_puppeteer/ui/update_wizard/__init__.pyPK›xH,8firefox_puppeteer/ui/update_wizard/dialog.pyPKxH)E001?firefox_puppeteer-4.0.0.dist-info/DESCRIPTION.rstPKxHFF/ @firefox_puppeteer-4.0.0.dist-info/metadata.jsonPKxHv /Bfirefox_puppeteer-4.0.0.dist-info/top_level.txtPKxH''\\'Bfirefox_puppeteer-4.0.0.dist-info/WHEELPKxH*Cfirefox_puppeteer-4.0.0.dist-info/METADATAPKxHL%7(Efirefox_puppeteer-4.0.0.dist-info/RECORDPK'' S