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