def verify(self): '''verify UI element is still exist ''' flag = True if self.UIAElement == ctypes.POINTER( UIA.UIA_wrapper.IUIAutomationElement)(): flag = False try: UIAElement = self.UIAElement.FindFirst( UIA.UIA_wrapper.TreeScope_Element, UIA.IUIAutomation_object.CreateTrueCondition()) except _ctypes.COMError: flag = False UIAElement = ctypes.POINTER(UIA.UIA_wrapper.IUIAutomationElement)() if UIAElement == ctypes.POINTER( UIA.UIA_wrapper.IUIAutomationElement)(): flag = False if not flag: LOGGER.warn("Current UIAElement is no longer exist") return None return UIElement(UIAElement)
def _find_by_UIA(self, translated_identifier, scope=UIA.UIA_wrapper.TreeScope_Descendants): target_UIAElement = self.UIAElement.FindFirst(scope, translated_identifier) if target_UIAElement == ctypes.POINTER(UIA.UIA_wrapper.IUIAutomationElement)(): LOGGER.debug("Find no element matching identifier") return None return UIElement(target_UIAElement)
def __call__(self, *in_args): ''' For output value, use original value For input arguments: 1. If required argument is an enum, check if input argument fit requirement 2. If required argument is "POINTER(IUIAutomationElement)", we accept UIElement object, get required pointer object from UIElement, and send it to function 3. Other, no change ''' args = list(in_args) if len(self.args) != len(args): LOGGER.warn("Input arguments number not match expected") return None for index, expected_arg in enumerate(self.args): expected_arg_type = expected_arg[0] if expected_arg_type == "POINTER(IUIAutomationElement)": #get the UIAElment args[index] = args[index].UIAElement elif expected_arg_type in UIA.UIA_enums: #enum should be an int value, if argument is a string, should translate to int if args[index] in UIA.UIA_enums[expected_arg_type]: args[index] = UIA.UIA_enums[expected_arg_type][args[index]] if args[index] not in list( UIA.UIA_enums[expected_arg_type].values()): LOGGER.debug("Input argument not in expected value: %s", args[index]) return None return self.function_object(*args)
def start(self, **kwargs): ''' get root ready like get root element in windows UIA, get browser to target website must have a "browser_name" argument in kwargs to indicate which browser to use other kwargs are same as normal selenium webdrivers ''' if not "browser_name" in kwargs: LOGGER.error("Browser name not specified") raise DriverException("Browser name not specified") browser_name = kwargs["browser_name"] if not browser_name.upper() in self.support_browsers: LOGGER.error("Unsupported browser name: %s" , browser_name) raise DriverException("Unsupported browser name: %s" % browser_name) #remove browser_name key from kwargs del kwargs["browser_name"] #for ie browser, need to ignore zoom settings if browser_name.upper() == "IE": if "capabilities" in kwargs: #insert "ignoreZoomSetting" in driver capabilities caps = kwargs["capabilities"] caps["ignoreZoomSetting"] = True else: #add default capabilities caps = DesiredCapabilities.INTERNETEXPLORER caps["ignoreZoomSetting"] = True kwargs["capabilities"] = caps self.webdriver = self.support_browsers[browser_name.upper()](**kwargs)
def __init__(self, UIAElement, pattern_identifier): self.UIAElement = UIAElement self.pattern_object = UIA.get_pattern_by_id(UIAElement, pattern_identifier) if self.pattern_object is None: raise DriverException( "Cannot get pattern, stop init pattern object") self.methods = {} self.properties = {} interface_description = UIA.UIA_control_pattern_interfaces[ pattern_identifier] for member_description in interface_description: flag, name, args = _unpack(*member_description) #do a check, see if member exist in pattern object #if not, skip this member try: getattr(self.pattern_object, name) except AttributeError: LOGGER.debug("%s not exist in Pattern:%s", name, pattern_identifier) continue if flag == "method": self.methods[name] = args elif flag == "property": self.properties[name] = args else: raise DriverException("Unrecognised flag %s" % flag)
def image_compare(image1, image2, diff_image_name="diff.bmp"): '''compare two images, return difference percentage #code from http://rosettacode.org/wiki/Percentage_difference_between_images#Python ''' gen_diff_image = image_config.gen_diff_image diff_image_location = image_config.diff_image_location i1 = Image.open(image1) i2 = Image.open(image2) assert i1.mode == i2.mode, "Different kinds of images: %s VS %s" % (i1.mode, i2.mode) assert i1.size == i2.size, "Different sizes: %s, %s" % (i1.size, i2.size) #generate diff bitmap if gen_diff_image: diff = ImageChops.difference(i1, i2) diff_image_path = os.path.join(diff_image_location, diff_image_name) diff.save(diff_image_path) LOGGER.debug("Diff image save to: %s" % diff_image_path) #caculate the diff percentage pairs = zip(i1.getdata(), i2.getdata()) if len(i1.getbands()) == 1: # for gray-scale jpegs dif = sum((p1==p2) and 1 or 0 for p1,p2 in pairs) else: dif = sum((c1==c2) and 1 or 0 for p1,p2 in pairs for c1,c2 in zip(p1,p2)) ncomponents = i1.size[0] * i1.size[1] * 3 dif_percentage = float(dif) / ncomponents LOGGER.debug("Difference (percentage): %f" % dif_percentage) return dif_percentage
def input(self, *values): '''send keyboard input to UI element Keyboard input string: (1) For normal charactors like [0~9][a~z][A~Z], input directly (2) For special charactors like "space", "tab", "newline", "F1~F12" You use {key_name} to replace them, all support keys in "selenium/webdriver/common/keys " ''' translated_values = [] for value in values: #still support selenium Keys object if isinstance(value, Keys): translated_values.append(value) elif isinstance(value, int): translated_values.append(str(value)) elif isinstance(value, str): #handle special keys if re.match("^{.*}$", value) != None: key = value.lstrip("{").rstrip("}") try: key_value = getattr(Keys, key) except AttributeError as e: LOGGER.warning("Input special key not support: %s, skip this input" , key) else: translated_values.append(key_value) else: translated_values.append(value) self.selenium_element.send_keys(*translated_values)
def start(self, **kwargs): ''' get root ready like get root element in windows UIA, get browser to target website must have a "browser_name" argument in kwargs to indicate which browser to use other kwargs are same as normal selenium webdrivers ''' if not "browser_name" in kwargs: LOGGER.error("Browser name not specified") raise DriverException("Browser name not specified") browser_name = kwargs["browser_name"] if not browser_name.upper() in self.support_browsers: LOGGER.error("Unsupported browser name: %s", browser_name) raise DriverException("Unsupported browser name: %s" % browser_name) #remove browser_name key from kwargs del kwargs["browser_name"] #for ie browser, need to ignore zoom settings if browser_name.upper() == "IE": if "capabilities" in kwargs: #insert "ignoreZoomSetting" in driver capabilities caps = kwargs["capabilities"] caps["ignoreZoomSetting"] = True else: #add default capabilities caps = DesiredCapabilities.INTERNETEXPLORER caps["ignoreZoomSetting"] = True kwargs["capabilities"] = caps self.webdriver = self.support_browsers[browser_name.upper()](**kwargs)
def __call__(self, *in_args): ''' For output value, use original value For input arguments: 1. If required argument is an enum, check if input argument fit requirement 2. If required argument is "POINTER(IUIAutomationElement)", we accept UIElement object, get required pointer object from UIElement, and send it to function 3. Other, no change ''' args = list(in_args) if len(self.args) != len(args): LOGGER.warn("Input arguments number not match expected") return None for index, expected_arg in enumerate(self.args): expected_arg_type = expected_arg[0] if expected_arg_type == "POINTER(IUIAutomationElement)": #get the UIAElment args[index] = args[index].UIAElement elif expected_arg_type in UIA.UIA_enums: #enum should be an int value, if argument is a string, should translate to int if args[index] in UIA.UIA_enums[expected_arg_type]: args[index] = UIA.UIA_enums[expected_arg_type][args[index]] if args[index] not in list(UIA.UIA_enums[expected_arg_type].values()): LOGGER.debug("Input argument not in expected value: %s" , args[index]) return None return self.function_object(*args)
def get_pattern_by_id(UIAElement, pattern_identifier): '''get UIA element pattern by identifier, return None if fail Arguments: UIAElement: UIA Element instance pattern_identifier: pattern identifier Returns: pattern instance if success None if pattern_identifier not valid or not supported by UIA Element ''' if pattern_identifier in UIA_control_pattern_identifers_mapping: UIA_pattern_identifier = UIA_control_pattern_identifers_mapping[pattern_identifier][0] UIA_pattern_interface = UIA_control_pattern_identifers_mapping[pattern_identifier][1] pattern = UIAElement.GetCurrentPatternAs(UIA_pattern_identifier, UIA_pattern_interface._iid_) if pattern is None: LOGGER.debug("This pattern:%s is not supported by this UIAElment" , pattern_identifier) return None return ctypes.POINTER(UIA_pattern_interface)(pattern) ''' #Use GetCurrentPatternAs to check if get pattern success pattern = UIAElement.GetCurrentPattern(UIA_pattern_identifier).QueryInterface(UIA_pattern_interface) return pattern ''' else: LOGGER.debug("This pattern identifier is not support: %s, cannot get it from UIA typelib" , pattern_identifier) return None
def input(self, *values): '''send keyboard input to UI element Keyboard input string: (1) For normal charactors like [0~9][a~z][A~Z], input directly (2) For special charactors like "space", "tab", "newline", "F1~F12" You use {key_name} to replace them, all support keys in "selenium/webdriver/common/keys " ''' translated_values = [] for value in values: #still support selenium Keys object if isinstance(value, Keys): translated_values.append(value) elif isinstance(value, int): translated_values.append(str(value)) elif isinstance(value, str): #handle special keys if re.match("^{.*}$", value) != None: key = value.lstrip("{").rstrip("}") try: key_value = getattr(Keys, key) except AttributeError as e: LOGGER.warning( "Input special key not support: %s, skip this input", key) else: translated_values.append(key_value) else: translated_values.append(value) self.selenium_element.send_keys(*translated_values)
def get_pattern_by_id(UIAElement, pattern_identifier): '''get UIA element pattern by identifier, return None if fail Arguments: UIAElement: UIA Element instance pattern_identifier: pattern identifier Returns: pattern instance if success None if pattern_identifier not valid or not supported by UIA Element ''' if pattern_identifier in UIA_control_pattern_identifers_mapping: UIA_pattern_identifier = UIA_control_pattern_identifers_mapping[ pattern_identifier][0] UIA_pattern_interface = UIA_control_pattern_identifers_mapping[ pattern_identifier][1] pattern = UIAElement.GetCurrentPatternAs(UIA_pattern_identifier, UIA_pattern_interface._iid_) if pattern is None: LOGGER.debug("This pattern:%s is not supported by this UIAElment", pattern_identifier) return None return ctypes.POINTER(UIA_pattern_interface)(pattern) ''' #Use GetCurrentPatternAs to check if get pattern success pattern = UIAElement.GetCurrentPattern(UIA_pattern_identifier).QueryInterface(UIA_pattern_interface) return pattern ''' else: LOGGER.debug( "This pattern identifier is not support: %s, cannot get it from UIA typelib", pattern_identifier) return None
def find_elements(self, parsed_identifier): '''find all matched element root find should only find in the first level to avoid search in all UI ''' if parsed_identifier is None: translated_identifier = UIA.IUIAutomation_object.CreateTrueCondition( ) else: translated_identifier = Translater.ID_Translater( parsed_identifier).get_translated() if translated_identifier[ 0] == "Coordinate" or translated_identifier[0] == "Index": LOGGER.warn( "find_elements method not support find by Coordinate or find by Index" ) return [] else: translated_identifier = translated_identifier[1] scope = UIA.UIA_wrapper.TreeScope_Children UIAElementArray = self.UIAElement.FindAll(scope, translated_identifier) UIElements = [] for i in range(UIAElementArray.Length): UIElements.append(UIElement(UIAElementArray.GetElement(i))) return UIElements
def image_compare(image1, image2, diff_image_name="diff.bmp"): '''compare two images, return difference percentage #code from http://rosettacode.org/wiki/Percentage_difference_between_images#Python ''' gen_diff_image = image_config.gen_diff_image diff_image_location = image_config.diff_image_location i1 = Image.open(image1) i2 = Image.open(image2) assert i1.mode == i2.mode, "Different kinds of images: %s VS %s" % ( i1.mode, i2.mode) assert i1.size == i2.size, "Different sizes: %s, %s" % (i1.size, i2.size) #generate diff bitmap if gen_diff_image: diff = ImageChops.difference(i1, i2) diff_image_path = os.path.join(diff_image_location, diff_image_name) diff.save(diff_image_path) LOGGER.debug("Diff image save to: %s" % diff_image_path) #caculate the diff percentage pairs = zip(i1.getdata(), i2.getdata()) if len(i1.getbands()) == 1: # for gray-scale jpegs dif = sum((p1 == p2) and 1 or 0 for p1, p2 in pairs) else: dif = sum((c1 == c2) and 1 or 0 for p1, p2 in pairs for c1, c2 in zip(p1, p2)) ncomponents = i1.size[0] * i1.size[1] * 3 dif_percentage = float(dif) / ncomponents LOGGER.debug("Difference (percentage): %f" % dif_percentage) return dif_percentage
def gui_execute(self, command): '''execute gui command command like "app_map1.app_map2...element1.element2...operation [parameter1 parameter2 ...]" ''' (object_name_list, parameter_list) = gui_command_parser.parse(command, lexer=gui_command_lexer) object_= self._get_object_by_name_list(object_name_list) LOGGER.debug("GUI execute %s %s" , object_name_list, parameter_list) object_(*parameter_list)
def run(self): if self.type == "GUI": LOGGER.debug("run gui command: %s" , self.command) self.app_map.gui_execute(self.command) elif self.type == "CLI": LOGGER.debug("run cli command: %s" , self.command) self.app_map.cli_execute(self.command) else: raise ValueError("step type must be GUI or CLI, get: %s" % self.type)
def _translated_atomic_identifier(self, parsed_atomic_id): if parsed_atomic_id[0] in UIA.UIA_automation_element_property_identifers_mapping: return UIA.IUIAutomation_object.CreatePropertyCondition(UIA.UIA_automation_element_property_identifers_mapping[parsed_atomic_id[0]], parsed_atomic_id[1]) elif parsed_atomic_id[0] in UIA.UIA_control_pattern_property_identifiers_mapping: return UIA.IUIAutomation_object.CreatePropertyCondition(UIA.UIA_control_pattern_property_identifiers_mapping[parsed_atomic_id[0]], parsed_atomic_id[1]) else: #use no UIA identifier will be skipped LOGGER.warn("identifier: %s not in UIA property maps" , parsed_atomic_id[0]) return None
def drag_drop(self, abs_source_coords, abs_dest_coords): '''Move: move mouse from source_coords to dest_coords mouse drag drop is not related with UI element so need use abs coords ''' LOGGER.debug("Mouse drag drop from: %s to %s" , repr(abs_source_coords), repr(abs_dest_coords)) self.UIElement.set_focus() SendMouseInput(abs_source_coords, button_down=True, button_up=False) self.Move(abs_source_coords, abs_dest_coords) SendMouseInput(abs_dest_coords, button_down=False, button_up=True)
def gui_execute(self, command): '''execute gui command command like "app_map1.app_map2...element1.element2...operation [parameter1 parameter2 ...]" ''' (object_name_list, parameter_list) = gui_command_parser.parse(command, lexer=gui_command_lexer) object_ = self._get_object_by_name_list(object_name_list) LOGGER.debug("GUI execute %s %s", object_name_list, parameter_list) object_(*parameter_list)
def drag_drop(self, abs_source_coords, abs_dest_coords): '''Move: move mouse from source_coords to dest_coords mouse drag drop is not related with UI element so need use abs coords ''' LOGGER.debug("Mouse drag drop from: %s to %s", repr(abs_source_coords), repr(abs_dest_coords)) self.UIElement.set_focus() SendMouseInput(abs_source_coords, button_down=True, button_up=False) self.Move(abs_source_coords, abs_dest_coords) SendMouseInput(abs_dest_coords, button_down=False, button_up=True)
def verification(self): '''verify the app map xml using according schema, need pyxb module ''' try: import pyxb except ImportError: LOGGER.debug("pyxb not install, skip app map verification") return from .validate import check_app_map check_app_map(core_config.query_schema_file("AXUI_app_map.xsd"), self.app_map_xml)
def _find_by_UIA(self, translated_identifier, scope=UIA.UIA_wrapper.TreeScope_Descendants): target_UIAElement = self.UIAElement.FindFirst(scope, translated_identifier) if target_UIAElement == ctypes.POINTER( UIA.UIA_wrapper.IUIAutomationElement)(): LOGGER.debug("Find no element matching identifier") return None return UIElement(target_UIAElement)
def find_element(self, parsed_identifier): ''' find the first child UI element via identifier, return one UIAElement if success, return None if not find ''' translated_identifier = Translater.ID_Translater(parsed_identifier).get_translated() try: selenium_element = self.selenium_element.find_element(by=translated_identifier[0], value=translated_identifier[1]) except NoSuchElementException: LOGGER.debug("Cannot find target element") return None else: return UIElement(selenium_element)
def set_focus(self): '''set foucs this element Will bring this element to the front, used by Keyboard, Mouse, Touch Arguments: Returns: ''' try: self.UIAElement.SetFocus() except _ctypes.COMError: LOGGER.warn("SetFocus fail on current element, maybe due to this element not support SetFocus")
def get_driver(): '''get driver Return: return driver module selected in config ''' try: import sys, os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) driver = __import__(driver_config.driver_used + "_driver") except ImportError as e: LOGGER.error("Driver load error: %s" % driver_config.driver_used) raise e return driver
def get_driver(): '''get driver Return: return driver module selected in config ''' try: import sys, os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) driver = __import__(driver_config.driver_used+"_driver") except ImportError as e: LOGGER.error("Driver load error: %s" % driver_config.driver_used) raise e return driver
def move(self, abs_source_coords, abs_dest_coords): '''Move: move mouse from source_coords to dest_coords mouse move is not related with UI element so need use abs coords ''' import time import random x_range = abs(abs_source_coords[0] - abs_dest_coords[0]) y_range = abs(abs_source_coords[1] - abs_dest_coords[1]) x_sample_size = x_range / 10 y_sample_size = y_range / 10 #choose the bigger one sample_size = x_sample_size > y_sample_size and x_sample_size or y_sample_size #build population if abs_source_coords[0] < abs_dest_coords[0]: x_population = list(range(abs_source_coords[0], abs_dest_coords[0])) else: x_population = list(range(abs_dest_coords[0], abs_source_coords[0])) while len(x_population) < sample_size: x_population = x_population * 2 if abs_source_coords[1] < abs_dest_coords[1]: y_population = list(range(abs_source_coords[1], abs_dest_coords[1])) else: y_population = list(range(abs_dest_coords[1], abs_source_coords[1])) while len(y_population) < sample_size: y_population = y_population * 2 #get coords if abs_source_coords[0] < abs_dest_coords[0]: x_coords = sorted(random.sample(x_population, sample_size)) else: x_coords = sorted(random.sample(x_population, sample_size), reverse=True) if abs_source_coords[1] < abs_dest_coords[1]: y_coords = sorted(random.sample(y_population, sample_size)) else: y_coords = sorted(random.sample(y_population, sample_size), reverse=True) #move mouse LOGGER.debug("Mouse move from: %s to %s", repr(abs_source_coords), repr(abs_dest_coords)) self.UIElement.set_focus() for i in range(sample_size): SendMouseInput([x_coords[i], y_coords[i]], button_down=False, button_up=False) time.sleep(0.1)
def set_focus(self): '''set foucs this element Will bring this element to the front, used by Keyboard, Mouse, Touch Arguments: Returns: ''' try: self.UIAElement.SetFocus() except _ctypes.COMError: LOGGER.warn( "SetFocus fail on current element, maybe due to this element not support SetFocus" )
def find_element(self, parsed_identifier): ''' find the first child UI element via identifier, return one UIAElement if success, return None if not find ''' translated_identifier = Translater.ID_Translater( parsed_identifier).get_translated() try: selenium_element = self.selenium_element.find_element( by=translated_identifier[0], value=translated_identifier[1]) except NoSuchElementException: LOGGER.debug("Cannot find target element") return None else: return UIElement(selenium_element)
def _translated_atomic_identifier(self, parsed_atomic_id): if parsed_atomic_id[ 0] in UIA.UIA_automation_element_property_identifers_mapping: return UIA.IUIAutomation_object.CreatePropertyCondition( UIA.UIA_automation_element_property_identifers_mapping[ parsed_atomic_id[0]], parsed_atomic_id[1]) elif parsed_atomic_id[ 0] in UIA.UIA_control_pattern_property_identifiers_mapping: return UIA.IUIAutomation_object.CreatePropertyCondition( UIA.UIA_control_pattern_property_identifiers_mapping[ parsed_atomic_id[0]], parsed_atomic_id[1]) else: #use no UIA identifier will be skipped LOGGER.warn("identifier: %s not in UIA property maps", parsed_atomic_id[0]) return None
def get_translated(self): ''' get translated result from parsed identifier ''' if len(self.parsed_identifier) == 2: name = self.parsed_identifier[0] value = self.parsed_identifier[1] try: getattr(MobileBy, name.upper()) except AttributeError: LOGGER.error("identifier not support: %s" , name) raise DriverException("identifier not support: %s" % name) return getattr(MobileBy, name.upper()), value else: LOGGER.error("Get error parsed_id: %s" , repr(self.parsed_identifier)) raise DriverException("Get error parsed_id: %s" % repr(self.parsed_identifier))
def _find_by_index(self, translated_identifier, scope=UIA.UIA_wrapper.TreeScope_Descendants): if isinstance(translated_identifier, tuple) and len(translated_identifier) == 2: identifier = translated_identifier[0] index = translated_identifier[1] elif isinstance(translated_identifier, int): identifier = UIA.IUIAutomation_object.CreateTrueCondition() index = translated_identifier else: LOGGER.warn("Index identifier is wrong, get %s" , repr(translated_identifier)) return None target_UIAElements = self.UIAElement.FindAll(scope, identifier) if index+1 > target_UIAElements.Length: LOGGER.warn("Find %d matched elements, index:%d out of range", target_UIAElements.Length, index) return None return UIElement(target_UIAElements.GetElement(index))
def screenshot(self, screenshot_location=""): '''take a screen shot for this element ''' if not os.path.isdir(screenshot_location): screenshot_location = self.screenshot_location self.start() filename = self.name + "_" + str(time.time()) + ".bmp" absfile = os.path.join(screenshot_location, filename) if os.path.isfile(absfile): os.remove(absfile) self.UIElement.screenshot(absfile) LOGGER.info("Screenshot taken: %s", absfile) return absfile
def right_click(self, relative_coords=None): '''RightClick: right click the UI element, or taget coords Arguments: coords: coordinate indicate where mouse click, default use UI element click point Returns: ''' if relative_coords is None: coords = self.UIElement.get_clickable_point() else: coords = [0, 0] coords[0] = relative_coords[0] + self.UIElement.coordinate[0] coords[1] = relative_coords[1] + self.UIElement.coordinate[1] LOGGER.debug("Mouse right click at: %s", repr(coords)) self.UIElement.set_focus() SendMouseInput(coords, button="right")
def right_click(self, relative_coords = None): '''RightClick: right click the UI element, or taget coords Arguments: coords: coordinate indicate where mouse click, default use UI element click point Returns: ''' if relative_coords is None: coords = self.UIElement.get_clickable_point() else: coords = [0, 0] coords[0] = relative_coords[0]+self.UIElement.coordinate[0] coords[1] = relative_coords[1]+self.UIElement.coordinate[1] LOGGER.debug("Mouse right click at: %s" , repr(coords)) self.UIElement.set_focus() SendMouseInput(coords, button="right")
def cli_execute(self, command): '''execute cli command command like "app parameter1 parameter 2" may contain variables ''' args = cli_command_parser.parse(command, lexer=cli_command_lexer) #replace variables for i, arg in enumerate(args): if not re.match("^{.*}$", arg) is None: args[i] = self.variables[arg.strip("{").strip("}")] #some app need to execute in their folder app_path = os.path.dirname(args[0]) if app_path: os.chdir(app_path) LOGGER.debug("CLI execute: %s", repr(args)) p = subprocess.Popen(args, shell=True)
def cli_execute(self, command): '''execute cli command command like "app parameter1 parameter 2" may contain variables ''' args = cli_command_parser.parse(command, lexer=cli_command_lexer) #replace variables for i, arg in enumerate(args): if not re.match("^{.*}$", arg) is None: args[i] = self.variables[arg.strip("{").strip("}")] #some app need to execute in their folder app_path = os.path.dirname(args[0]) if app_path: os.chdir(app_path) LOGGER.debug("CLI execute: %s" , repr(args)) p = subprocess.Popen(args, shell=True)
def _wait_stop(self): '''wait until UIElement is not valid or timeout ''' LOGGER.info("Waiting Element to stop, timeout %s" % self.timeout) if not self.identifier is None: #keep verify the element, until not found or timeout start_time = time.time() while True: self.UIElement = self.verify() if self.UIElement is None: return time.sleep(0.1) current_time = time.time() if current_time - start_time > self.timeout: raise TimeOutError( "time out encounter, during stop element:%s" % self.name)
def get_translated(self): ''' get translated result from parsed identifier ''' if len(self.parsed_identifier) == 2: name = self.parsed_identifier[0] value = self.parsed_identifier[1] try: getattr(By, name.upper()) except AttributeError: LOGGER.error("identifier not support: %s", name) raise DriverException("identifier not support: %s" % name) return getattr(By, name.upper()), value else: LOGGER.error("Get error parsed_id: %s", repr(self.parsed_identifier)) raise DriverException("Get error parsed_id: %s" % repr(self.parsed_identifier))
def move(self, abs_source_coords, abs_dest_coords): '''Move: move mouse from source_coords to dest_coords mouse move is not related with UI element so need use abs coords ''' import time import random x_range = abs(abs_source_coords[0] - abs_dest_coords[0]) y_range = abs(abs_source_coords[1] - abs_dest_coords[1]) x_sample_size = x_range/10 y_sample_size = y_range/10 #choose the bigger one sample_size = x_sample_size > y_sample_size and x_sample_size or y_sample_size #build population if abs_source_coords[0] < abs_dest_coords[0]: x_population = list(range(abs_source_coords[0], abs_dest_coords[0])) else: x_population = list(range(abs_dest_coords[0], abs_source_coords[0])) while len(x_population)<sample_size: x_population = x_population*2 if abs_source_coords[1] < abs_dest_coords[1]: y_population = list(range(abs_source_coords[1], abs_dest_coords[1])) else: y_population = list(range(abs_dest_coords[1], abs_source_coords[1])) while len(y_population)<sample_size: y_population = y_population*2 #get coords if abs_source_coords[0] < abs_dest_coords[0]: x_coords = sorted(random.sample(x_population, sample_size)) else: x_coords = sorted(random.sample(x_population, sample_size), reverse=True) if abs_source_coords[1] < abs_dest_coords[1]: y_coords = sorted(random.sample(y_population, sample_size)) else: y_coords = sorted(random.sample(y_population, sample_size), reverse=True) #move mouse LOGGER.debug("Mouse move from: %s to %s" , repr(abs_source_coords), repr(abs_dest_coords)) self.UIElement.set_focus() for i in range(sample_size): SendMouseInput([x_coords[i], y_coords[i]], button_down=False, button_up=False) time.sleep(0.1)
def find_elements(self, parsed_identifier): '''find all matched element root find should only find in the first level to avoid search in all UI ''' if parsed_identifier is None: translated_identifier = UIA.IUIAutomation_object.CreateTrueCondition() else: translated_identifier = Translater.ID_Translater(parsed_identifier).get_translated() if translated_identifier[0] == "Coordinate" or translated_identifier[0] == "Index": LOGGER.warn("find_elements method not support find by Coordinate or find by Index") return [] else: translated_identifier = translated_identifier[1] scope = UIA.UIA_wrapper.TreeScope_Children UIAElementArray = self.UIAElement.FindAll(scope, translated_identifier) UIElements = [] for i in range(UIAElementArray.Length): UIElements.append(UIElement(UIAElementArray.GetElement(i))) return UIElements
def screenshot(filename, coordinate=None): '''screenshot for target UI area parameters: handle: window handler for target UI filename: screenshot filename, should be .bmp file coordinate: (left, top, right, bottom) coordinate for target area ''' current_dir = os.path.dirname(__file__) screenshot_app = os.path.join(current_dir, "screenshot.exe") if coordinate is None: cmd = '"%s" -f %s' % (screenshot_app, filename) else: cmd = '"%s" -f %s -l %s -t %s -r %s -b %s' %\ (screenshot_app, filename, coordinate[0], coordinate[1], coordinate[2], coordinate[3]) LOGGER.debug("screenshot command: %s", cmd) process = subprocess.Popen(cmd) return process.wait()
def verify(self): '''verify UI element is still exist ''' flag = True if self.UIAElement == ctypes.POINTER(UIA.UIA_wrapper.IUIAutomationElement)(): flag = False try: UIAElement = self.UIAElement.FindFirst(UIA.UIA_wrapper.TreeScope_Element, UIA.IUIAutomation_object.CreateTrueCondition()) except _ctypes.COMError: flag = False UIAElement = ctypes.POINTER(UIA.UIA_wrapper.IUIAutomationElement)() if UIAElement == ctypes.POINTER(UIA.UIA_wrapper.IUIAutomationElement)(): flag = False if not flag: LOGGER.warn("Current UIAElement is no longer exist") return None return UIElement(UIAElement)
def screenshot(filename, coordinate=None): '''screenshot for target UI area parameters: handle: window handler for target UI filename: screenshot filename, should be .bmp file coordinate: (left, top, right, bottom) coordinate for target area ''' current_dir = os.path.dirname(__file__) screenshot_app = os.path.join(current_dir, "screenshot.exe") if coordinate is None: cmd = '"%s" -f %s' % (screenshot_app, filename) else: cmd = '"%s" -f %s -l %s -t %s -r %s -b %s' %\ (screenshot_app, filename, coordinate[0], coordinate[1], coordinate[2], coordinate[3]) LOGGER.debug("screenshot command: %s" , cmd) process = subprocess.Popen(cmd) return process.wait()
def _find_by_index(self, translated_identifier, scope=UIA.UIA_wrapper.TreeScope_Descendants): if isinstance(translated_identifier, tuple) and len(translated_identifier) == 2: identifier = translated_identifier[0] index = translated_identifier[1] elif isinstance(translated_identifier, int): identifier = UIA.IUIAutomation_object.CreateTrueCondition() index = translated_identifier else: LOGGER.warn("Index identifier is wrong, get %s", repr(translated_identifier)) return None target_UIAElements = self.UIAElement.FindAll(scope, identifier) if index + 1 > target_UIAElements.Length: LOGGER.warn("Find %d matched elements, index:%d out of range", target_UIAElements.Length, index) return None return UIElement(target_UIAElements.GetElement(index))
def get_property_by_id(UIAElement, property_identifier): '''get UIA element property by identifier, return None if fail Arguments: UIAElement: UIA Element instance property_identifier: property identifier Returns: property_value if success None if property_identifier not valid or not supported by UIA Element ''' if property_identifier in UIA_automation_element_property_identifers_mapping: property_value = UIAElement.GetCurrentPropertyValue( UIA_automation_element_property_identifers_mapping[ property_identifier]) if property_value is None: LOGGER.debug("This property:%s is not supported by this UIAElment", property_identifier) return "" return property_value elif property_identifier in UIA_control_pattern_availability_property_identifiers_mapping: property_value = UIAElement.GetCurrentPropertyValue( UIA_control_pattern_availability_property_identifiers_mapping[ property_identifier]) if property_value is None: LOGGER.debug("This property:%s is not supported by this UIAElment", property_identifier) return "" return property_value else: LOGGER.debug( "This property identifier is not support: %s, cannot get it from UIA typelib" % property_identifier) return None
def get_property_by_id(UIAElement, property_identifier): '''get UIA element property by identifier, return None if fail Arguments: UIAElement: UIA Element instance property_identifier: property identifier Returns: property_value if success None if property_identifier not valid or not supported by UIA Element ''' if property_identifier in UIA_automation_element_property_identifers_mapping: property_value = UIAElement.GetCurrentPropertyValue(UIA_automation_element_property_identifers_mapping[property_identifier]) if property_value is None: LOGGER.debug("This property:%s is not supported by this UIAElment" , property_identifier) return "" return property_value elif property_identifier in UIA_control_pattern_availability_property_identifiers_mapping: property_value = UIAElement.GetCurrentPropertyValue(UIA_control_pattern_availability_property_identifiers_mapping[property_identifier]) if property_value is None: LOGGER.debug("This property:%s is not supported by this UIAElment" , property_identifier) return "" return property_value else: LOGGER.debug("This property identifier is not support: %s, cannot get it from UIA typelib" % property_identifier) return None
def _wait_start(self): '''wait until UIElement is valid or timeout ''' #keep finding the element by identifier, until found or timeout LOGGER.info("Waiting Element show up, timeout %s", self.timeout) start_time = time.time() while True: current_time = time.time() self.UIElement = self.verify() if not self.UIElement is None: LOGGER.info("Find element: %s after waiting %ss, continue", self.name, current_time - start_time) #do a desktop screenshot here as required if self.screenshot_on: #get root element = self parent = element.parent while not parent is None: element = parent parent = element.parent #screenshot element.screenshot() return time.sleep(0.1) if current_time - start_time > self.timeout: #do a desktop screenshot here as required if not self.screenshot_off: #get root element = self parent = element.parent while not parent is None: element = parent parent = element.parent #screenshot element.screenshot() raise TimeOutError( "time out encounter, during start element:%s" % self.name)
def __init__(self, UIAElement, pattern_identifier): self.UIAElement = UIAElement self.pattern_object = UIA.get_pattern_by_id(UIAElement, pattern_identifier) if self.pattern_object is None: raise DriverException("Cannot get pattern, stop init pattern object") self.methods = {} self.properties = {} interface_description = UIA.UIA_control_pattern_interfaces[pattern_identifier] for member_description in interface_description: flag, name, args = _unpack(*member_description) #do a check, see if member exist in pattern object #if not, skip this member try: getattr(self.pattern_object, name) except AttributeError: LOGGER.debug("%s not exist in Pattern:%s", name, pattern_identifier) continue if flag == "method": self.methods[name] = args elif flag == "property": self.properties[name] = args else: raise DriverException("Unrecognised flag %s" % flag)
def check_app_map(XSD_file, app_map_file): XSD_module_name = os.path.basename(XSD_file).split(".")[0] XSD_module_dir = os.path.dirname(XSD_file) generate_module(XSD_file, XSD_module_name, XSD_module_dir) sys.path.insert(0, XSD_module_dir) XSD_module = __import__(XSD_module_name) with open(app_map_file) as app_map: try: XSD_module.CreateFromDocument(app_map.read()) LOGGER.debug("Check successful") except pyxb.UnrecognizedContentError as e: LOGGER.warn(e.details()) except pyxb.IncompleteElementContentError as e: LOGGER.warn(e.details()) except pyxb.ValidationError as e: LOGGER.warn(e.details())
def get_property(self, name): ''' get property value ''' try: obj = getattr(self.selenium_element, name) except AttributeError: LOGGER.debug("Cannot find this attribute: %s", name) if hasattr(self.selenium_element, "get_attribute"): LOGGER.debug("Try get_attribute method") return self.selenium_element.get_attribute(name) else: if inspect.ismethod(obj): LOGGER.debug("This is a method, not a property: %s", name) return None else: return obj
def start(self): '''start and find this UIElement ''' #need to start parent element first self.parent.start() if self.verify() is None: LOGGER.info( "Start function triggered, due to cannot find element: %s", self.name) #run start func if self.start_func: self.start_func.run() else: LOGGER.info("Element doesn't have start function") self._wait_start() else: LOGGER.info("Find element: %s, continue", self.name)
def get_property(self, name): ''' get property value ''' try: obj = getattr(self.selenium_element, name) except AttributeError: LOGGER.debug("Cannot find this attribute: %s" , name) if hasattr(self.selenium_element, "get_attribute"): LOGGER.debug("Try get_attribute method") return self.selenium_element.get_attribute(name) else: if inspect.ismethod(obj): LOGGER.debug("This is a method, not a property: %s" , name) return None else: return obj
def input(self, *values): '''take a string, translate to win32 event, and input to system Arguments: string: a string represent the keys input pause: global pause time during each key input, default to 0.05s Returns: Keyboard input string introduce: (1) For normal charactors like [0~9][a~z][A~Z], input directly (2) For special charactors like "space", "tab", "newline", "F1~F12" You use {key_name} to replace them Here are the support key list: backspace : {BACK}, {BACKSPACE}, {BKSP}, {BS} break : {BREAK} capslock : {CAP}, {CAPSLOCK} delete : {DEL}, {DELETE} down : {DOWN} end : {END} enter : {ENTER} ESC : {ESC} F1~F24 : {F1}~{F24} help : {HELP} home : {HOME} insert : {INS}, {INSERT} left : {LEFT} left windows : {LWIN} number lock : {NUMLOCK} page down : {PGDN} page up : {PGUP} print screen : {PRTSC} right : {RIGHT} right menu : {RMENU} right windows : {RWIN} scroll lock : {SCROLLLOCK} space : {SPACE} tab : {TAB} up : {UP} (3) For key combinations ctrl+X : {CTRL+X} shift+X : {SHIFT+X} menu+X : {MENU+X} windows key+X : {LWIN+X} ctrl+shift+X : {CTRL+SHIFT+X} ctrl+(XYZ) : {CTRL+XYZ} ''' #set focus before input keys self.UIElement.set_focus() #translate arguments to a string LOGGER.debug("Before translate, value: %s" , repr(values)) string = "" for value in values: #value should only allow to be string or int if isinstance(value, int): translated_value = str(value) string += translated_value elif isinstance(value, str): if re.match("^{\+*}$", value) != None: #for key combinations, we need to do some translation #like "{CTRL+A}" to "^A", "{CTRL+SHIFT+B}" to "^+B" keys = value.lstrip("{").rstrip("}").split("+") for key in keys: if key.upper() in self.key_combinations: translated_value += self.key_combinations[key.upper()] else: translated_value += "("+key+")" else: translated_value = value string += translated_value else: LOGGER.warning("keyboard input method arguments can only be string or int value, other value will be skipped") LOGGER.debug("Translated string: %s" , string) keys = parse_keys(string) LOGGER.debug("Keyboard input string: %s, parsed keys: %s" , string, repr(keys)) for k in keys: k.Run() time.sleep(0.05)
#set DPI awareness for win8.1 and win10 from AXUI.logger import LOGGER import subprocess p = subprocess.Popen("wmic path win32_operatingsystem get version", stdout=subprocess.PIPE) (stdout, stderr) = p.communicate() windows_version = stdout.split()[1] LOGGER.debug("Windows verison: %s" , windows_version) #need set DPI awareness for win8.1 and win10 if (int(windows_version.split(".")[0]) >= 6 and int(windows_version.split(".")[1]) >= 3) \ or int(windows_version.split(".")[0]) >= 10: LOGGER.debug("Set DPI awareness for windows verison: %s" , windows_version) import ctypes shcore = ctypes.windll.shcore shcore.SetProcessDpiAwareness(2) from .UIElement import UIElement, Root
def t_error(t): LOGGER.debug("Illegal character %s in Ln: %d" , repr(t.value[0]), t.lexer.lineno) raise Exception("") t.lexer.skip(1)
def p_error(p): LOGGER.debug("Syntax error in input: %s, Ln: %d" , repr(p.value), p.lineno) raise Exception("")