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