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