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, os, re
6
7from autotest_lib.client.common_lib.cros import arc_util
8from autotest_lib.client.cros import constants
9from autotest_lib.client.bin import utils
10from telemetry.core import cros_interface, exceptions, util
11from telemetry.internal.browser import browser_finder, browser_options
12from telemetry.internal.browser import extension_to_load
13
14CAP_USERNAME = 'crosautotest@gmail.com'
15CAP_URL = ('https://sites.google.com/a/chromium.org/dev/chromium-os'
16           '/testing/cros-autotest/cap')
17
18Error = exceptions.Error
19
20def NormalizeEmail(username):
21    """Remove dots from username. Add @gmail.com if necessary.
22
23    TODO(achuith): Get rid of this when crbug.com/358427 is fixed.
24
25    @param username: username/email to be scrubbed.
26    """
27    parts = re.split('@', username)
28    parts[0] = re.sub('\.', '', parts[0])
29
30    if len(parts) == 1:
31        parts.append('gmail.com')
32    return '@'.join(parts)
33
34
35class Chrome(object):
36    """Wrapper for creating a telemetry browser instance with extensions.
37
38    The recommended way to use this class is to create the instance using the
39    with statement:
40
41    >>> with chrome.Chrome(...) as cr:
42    >>>     # Do whatever you need with cr.
43    >>>     pass
44
45    This will make sure all the clean-up functions are called.  If you really
46    need to use this class without the with statement, make sure to call the
47    close() method once you're done with the Chrome instance.
48    """
49
50
51    BROWSER_TYPE_LOGIN = 'system'
52    BROWSER_TYPE_GUEST = 'system-guest'
53
54
55    def __init__(self, logged_in=True, extension_paths=None, autotest_ext=False,
56                 num_tries=3, extra_browser_args=None,
57                 clear_enterprise_policy=True, expect_policy_fetch=False,
58                 dont_override_profile=False, disable_gaia_services=True,
59                 disable_default_apps = True, auto_login=True, gaia_login=False,
60                 username=None, password=None, gaia_id=None,
61                 arc_mode=None, disable_arc_opt_in=True,
62                 init_network_controller=False, login_delay=0):
63        """
64        Constructor of telemetry wrapper.
65
66        @param logged_in: Regular user (True) or guest user (False).
67        @param extension_paths: path of unpacked extension to install.
68        @param autotest_ext: Load a component extension with privileges to
69                             invoke chrome.autotestPrivate.
70        @param num_tries: Number of attempts to log in.
71        @param extra_browser_args: Additional argument(s) to pass to the
72                                   browser. It can be a string or a list.
73        @param clear_enterprise_policy: Clear enterprise policy before
74                                        logging in.
75        @param expect_policy_fetch: Expect that chrome can reach the device
76                                    management server and download policy.
77        @param dont_override_profile: Don't delete cryptohome before login.
78                                      Telemetry will output a warning with this
79                                      option.
80        @param disable_gaia_services: For enterprise autotests, this option may
81                                      be used to enable policy fetch.
82        @param disable_default_apps: For tests that exercise default apps.
83        @param auto_login: Does not login automatically if this is False.
84                           Useful if you need to examine oobe.
85        @param gaia_login: Logs in to real gaia.
86        @param username: Log in using this username instead of the default.
87        @param password: Log in using this password instead of the default.
88        @param gaia_id: Log in using this gaia_id instead of the default.
89        @param arc_mode: How ARC instance should be started.  Default is to not
90                         start.
91        @param disable_arc_opt_in: For opt in flow autotest. This option is used
92                                   to disable the arc opt in flow.
93        @param login_delay: Time for idle in login screen to simulate the time
94                            required for password typing.
95        """
96        self._autotest_ext_path = None
97
98        # Force autotest extension if we need enable Play Store.
99        if (utils.is_arc_available() and (arc_util.should_start_arc(arc_mode)
100            or not disable_arc_opt_in)):
101            autotest_ext = True
102
103        if extension_paths is None:
104            extension_paths = []
105
106        if autotest_ext:
107            self._autotest_ext_path = os.path.join(os.path.dirname(__file__),
108                                                   'autotest_private_ext')
109            extension_paths.append(self._autotest_ext_path)
110
111        finder_options = browser_options.BrowserFinderOptions()
112        if utils.is_arc_available() and arc_util.should_start_arc(arc_mode):
113            if disable_arc_opt_in:
114                finder_options.browser_options.AppendExtraBrowserArgs(
115                        arc_util.get_extra_chrome_flags())
116            logged_in = True
117
118        self._browser_type = (self.BROWSER_TYPE_LOGIN
119                if logged_in else self.BROWSER_TYPE_GUEST)
120        finder_options.browser_type = self.browser_type
121        if extra_browser_args:
122            finder_options.browser_options.AppendExtraBrowserArgs(
123                    extra_browser_args)
124
125        # finder options must be set before parse_args(), browser options must
126        # be set before Create().
127        # TODO(crbug.com/360890) Below MUST be '2' so that it doesn't inhibit
128        # autotest debug logs
129        finder_options.verbosity = 2
130        finder_options.CreateParser().parse_args(args=[])
131        b_options = finder_options.browser_options
132        b_options.disable_component_extensions_with_background_pages = False
133        b_options.create_browser_with_oobe = True
134        b_options.clear_enterprise_policy = clear_enterprise_policy
135        b_options.dont_override_profile = dont_override_profile
136        b_options.disable_gaia_services = disable_gaia_services
137        b_options.disable_default_apps = disable_default_apps
138        b_options.disable_component_extensions_with_background_pages = disable_default_apps
139        b_options.disable_background_networking = False
140        b_options.expect_policy_fetch = expect_policy_fetch
141
142        b_options.auto_login = auto_login
143        b_options.gaia_login = gaia_login
144        b_options.login_delay = login_delay
145
146        if utils.is_arc_available() and not disable_arc_opt_in:
147            arc_util.set_browser_options_for_opt_in(b_options)
148
149        self.username = b_options.username if username is None else username
150        self.password = b_options.password if password is None else password
151        self.username = NormalizeEmail(self.username)
152        b_options.username = self.username
153        b_options.password = self.password
154        self.gaia_id = b_options.gaia_id if gaia_id is None else gaia_id
155        b_options.gaia_id = self.gaia_id
156
157        self.arc_mode = arc_mode
158
159        if logged_in:
160            extensions_to_load = b_options.extensions_to_load
161            for path in extension_paths:
162                extension = extension_to_load.ExtensionToLoad(
163                        path, self.browser_type)
164                extensions_to_load.append(extension)
165            self._extensions_to_load = extensions_to_load
166
167        # Turn on collection of Chrome coredumps via creation of a magic file.
168        # (Without this, Chrome coredumps are trashed.)
169        open(constants.CHROME_CORE_MAGIC_FILE, 'w').close()
170
171        self._browser_to_create = browser_finder.FindBrowser(
172            finder_options)
173        self._browser_to_create.SetUpEnvironment(b_options)
174        for i in range(num_tries):
175            try:
176                self._browser = self._browser_to_create.Create()
177                self._browser_pid = \
178                    cros_interface.CrOSInterface().GetChromePid()
179                if utils.is_arc_available():
180                    if disable_arc_opt_in:
181                        if arc_util.should_start_arc(arc_mode):
182                            arc_util.enable_play_store(self.autotest_ext, True)
183                    else:
184                        arc_util.opt_in(self.browser, self.autotest_ext)
185                    arc_util.post_processing_after_browser(self)
186                break
187            except exceptions.LoginException as e:
188                logging.error('Timed out logging in, tries=%d, error=%s',
189                              i, repr(e))
190                if i == num_tries-1:
191                    raise
192        if init_network_controller:
193          self._browser.platform.network_controller.Open()
194
195    def __enter__(self):
196        return self
197
198
199    def __exit__(self, *args):
200        self.close()
201
202
203    @property
204    def browser(self):
205        """Returns a telemetry browser instance."""
206        return self._browser
207
208
209    def get_extension(self, extension_path):
210        """Fetches a telemetry extension instance given the extension path."""
211        for ext in self._extensions_to_load:
212            if extension_path == ext.path:
213                return self.browser.extensions[ext]
214        return None
215
216
217    @property
218    def autotest_ext(self):
219        """Returns the autotest extension."""
220        return self.get_extension(self._autotest_ext_path)
221
222
223    @property
224    def login_status(self):
225        """Returns login status."""
226        ext = self.autotest_ext
227        if not ext:
228            return None
229
230        ext.ExecuteJavaScript('''
231            window.__login_status = null;
232            chrome.autotestPrivate.loginStatus(function(s) {
233              window.__login_status = s;
234            });
235        ''')
236        return ext.EvaluateJavaScript('window.__login_status')
237
238
239    def get_visible_notifications(self):
240        """Returns an array of visible notifications of Chrome.
241
242        For specific type of each notification, please refer to Chromium's
243        chrome/common/extensions/api/autotest_private.idl.
244        """
245        ext = self.autotest_ext
246        if not ext:
247            return None
248
249        ext.ExecuteJavaScript('''
250            window.__items = null;
251            chrome.autotestPrivate.getVisibleNotifications(function(items) {
252              window.__items  = items;
253            });
254        ''')
255        if ext.EvaluateJavaScript('window.__items') is None:
256            return None
257        return ext.EvaluateJavaScript('window.__items')
258
259
260    @property
261    def browser_type(self):
262        """Returns the browser_type."""
263        return self._browser_type
264
265
266    @staticmethod
267    def did_browser_crash(func):
268        """Runs func, returns True if the browser crashed, False otherwise.
269
270        @param func: function to run.
271
272        """
273        try:
274            func()
275        except Error:
276            return True
277        return False
278
279
280    @staticmethod
281    def wait_for_browser_restart(func, browser):
282        """Runs func, and waits for a browser restart.
283
284        @param func: function to run.
285
286        """
287        _cri = cros_interface.CrOSInterface()
288        pid = _cri.GetChromePid()
289        Chrome.did_browser_crash(func)
290        utils.poll_for_condition(lambda: pid != _cri.GetChromePid(), timeout=60)
291        browser.WaitForBrowserToComeUp()
292
293
294    def wait_for_browser_to_come_up(self):
295        """Waits for the browser to come up. This should only be called after a
296        browser crash.
297        """
298        def _BrowserReady(cr):
299            tabs = []  # Wrapper for pass by reference.
300            if self.did_browser_crash(
301                    lambda: tabs.append(cr.browser.tabs.New())):
302                return False
303            try:
304                tabs[0].Close()
305            except:
306                # crbug.com/350941
307                logging.error('Timed out closing tab')
308            return True
309        util.WaitFor(lambda: _BrowserReady(self), timeout=10)
310
311
312    def close(self):
313        """Closes the browser.
314        """
315        try:
316            if utils.is_arc_available():
317                arc_util.pre_processing_before_close(self)
318        finally:
319            # Calling platform.StopAllLocalServers() to tear down the telemetry
320            # server processes such as the one started by
321            # platform.SetHTTPServerDirectories().  Not calling this function
322            # will leak the process and may affect test results.
323            # (crbug.com/663387)
324            self._browser.platform.StopAllLocalServers()
325            self._browser.Close()
326            self._browser_to_create.CleanUpEnvironment()
327            self._browser.platform.network_controller.Close()
328