• 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.MULTIMEDIA_TEST_EXTENSION],
139            'extra_browser_args': self.EXTRA_BROWSER_ARGS,
140            'clear_enterprise_policy': not restart,
141            'arc_mode': arc_mode,
142            'autotest_ext': True
143        }
144        if extra_browser_args:
145            kwargs['extra_browser_args'] += extra_browser_args
146        return self.start_custom_chrome(kwargs)
147
148
149    def __enter__(self):
150        return self
151
152
153    def __exit__(self, *args):
154        if self._chrome:
155            self._chrome.close()
156            self._chrome = None
157
158
159    @staticmethod
160    def _generate_tab_descriptor(tab):
161        """Generate tab descriptor by tab object.
162
163        @param tab: the tab object.
164        @return a str, the tab descriptor of the tab.
165
166        """
167        return hex(id(tab))
168
169
170    def clean_unexpected_tabs(self):
171        """Clean all tabs that are not opened by facade_resource
172
173        It is used to make sure our chrome browser is clean.
174
175        """
176        # If they have the same length we can assume there is no unexpected
177        # tabs.
178        browser_tabs = self.get_tabs()
179        if len(browser_tabs) == len(self._tabs):
180            return
181
182        for tab in browser_tabs:
183            if self._generate_tab_descriptor(tab) not in self._tabs:
184                # TODO(mojahsu): Reevaluate this code. crbug.com/719592
185                try:
186                    tab.Close()
187                except py_utils.TimeoutException:
188                    logging.warn('close tab timeout %r, %s', tab, tab.url)
189
190
191    @retry_chrome_call
192    def get_extension(self, extension_path=None):
193        """Gets the extension from the indicated path.
194
195        @param extension_path: the path of the target extension.
196                               Set to None to get autotest extension.
197                               Defaults to None.
198        @return an extension object.
199
200        @raise RuntimeError if the extension is not found.
201        @raise chrome.Error if the found extension has not yet been
202               retrieved succesfully.
203
204        """
205        try:
206            if extension_path is None:
207                extension = self._chrome.autotest_ext
208            else:
209                extension = self._chrome.get_extension(extension_path)
210        except KeyError, errmsg:
211            # Trigger retry_chrome_call to retry to retrieve the
212            # found extension.
213            raise chrome.Error(errmsg)
214        if not extension:
215            if extension_path is None:
216                raise RuntimeError('Autotest extension not found')
217            else:
218                raise RuntimeError('Extension not found in %r'
219                                    % extension_path)
220        return extension
221
222
223    @retry_chrome_call
224    def load_url(self, url):
225        """Loads the given url in a new tab. The new tab will be active.
226
227        @param url: The url to load as a string.
228        @return a str, the tab descriptor of the opened tab.
229
230        """
231        tab = self._browser.tabs.New()
232        tab.Navigate(url)
233        tab.Activate()
234        tab.WaitForDocumentReadyStateToBeComplete()
235        tab_descriptor = self._generate_tab_descriptor(tab)
236        self._tabs[tab_descriptor] = tab
237        self.clean_unexpected_tabs()
238        return tab_descriptor
239
240
241    def get_tabs(self):
242        """Gets the tabs opened by browser.
243
244        @returns: The tabs attribute in telemetry browser object.
245
246        """
247        return self._browser.tabs
248
249
250    def get_tab_by_descriptor(self, tab_descriptor):
251        """Gets the tab by the tab descriptor.
252
253        @returns: The tab object indicated by the tab descriptor.
254
255        """
256        return self._tabs[tab_descriptor]
257
258
259    @retry_chrome_call
260    def close_tab(self, tab_descriptor):
261        """Closes the tab.
262
263        @param tab_descriptor: Indicate which tab to be closed.
264
265        """
266        if tab_descriptor not in self._tabs:
267            raise RuntimeError('There is no tab for %s' % tab_descriptor)
268        tab = self._tabs[tab_descriptor]
269        del self._tabs[tab_descriptor]
270        tab.Close()
271        self.clean_unexpected_tabs()
272
273
274    def wait_for_javascript_expression(
275            self, tab_descriptor, expression, timeout):
276        """Waits for the given JavaScript expression to be True on the given tab
277
278        @param tab_descriptor: Indicate on which tab to wait for the expression.
279        @param expression: Indiate for what expression to wait.
280        @param timeout: Indicate the timeout of the expression.
281        """
282        if tab_descriptor not in self._tabs:
283            raise RuntimeError('There is no tab for %s' % tab_descriptor)
284        self._tabs[tab_descriptor].WaitForJavaScriptCondition(
285                expression, timeout=timeout)
286
287
288    def execute_javascript(self, tab_descriptor, statement, timeout):
289        """Executes a JavaScript statement on the given tab.
290
291        @param tab_descriptor: Indicate on which tab to execute the statement.
292        @param statement: Indiate what statement to execute.
293        @param timeout: Indicate the timeout of the statement.
294        """
295        if tab_descriptor not in self._tabs:
296            raise RuntimeError('There is no tab for %s' % tab_descriptor)
297        self._tabs[tab_descriptor].ExecuteJavaScript(
298                statement, timeout=timeout)
299
300
301    def evaluate_javascript(self, tab_descriptor, expression, timeout):
302        """Evaluates a JavaScript expression on the given tab.
303
304        @param tab_descriptor: Indicate on which tab to evaluate the expression.
305        @param expression: Indiate what expression to evaluate.
306        @param timeout: Indicate the timeout of the expression.
307        @return the JSONized result of the given expression
308        """
309        if tab_descriptor not in self._tabs:
310            raise RuntimeError('There is no tab for %s' % tab_descriptor)
311        return self._tabs[tab_descriptor].EvaluateJavaScript(
312                expression, timeout=timeout)
313