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