#!/usr/bin/env python # Copyright (c) 2011 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """ This module is a simple qa tool that installs extensions and tests whether the browser crashes while visiting a list of urls. Usage: python extensions.py -v Note: This assumes that there is a directory of extensions called 'extensions-tool' and that there is a file of newline-separated urls to visit called 'urls.txt' in the data directory. """ import glob import logging import os import sys import pyauto_functional # must be imported before pyauto import pyauto class ExtensionsPage(object): """Access options in extensions page (chrome://extensions-frame).""" _URL = 'chrome://extensions-frame' def __init__(self, driver): self._driver = driver self._driver.get(ExtensionsPage._URL) def CheckExtensionVisible(self, ext_id): """Returns True if |ext_id| exists on page.""" return len(self._driver.find_elements_by_id(ext_id)) == 1 def SetEnabled(self, ext_id, enabled): """Clicks on 'Enabled' checkbox for specified extension. Args: ext_id: Extension ID to be enabled or disabled. enabled: Boolean indicating whether |ext_id| is to be enabled or disabled. """ checkbox = self._driver.find_element_by_xpath( '//*[@id="%s"]//*[@class="enable-controls"]//*[@type="checkbox"]' % ext_id) if checkbox != enabled: checkbox.click() # Reload page to ensure that the UI is recreated. self._driver.get(ExtensionsPage._URL) def SetAllowInIncognito(self, ext_id, allowed): """Clicks on 'Allow in incognito' checkbox for specified extension. Args: ext_id: Extension ID to be enabled or disabled. allowed: Boolean indicating whether |ext_id| is to be allowed or disallowed in incognito. """ checkbox = self._driver.find_element_by_xpath( '//*[@id="%s"]//*[@class="incognito-control"]//*[@type="checkbox"]' % ext_id) if checkbox.is_selected() != allowed: checkbox.click() # Reload page to ensure that the UI is recreated. self._driver.get(ExtensionsPage._URL) def SetAllowAccessFileURLs(self, ext_id, allowed): """Clicks on 'Allow access to file URLs' checkbox for specified extension. Args: ext_id: Extension ID to be enabled or disabled. allowed: Boolean indicating whether |ext_id| is to be allowed access to file URLs. """ checkbox = self._driver.find_element_by_xpath( '//*[@id="%s"]//*[@class="file-access-control"]//*[@type="checkbox"]' % ext_id) if checkbox.is_selected() != allowed: checkbox.click() class ExtensionsTest(pyauto.PyUITest): """Test of extensions.""" def Debug(self): """Test method for experimentation. This method is not run automatically. """ while True: raw_input('Interact with the browser and hit <enter> to dump history.') print '*' * 20 self.pprint(self.GetExtensionsInfo()) def _GetInstalledExtensionIds(self): return [extension['id'] for extension in self.GetExtensionsInfo()] def _ReturnCrashingExtensions(self, extensions, group_size, top_urls): """Returns the group of extensions that crashes (if any). Install the given extensions in groups of group_size and return the group of extensions that crashes (if any). Args: extensions: A list of extensions to install. group_size: The number of extensions to install at one time. top_urls: The list of top urls to visit. Returns: The extensions in the crashing group or None if there is no crash. """ curr_extension = 0 num_extensions = len(extensions) self.RestartBrowser() orig_extension_ids = self._GetInstalledExtensionIds() while curr_extension < num_extensions: logging.debug('New group of %d extensions.', group_size) group_end = curr_extension + group_size for extension in extensions[curr_extension:group_end]: logging.debug('Installing extension: %s', extension) self.InstallExtension(extension) for url in top_urls: self.NavigateToURL(url) def _LogAndReturnCrashing(): crashing_extensions = extensions[curr_extension:group_end] logging.debug('Crashing extensions: %s', crashing_extensions) return crashing_extensions # If the browser has crashed, return the extensions in the failing group. try: num_browser_windows = self.GetBrowserWindowCount() except: return _LogAndReturnCrashing() else: if not num_browser_windows: return _LogAndReturnCrashing() else: # Uninstall all extensions that aren't installed by default. new_extension_ids = [id for id in self._GetInstalledExtensionIds() if id not in orig_extension_ids] for extension_id in new_extension_ids: self.UninstallExtensionById(extension_id) curr_extension = group_end # None of the extensions crashed. return None def _GetExtensionInfoById(self, extensions, id): for x in extensions: if x['id'] == id: return x return None def ExtensionCrashes(self): """Add top extensions; confirm browser stays up when visiting top urls.""" # TODO: provide a way in pyauto to pass args to a test - take these as args extensions_dir = os.path.join(self.DataDir(), 'extensions-tool') urls_file = os.path.join(self.DataDir(), 'urls.txt') error_msg = 'The dir "%s" must exist' % os.path.abspath(extensions_dir) assert os.path.exists(extensions_dir), error_msg error_msg = 'The file "%s" must exist' % os.path.abspath(urls_file) assert os.path.exists(urls_file), error_msg num_urls_to_visit = 100 extensions_group_size = 20 top_urls = [l.rstrip() for l in open(urls_file).readlines()[:num_urls_to_visit]] failed_extensions = glob.glob(os.path.join(extensions_dir, '*.crx')) group_size = extensions_group_size while (group_size and failed_extensions): failed_extensions = self._ReturnCrashingExtensions( failed_extensions, group_size, top_urls) group_size = group_size // 2 self.assertFalse(failed_extensions, 'Extension(s) in failing group: %s' % failed_extensions) def _InstallExtensionCheckDefaults(self, crx_file): """Installs extension at extensions/|crx_file| and checks default status. Checks that the installed extension is enabled and not allowed in incognito. Args: crx_file: Relative path from self.DataDir()/extensions to .crx extension to be installed. Returns: The extension ID. """ crx_file_path = os.path.abspath( os.path.join(self.DataDir(), 'extensions', crx_file)) ext_id = self.InstallExtension(crx_file_path) extension = self._GetExtensionInfoById(self.GetExtensionsInfo(), ext_id) self.assertTrue(extension['is_enabled'], msg='Extension was not enabled on installation') self.assertFalse(extension['allowed_in_incognito'], msg='Extension was allowed in incognito on installation.') return ext_id def _ExtensionValue(self, ext_id, key): """Returns the value of |key| for |ext_id|. Args: ext_id: The extension ID. key: The key for which the extensions info value is required. Returns: The value of extensions info |key| for |ext_id|. """ return self._GetExtensionInfoById(self.GetExtensionsInfo(), ext_id)[key] def _FileAccess(self, ext_id): """Returns the value of newAllowFileAccess for |ext_id|. Args: ext_id: The extension ID. Returns: The value of extensions settings newAllowFileAccess for |ext_id|. """ extension_settings = self.GetPrefsInfo().Prefs()['extensions']['settings'] return extension_settings[ext_id]['newAllowFileAccess'] def testGetExtensionPermissions(self): """Ensures we can retrieve the host/api permissions for an extension. This test assumes that the 'Bookmark Manager' extension exists in a fresh profile. """ extensions_info = self.GetExtensionsInfo() bm_exts = [x for x in extensions_info if x['name'] == 'Bookmark Manager'] self.assertTrue(bm_exts, msg='Could not find info for the Bookmark Manager ' 'extension.') ext = bm_exts[0] permissions_host = ext['host_permissions'] self.assertTrue(len(permissions_host) == 2 and 'chrome://favicon/*' in permissions_host and 'chrome://resources/*' in permissions_host, msg='Unexpected host permissions information.') permissions_api = ext['api_permissions'] print permissions_api self.assertTrue(len(permissions_api) == 5 and 'bookmarks' in permissions_api and 'bookmarkManagerPrivate' in permissions_api and 'metricsPrivate' in permissions_api and 'systemPrivate' in permissions_api and 'tabs' in permissions_api, msg='Unexpected API permissions information.') def testDisableEnableExtension(self): """Tests that an extension can be disabled and enabled with the UI.""" ext_id = self._InstallExtensionCheckDefaults('good.crx') # Disable extension. driver = self.NewWebDriver() ext_page = ExtensionsPage(driver) self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id]) ext_page.SetEnabled(ext_id, False) self.WaitUntil(self._ExtensionValue, args=[ext_id, 'is_enabled'], expect_retval=False) self.assertFalse(self._ExtensionValue(ext_id, 'is_enabled'), msg='Extension did not get disabled.') # Enable extension. self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id]) ext_page.SetEnabled(ext_id, True) self.WaitUntil(self._ExtensionValue, args=[ext_id, 'is_enabled'], expect_retval=True) self.assertTrue(self._ExtensionValue(ext_id, 'is_enabled'), msg='Extension did not get enabled.') def testAllowIncognitoExtension(self): """Tests allowing and disallowing an extension in incognito mode.""" ext_id = self._InstallExtensionCheckDefaults('good.crx') # Allow in incognito. driver = self.NewWebDriver() ext_page = ExtensionsPage(driver) self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id]) ext_page.SetAllowInIncognito(ext_id, True) # Check extension now allowed in incognito. self.WaitUntil(self._ExtensionValue, args=[ext_id, 'allowed_in_incognito'], expect_retval=True) self.assertTrue(self._ExtensionValue(ext_id, 'allowed_in_incognito'), msg='Extension did not get allowed in incognito.') # Disallow in incognito. self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id]) ext_page.SetAllowInIncognito(ext_id, False) # Check extension now disallowed in incognito. self.WaitUntil(self._ExtensionValue, args=[ext_id, 'allowed_in_incognito'], expect_retval=False) self.assertFalse(self._ExtensionValue(ext_id, 'allowed_in_incognito'), msg='Extension did not get disallowed in incognito.') def testAllowAccessFileURLs(self): """Tests disallowing and allowing and extension access to file URLs.""" ext_id = self._InstallExtensionCheckDefaults(os.path.join('permissions', 'files')) # Check extension allowed access to file URLs by default. extension_settings = self.GetPrefsInfo().Prefs()['extensions']['settings'] self.assertTrue(extension_settings[ext_id]['newAllowFileAccess'], msg='Extension was not allowed access to file URLs on ' 'installation') # Disallow access to file URLs. driver = self.NewWebDriver() ext_page = ExtensionsPage(driver) self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id]) ext_page.SetAllowAccessFileURLs(ext_id, False) # Check that extension does not have access to file URLs. self.WaitUntil(self._FileAccess, args=[ext_id], expect_retval=False) self.assertFalse(self._FileAccess(ext_id), msg='Extension did not have access to file URLs denied.') # Allow access to file URLs. self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id]) ext_page.SetAllowAccessFileURLs(ext_id, True) # Check that extension now has access to file URLs. self.WaitUntil(self._FileAccess, args=[ext_id], expect_retval=True) self.assertTrue(self._FileAccess(ext_id), msg='Extension did not have access to file URLs granted.') if __name__ == '__main__': pyauto_functional.Main()