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 5import logging, os, re 6 7from autotest_lib.client.common_lib.cros import arc_common 8from autotest_lib.client.common_lib.cros import arc_util 9from autotest_lib.client.common_lib.cros import assistant_util 10from autotest_lib.client.cros import constants 11from autotest_lib.client.bin import utils 12from telemetry.core import cros_interface, exceptions, util 13from telemetry.internal.browser import browser_finder, browser_options 14from telemetry.internal.browser import extension_to_load 15 16Error = exceptions.Error 17 18def NormalizeEmail(username): 19 """Remove dots from username. Add @gmail.com if necessary. 20 21 TODO(achuith): Get rid of this when crbug.com/358427 is fixed. 22 23 @param username: username/email to be scrubbed. 24 """ 25 parts = re.split('@', username) 26 parts[0] = re.sub('\.', '', parts[0]) 27 28 if len(parts) == 1: 29 parts.append('gmail.com') 30 return '@'.join(parts) 31 32 33class Chrome(object): 34 """Wrapper for creating a telemetry browser instance with extensions. 35 36 The recommended way to use this class is to create the instance using the 37 with statement: 38 39 >>> with chrome.Chrome(...) as cr: 40 >>> # Do whatever you need with cr. 41 >>> pass 42 43 This will make sure all the clean-up functions are called. If you really 44 need to use this class without the with statement, make sure to call the 45 close() method once you're done with the Chrome instance. 46 """ 47 48 49 BROWSER_TYPE_LOGIN = 'system' 50 BROWSER_TYPE_GUEST = 'system-guest' 51 52 53 def __init__(self, logged_in=True, extension_paths=None, autotest_ext=False, 54 num_tries=3, extra_browser_args=None, 55 clear_enterprise_policy=True, expect_policy_fetch=False, 56 dont_override_profile=False, disable_gaia_services=True, 57 disable_default_apps=True, auto_login=True, gaia_login=False, 58 username=None, password=None, gaia_id=None, 59 arc_mode=None, disable_arc_opt_in=True, 60 disable_arc_opt_in_verification=True, 61 disable_app_sync=False, 62 disable_play_auto_install=False, 63 disable_locale_sync=True, 64 disable_play_store_auto_update=True, 65 enable_assistant=False, 66 enterprise_arc_test=False, 67 init_network_controller=False, 68 login_delay=0): 69 """ 70 Constructor of telemetry wrapper. 71 72 @param logged_in: Regular user (True) or guest user (False). 73 @param extension_paths: path of unpacked extension to install. 74 @param autotest_ext: Load a component extension with privileges to 75 invoke chrome.autotestPrivate. 76 @param num_tries: Number of attempts to log in. 77 @param extra_browser_args: Additional argument(s) to pass to the 78 browser. It can be a string or a list. 79 @param clear_enterprise_policy: Clear enterprise policy before 80 logging in. 81 @param expect_policy_fetch: Expect that chrome can reach the device 82 management server and download policy. 83 @param dont_override_profile: Don't delete cryptohome before login. 84 Telemetry will output a warning with this 85 option. 86 @param disable_gaia_services: For enterprise autotests, this option may 87 be used to enable policy fetch. 88 @param disable_default_apps: For tests that exercise default apps. 89 @param auto_login: Does not login automatically if this is False. 90 Useful if you need to examine oobe. 91 @param gaia_login: Logs in to real gaia. 92 @param username: Log in using this username instead of the default. 93 @param password: Log in using this password instead of the default. 94 @param gaia_id: Log in using this gaia_id instead of the default. 95 @param arc_mode: How ARC instance should be started. Default is to not 96 start. 97 @param disable_arc_opt_in: For opt in flow autotest. This option is used 98 to disable the arc opt in flow. 99 @param disable_arc_opt_in_verification: 100 Adds --disable-arc-opt-in-verification to browser args. This should 101 generally be enabled when disable_arc_opt_in is enabled. However, 102 for data migration tests where user's home data is already set up 103 with opted-in state before login, this option needs to be set to 104 False with disable_arc_opt_in=True to make ARC container work. 105 @param disable_app_sync: 106 Adds --arc-disable-app-sync to browser args and this disables ARC 107 app sync flow. By default it is enabled. 108 @param disable_play_auto_install: 109 Adds --arc-disable-play-auto-install to browser args and this 110 disables ARC Play Auto Install flow. By default it is enabled. 111 @param enterprise_arc_test: Skips opt_in causing enterprise tests to fail 112 @param disable_locale_sync: 113 Adds --arc-disable-locale-sync to browser args and this 114 disables locale sync between Chrome and Android container. In case 115 of disabling sync, Android container is started with language and 116 preference language list as it was set on the moment of starting 117 full instance. Used to prevent random app restarts caused by racy 118 locale change, coming from profile sync. By default locale sync is 119 disabled. 120 @param disable_play_store_auto_update: 121 Adds --arc-play-store-auto-update=off to browser args and this 122 disables Play Store, GMS Core and third-party apps auto-update. 123 By default auto-update is off to have stable autotest environment. 124 @param login_delay: Time for idle in login screen to simulate the time 125 required for password typing. 126 @param enable_assistant: For tests that require to enable Google 127 Assistant service. Default is False. 128 """ 129 self._autotest_ext_path = None 130 131 # Force autotest extension if we need enable Play Store. 132 if (utils.is_arc_available() and (arc_util.should_start_arc(arc_mode) 133 or not disable_arc_opt_in)): 134 autotest_ext = True 135 136 if extension_paths is None: 137 extension_paths = [] 138 139 if autotest_ext: 140 self._autotest_ext_path = os.path.join(os.path.dirname(__file__), 141 'autotest_private_ext') 142 extension_paths.append(self._autotest_ext_path) 143 144 finder_options = browser_options.BrowserFinderOptions() 145 if utils.is_arc_available() and arc_util.should_start_arc(arc_mode): 146 if disable_arc_opt_in and disable_arc_opt_in_verification: 147 finder_options.browser_options.AppendExtraBrowserArgs( 148 ['--disable-arc-opt-in-verification']) 149 if disable_app_sync: 150 finder_options.browser_options.AppendExtraBrowserArgs( 151 ['--arc-disable-app-sync']) 152 if disable_play_auto_install: 153 finder_options.browser_options.AppendExtraBrowserArgs( 154 ['--arc-disable-play-auto-install']) 155 if disable_locale_sync: 156 finder_options.browser_options.AppendExtraBrowserArgs( 157 ['--arc-disable-locale-sync']) 158 if disable_play_store_auto_update: 159 finder_options.browser_options.AppendExtraBrowserArgs( 160 ['--arc-play-store-auto-update=off']) 161 logged_in = True 162 163 self._browser_type = (self.BROWSER_TYPE_LOGIN 164 if logged_in else self.BROWSER_TYPE_GUEST) 165 finder_options.browser_type = self.browser_type 166 if extra_browser_args: 167 finder_options.browser_options.AppendExtraBrowserArgs( 168 extra_browser_args) 169 170 # finder options must be set before parse_args(), browser options must 171 # be set before Create(). 172 # TODO(crbug.com/360890) Below MUST be '2' so that it doesn't inhibit 173 # autotest debug logs 174 finder_options.verbosity = 2 175 finder_options.CreateParser().parse_args(args=[]) 176 b_options = finder_options.browser_options 177 b_options.disable_component_extensions_with_background_pages = False 178 b_options.create_browser_with_oobe = True 179 b_options.clear_enterprise_policy = clear_enterprise_policy 180 b_options.dont_override_profile = dont_override_profile 181 b_options.disable_gaia_services = disable_gaia_services 182 b_options.disable_default_apps = disable_default_apps 183 b_options.disable_component_extensions_with_background_pages = disable_default_apps 184 b_options.disable_background_networking = False 185 b_options.expect_policy_fetch = expect_policy_fetch 186 187 b_options.auto_login = auto_login 188 b_options.gaia_login = gaia_login 189 b_options.login_delay = login_delay 190 191 if utils.is_arc_available() and not disable_arc_opt_in: 192 arc_util.set_browser_options_for_opt_in(b_options) 193 194 self.username = b_options.username if username is None else username 195 self.password = b_options.password if password is None else password 196 self.username = NormalizeEmail(self.username) 197 b_options.username = self.username 198 b_options.password = self.password 199 self.gaia_id = b_options.gaia_id if gaia_id is None else gaia_id 200 b_options.gaia_id = self.gaia_id 201 202 self.arc_mode = arc_mode 203 204 if logged_in: 205 extensions_to_load = b_options.extensions_to_load 206 for path in extension_paths: 207 extension = extension_to_load.ExtensionToLoad( 208 path, self.browser_type) 209 extensions_to_load.append(extension) 210 self._extensions_to_load = extensions_to_load 211 212 # Turn on collection of Chrome coredumps via creation of a magic file. 213 # (Without this, Chrome coredumps are trashed.) 214 open(constants.CHROME_CORE_MAGIC_FILE, 'w').close() 215 216 self._browser_to_create = browser_finder.FindBrowser( 217 finder_options) 218 self._browser_to_create.SetUpEnvironment(b_options) 219 for i in range(num_tries): 220 try: 221 self._browser = self._browser_to_create.Create() 222 self._browser_pid = \ 223 cros_interface.CrOSInterface().GetChromePid() 224 if utils.is_arc_available(): 225 if disable_arc_opt_in: 226 if arc_util.should_start_arc(arc_mode): 227 arc_util.enable_play_store(self.autotest_ext, True) 228 else: 229 if not enterprise_arc_test: 230 wait_for_provisioning = \ 231 arc_mode != arc_common.ARC_MODE_ENABLED_ASYNC 232 arc_util.opt_in( 233 browser = self.browser, 234 autotest_ext = self.autotest_ext, 235 wait_for_provisioning = wait_for_provisioning) 236 arc_util.post_processing_after_browser(self) 237 if enable_assistant: 238 assistant_util.enable_assistant(self.autotest_ext) 239 break 240 except exceptions.LoginException as e: 241 logging.error('Timed out logging in, tries=%d, error=%s', 242 i, repr(e)) 243 if i == num_tries-1: 244 raise 245 if init_network_controller: 246 self._browser.platform.network_controller.Open() 247 248 def __enter__(self): 249 return self 250 251 252 def __exit__(self, *args): 253 self.close() 254 255 256 @property 257 def browser(self): 258 """Returns a telemetry browser instance.""" 259 return self._browser 260 261 262 def get_extension(self, extension_path): 263 """Fetches a telemetry extension instance given the extension path.""" 264 for ext in self._extensions_to_load: 265 if extension_path == ext.path: 266 return self.browser.extensions[ext] 267 return None 268 269 270 @property 271 def autotest_ext(self): 272 """Returns the autotest extension.""" 273 return self.get_extension(self._autotest_ext_path) 274 275 276 @property 277 def login_status(self): 278 """Returns login status.""" 279 ext = self.autotest_ext 280 if not ext: 281 return None 282 283 ext.ExecuteJavaScript(''' 284 window.__login_status = null; 285 chrome.autotestPrivate.loginStatus(function(s) { 286 window.__login_status = s; 287 }); 288 ''') 289 return utils.poll_for_condition( 290 lambda: ext.EvaluateJavaScript('window.__login_status'), 291 timeout=10) 292 293 294 def get_visible_notifications(self): 295 """Returns an array of visible notifications of Chrome. 296 297 For specific type of each notification, please refer to Chromium's 298 chrome/common/extensions/api/autotest_private.idl. 299 """ 300 ext = self.autotest_ext 301 if not ext: 302 return None 303 304 ext.ExecuteJavaScript(''' 305 window.__items = null; 306 chrome.autotestPrivate.getVisibleNotifications(function(items) { 307 window.__items = items; 308 }); 309 ''') 310 if ext.EvaluateJavaScript('window.__items') is None: 311 return None 312 return ext.EvaluateJavaScript('window.__items') 313 314 315 @property 316 def browser_type(self): 317 """Returns the browser_type.""" 318 return self._browser_type 319 320 321 @staticmethod 322 def did_browser_crash(func): 323 """Runs func, returns True if the browser crashed, False otherwise. 324 325 @param func: function to run. 326 327 """ 328 try: 329 func() 330 except Error: 331 return True 332 return False 333 334 335 @staticmethod 336 def wait_for_browser_restart(func, browser): 337 """Runs func, and waits for a browser restart. 338 339 @param func: function to run. 340 341 """ 342 _cri = cros_interface.CrOSInterface() 343 pid = _cri.GetChromePid() 344 Chrome.did_browser_crash(func) 345 utils.poll_for_condition(lambda: pid != _cri.GetChromePid(), timeout=60) 346 browser.WaitForBrowserToComeUp() 347 348 349 def wait_for_browser_to_come_up(self): 350 """Waits for the browser to come up. This should only be called after a 351 browser crash. 352 """ 353 def _BrowserReady(cr): 354 tabs = [] # Wrapper for pass by reference. 355 if self.did_browser_crash( 356 lambda: tabs.append(cr.browser.tabs.New())): 357 return False 358 try: 359 tabs[0].Close() 360 except: 361 # crbug.com/350941 362 logging.error('Timed out closing tab') 363 return True 364 util.WaitFor(lambda: _BrowserReady(self), timeout=10) 365 366 367 def close(self): 368 """Closes the browser. 369 """ 370 try: 371 if utils.is_arc_available(): 372 arc_util.pre_processing_before_close(self) 373 finally: 374 # Calling platform.StopAllLocalServers() to tear down the telemetry 375 # server processes such as the one started by 376 # platform.SetHTTPServerDirectories(). Not calling this function 377 # will leak the process and may affect test results. 378 # (crbug.com/663387) 379 self._browser.platform.StopAllLocalServers() 380 self._browser.Close() 381 self._browser_to_create.CleanUpEnvironment() 382 self._browser.platform.network_controller.Close() 383