Home | History | Annotate | Download | only in functional
      1 #!/usr/bin/env python
      2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """
      7 This module is a simple qa tool that installs extensions and tests whether the
      8 browser crashes while visiting a list of urls.
      9 
     10 Usage: python extensions.py -v
     11 
     12 Note: This assumes that there is a directory of extensions called
     13 'extensions-tool' and that there is a file of newline-separated urls to visit
     14 called 'urls.txt' in the data directory.
     15 """
     16 
     17 import glob
     18 import logging
     19 import os
     20 import sys
     21 
     22 import pyauto_functional # must be imported before pyauto
     23 import pyauto
     24 
     25 
     26 class ExtensionsPage(object):
     27   """Access options in extensions page (chrome://extensions-frame)."""
     28 
     29   _URL = 'chrome://extensions-frame'
     30 
     31   def __init__(self, driver):
     32     self._driver = driver
     33     self._driver.get(ExtensionsPage._URL)
     34 
     35   def CheckExtensionVisible(self, ext_id):
     36     """Returns True if |ext_id| exists on page."""
     37     return len(self._driver.find_elements_by_id(ext_id)) == 1
     38 
     39   def SetEnabled(self, ext_id, enabled):
     40     """Clicks on 'Enabled' checkbox for specified extension.
     41 
     42     Args:
     43       ext_id: Extension ID to be enabled or disabled.
     44       enabled: Boolean indicating whether |ext_id| is to be enabled or disabled.
     45     """
     46     checkbox = self._driver.find_element_by_xpath(
     47         '//*[@id="%s"]//*[@class="enable-controls"]//*[@type="checkbox"]' %
     48         ext_id)
     49     if checkbox != enabled:
     50       checkbox.click()
     51     # Reload page to ensure that the UI is recreated.
     52     self._driver.get(ExtensionsPage._URL)
     53 
     54   def SetAllowInIncognito(self, ext_id, allowed):
     55     """Clicks on 'Allow in incognito' checkbox for specified extension.
     56 
     57     Args:
     58       ext_id: Extension ID to be enabled or disabled.
     59       allowed: Boolean indicating whether |ext_id| is to be allowed or
     60           disallowed in incognito.
     61     """
     62     checkbox = self._driver.find_element_by_xpath(
     63         '//*[@id="%s"]//*[@class="incognito-control"]//*[@type="checkbox"]' %
     64         ext_id)
     65     if checkbox.is_selected() != allowed:
     66       checkbox.click()
     67     # Reload page to ensure that the UI is recreated.
     68     self._driver.get(ExtensionsPage._URL)
     69 
     70   def SetAllowAccessFileURLs(self, ext_id, allowed):
     71     """Clicks on 'Allow access to file URLs' checkbox for specified extension.
     72 
     73     Args:
     74       ext_id: Extension ID to be enabled or disabled.
     75       allowed: Boolean indicating whether |ext_id| is to be allowed access to
     76           file URLs.
     77     """
     78     checkbox = self._driver.find_element_by_xpath(
     79         '//*[@id="%s"]//*[@class="file-access-control"]//*[@type="checkbox"]' %
     80         ext_id)
     81     if checkbox.is_selected() != allowed:
     82       checkbox.click()
     83 
     84 
     85 class ExtensionsTest(pyauto.PyUITest):
     86   """Test of extensions."""
     87 
     88   def Debug(self):
     89     """Test method for experimentation.
     90 
     91     This method is not run automatically.
     92     """
     93     while True:
     94       raw_input('Interact with the browser and hit <enter> to dump history.')
     95       print '*' * 20
     96       self.pprint(self.GetExtensionsInfo())
     97 
     98   def _GetInstalledExtensionIds(self):
     99     return [extension['id'] for extension in self.GetExtensionsInfo()]
    100 
    101   def _ReturnCrashingExtensions(self, extensions, group_size, top_urls):
    102     """Returns the group of extensions that crashes (if any).
    103 
    104     Install the given extensions in groups of group_size and return the
    105     group of extensions that crashes (if any).
    106 
    107     Args:
    108       extensions: A list of extensions to install.
    109       group_size: The number of extensions to install at one time.
    110       top_urls: The list of top urls to visit.
    111 
    112     Returns:
    113       The extensions in the crashing group or None if there is no crash.
    114     """
    115     curr_extension = 0
    116     num_extensions = len(extensions)
    117     self.RestartBrowser()
    118     orig_extension_ids = self._GetInstalledExtensionIds()
    119 
    120     while curr_extension < num_extensions:
    121       logging.debug('New group of %d extensions.', group_size)
    122       group_end = curr_extension + group_size
    123       for extension in extensions[curr_extension:group_end]:
    124         logging.debug('Installing extension: %s', extension)
    125         self.InstallExtension(extension)
    126 
    127       for url in top_urls:
    128         self.NavigateToURL(url)
    129 
    130       def _LogAndReturnCrashing():
    131         crashing_extensions = extensions[curr_extension:group_end]
    132         logging.debug('Crashing extensions: %s', crashing_extensions)
    133         return crashing_extensions
    134 
    135       # If the browser has crashed, return the extensions in the failing group.
    136       try:
    137         num_browser_windows = self.GetBrowserWindowCount()
    138       except:
    139         return _LogAndReturnCrashing()
    140       else:
    141         if not num_browser_windows:
    142           return _LogAndReturnCrashing()
    143         else:
    144           # Uninstall all extensions that aren't installed by default.
    145           new_extension_ids = [id for id in self._GetInstalledExtensionIds()
    146                                if id not in orig_extension_ids]
    147           for extension_id in new_extension_ids:
    148             self.UninstallExtensionById(extension_id)
    149 
    150       curr_extension = group_end
    151 
    152     # None of the extensions crashed.
    153     return None
    154 
    155   def _GetExtensionInfoById(self, extensions, id):
    156     for x in extensions:
    157       if x['id'] == id:
    158         return x
    159     return None
    160 
    161   def ExtensionCrashes(self):
    162     """Add top extensions; confirm browser stays up when visiting top urls."""
    163     # TODO: provide a way in pyauto to pass args to a test - take these as args
    164     extensions_dir = os.path.join(self.DataDir(), 'extensions-tool')
    165     urls_file = os.path.join(self.DataDir(), 'urls.txt')
    166 
    167     error_msg = 'The dir "%s" must exist' % os.path.abspath(extensions_dir)
    168     assert os.path.exists(extensions_dir), error_msg
    169     error_msg = 'The file "%s" must exist' % os.path.abspath(urls_file)
    170     assert os.path.exists(urls_file), error_msg
    171 
    172     num_urls_to_visit = 100
    173     extensions_group_size = 20
    174 
    175     top_urls = [l.rstrip() for l in
    176                 open(urls_file).readlines()[:num_urls_to_visit]]
    177 
    178     failed_extensions = glob.glob(os.path.join(extensions_dir, '*.crx'))
    179     group_size = extensions_group_size
    180 
    181     while (group_size and failed_extensions):
    182       failed_extensions = self._ReturnCrashingExtensions(
    183           failed_extensions, group_size, top_urls)
    184       group_size = group_size // 2
    185 
    186     self.assertFalse(failed_extensions,
    187                      'Extension(s) in failing group: %s' % failed_extensions)
    188 
    189   def _InstallExtensionCheckDefaults(self, crx_file):
    190     """Installs extension at extensions/|crx_file| and checks default status.
    191 
    192     Checks that the installed extension is enabled and not allowed in incognito.
    193 
    194     Args:
    195       crx_file: Relative path from self.DataDir()/extensions to .crx extension
    196                 to be installed.
    197 
    198     Returns:
    199       The extension ID.
    200     """
    201     crx_file_path = os.path.abspath(
    202         os.path.join(self.DataDir(), 'extensions', crx_file))
    203     ext_id = self.InstallExtension(crx_file_path)
    204     extension = self._GetExtensionInfoById(self.GetExtensionsInfo(), ext_id)
    205     self.assertTrue(extension['is_enabled'],
    206                     msg='Extension was not enabled on installation')
    207     self.assertFalse(extension['allowed_in_incognito'],
    208                      msg='Extension was allowed in incognito on installation.')
    209 
    210     return ext_id
    211 
    212   def _ExtensionValue(self, ext_id, key):
    213     """Returns the value of |key| for |ext_id|.
    214 
    215     Args:
    216       ext_id: The extension ID.
    217       key: The key for which the extensions info value is required.
    218 
    219     Returns:
    220       The value of extensions info |key| for |ext_id|.
    221     """
    222     return self._GetExtensionInfoById(self.GetExtensionsInfo(), ext_id)[key]
    223 
    224   def _FileAccess(self, ext_id):
    225     """Returns the value of newAllowFileAccess for |ext_id|.
    226 
    227     Args:
    228       ext_id: The extension ID.
    229 
    230     Returns:
    231       The value of extensions settings newAllowFileAccess for |ext_id|.
    232     """
    233     extension_settings = self.GetPrefsInfo().Prefs()['extensions']['settings']
    234     return extension_settings[ext_id]['newAllowFileAccess']
    235 
    236   def testGetExtensionPermissions(self):
    237     """Ensures we can retrieve the host/api permissions for an extension.
    238 
    239     This test assumes that the 'Bookmark Manager' extension exists in a fresh
    240     profile.
    241     """
    242     extensions_info = self.GetExtensionsInfo()
    243     bm_exts = [x for x in extensions_info if x['name'] == 'Bookmark Manager']
    244     self.assertTrue(bm_exts,
    245                     msg='Could not find info for the Bookmark Manager '
    246                     'extension.')
    247     ext = bm_exts[0]
    248 
    249     permissions_host = ext['host_permissions']
    250     self.assertTrue(len(permissions_host) == 2 and
    251                     'chrome://favicon/*' in permissions_host and
    252                     'chrome://resources/*' in permissions_host,
    253                     msg='Unexpected host permissions information.')
    254 
    255     permissions_api = ext['api_permissions']
    256     print permissions_api
    257     self.assertTrue(len(permissions_api) == 5 and
    258                     'bookmarks' in permissions_api and
    259                     'bookmarkManagerPrivate' in permissions_api and
    260                     'metricsPrivate' in permissions_api and
    261                     'systemPrivate' in permissions_api and
    262                     'tabs' in permissions_api,
    263                     msg='Unexpected API permissions information.')
    264 
    265   def testDisableEnableExtension(self):
    266     """Tests that an extension can be disabled and enabled with the UI."""
    267     ext_id = self._InstallExtensionCheckDefaults('good.crx')
    268 
    269     # Disable extension.
    270     driver = self.NewWebDriver()
    271     ext_page = ExtensionsPage(driver)
    272     self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id])
    273     ext_page.SetEnabled(ext_id, False)
    274     self.WaitUntil(self._ExtensionValue, args=[ext_id, 'is_enabled'],
    275                    expect_retval=False)
    276     self.assertFalse(self._ExtensionValue(ext_id, 'is_enabled'),
    277                      msg='Extension did not get disabled.')
    278 
    279     # Enable extension.
    280     self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id])
    281     ext_page.SetEnabled(ext_id, True)
    282     self.WaitUntil(self._ExtensionValue, args=[ext_id, 'is_enabled'],
    283                    expect_retval=True)
    284     self.assertTrue(self._ExtensionValue(ext_id, 'is_enabled'),
    285                     msg='Extension did not get enabled.')
    286 
    287   def testAllowIncognitoExtension(self):
    288     """Tests allowing and disallowing an extension in incognito mode."""
    289     ext_id = self._InstallExtensionCheckDefaults('good.crx')
    290 
    291     # Allow in incognito.
    292     driver = self.NewWebDriver()
    293     ext_page = ExtensionsPage(driver)
    294     self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id])
    295     ext_page.SetAllowInIncognito(ext_id, True)
    296 
    297     # Check extension now allowed in incognito.
    298     self.WaitUntil(self._ExtensionValue, args=[ext_id, 'allowed_in_incognito'],
    299                    expect_retval=True)
    300     self.assertTrue(self._ExtensionValue(ext_id, 'allowed_in_incognito'),
    301                     msg='Extension did not get allowed in incognito.')
    302 
    303     # Disallow in incognito.
    304     self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id])
    305     ext_page.SetAllowInIncognito(ext_id, False)
    306 
    307     # Check extension now disallowed in incognito.
    308     self.WaitUntil(self._ExtensionValue, args=[ext_id, 'allowed_in_incognito'],
    309                    expect_retval=False)
    310     self.assertFalse(self._ExtensionValue(ext_id, 'allowed_in_incognito'),
    311                      msg='Extension did not get disallowed in incognito.')
    312 
    313   def testAllowAccessFileURLs(self):
    314     """Tests disallowing and allowing and extension access to file URLs."""
    315     ext_id = self._InstallExtensionCheckDefaults(os.path.join('permissions',
    316                                                               'files'))
    317 
    318     # Check extension allowed access to file URLs by default.
    319     extension_settings = self.GetPrefsInfo().Prefs()['extensions']['settings']
    320     self.assertTrue(extension_settings[ext_id]['newAllowFileAccess'],
    321                     msg='Extension was not allowed access to file URLs on '
    322                     'installation')
    323 
    324     # Disallow access to file URLs.
    325     driver = self.NewWebDriver()
    326     ext_page = ExtensionsPage(driver)
    327     self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id])
    328     ext_page.SetAllowAccessFileURLs(ext_id, False)
    329 
    330     # Check that extension does not have access to file URLs.
    331     self.WaitUntil(self._FileAccess, args=[ext_id], expect_retval=False)
    332     self.assertFalse(self._FileAccess(ext_id),
    333                      msg='Extension did not have access to file URLs denied.')
    334 
    335     # Allow access to file URLs.
    336     self.WaitUntil(ext_page.CheckExtensionVisible, args=[ext_id])
    337     ext_page.SetAllowAccessFileURLs(ext_id, True)
    338 
    339     # Check that extension now has access to file URLs.
    340     self.WaitUntil(self._FileAccess, args=[ext_id], expect_retval=True)
    341     self.assertTrue(self._FileAccess(ext_id),
    342                     msg='Extension did not have access to file URLs granted.')
    343 
    344 
    345 if __name__ == '__main__':
    346   pyauto_functional.Main()
    347