• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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