Home | History | Annotate | Download | only in cros
      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_common
      8 from autotest_lib.client.common_lib.cros import arc_util
      9 from autotest_lib.client.common_lib.cros import assistant_util
     10 from autotest_lib.client.cros import constants
     11 from autotest_lib.client.bin import utils
     12 from telemetry.core import cros_interface, exceptions, util
     13 from telemetry.internal.browser import browser_finder, browser_options
     14 from telemetry.internal.browser import extension_to_load
     15 
     16 Error = exceptions.Error
     17 
     18 def 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 
     33 class 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