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