xref: /aosp_15_r20/external/autotest/client/common_lib/cros/chrome.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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