1# Lint as: python2, python3 2from __future__ import absolute_import 3from __future__ import division 4from __future__ import print_function 5 6import logging 7import os 8from six.moves import range 9import time 10 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.common_lib import utils 13 14 15class UIScreenshoter(object): 16 """Simple class to take screenshots within the ui_utils framework.""" 17 18 _SCREENSHOT_DIR_PATH = '/var/log/ui_utils' 19 _SCREENSHOT_BASENAME = 'ui-screenshot' 20 21 def __init__(self): 22 if not os.path.exists(self._SCREENSHOT_DIR_PATH): 23 os.mkdir(self._SCREENSHOT_DIR_PATH, 0o755) 24 self.screenshot_num = 0 25 26 def take_ss(self): 27 try: 28 utils.run('screenshot "{}/{}_iter{}.png"'.format( 29 self._SCREENSHOT_DIR_PATH, self._SCREENSHOT_BASENAME, 30 self.screenshot_num)) 31 self.screenshot_num += 1 32 except Exception as e: 33 logging.warning('Unable to capture screenshot. %s', e) 34 35class UI_Handler(object): 36 37 REGEX_ALL = '/(.*?)/' 38 39 PROMISE_TEMPLATE = \ 40 '''new Promise(function(resolve, reject) { 41 chrome.automation.getDesktop(function(root) { 42 resolve(%s); 43 }) 44 })''' 45 46 _GET_ON_SCREEN_ITEMS = "findAll({attributes:{role: 'staticText'},state:{" \ 47 "offscreen: false}}).map(node => node.name)" 48 49 def __init__(self): 50 self.screenshoter = UIScreenshoter() 51 52 def start_ui_root(self, cr): 53 """Start the UI root object for testing.""" 54 self.ext = cr.autotest_ext 55 56 def is_obj_restricted(self, name, isRegex=False, role=None): 57 """ 58 Return True if the object restriction is 'disabled'. 59 This usually means the button is either greyed out, locked, etc. 60 61 @param name: Parameter to provide to the 'name' attribute. 62 @param isRegex: bool, if the item is a regex. 63 @param role: Parameter to provide to the 'role' attribute. 64 65 """ 66 FindParams = self._get_FindParams_str(name=name, 67 role=role, 68 isRegex=isRegex) 69 try: 70 restriction = self.ext.EvaluateJavaScript( 71 self.PROMISE_TEMPLATE % ("%s.restriction" % FindParams), 72 promise=True) 73 except Exception: 74 raise error.TestError( 75 'Could not find object {}.'.format(name)) 76 if restriction == 'disabled': 77 return True 78 return False 79 80 def item_present(self, name, isRegex=False, flip=False, role=None): 81 """ 82 Determines if an object is present on the screen 83 84 @param name: Parameter to provide to the 'name' attribute. 85 @param isRegex: bool, if the 'name' is a regex. 86 @param flip: Flips the return status. 87 @param role: Parameter to provide to the 'role' attribute. 88 89 @returns: 90 True if object is present and flip is False. 91 False if object is present and flip is True. 92 False if object is not present and flip is False. 93 True if object is not present and flip is True. 94 95 """ 96 FindParams = self._get_FindParams_str(name=name, 97 role=role, 98 isRegex=isRegex) 99 100 if flip is True: 101 return not self._is_item_present(FindParams) 102 return self._is_item_present(FindParams) 103 104 def wait_for_ui_obj(self, 105 name, 106 isRegex=False, 107 remove=False, 108 role=None, 109 timeout=10): 110 """ 111 Waits for the UI object specified. 112 113 @param name: Parameter to provide to the 'name' attribute. 114 @param isRegex: bool, if the 'name' is a regex. 115 @param remove: bool, if you are waiting for the item to be removed. 116 @param role: Parameter to provide to the 'role' attribute. 117 @param timeout: int, time to wait for the item. 118 119 @raises error.TestError if the element is not loaded (or removed). 120 121 """ 122 try: 123 utils.poll_for_condition( 124 condition=lambda: self.item_present(name=name, 125 isRegex=isRegex, 126 flip=remove, 127 role=role), 128 timeout=timeout, 129 exception=error.TestError('{} did not load' 130 .format(name))) 131 except error.TestError: 132 self.screenshoter.take_ss() 133 logging.debug("CURRENT UI ITEMS VISIBLE {}".format( 134 self.list_screen_items())) 135 raise error.TestError('{} did not load'.format(name)) 136 137 def did_obj_not_load(self, name, isRegex=False, timeout=5): 138 """ 139 Specifically used to wait and see if an item appears on the UI. 140 141 NOTE: This is different from wait_for_ui_obj because that returns as 142 soon as the object is either loaded or not loaded. This function will 143 wait to ensure over the timeout period the object never loads. 144 Additionally it will return as soon as it does load. Basically a fancy 145 time.sleep() 146 147 @param name: Parameter to provide to the 'name' attribute. 148 @param isRegex: bool, if the item is a regex. 149 @param timeout: Time in seconds to wait for the object to appear. 150 151 @returns: True if object never loaded within the timeout period, 152 else False. 153 154 """ 155 t1 = time.time() 156 while time.time() - t1 < timeout: 157 if self.item_present(name=name, isRegex=isRegex): 158 return False 159 time.sleep(1) 160 return True 161 162 def doDefault_on_obj(self, name, isRegex=False, role=None): 163 """Runs the .doDefault() js command on the element.""" 164 FindParams = self._get_FindParams_str(name=name, 165 role=role, 166 isRegex=isRegex) 167 try: 168 self.ext.EvaluateJavaScript( 169 self.PROMISE_TEMPLATE % ("%s.doDefault()" % FindParams), 170 promise=True) 171 except: 172 logging.info('Unable to .doDefault() on {}. All items: {}' 173 .format(FindParams, self.list_screen_items())) 174 raise error.TestError("doDefault failed on {}".format(FindParams)) 175 176 def doCommand_on_obj(self, name, cmd, isRegex=False, role=None): 177 """Run the specified command on the element.""" 178 FindParams = self._get_FindParams_str(name=name, 179 role=role, 180 isRegex=isRegex) 181 return self.ext.EvaluateJavaScript(self.PROMISE_TEMPLATE % """ 182 %s.%s""" % (FindParams, cmd), promise=True) 183 184 def list_screen_items(self, 185 role=None, 186 name=None, 187 isRegex=False, 188 attr='name'): 189 190 """ 191 List all the items currently visable on the screen. 192 193 If no paramters are given, it will return the name of each item, 194 including items with empty names. 195 196 @param role: The role of the items to use (ie button). 197 @param name: Parameter to provide to the 'name' attribute. 198 @param isRegex: bool, if the obj is a regex. 199 @param attr: Str, the attribute you want returned in the list 200 (eg 'name'). 201 202 """ 203 204 if isRegex: 205 if name is None: 206 raise error.TestError('If regex is True name must be given') 207 name = self._format_obj(name, isRegex) 208 elif name is not None: 209 name = self._format_obj(name, isRegex) 210 name = self.REGEX_ALL if name is None else name 211 role = self.REGEX_ALL if role is None else self._format_obj(role, 212 False) 213 214 new_promise = self.PROMISE_TEMPLATE % """root.findAll({attributes: 215 {name: %s, role: %s}}).map(node => node.%s)""" % (name, role, attr) 216 return self.ext.EvaluateJavaScript(new_promise, promise=True) 217 218 def get_name_role_list(self, name=None, role=None): 219 """ 220 Return [{}, {}] containing the name/role of everything on screen. 221 222 """ 223 name = self.REGEX_ALL if name is None else name 224 role = self.REGEX_ALL if role is None else self._format_obj(role, 225 False) 226 227 new_promise = self.PROMISE_TEMPLATE % """root.findAll({attributes: 228 {name: %s, role: %s}}).map(node => 229 {return {name: node.name, role: node.role} })""" % (name, role) 230 231 return self.ext.EvaluateJavaScript(new_promise, promise=True) 232 233 def _format_obj(self, name, isRegex): 234 """ 235 Formats the object for use in the javascript name attribute. 236 237 When searching for an element on the UI, a regex expression or string 238 can be used. If the search is using a string, the obj will need to be 239 wrapped in quotes. A Regex is not. 240 241 @param name: Parameter to provide to the 'name' attribute. 242 @param isRegex: if True, the object will be returned as is, if False 243 the obj will be returned wrapped in quotes. 244 245 @returns: The formatted string for regex/name. 246 """ 247 if isRegex: 248 return name 249 else: 250 return '"{}"'.format(name) 251 252 def _get_FindParams_str(self, name, role, isRegex): 253 """Returns the FindParms string, so that automation node functions 254 can be run on it 255 256 @param role: The role of the items to use (ie button). 257 @param name: Parameter to provide to the 'name' attribute. 258 @param isRegex: bool, if the obj is a regex. 259 260 @returns: The ".find($FindParams)" string, which can be used to run 261 automation node commands, such as .doDefault() 262 263 """ 264 FINDPARAMS_BASE = """ 265 root.find({attributes: 266 {name: %s, 267 role: %s}} 268 )""" 269 270 name = self._format_obj(name, isRegex) 271 if role is None: 272 role = self.REGEX_ALL 273 else: 274 role = self._format_obj(role, False) 275 return (FINDPARAMS_BASE % (name, role)) 276 277 def _is_item_present(self, findParams): 278 """Return False if tempVar is None, else True.""" 279 item_present = self.ext.EvaluateJavaScript( 280 self.PROMISE_TEMPLATE % findParams, 281 promise=True) 282 if item_present is None: 283 return False 284 return True 285 286 def click_and_wait_for_item_with_retries(self, 287 item_to_click, 288 item_to_wait_for, 289 isRegex_click=False, 290 isRegex_wait=False, 291 click_role=None, 292 wait_role=None): 293 """ 294 Click on an item, and wait for a subsequent item to load. If the new 295 item does not load, we attempt to click the button again. 296 297 This being done to remove the corner case of button being visually 298 loaded, but not fully ready to be clicked yet. In simple terms: 299 Click button --> Check for next button to appear 300 IF button does not appear, its likely the original button click did 301 not work, thus reclick that button --> recheck for the next button. 302 303 If button did appear, stop clicking. 304 305 """ 306 self.doDefault_on_obj(item_to_click, 307 role=click_role, 308 isRegex=isRegex_click) 309 for retry in range(3): 310 try: 311 self.wait_for_ui_obj(item_to_wait_for, 312 role=wait_role, 313 isRegex=isRegex_wait, 314 timeout=6) 315 break 316 except error.TestError: 317 self.doDefault_on_obj(item_to_click, 318 role=click_role, 319 isRegex=isRegex_click) 320 else: 321 raise error.TestError('Item {} did not load after 2 tries'.format( 322 item_to_wait_for)) 323