• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 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
5"""A module providing common resources for different facades."""
6
7import exceptions
8import logging
9import time
10
11from autotest_lib.client.bin import utils
12from autotest_lib.client.common_lib.cros import chrome
13from autotest_lib.client.common_lib.cros import retry
14from autotest_lib.client.cros import constants
15
16import py_utils
17
18_FLAKY_CALL_RETRY_TIMEOUT_SEC = 60
19_FLAKY_CHROME_CALL_RETRY_DELAY_SEC = 1
20
21retry_chrome_call = retry.retry(
22        (chrome.Error, exceptions.IndexError, exceptions.Exception),
23        timeout_min=_FLAKY_CALL_RETRY_TIMEOUT_SEC / 60.0,
24        delay_sec=_FLAKY_CHROME_CALL_RETRY_DELAY_SEC)
25
26
27class FacadeResoureError(Exception):
28    """Error in FacadeResource."""
29    pass
30
31
32_FLAKY_CHROME_START_RETRY_TIMEOUT_SEC = 120
33_FLAKY_CHROME_START_RETRY_DELAY_SEC = 10
34
35
36# Telemetry sometimes fails to start Chrome.
37retry_start_chrome = retry.retry(
38        (Exception,),
39        timeout_min=_FLAKY_CHROME_START_RETRY_TIMEOUT_SEC / 60.0,
40        delay_sec=_FLAKY_CHROME_START_RETRY_DELAY_SEC,
41        exception_to_raise=FacadeResoureError,
42        label='Start Chrome')
43
44
45class FacadeResource(object):
46    """This class provides access to telemetry chrome wrapper."""
47
48    ARC_DISABLED = 'disabled'
49    ARC_ENABLED = 'enabled'
50    ARC_VERSION = 'CHROMEOS_ARC_VERSION'
51    EXTRA_BROWSER_ARGS = ['--enable-gpu-benchmarking', '--use-fake-ui-for-media-stream']
52
53    def __init__(self, chrome_object=None, restart=False):
54        """Initializes a FacadeResource.
55
56        @param chrome_object: A chrome.Chrome object or None.
57        @param restart: Preserve the previous browser state.
58
59        """
60        self._chrome = chrome_object
61
62    @property
63    def _browser(self):
64        """Gets the browser object from Chrome."""
65        return self._chrome.browser
66
67
68    @retry_start_chrome
69    def _start_chrome(self, kwargs):
70        """Start a Chrome with given arguments.
71
72        @param kwargs: A dict of keyword arguments passed to Chrome.
73
74        @return: A chrome.Chrome object.
75
76        """
77        logging.debug('Try to start Chrome with kwargs: %s', kwargs)
78        return chrome.Chrome(**kwargs)
79
80
81    def start_custom_chrome(self, kwargs):
82        """Start a custom Chrome with given arguments.
83
84        @param kwargs: A dict of keyword arguments passed to Chrome.
85
86        @return: True on success, False otherwise.
87
88        """
89        # Close the previous Chrome.
90        if self._chrome:
91            self._chrome.close()
92
93        # Start the new Chrome.
94        try:
95            self._chrome = self._start_chrome(kwargs)
96        except FacadeResoureError:
97            logging.error('Failed to start Chrome after retries')
98            return False
99        else:
100            logging.info('Chrome started successfully')
101
102        # The opened tabs are stored by tab descriptors.
103        # Key is the tab descriptor string.
104        # We use string as the key because of RPC Call. Client can use the
105        # string to locate the tab object.
106        # Value is the tab object.
107        self._tabs = dict()
108
109        # Workaround for issue crbug.com/588579.
110        # On daisy, Chrome freezes about 30 seconds after login because of
111        # TPM error. Avoid test accessing Chrome during this time.
112        # Check issue crbug.com/588579 and crbug.com/591646.
113        if utils.get_board() == 'daisy':
114            logging.warning('Delay 30s for issue 588579 on daisy')
115            time.sleep(30)
116
117        return True
118
119
120    def start_default_chrome(self, restart=False, extra_browser_args=None):
121        """Start the default Chrome.
122
123        @param restart: True to start Chrome without clearing previous state.
124        @param extra_browser_args: A list containing extra browser args passed
125                                   to Chrome. This list will be appened to
126                                   default EXTRA_BROWSER_ARGS.
127
128        @return: True on success, False otherwise.
129
130        """
131        # TODO: (crbug.com/618111) Add test driven switch for
132        # supporting arc_mode enabled or disabled. At this time
133        # if ARC build is tested, arc_mode is always enabled.
134        arc_mode = self.ARC_DISABLED
135        if utils.get_board_property(self.ARC_VERSION):
136            arc_mode = self.ARC_ENABLED
137        kwargs = {
138            'extension_paths': [constants.AUDIO_TEST_EXTENSION,
139                                constants.DISPLAY_TEST_EXTENSION],
140            'extra_browser_args': self.EXTRA_BROWSER_ARGS,
141            'clear_enterprise_policy': not restart,
142            'arc_mode': arc_mode,
143            'autotest_ext': True
144        }
145        if extra_browser_args:
146            kwargs['extra_browser_args'] += extra_browser_args
147        return self.start_custom_chrome(kwargs)
148
149
150    def __enter__(self):
151        return self
152
153
154    def __exit__(self, *args):
155        if self._chrome:
156            self._chrome.close()
157            self._chrome = None
158
159
160    @staticmethod
161    def _generate_tab_descriptor(tab):
162        """Generate tab descriptor by tab object.
163
164        @param tab: the tab object.
165        @return a str, the tab descriptor of the tab.
166
167        """
168        return hex(id(tab))
169
170
171    def clean_unexpected_tabs(self):
172        """Clean all tabs that are not opened by facade_resource
173
174        It is used to make sure our chrome browser is clean.
175
176        """
177        # If they have the same length we can assume there is no unexpected
178        # tabs.
179        browser_tabs = self.get_tabs()
180        if len(browser_tabs) == len(self._tabs):
181            return
182
183        for tab in browser_tabs:
184            if self._generate_tab_descriptor(tab) not in self._tabs:
185                # TODO(mojahsu): Reevaluate this code. crbug.com/719592
186                try:
187                    tab.Close()
188                except py_utils.TimeoutException:
189                    logging.warn('close tab timeout %r, %s', tab, tab.url)
190
191
192    @retry_chrome_call
193    def get_extension(self, extension_path=None):
194        """Gets the extension from the indicated path.
195
196        @param extension_path: the path of the target extension.
197                               Set to None to get autotest extension.
198                               Defaults to None.
199        @return an extension object.
200
201        @raise RuntimeError if the extension is not found.
202        @raise chrome.Error if the found extension has not yet been
203               retrieved succesfully.
204
205        """
206        try:
207            if extension_path is None:
208                extension = self._chrome.autotest_ext
209            else:
210                extension = self._chrome.get_extension(extension_path)
211        except KeyError, errmsg:
212            # Trigger retry_chrome_call to retry to retrieve the
213            # found extension.
214            raise chrome.Error(errmsg)
215        if not extension:
216            if extension_path is None:
217                raise RuntimeError('Autotest extension not found')
218            else:
219                raise RuntimeError('Extension not found in %r'
220                                    % extension_path)
221        return extension
222
223
224    def get_visible_notifications(self):
225        """Gets the visible notifications
226
227        @return: Returns all visible notifications in list format. Ex:
228                [{title:'', message:'', prority:'', id:''}]
229        """
230        return self._chrome.get_visible_notifications()
231
232
233    @retry_chrome_call
234    def load_url(self, url):
235        """Loads the given url in a new tab. The new tab will be active.
236
237        @param url: The url to load as a string.
238        @return a str, the tab descriptor of the opened tab.
239
240        """
241        tab = self._browser.tabs.New()
242        tab.Navigate(url)
243        tab.Activate()
244        tab.WaitForDocumentReadyStateToBeComplete()
245        tab_descriptor = self._generate_tab_descriptor(tab)
246        self._tabs[tab_descriptor] = tab
247        self.clean_unexpected_tabs()
248        return tab_descriptor
249
250
251    def set_http_server_directories(self, directories):
252        """Starts an HTTP server.
253
254        @param directories: Directories to start serving.
255
256        @return True on success. False otherwise.
257
258        """
259        return self._chrome.browser.platform.SetHTTPServerDirectories(directories)
260
261
262    def http_server_url_of(self, fullpath):
263        """Converts a path to a URL.
264
265        @param fullpath: String containing the full path to the content.
266
267        @return the URL for the provided path.
268
269        """
270        return self._chrome.browser.platform.http_server.UrlOf(fullpath)
271
272
273    def get_tabs(self):
274        """Gets the tabs opened by browser.
275
276        @returns: The tabs attribute in telemetry browser object.
277
278        """
279        return self._browser.tabs
280
281
282    def get_tab_by_descriptor(self, tab_descriptor):
283        """Gets the tab by the tab descriptor.
284
285        @returns: The tab object indicated by the tab descriptor.
286
287        """
288        return self._tabs[tab_descriptor]
289
290
291    @retry_chrome_call
292    def close_tab(self, tab_descriptor):
293        """Closes the tab.
294
295        @param tab_descriptor: Indicate which tab to be closed.
296
297        """
298        if tab_descriptor not in self._tabs:
299            raise RuntimeError('There is no tab for %s' % tab_descriptor)
300        tab = self._tabs[tab_descriptor]
301        del self._tabs[tab_descriptor]
302        tab.Close()
303        self.clean_unexpected_tabs()
304
305
306    def wait_for_javascript_expression(
307            self, tab_descriptor, expression, timeout):
308        """Waits for the given JavaScript expression to be True on the given tab
309
310        @param tab_descriptor: Indicate on which tab to wait for the expression.
311        @param expression: Indiate for what expression to wait.
312        @param timeout: Indicate the timeout of the expression.
313        """
314        if tab_descriptor not in self._tabs:
315            raise RuntimeError('There is no tab for %s' % tab_descriptor)
316        self._tabs[tab_descriptor].WaitForJavaScriptCondition(
317                expression, timeout=timeout)
318
319
320    def execute_javascript(self, tab_descriptor, statement, timeout):
321        """Executes a JavaScript statement on the given tab.
322
323        @param tab_descriptor: Indicate on which tab to execute the statement.
324        @param statement: Indiate what statement to execute.
325        @param timeout: Indicate the timeout of the statement.
326        """
327        if tab_descriptor not in self._tabs:
328            raise RuntimeError('There is no tab for %s' % tab_descriptor)
329        self._tabs[tab_descriptor].ExecuteJavaScript(
330                statement, timeout=timeout)
331
332
333    def evaluate_javascript(self, tab_descriptor, expression, timeout):
334        """Evaluates a JavaScript expression on the given tab.
335
336        @param tab_descriptor: Indicate on which tab to evaluate the expression.
337        @param expression: Indiate what expression to evaluate.
338        @param timeout: Indicate the timeout of the expression.
339        @return the JSONized result of the given expression
340        """
341        if tab_descriptor not in self._tabs:
342            raise RuntimeError('There is no tab for %s' % tab_descriptor)
343        return self._tabs[tab_descriptor].EvaluateJavaScript(
344                expression, timeout=timeout)
345