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