1# Lint as: python2, python3 2# Copyright 2015 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""A module providing common resources for different facades.""" 7 8import logging 9import time 10 11from autotest_lib.client.bin import utils 12from autotest_lib.client.common_lib import error 13from autotest_lib.client.common_lib.cros import chrome 14from autotest_lib.client.common_lib.cros import retry 15from autotest_lib.client.cros import constants 16from telemetry.internal.backends.chrome_inspector import devtools_http 17 18import py_utils 19 20_FLAKY_CALL_RETRY_TIMEOUT_SEC = 60 21_FLAKY_CHROME_CALL_RETRY_DELAY_SEC = 1 22 23retry_chrome_call = retry.retry( 24 (chrome.Error, IndexError, Exception), 25 timeout_min=_FLAKY_CALL_RETRY_TIMEOUT_SEC / 60.0, 26 delay_sec=_FLAKY_CHROME_CALL_RETRY_DELAY_SEC) 27 28 29class FacadeResoureError(Exception): 30 """Error in FacadeResource.""" 31 pass 32 33 34_FLAKY_CHROME_START_RETRY_TIMEOUT_SEC = 120 35_FLAKY_CHROME_START_RETRY_DELAY_SEC = 10 36 37 38# Telemetry sometimes fails to start Chrome. 39retry_start_chrome = retry.retry( 40 (Exception,), 41 timeout_min=_FLAKY_CHROME_START_RETRY_TIMEOUT_SEC / 60.0, 42 delay_sec=_FLAKY_CHROME_START_RETRY_DELAY_SEC, 43 exception_to_raise=FacadeResoureError, 44 label='Start Chrome') 45 46 47class FacadeResource(object): 48 """This class provides access to telemetry chrome wrapper.""" 49 50 ARC_DISABLED = 'disabled' 51 ARC_ENABLED = 'enabled' 52 ARC_VERSION = 'CHROMEOS_ARC_VERSION' 53 EXTRA_BROWSER_ARGS = ['--enable-gpu-benchmarking', '--use-fake-ui-for-media-stream'] 54 55 def __init__(self, chrome_object=None, restart=False): 56 """Initializes a FacadeResource. 57 58 @param chrome_object: A chrome.Chrome object or None. 59 @param restart: Preserve the previous browser state. 60 61 """ 62 self._chrome = chrome_object 63 64 @property 65 def _browser(self): 66 """Gets the browser object from Chrome.""" 67 return self._chrome.browser 68 69 70 @retry_start_chrome 71 def _start_chrome(self, kwargs): 72 """Start a Chrome with given arguments. 73 74 @param kwargs: A dict of keyword arguments passed to Chrome. 75 76 @return: A chrome.Chrome object. 77 78 """ 79 logging.debug('Try to start Chrome with kwargs: %s', kwargs) 80 return chrome.Chrome(**kwargs) 81 82 83 def start_custom_chrome(self, kwargs): 84 """Start a custom Chrome with given arguments. 85 86 @param kwargs: A dict of keyword arguments passed to Chrome. 87 88 @return: True on success, False otherwise. 89 90 """ 91 # Close the previous Chrome. 92 if self._chrome: 93 self._chrome.close() 94 95 # Start the new Chrome. 96 try: 97 self._chrome = self._start_chrome(kwargs) 98 except FacadeResoureError: 99 logging.error('Failed to start Chrome after retries') 100 return False 101 else: 102 logging.info('Chrome started successfully') 103 104 # The opened tabs are stored by tab descriptors. 105 # Key is the tab descriptor string. 106 # We use string as the key because of RPC Call. Client can use the 107 # string to locate the tab object. 108 # Value is the tab object. 109 self._tabs = dict() 110 111 # Workaround for issue crbug.com/588579. 112 # On daisy, Chrome freezes about 30 seconds after login because of 113 # TPM error. Avoid test accessing Chrome during this time. 114 # Check issue crbug.com/588579 and crbug.com/591646. 115 if utils.get_board() == 'daisy': 116 logging.warning('Delay 30s for issue 588579 on daisy') 117 time.sleep(30) 118 119 return True 120 121 122 def start_default_chrome(self, restart=False, extra_browser_args=None, 123 disable_arc=False): 124 """Start the default Chrome. 125 126 @param restart: True to start Chrome without clearing previous state. 127 @param extra_browser_args: A list containing extra browser args passed 128 to Chrome. This list will be appened to 129 default EXTRA_BROWSER_ARGS. 130 @param disable_arc: True to disable ARC++. 131 132 @return: True on success, False otherwise. 133 134 """ 135 # TODO: (crbug.com/618111) Add test driven switch for 136 # supporting arc_mode enabled or disabled. At this time 137 # if ARC build is tested, arc_mode is always enabled. 138 if not disable_arc and utils.get_board_property(self.ARC_VERSION): 139 arc_mode = self.ARC_ENABLED 140 else: 141 arc_mode = self.ARC_DISABLED 142 kwargs = { 143 'extension_paths': [constants.AUDIO_TEST_EXTENSION, 144 constants.DISPLAY_TEST_EXTENSION], 145 'extra_browser_args': self.EXTRA_BROWSER_ARGS, 146 'clear_enterprise_policy': not restart, 147 'arc_mode': arc_mode, 148 'autotest_ext': True 149 } 150 if extra_browser_args: 151 kwargs['extra_browser_args'] += extra_browser_args 152 return self.start_custom_chrome(kwargs) 153 154 155 def __enter__(self): 156 return self 157 158 159 def __exit__(self, *args): 160 if self._chrome: 161 self._chrome.close() 162 self._chrome = None 163 164 165 @staticmethod 166 def _generate_tab_descriptor(tab): 167 """Generate tab descriptor by tab object. 168 169 @param tab: the tab object. 170 @return a str, the tab descriptor of the tab. 171 172 """ 173 return hex(id(tab)) 174 175 176 def clean_unexpected_tabs(self): 177 """Clean all tabs that are not opened by facade_resource 178 179 It is used to make sure our chrome browser is clean. 180 181 """ 182 # If they have the same length we can assume there is no unexpected 183 # tabs. 184 browser_tabs = self.get_tabs() 185 if len(browser_tabs) == len(self._tabs): 186 return 187 188 for tab in browser_tabs: 189 if self._generate_tab_descriptor(tab) not in self._tabs: 190 # TODO(mojahsu): Reevaluate this code. crbug.com/719592 191 try: 192 tab.Close() 193 except py_utils.TimeoutException: 194 logging.warning('close tab timeout %r, %s', tab, tab.url) 195 196 197 @retry_chrome_call 198 def get_extension(self, extension_path=None): 199 """Gets the extension from the indicated path. 200 201 @param extension_path: the path of the target extension. 202 Set to None to get autotest extension. 203 Defaults to None. 204 @return an extension object. 205 206 @raise RuntimeError if the extension is not found. 207 @raise chrome.Error if the found extension has not yet been 208 retrieved succesfully. 209 210 """ 211 try: 212 if extension_path is None: 213 extension = self._chrome.autotest_ext 214 else: 215 extension = self._chrome.get_extension(extension_path) 216 except KeyError as errmsg: 217 # Trigger retry_chrome_call to retry to retrieve the 218 # found extension. 219 raise chrome.Error(errmsg) 220 if not extension: 221 if extension_path is None: 222 raise RuntimeError('Autotest extension not found') 223 else: 224 raise RuntimeError('Extension not found in %r' 225 % extension_path) 226 return extension 227 228 229 def get_visible_notifications(self): 230 """Gets the visible notifications 231 232 @return: Returns all visible notifications in list format. Ex: 233 [{title:'', message:'', prority:'', id:''}] 234 """ 235 return self._chrome.get_visible_notifications() 236 237 238 @retry_chrome_call 239 def load_url(self, url): 240 """Loads the given url in a new tab. The new tab will be active. 241 242 @param url: The url to load as a string. 243 @return a str, the tab descriptor of the opened tab. 244 245 """ 246 tab = self._browser.tabs.New() 247 tab.Navigate(url) 248 tab.Activate() 249 tab.WaitForDocumentReadyStateToBeComplete() 250 tab_descriptor = self._generate_tab_descriptor(tab) 251 self._tabs[tab_descriptor] = tab 252 self.clean_unexpected_tabs() 253 return tab_descriptor 254 255 256 def set_http_server_directories(self, directories): 257 """Starts an HTTP server. 258 259 @param directories: Directories to start serving. 260 261 @return True on success. False otherwise. 262 263 """ 264 return self._chrome.browser.platform.SetHTTPServerDirectories(directories) 265 266 267 def http_server_url_of(self, fullpath): 268 """Converts a path to a URL. 269 270 @param fullpath: String containing the full path to the content. 271 272 @return the URL for the provided path. 273 274 """ 275 return self._chrome.browser.platform.http_server.UrlOf(fullpath) 276 277 278 def get_tabs(self): 279 """Gets the tabs opened by browser. 280 281 @returns: The tabs attribute in telemetry browser object. 282 283 """ 284 return self._browser.tabs 285 286 287 def get_tab_by_descriptor(self, tab_descriptor): 288 """Gets the tab by the tab descriptor. 289 290 @returns: The tab object indicated by the tab descriptor. 291 292 """ 293 return self._tabs[tab_descriptor] 294 295 296 @retry_chrome_call 297 def close_tab(self, tab_descriptor): 298 """Closes the tab. 299 300 @param tab_descriptor: Indicate which tab to be closed. 301 302 """ 303 if tab_descriptor not in self._tabs: 304 raise RuntimeError('There is no tab for %s' % tab_descriptor) 305 tab = self._tabs[tab_descriptor] 306 del self._tabs[tab_descriptor] 307 tab.Close() 308 self.clean_unexpected_tabs() 309 310 311 def wait_for_javascript_expression( 312 self, tab_descriptor, expression, timeout): 313 """Waits for the given JavaScript expression to be True on the given tab 314 315 @param tab_descriptor: Indicate on which tab to wait for the expression. 316 @param expression: Indiate for what expression to wait. 317 @param timeout: Indicate the timeout of the expression. 318 """ 319 if tab_descriptor not in self._tabs: 320 raise RuntimeError('There is no tab for %s' % tab_descriptor) 321 self._tabs[tab_descriptor].WaitForJavaScriptCondition( 322 expression, timeout=timeout) 323 324 325 def execute_javascript(self, tab_descriptor, statement, timeout): 326 """Executes a JavaScript statement on the given tab. 327 328 @param tab_descriptor: Indicate on which tab to execute the statement. 329 @param statement: Indiate what statement to execute. 330 @param timeout: Indicate the timeout of the statement. 331 """ 332 if tab_descriptor not in self._tabs: 333 raise RuntimeError('There is no tab for %s' % tab_descriptor) 334 self._tabs[tab_descriptor].ExecuteJavaScript( 335 statement, timeout=timeout) 336 337 338 def evaluate_javascript(self, tab_descriptor, expression, timeout): 339 """Evaluates a JavaScript expression on the given tab. 340 341 @param tab_descriptor: Indicate on which tab to evaluate the expression. 342 @param expression: Indiate what expression to evaluate. 343 @param timeout: Indicate the timeout of the expression. 344 @return the JSONized result of the given expression 345 """ 346 if tab_descriptor not in self._tabs: 347 raise RuntimeError('There is no tab for %s' % tab_descriptor) 348 return self._tabs[tab_descriptor].EvaluateJavaScript( 349 expression, timeout=timeout) 350 351class Application(FacadeResource): 352 """ This class provides access to WebStore Applications""" 353 354 APP_NAME_IDS = { 355 'camera' : 'njfbnohfdkmbmnjapinfcopialeghnmh', 356 'files' : 'hhaomjibdihmijegdhdafkllkbggdgoj' 357 } 358 # Time in seconds to load the app 359 LOAD_TIME = 5 360 361 def __init__(self, chrome_object=None): 362 super(Application, self).__init__(chrome_object) 363 364 @retry_chrome_call 365 def evaluate_javascript(self, code): 366 """Executes javascript and returns some result. 367 368 Occasionally calls to EvaluateJavascript on the autotest_ext will fail 369 to find the extension. Instead of wrapping every call in a try/except, 370 calls will go through this function instead. 371 372 @param code: The javascript string to execute 373 374 """ 375 try: 376 result = self._chrome.autotest_ext.EvaluateJavaScript(code) 377 return result 378 except KeyError: 379 logging.exception('Could not find autotest_ext') 380 except (devtools_http.DevToolsClientUrlError, 381 devtools_http.DevToolsClientConnectionError): 382 logging.exception('Could not connect to DevTools') 383 384 raise error.TestError("Could not execute %s" % code) 385 386 def click_on(self, ui, name, isRegex=False, role=None): 387 """ 388 Click on given role and name matches 389 390 @ui: ui_utils object 391 @param name: item node name. 392 @param isRegex: If name is in regex format then isRegex should be 393 True otherwise False. 394 @param role: role of the element. Example: button or window etc. 395 @raise error.TestError if the test is failed to find given node 396 """ 397 if not ui.item_present(name, isRegex=isRegex, role=role): 398 raise error.TestError("name=%s, role=%s did not appeared with in " 399 "time" % (name, role)) 400 ui.doDefault_on_obj(name, isRegex=isRegex, role=role) 401 402 def is_app_opened(self, name): 403 """ 404 Verify if the Webstore app is opened or not 405 406 @param name: Name of the app to verify. 407 408 """ 409 self.evaluate_javascript("var isShown = null;") 410 is_app_shown_js = """ 411 chrome.autotestPrivate.isAppShown('%s', 412 function(appShown){isShown = appShown}); 413 """ % self.APP_NAME_IDS[name.lower()] 414 self.evaluate_javascript(is_app_shown_js) 415 return self.evaluate_javascript('isShown') 416 417 def launch_app(self, name): 418 """ 419 Launch the app/extension by its ID and verify that it opens. 420 421 @param name: Name of the app to launch. 422 423 """ 424 logging.info("Launching %s app" % name) 425 if name == "camera": 426 webapps_js = "chrome.autotestPrivate.waitForSystemWebAppsInstall(" \ 427 "function(){})" 428 self.evaluate_javascript(webapps_js) 429 launch_js = "chrome.autotestPrivate.launchSystemWebApp('%s', '%s', " \ 430 "function(){})" % ("Camera", 431 "chrome://camera-app/views/main.html") 432 else: 433 launch_js = "chrome.autotestPrivate.launchApp('%s', function(){})" \ 434 % self.APP_NAME_IDS[name.lower()] 435 self.evaluate_javascript(launch_js) 436 def is_app_opened(): 437 return self.is_app_opened(name) 438 utils.poll_for_condition(condition=is_app_opened, 439 desc="%s app is not launched" % name, 440 timeout=self.LOAD_TIME) 441 logging.info('%s app is launched', name) 442 443 def close_app(self, name): 444 """ 445 Close the app/extension by its ID and verify that it closes. 446 447 @param name: Name of the app to close. 448 449 """ 450 close_js = "chrome.autotestPrivate.closeApp('%s', function(){})" \ 451 % self.APP_NAME_IDS[name.lower()] 452 self.evaluate_javascript(close_js) 453 def is_app_closed(): 454 return not self.is_app_opened(name) 455 utils.poll_for_condition(condition=is_app_closed, 456 desc="%s app is not closed" % name, 457 timeout=self.LOAD_TIME) 458 logging.info('%s app is closed', name) 459