1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import logging, os, re 6 7 from autotest_lib.client.common_lib.cros import arc_util 8 from autotest_lib.client.cros import constants 9 from autotest_lib.client.bin import utils 10 from telemetry.core import cros_interface, exceptions, util 11 from telemetry.internal.browser import browser_finder, browser_options 12 from telemetry.internal.browser import extension_to_load 13 14 CAP_USERNAME = 'crosautotest (at] gmail.com' 15 CAP_URL = ('https://sites.google.com/a/chromium.org/dev/chromium-os' 16 '/testing/cros-autotest/cap') 17 18 Error = exceptions.Error 19 20 def NormalizeEmail(username): 21 """Remove dots from username. Add @gmail.com if necessary. 22 23 TODO(achuith): Get rid of this when crbug.com/358427 is fixed. 24 25 @param username: username/email to be scrubbed. 26 """ 27 parts = re.split('@', username) 28 parts[0] = re.sub('\.', '', parts[0]) 29 30 if len(parts) == 1: 31 parts.append('gmail.com') 32 return '@'.join(parts) 33 34 35 class Chrome(object): 36 """Wrapper for creating a telemetry browser instance with extensions. 37 38 The recommended way to use this class is to create the instance using the 39 with statement: 40 41 >>> with chrome.Chrome(...) as cr: 42 >>> # Do whatever you need with cr. 43 >>> pass 44 45 This will make sure all the clean-up functions are called. If you really 46 need to use this class without the with statement, make sure to call the 47 close() method once you're done with the Chrome instance. 48 """ 49 50 51 BROWSER_TYPE_LOGIN = 'system' 52 BROWSER_TYPE_GUEST = 'system-guest' 53 54 55 def __init__(self, logged_in=True, extension_paths=[], autotest_ext=False, 56 num_tries=3, extra_browser_args=None, 57 clear_enterprise_policy=True, dont_override_profile=False, 58 disable_gaia_services=True, disable_default_apps = True, 59 auto_login=True, gaia_login=False, 60 username=None, password=None, gaia_id=None, 61 arc_mode=None, disable_arc_opt_in=True, 62 init_network_controller=False): 63 """ 64 Constructor of telemetry wrapper. 65 66 @param logged_in: Regular user (True) or guest user (False). 67 @param extension_paths: path of unpacked extension to install. 68 @param autotest_ext: Load a component extension with privileges to 69 invoke chrome.autotestPrivate. 70 @param num_tries: Number of attempts to log in. 71 @param extra_browser_args: Additional argument(s) to pass to the 72 browser. It can be a string or a list. 73 @param clear_enterprise_policy: Clear enterprise policy before 74 logging in. 75 @param dont_override_profile: Don't delete cryptohome before login. 76 Telemetry will output a warning with this 77 option. 78 @param disable_gaia_services: For enterprise autotests, this option may 79 be used to enable policy fetch. 80 @param disable_default_apps: For tests that exercise default apps. 81 @param auto_login: Does not login automatically if this is False. 82 Useful if you need to examine oobe. 83 @param gaia_login: Logs in to real gaia. 84 @param username: Log in using this username instead of the default. 85 @param password: Log in using this password instead of the default. 86 @param gaia_id: Log in using this gaia_id instead of the default. 87 @param arc_mode: How ARC instance should be started. Default is to not 88 start. 89 @param disable_arc_opt_in: For opt in flow autotest. This option is used 90 to disable the arc opt in flow. 91 """ 92 self._autotest_ext_path = None 93 if autotest_ext: 94 self._autotest_ext_path = os.path.join(os.path.dirname(__file__), 95 'autotest_private_ext') 96 extension_paths.append(self._autotest_ext_path) 97 98 finder_options = browser_options.BrowserFinderOptions() 99 if utils.is_arc_available() and arc_util.should_start_arc(arc_mode): 100 if disable_arc_opt_in: 101 finder_options.browser_options.AppendExtraBrowserArgs( 102 arc_util.get_extra_chrome_flags()) 103 logged_in = True 104 105 self._browser_type = (self.BROWSER_TYPE_LOGIN 106 if logged_in else self.BROWSER_TYPE_GUEST) 107 finder_options.browser_type = self.browser_type 108 if extra_browser_args: 109 finder_options.browser_options.AppendExtraBrowserArgs( 110 extra_browser_args) 111 112 # finder options must be set before parse_args(), browser options must 113 # be set before Create(). 114 # TODO(crbug.com/360890) Below MUST be '2' so that it doesn't inhibit 115 # autotest debug logs 116 finder_options.verbosity = 2 117 finder_options.CreateParser().parse_args(args=[]) 118 b_options = finder_options.browser_options 119 b_options.disable_component_extensions_with_background_pages = False 120 b_options.create_browser_with_oobe = True 121 b_options.clear_enterprise_policy = clear_enterprise_policy 122 b_options.dont_override_profile = dont_override_profile 123 b_options.disable_gaia_services = disable_gaia_services 124 b_options.disable_default_apps = disable_default_apps 125 b_options.disable_component_extensions_with_background_pages = disable_default_apps 126 127 b_options.auto_login = auto_login 128 b_options.gaia_login = gaia_login 129 130 if utils.is_arc_available() and not disable_arc_opt_in: 131 arc_util.set_browser_options_for_opt_in(b_options) 132 133 self.username = b_options.username if username is None else username 134 self.password = b_options.password if password is None else password 135 self.username = NormalizeEmail(self.username) 136 b_options.username = self.username 137 b_options.password = self.password 138 self.gaia_id = b_options.gaia_id if gaia_id is None else gaia_id 139 b_options.gaia_id = self.gaia_id 140 141 self.arc_mode = arc_mode 142 143 if logged_in: 144 extensions_to_load = b_options.extensions_to_load 145 for path in extension_paths: 146 extension = extension_to_load.ExtensionToLoad( 147 path, self.browser_type) 148 extensions_to_load.append(extension) 149 self._extensions_to_load = extensions_to_load 150 151 # Turn on collection of Chrome coredumps via creation of a magic file. 152 # (Without this, Chrome coredumps are trashed.) 153 open(constants.CHROME_CORE_MAGIC_FILE, 'w').close() 154 155 for i in range(num_tries): 156 try: 157 browser_to_create = browser_finder.FindBrowser(finder_options) 158 self._browser = browser_to_create.Create(finder_options) 159 if utils.is_arc_available(): 160 if disable_arc_opt_in: 161 if arc_util.should_start_arc(arc_mode): 162 arc_util.enable_arc_setting(self.browser) 163 else: 164 arc_util.opt_in(self.browser) 165 arc_util.post_processing_after_browser(self) 166 break 167 except exceptions.LoginException as e: 168 logging.error('Timed out logging in, tries=%d, error=%s', 169 i, repr(e)) 170 if i == num_tries-1: 171 raise 172 if init_network_controller: 173 self._browser.platform.network_controller.InitializeIfNeeded() 174 175 def __enter__(self): 176 return self 177 178 179 def __exit__(self, *args): 180 self.close() 181 182 183 @property 184 def browser(self): 185 """Returns a telemetry browser instance.""" 186 return self._browser 187 188 189 def get_extension(self, extension_path): 190 """Fetches a telemetry extension instance given the extension path.""" 191 for ext in self._extensions_to_load: 192 if extension_path == ext.path: 193 return self.browser.extensions[ext] 194 return None 195 196 197 @property 198 def autotest_ext(self): 199 """Returns the autotest extension.""" 200 return self.get_extension(self._autotest_ext_path) 201 202 203 @property 204 def login_status(self): 205 """Returns login status.""" 206 ext = self.autotest_ext 207 if not ext: 208 return None 209 210 ext.ExecuteJavaScript(''' 211 window.__login_status = null; 212 chrome.autotestPrivate.loginStatus(function(s) { 213 window.__login_status = s; 214 }); 215 ''') 216 return ext.EvaluateJavaScript('window.__login_status') 217 218 219 def get_visible_notifications(self): 220 """Returns an array of visible notifications of Chrome. 221 222 For specific type of each notification, please refer to Chromium's 223 chrome/common/extensions/api/autotest_private.idl. 224 """ 225 ext = self.autotest_ext 226 if not ext: 227 return None 228 229 ext.ExecuteJavaScript(''' 230 window.__items = null; 231 chrome.autotestPrivate.getVisibleNotifications(function(items) { 232 window.__items = items; 233 }); 234 ''') 235 if ext.EvaluateJavaScript('window.__items') is None: 236 return None 237 return ext.EvaluateJavaScript('window.__items') 238 239 240 @property 241 def browser_type(self): 242 """Returns the browser_type.""" 243 return self._browser_type 244 245 246 @staticmethod 247 def did_browser_crash(func): 248 """Runs func, returns True if the browser crashed, False otherwise. 249 250 @param func: function to run. 251 252 """ 253 try: 254 func() 255 except Error: 256 return True 257 return False 258 259 260 @staticmethod 261 def wait_for_browser_restart(func): 262 """Runs func, and waits for a browser restart. 263 264 @param func: function to run. 265 266 """ 267 _cri = cros_interface.CrOSInterface() 268 pid = _cri.GetChromePid() 269 Chrome.did_browser_crash(func) 270 utils.poll_for_condition(lambda: pid != _cri.GetChromePid(), timeout=60) 271 272 273 def wait_for_browser_to_come_up(self): 274 """Waits for the browser to come up. This should only be called after a 275 browser crash. 276 """ 277 def _BrowserReady(cr): 278 tabs = [] # Wrapper for pass by reference. 279 if self.did_browser_crash( 280 lambda: tabs.append(cr.browser.tabs.New())): 281 return False 282 try: 283 tabs[0].Close() 284 except: 285 # crbug.com/350941 286 logging.error('Timed out closing tab') 287 return True 288 util.WaitFor(lambda: _BrowserReady(self), timeout=10) 289 290 291 def close(self): 292 """Closes the browser. 293 """ 294 try: 295 if utils.is_arc_available(): 296 arc_util.pre_processing_before_close(self) 297 finally: 298 # Calling platform.StopAllLocalServers() to tear down the telemetry 299 # server processes such as the one started by 300 # platform.SetHTTPServerDirectories(). Not calling this function 301 # will leak the process and may affect test results. 302 # (crbug.com/663387) 303 self._browser.platform.StopAllLocalServers() 304 self._browser.Close() 305 self._browser.platform.network_controller.Close() 306