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