def __init__(self, marionette): BaseLib.__init__(self, marionette) self.app_info = AppInfo(marionette) self.prefs = Preferences(marionette) self._mar_channels = MARChannels(marionette) self._active_update = ActiveUpdate(marionette)
def download_via_update_wizard(dialog): """ Download the update via the old update wizard dialog. :param dialog: Instance of :class:`UpdateWizardDialog`. """ prefs = Preferences(self.marionette) prefs.set_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE, dialog.window_type) try: # If updates have already been found, proceed to download if dialog.wizard.selected_panel in [ dialog.wizard.updates_found_basic, dialog.wizard.error_patching, ]: dialog.select_next_page() # If incompatible add-on are installed, skip over the wizard page # TODO: Remove once we no longer support version Firefox 45.0ESR if self.puppeteer.utils.compare_version( self.puppeteer.appinfo.version, '49.0a1') == -1: if dialog.wizard.selected_panel == dialog.wizard.incompatible_list: dialog.select_next_page() # Updates were stored in the cache, so no download is necessary if dialog.wizard.selected_panel in [ dialog.wizard.finished, dialog.wizard.finished_background, ]: pass # Download the update elif dialog.wizard.selected_panel == dialog.wizard.downloading: if wait_for_finish: start_time = datetime.now() self.wait_for_download_finished(dialog, timeout) self.download_duration = (datetime.now() - start_time).total_seconds() Wait(self.marionette).until( lambda _: (dialog.wizard.selected_panel in [ dialog.wizard.finished, dialog.wizard.finished_background, ]), message='Final wizard page has been selected.') else: raise Exception( 'Invalid wizard page for downloading an update: {}'. format(dialog.wizard.selected_panel)) finally: self.marionette.clear_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE)
def __init__(self, marionette): BaseLib.__init__(self, marionette) self.prefs = Preferences(marionette) 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; """)
def download_via_update_wizard(dialog): """ Download the update via the old update wizard dialog. :param dialog: Instance of :class:`UpdateWizardDialog`. """ prefs = Preferences(lambda: self.marionette) prefs.set_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE, dialog.window_type) try: # If updates have already been found, proceed to download if dialog.wizard.selected_panel in [dialog.wizard.updates_found_basic, dialog.wizard.updates_found_billboard, dialog.wizard.error_patching, ]: dialog.select_next_page() # If incompatible add-on are installed, skip over the wizard page # TODO: Remove once we no longer support version Firefox 45.0ESR if self.utils.compare_version(self.appinfo.version, '49.0a1') == -1: if dialog.wizard.selected_panel == dialog.wizard.incompatible_list: dialog.select_next_page() # Updates were stored in the cache, so no download is necessary if dialog.wizard.selected_panel in [dialog.wizard.finished, dialog.wizard.finished_background, ]: pass # Download the update elif dialog.wizard.selected_panel == dialog.wizard.downloading: if wait_for_finish: start_time = datetime.now() self.wait_for_download_finished(dialog, timeout) self.download_duration = (datetime.now() - start_time).total_seconds() Wait(self.marionette).until(lambda _: ( dialog.wizard.selected_panel in [dialog.wizard.finished, dialog.wizard.finished_background, ]), message='Final wizard page has been selected.') else: raise Exception('Invalid wizard page for downloading an update: {}'.format( dialog.wizard.selected_panel)) finally: prefs.restore_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE)
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
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): BaseLib.__init__(self, marionette) self.prefs = Preferences(marionette) 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)
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)
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_CHANNEL = 'app.update.channel' PREF_APP_UPDATE_URL = 'app.update.url' PREF_DISABLED_ADDONS = 'extensions.disabledAddons' def __init__(self, marionette): BaseLib.__init__(self, marionette) self.app_info = AppInfo(marionette) self.prefs = Preferences(marionette) self._mar_channels = MARChannels(marionette) self._active_update = ActiveUpdate(marionette) @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 """ update_url = self.get_formatted_update_url(True) return { 'buildid': self.app_info.appBuildID, 'channel': self.update_channel, 'disabled_addons': self.prefs.get_pref(self.PREF_DISABLED_ADDONS), 'locale': self.app_info.locale, 'mar_channels': self.mar_channels.channels, 'update_url': update_url, 'update_snippet': self.get_update_snippet(update_url), '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} 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): """Return the currently used update channel.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_CHANNEL, default_branch=True) @update_channel.setter def update_channel(self, channel): """Set the update channel to be used for update checks. :param channel: New update channel to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_CHANNEL, channel, default_branch=True) @property def update_url(self): """Return the update URL used for update checks.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_URL, default_branch=True) @update_url.setter def update_url(self, url): """Set the update URL to be used for update checks. :param url: New update URL to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_URL, url, default_branch=True) @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_snippet(self, update_url): """Retrieve contents of the update snippet. :param update_url: URL to the update snippet """ snippet = None try: import urllib2 response = urllib2.urlopen(update_url) snippet = response.read() except Exception: pass return snippet def get_formatted_update_url(self, force=False): """Retrieve the formatted 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 """ # Format the URL by replacing placeholders url = self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/UpdateUtils.jsm") return UpdateUtils.formatUpdateURL(arguments[0]); """, script_args=[self.update_url]) if force: if '?' in url: url += '&' else: url += '?' url += 'force=1' return url
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_CHANNEL = "app.update.channel" PREF_APP_UPDATE_URL = "app.update.url" PREF_DISABLED_ADDONS = "extensions.disabledAddons" def __init__(self, marionette): BaseLib.__init__(self, marionette) self.app_info = AppInfo(marionette) self.prefs = Preferences(marionette) self._mar_channels = MARChannels(marionette) self._active_update = ActiveUpdate(marionette) @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 """ update_url = self.get_formatted_update_url(True) return { "buildid": self.app_info.appBuildID, "channel": self.update_channel, "disabled_addons": self.prefs.get_pref(self.PREF_DISABLED_ADDONS), "locale": self.app_info.locale, "mar_channels": self.mar_channels.channels, "update_url": update_url, "update_snippet": self.get_update_snippet(update_url), "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} 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): """Return the currently used update channel.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_CHANNEL, default_branch=True) @update_channel.setter def update_channel(self, channel): """Set the update channel to be used for update checks. :param channel: New update channel to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_CHANNEL, channel, default_branch=True) @property def update_url(self): """Return the update URL used for update checks.""" return self.prefs.get_pref(self.PREF_APP_UPDATE_URL, default_branch=True) @update_url.setter def update_url(self, url): """Set the update URL to be used for update checks. :param url: New update URL to use """ self.prefs.set_pref(self.PREF_APP_UPDATE_URL, url, default_branch=True) @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_snippet(self, update_url): """Retrieve contents of the update snippet. :param update_url: URL to the update snippet """ snippet = None try: import urllib2 response = urllib2.urlopen(update_url) snippet = response.read() except Exception: pass return snippet def get_formatted_update_url(self, force=False): """Retrieve the formatted 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 """ # Format the URL by replacing placeholders url = self.marionette.execute_script( """ Components.utils.import("resource://gre/modules/UpdateUtils.jsm") return UpdateUtils.formatUpdateURL(arguments[0]); """, script_args=[self.update_url], ) if force: if "?" in url: url += "&" else: url += "?" url += "force=1" return url
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): BaseLib.__init__(self, marionette) self.app_info = AppInfo(marionette) self.prefs = Preferences(marionette) self._update_channel = UpdateChannel(marionette) self._mar_channels = MARChannels(marionette) self._active_update = ActiveUpdate(marionette) @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 """ update_url = self.get_update_url(True) 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, 'update_url': update_url, 'update_snippet': self.get_update_snippet(update_url), '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_snippet(self, update_url): """Retrieve contents of the update snippet. :param update_url: URL to the update snippet """ snippet = None try: import urllib2 response = urllib2.urlopen(update_url) snippet = response.read() except Exception: pass return snippet 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) # Format the URL by replacing placeholders url = self.marionette.execute_script(""" Components.utils.import("resource://gre/modules/UpdateUtils.jsm") return UpdateUtils.formatUpdateURL(arguments[0]); """, script_args=[url]) if force: if '?' in url: url += '&' else: url += '?' url += 'force=1' return url
def __init__(self, *args, **kwargs): BaseWindow.__init__(self, *args, **kwargs) self._prefs = Preferences(lambda: self.marionette) self._software_update = SoftwareUpdate(lambda: self.marionette) self._download_duration = None
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', ] # For the old update wizard, the errors are displayed inside the dialog. For the # handling of updates in the about window the errors are displayed in new dialogs. # When the old wizard is open we have to set the preference, so the errors will be # shown as expected, otherwise we would have unhandled modal dialogs when errors are raised. # See: # http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/update/nsUpdateService.js?rev=a9240b1eb2fb#4813 # http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/update/nsUpdateService.js?rev=a9240b1eb2fb#4756 PREF_APP_UPDATE_ALTWINDOWTYPE = 'app.update.altwindowtype' TIMEOUT_UPDATE_DOWNLOAD = 360 def __init__(self, *args, **kwargs): BaseWindow.__init__(self, *args, **kwargs) self._prefs = Preferences(lambda: self.marionette) self._software_update = SoftwareUpdate(lambda: self.marionette) self._download_duration = None @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) @property def patch_info(self): """ Returns information about the active update in the queue. :returns: A dictionary with information about the active patch """ patch = self._software_update.patch_info patch['download_duration'] = self._download_duration return patch def download(self, wait_for_finish=True, timeout=TIMEOUT_UPDATE_DOWNLOAD): """ Download the update. :param wait_for_finish: Optional, if True the function has to wait for the download to be finished, default to `True` :param timeout: Optional, How long to wait for the download to finish, default to 360 seconds """ self._prefs.set_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE, self.window_type) try: # If updates have already been found, proceed to download if self.wizard.selected_panel in (self.wizard.updates_found_basic, self.wizard.updates_found_billboard, self.wizard.error_patching, ): self.select_next_page() # If incompatible add-on are installed, skip over the wizard page if self.wizard.selected_panel == self.wizard.incompatible_list: self.select_next_page() # Updates were stored in the cache, so no download is necessary if self.wizard.selected_panel in (self.wizard.finished, self.wizard.finished_background, ): pass # Download the update elif self.wizard.selected_panel == self.wizard.downloading: if wait_for_finish: start_time = datetime.now() self.wait_for_download_finished(timeout) self._download_duration = (datetime.now() - start_time).total_seconds() Wait(self.marionette).until( lambda _: self.wizard.selected_panel in (self.wizard.finished, self.wizard.finished_background, ), message='Final wizard page has been selected.') else: raise Exception('Invalid wizard page for downloading an update: {}'.format( self.wizard.selected_panel)) finally: self._prefs.restore_pref(self.PREF_APP_UPDATE_ALTWINDOWTYPE) 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) def wait_for_download_finished(self, timeout=TIMEOUT_UPDATE_DOWNLOAD): """Waits until download is completed. :param timeout: Optional, How long to wait for the download to finish, default to 360 seconds. """ Wait(self.marionette, timeout=timeout).until( lambda _: self.wizard.selected_panel != self.wizard.downloading, message='Download has been completed.') assert self.wizard.selected_panel not in (self.wizard.error, self.wizard.error_extra, ), \ 'Update has been successfully downloaded.'