自动化测试框架uiautomation使用说明

简介

在常规的模拟鼠标和键盘操作,我们一般使用pyautogui,uiautomation模块不仅能够直接支持这些操作,还能通过控件定位的方式直接定位到目标控件的位置,而不需要自己去获取对应的坐标位置。uiautomation不仅支持任意坐标位置截图,还支持目标控件的截图,缺点在于只能导出后让PIL图像处理库重新读取。其还能获取到ScrollItemPattern对象的控件,通过ScrollIntoView方法进行视图定位,与浏览器的元素定位效果几乎一致。
在常规的热键功能,我们一般使用pynput实现,但是uiautomation的热键注册会比pynput更简单,功能更强,还能直接让你的python程序实现管理员提权
基本原理
项目地址:https://github.com/yinkaisheng/Python-UIAutomation-for-Windows/blob/master/readme_cn.md
uiautomation只支持python3,依赖comtypes和typing这2个包,注意不要使用3.7.6和3.8.1这个2个版本
uiautomation操作程序时会给程序发送WM_GETOBJECT消息,如果程序处理WM_GETOBJECT消息,实现UI Automation Provider,并调用函数UiaReturnRawElementProvider(HWND hwnd,WPARAM wparam,LPARAM lparam,IRawElementProviderSimple *el),此程序就支持UiAutomation。IRawElementProviderSimple就是UI Automation Provider,其包含了各种控件信息,如Name,ClassName,ContorlType,坐标等

基础库源码

import importlib
import json
import sys
import os
import logging
import re
import time
import uiautomation as auto
import subprocess
import shutil
from resource.common_locator import *
from common.common import *
# from lib.Windows import wins
class UIautomation(object):
    '''
    uiautomation相关操作
    '''
    element_searchProperties = {}
    def __init__(self,report_dir=None,logger=None,**kwargs) -> None:
        self.report_dir = report_dir if report_dir else os.getcwd()
        self.logger = logger if logger else logging.getLogger(self.__class__.__name__)
        self.locator_def = {}
        self.root_element = ''
        auto.uiautomation.SetGlobalSearchTimeout(15)

    @property
    def UIA_element(self):
        return {
            'WindowControl':self.root_element.WindowControl(**self.element_searchProperties),
            'EditControl':self.root_element.EditControl(**self.element_searchProperties),
            'ButtonControl':self.root_element.ButtonControl(**self.element_searchProperties),
            'CustomControl':self.root_element.CustomControl(**self.element_searchProperties),
            'TextControl':self.root_element.TextControl(**self.element_searchProperties),
            'TableControl':self.root_element.TabControl(**self.element_searchProperties),
            'PaneControl':self.root_element.PaneControl(**self.element_searchProperties),
            'DataItemControl':self.root_element.DataItemControl(**self.element_searchProperties),
            'TabItemControl':self.root_element.TabItemControl(**self.element_searchProperties),
            'ComboBoxControl':self.root_element.ComboBoxControl(**self.element_searchProperties),
            'ListControl':self.root_element.ListControl(**self.element_searchProperties),
            'ListItemControl':self.root_element.ListItemControl(**self.element_searchProperties),
            'CheckBoxControl':self.root_element.CheckBoxControl(**self.element_searchProperties),
            'MenuBarControl':self.root_element.MenuBarControl(**self.element_searchProperties),
            'MenuItemControl':self.root_element.MenuItemControl(**self.element_searchProperties),
            'ImageControl':self.root_element.ImageControl(**self.element_searchProperties),
            'TreeItemControl':self.root_element.TreeItemControl(**self.element_searchProperties),
            'ProgressBarControl':self.root_element.ProgressBarControl(**self.element_searchProperties),
            'RadioButtonControl':self.root_element.RadioButtonControl(**self.element_searchProperties),
            'GroupControl':self.root_element.GroupControl(**self.element_searchProperties),
            'ToolBarControl':self.root_element.ToolBarControl(**self.element_searchProperties),
            'SpinnerControl':self.root_element.SpinnerControl(**self.element_searchProperties),
        }
    def uiautomation_setup(self,common_locator_def={},project_locator_def={}):
        '''
        1.在common_locator_def的基础上更新locator_def,作为locator的定义查找
        '''
        self.locator_def = common_locator_def
        for app_name,locator_details in project_locator_def.items():
            if app_name in self.locator_def:
                self.locator_def[app_name].update(locator_details)
            else:
                self.locator_def[app_name] = locator_details
        # self._uiautomation_folder_setup()

    def _uiautomation_folder_setup(self):
        self.UIAutomation_Timestamp_Folder = time.strftime(
            "%Y-%m-%d-%H-%M-%S", time.localtime())
        self.UIAUTOMATION_FOLDER = os.path.join(
            self.report_dir, "UIAutomation", self.UIAutomation_Timestamp_Folder)
        if not os.path.exists(self.UIAUTOMATION_FOLDER):
            os.makedirs(self.UIAUTOMATION_FOLDER)

    def uiautomation_teardown(self,clear=False):
        self._uiautomation_folder_teardown(clear)
        
    def _uiautomation_folder_teardown(self,clear):
        if clear and os.path.exists(self.UIAUTOMATION_FOLDER):
            shutil.rmtree(self.UIAUTOMATION_FOLDER,ignore_errors=True)

    def _create_uiobj_string(self,locator=None,**kwargs):
        if locator:
            if re.match("\(.*\).*", locator):
                string = locator
            else:
                app_name = locator.split('_')[0]
                if app_name in self.locator_def and locator in self.locator_def[app_name]:
                    string = self.locator_def[app_name][locator]
                else:
                    raise Exception(f'{locator} not defined or cannot be found in resource')
        elif kwargs:
            kwargs_dict = dict(map(lambda kv:(kv[0].lower(),kv[1]),kwargs.items()))
            if 'type' not in kwargs_dict:
                raise Exception('must set the type of locator')
            string = "({})".format(",".join(["{}={}".format(key, value) for
                                        key, value in kwargs.items()]))
        return string
    def _create_uiobj_from_string(self,string):
        self.root_element = auto
        self._update_element_properties(string)
        if 'Type' not in self.element_searchProperties:
            raise Exception(f'the {string} must have type')
        element = self.UIA_element[self.element_searchProperties['Type']]
        return element
    
    def _update_element_properties(self,string):
        self.element_searchProperties = {}
        match_list = re.findall("(?<=\()(.*?)(?=\)$)", string)
        int_attr = ['Depth','searchDepth','foundIndex']
        for attr_details in match_list:
            if attr_details:
                for attr_item in attr_details.split(','):
                    attr_name,attr_value = attr_item.split('=',1)
                    if attr_name in int_attr:
                        attr_value = int(attr_value)
                    self.element_searchProperties[attr_name] = attr_value
                    
    def _create_uiobj(self,locator=None,**kwargs):
        item_string = self._create_uiobj_string(locator,**kwargs)
        print(item_string)
        item_string_list = re.split('(?<=\))\.',item_string)
        item_name_list = re.findall('(?<=\)\.)(.*?)(?=\()',item_string)
        root_item = self._create_uiobj_from_string(item_string_list[0])
        if item_name_list:
            item_info = dict(zip(item_name_list,item_string_list[1:]))
            for item_name,item_attr in item_info.items():
                if item_name == 'child':
                    self._update_element_properties(item_attr)
                    self.root_element = root_item
                    element = self.UIA_element[self.element_searchProperties['Type']]
                elif item_name == 'next':
                    element = root_item.GetNextSiblingControl()
                elif item_name == 'prev':
                    element = root_item.GetPreviousSiblingControl()
                elif item_name == 'parent':
                    element = root_item.GetParentControl()
                root_item = element
        else:
            element = root_item
        return element
    def element_is_exists(self,locator,searchtime=10,**kwargs):
        uiobj = self._create_uiobj(locator,**kwargs)
        return True if uiobj.Exists(searchtime,1) else False

	def check_element_exists(self,locator,**kwargs):
        is_exists = self.element_is_exists(locator=locator,**kwargs)
        if not is_exists:
            raise Exception("element not exit in current page")

    def check_element_not_exists(self,locator,**kwargs):
        is_exit = self.element_is_exists(locator=locator, **kwargs)
        if is_exit:
            raise Exception("element exit in current page")
        else:
            return True

	def click_element(self,locator=None,position='left',searchtime=10,**kwargs):
        '''
        点击element, element的定义根据locator或kwargs。 用法举例:
            1. click_element(classname=xx, src=yy, text=zz) # 单层关系kwargs键值对定义方式
            2. click_element(locator='(Classname=xx, src=yy, text=zz)')  # 单层关系locator定义方式
            3. click_element(locator='(ClassName=xx, res=yy).parent().child(text=xx)')   # 多层关系只能用locator定义
        :param locator: 参照_create_uiobj说明
        :param position: 鼠标点击的位置,默认为左键点击,right表示右击,middle表示中间
        :param searchtime: 元素定位最大的搜索时间,默认为10s
        :param kwargs: 参照_create_uiobj说明
        :return: None
        '''
        uiobj = self._create_uiobj(locator,**kwargs)
        if uiobj.Exists(searchtime,1):
            if  position == 'left':
                uiobj.Click()
            elif position == 'right':
                uiobj.RightClick()
            elif position == 'middle':
                uiobj.MiddleClick()
        else:
            raise Exception(f'{locator} click failed')
    def doubleclick_element(self,locator=None,searchtime=10,**kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        if uiobj.Exists(searchtime,1):
            uiobj.DoubleClick()
        else:
            raise Exception(f'{locator} doubleclick failed')
    def doubleclick_element_ingore(self,locator=None,**kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        try:
            uiobj.DoubleClick()
        except:
            logging.info(f"the {locator} doubleclick")
    def click_element_ignore(self,locator=None,position='left',**kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        try:
            if  position == 'left':
                uiobj.Click()
            elif position == 'right':
                uiobj.RightClick()
            elif position == 'middle':
                uiobj.MiddleClick()
        except:
            pass
    def longclick_element(self,locator=None,position='left',presstime=10,**kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        if position == 'left':
            uiobj.PressMouse()
            time.sleep(presstime)
            uiobj.ReleaseMouse()
        elif position == 'right':
            uiobj.RightPressMouse()
            time.sleep(presstime)
            uiobj.RightReleaseMouse()
        elif position == 'middle':
            uiobj.MiddlePressMouse()
            time.sleep(presstime)
            uiobj.MiddleReleaseMouse()
    def drapdrop_element(self,x,y,locator=None,position='left',movespeeds=2,**kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        if position == 'left':
            uiobj.PressMouse()
            auto.MoveTo(x,y,movespeeds)
            auto.ReleaseMouse()
        elif position == 'right':
            uiobj.RightPressMouse()
            auto.MoveTo(x,y,movespeeds)
            auto.RightReleaseMouse()
            
    def wait_element_appear(self,locator=None,timeout=5,**kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        if not auto.WaitForExist(uiobj,timeout=timeout):
            raise Exception(f'the {uiobj} not exists in {timeout}')
    def wait_element_disappear(self, locator=None, timeout=5, **kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        if not auto.WaitForDisappear(uiobj,timeout=timeout):
            raise Exception(f'the {uiobj} not disappear in {timeout}')

    def click_by_coordinate(self,x,y):
        auto.Click(x,y)
    def select_element(self,locator=None,select_text=None,**kwargs):
        '''
        点击locator后选择下拉框select_locator, locator表示哪个元素是可以进行下拉的,select_locator表示具体的下拉框元素
        :param locator: 参照_create_uiobj说明
        :param select_locator: 下拉框元素
        :param select_text: 下拉元素的文本
        :param kwargs: 参照_create_uiobj说明
        :return: None
        '''
        select_text = select_text.upper()
        combox = self._create_uiobj(locator=locator,**kwargs)
        try:
            for item,depth in auto.WalkControl(combox,includeTop=True,maxDepth=2):
                if item.Name == select_text:
                    combox.Select(select_text)
                    break
                elif depth != 2 or item.ControlType != auto.ControlType.ListItemControl:
                    continue
        except:
            raise Exception(f'the {select_text} not find in {locator}')
    def clear_element_text(self,locator=None,**kwargs):
        '''
        清除文本编辑框中所有内容,locator的type一定为EditControl,且能进行编辑
        '''
        self.click_element(locator=locator,**kwargs)
        auto.SendKeys('{Ctrl}(A)')
        auto.SendKey(auto.Keys.VK_CLEAR)

    def send_text(self,text=None):
        '''
        发送文字,符号,英文字母到文本中,locator的type是EditControl,且能进行编辑
        '''
        # self.click_element(locator=locator,**kwargs)
        auto.SendKeys(text=text)

    def send_keyboard(self,keyboard):
        '''
        按键操作,对键盘进行单个按钮操作,无组合
        param keyboard:表示具体的键盘按键,例如:ESC,BACK,F1,0,o,CAPITAL等
        '''
        upperKey = keyboard.upper()
        if upperKey in auto.SpecialKeyNames:
            auto.SendKey(auto.SpecialKeyNames[upperKey])
        elif keyboard in auto.CharacterCodes:
            auto.SendKey(auto.CharacterCodes[keyboard])
        else:
            logging.error(f'SendKey failed the {keyboard} not in auto dict')
    def press_keyborad(self,keyboard):
        upperKey = keyboard.upper()
        if upperKey in auto.SpecialKeyNames:
            auto.PressKey(auto.SpecialKeyNames[upperKey])
        elif keyboard in auto.CharacterCodes:
            auto.PressKey(auto.CharacterCodes[keyboard])
        else:
            logging.error(f'PressKey failed the {keyboard} not in auto dict')

    def release_keyboard(self,keyboard):
        upperKey = keyboard.upper()
        if upperKey in auto.SpecialKeyNames:
            auto.ReleaseKey(auto.SpecialKeyNames[upperKey])
        elif keyboard in auto.CharacterCodes:
            auto.ReleaseKey(auto.CharacterCodes[keyboard])
        else:
            logging.error(f'ReleaseKey failed the {keyboard} not in auto dict')

    def sendkeys(self,keyboard):
        '''
        满足各种键盘操作,可发送文字,按键,组合按键等
        param keyboard: 可以发送文字,英文,数字,特殊符号等
        发送组合按键如下: {Ctrl}(AB) ctrl+A+B
                        {ALT}(n) ALT+n
                        {Ctrl}{v 3} 表示同时按下 Ctrl+v,松开 Ctrl+v,然后键入'v'2次 
                        {a 3}{B 5} 表示输入a3次,b5次
                        {Ctrl}{Shift}{A} 
                        {{}hello{}}{Enter} 输入{hello}并按下Enter 
        '''
        auto.SendKeys(keyboard)
    def set_element_value(self,locator=None,value=None,**kwargs):
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        uiobj.GetValuePattern().SetValue(value)

    def get_element_value(self,locator=None,**kwargs):
        '''
        获取元素value值,如果没有返回None
        '''
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        return uiobj.GetValuePattern().Value if uiobj.GetValuePattern().Value else None

    def reset_element_value(self,locator=None,new_value=None,**kwargs):
        '''
        通过按键操作对可输入框进行重新赋值,会将原输入框内容全部替换掉
        1.点击输入框
        2.cltr+a选择全部内容
        3.重新键入新的内容
        4.按Enter确认输入完成
        '''
        self.clear_element_text(locator=locator,**kwargs)
        self.send_text(new_value)
        time.sleep(1)
        self.send_keyboard('Enter')
    def get_element_attr(self,attr=None,locator=None,**kwargs):
        '''
        获取元素属性值
        '''
        value = ''
        attr = attr.lower()
        uiobj = self._create_uiobj(locator=locator,**kwargs)
        if self.element_is_exists(locator=locator,**kwargs):
            if attr == 'name':
                value = uiobj.Name 
            elif attr == 'value':
                value = uiobj.GetValuePattern().value
            elif attr == 'depth':
                value = uiobj.Depth
            elif attr == 'type':
                value = uiobj.ControlType
            elif attr == 'classname':
                value = uiobj.ClassName
            elif attr == 'id':
                value = uiobj.AutomationId
            elif attr == 'isselect':
                value = uiobj.GetSelectionItemPattern().IsSelected 
        return value
    def scroll_element_until_first(self,locator=None,**kwargs):
        '''
        向上滑动直到第一个元素
        '''
        self.click_element(locator=locator,**kwargs)
        old_name = self.get_element_attr(attr='name',locator=locator,**kwargs)
        while True:
            self.send_keyboard('UP')
            element_name = self.get_element_attr(attr='name',locator=locator,**kwargs)
            if element_name == old_name:
                self.send_keyboard('Enter')
                logging.info(f'the {locator} have up to the first')
                break
            else:
                old_name = element_name

    def scroll_element_until_last(self,locator=None,**kwargs):
        '''
        向下滑动直到最后一个元素
        '''
        self.click_element(locator=locator,**kwargs)
        old_name = self.get_element_attr(attr='name',locator=locator,**kwargs)
        while True:
            self.send_keyboard('DOWN')
            element_name = self.get_element_attr(attr='name',locator=locator,**kwargs)
        	if element_name == old_name:
                self.send_keyboard('Enter')
                logging.info(f'the {locator} have up to the last')
                break
            else:
                old_name = element_name
    def scroll_element(self,locator=None,direction='down',step=1,**kwargs):
        '''
        切换下拉框操作
        param direction:切换方向,默认向下
        param step:表示按键次数,即up/down的按键次数,默认为1
        '''
        self.click_element(locator=locator,**kwargs)
        for i in range(step):
            self.send_keyboard(direction)
        self.send_keyboard('Enter')

    def scroll_until_element_appear(self,locator=None,text=None,**kwargs):
        '''
        来回切换下拉框直到需要的元素出现
        param text:表示下拉框元素Name,如果下拉元素不存在,则停留到最上面元素
        '''
        self.click_element(locator=locator,**kwargs)
        current_name = self.get_element_attr('name',locator=locator,**kwargs)
        direction = 'down'
        change_direction_time = 0
        while True:
            if current_name == text:
                logging.info(f'the {locator} have find {text}')
                self.send_keyboard('Enter')
                break
            else:
                old_name = current_name
                self.send_keyboard(direction)
                current_name = self.get_element_attr('name',locator=locator,**kwargs)
                if current_name == old_name:
                    logger.info(f'the element have {direction} to last')
                    direction = 'up'
                    change_direction_time += 1
                if change_direction_time >= 2:
                    raise Exception(f'the {text} no find in {locator}')
    def take_screenshot(self):
        c = auto.GetRootControl()
        rects = auto.GetMonitorsRect()
        for rect in rects:
            c.CaptureToImage(PICPATH,rect.left,rect.top,rect.width(),rect.height())
        return PICPATH
	
ui = UIautomation(report_dir=REPORT_DIR)

控件控制入门

pip3 install uiautomation

控件分析与可用参数

import subprocess
import uiautomation as auto

subprocess.Popen('notepad.exe')
从桌面的第一层子控件中找到记事本程序的窗口WindowControl
notepadWindow = auto.WindowControl(searchDepth=1, ClassName='Notepad')
print(notepadWindow.Name)
# 设置窗口前置
notepadWindow.SetTopmost(True)

可用参数

● searchFromControl = None 从哪个控件开始找,如果为None,从根控件Desktop开始查找
● searchDepth = 0xFFFFFFFF 搜索深度
● searchInterval = SEARCH_INTERVAL 搜索间隔
● foundIndex = 1 搜索到满足搜索条件的控件索引,从索引1开始
● Name:控件名字
● SubName:控件部分名字
● RegexName:使用re.match匹配符合正则表达式的名字,Name,Subname,RegexName只能使用一个,不能同时使用
● ClassName:类名字
● AutomationId:控件的AuautomationId
● ControlType:控件类型
● Depth:控件相对于searchFromControl的精确深度
● Compare:自定义比较函数function(control:Control,depth:int)->bool

searchDepth和Depth的区别

  1. searchDepth在指定的深度范围内(1~searchDepth层中的所有子孙控件)搜索第一个满足搜索条件的控件
  2. Depth只在Depth所在的深度(如果Depth>1,排除1~searchDepth-1层中所有子孙控件)搜索第一个满足搜索条件的控件

具体操作

获取控件信息

安装pip install uiautomation后,在Python的Scripts(比如C:\Python37\Scripts)目录中会有一个文件automation.py, 或者使用源码根目录里的automation.py。automation.py是用来枚举控件树结构的一个脚本。运行'automation.py -h',查看命令帮助,写自动化代码时要根据它的输出结果来写对应的代码。

理解上图中各个参数的意义并运行下面命令查看程序的执行结果。
automation.py -t 0, 打印当前激活窗口的所有控件
automation.py -r -d 1 -t 0, 打印桌面(树的根控件 )和它的第一层子窗口(TopLevel顶层窗口)
将鼠标移动到记事本框架后,执行:python automation.py -t0 -c即可以得到编辑器全部子控件的信息

ControlType: PaneControl ClassName: #32769 Name: 桌面 Depth: 0 (桌面窗口,树的根控件)
ControlType: WindowControl ClassName: Notepad Depth: 1 (顶层窗口,记事本窗口)
 	ControlType: EditControl ClassName: Edit Depth: 2
 		ControlType: ScrollBarControl ClassName: Depth: 3
 			ControlType: ButtonControl ClassName: Depth: 4
 			ControlType: ButtonControl ClassName: Depth: 4
 		ControlType: ThumbControl ClassName: Depth: 3
 	ControlType: TitleBarControl ClassName: Depth: 2
		 ControlType: MenuBarControl ClassName: Depth: 3
 			ControlType: MenuItemControl ClassName: Depth: 4
		 ControlType: ButtonControl ClassName: Name: 最小化 Depth: 3
		 ControlType: ButtonControl ClassName: Name: 最大化 Depth: 3
		 ControlType: ButtonControl ClassName: Name: 关闭 Depth: 3
先从root查找,然后逐渐往深层次找,不要就直接写,auto.ButtonControl(searchDepth=3,Name='最大化'),能节省查找资源
1.首先从桌面的第一层子控件中找到记事本程序的窗口WindowControl,再从这个窗口查找子控件
notepadWindow = auto.WindowControl(searchDepth=1, ClassName='Notepad')
2.查找notepadWindow所有子孙控件中的第一个EditControl,因为EditControl是第一个子控件,可以不指定深度
edit = notepadWindow.EditControl()
3.先从notepadWindow的第一层子控件中查找TitleBarControl, 
4.然后从TitleBarControl的子孙控件中找第二个ButtonControl, 即最大化按钮,并点击按钮
notepadWindow.TitleBarControl(Depth=1).ButtonControl(foundIndex=2).Click()

获取编辑框中的文本

print(edit.GetValuePattern().Value)

输入文本

● 方法1:使用EditControl的ValuePattern():


edit = notepadWindow.EditControl()
edit.GetValuePattern().SetValue('方法1')

● 方法2:直接发送按键指令输入文本

edit.SendKeys('方法2')

● 方法3:复制文本后到剪切板粘贴

auto.SetClipboardText("方法3")
edit.SendKeys('{Ctrl}v')

保存关闭

notepadWindow.TitleBarControl(Depth=1).ButtonControl(foundIndex=3).Click()

确认保存

auto.SendKeys('{ALT}s')

输入文件名,并快捷键点击保存

auto.SendKeys('自动保存{ALT}s')

如果弹出文件名冲突提示,则确认覆盖

auto.SendKeys('{ALT}y')

延迟搜索机制

底层COM对象
Control.Element返回的是UIAutomation底层COM对象IUIAutomationElement,基本上Control的所有属性或者方法都是通过调用IUIAutomationElement COM API和Win32 API实现的
延迟搜索控件
当我们创建一个Control对象时,uiautomation并不会马上开始搜索控件,而是当使用其属性或方法,并且内部的Control.Element是None时,uiautomation才开始搜索控件。如果在uiautomation.TIME_OUT_SECOND(默认为10s)内找不到控件,uiautomation会抛出一个LookupError异常。也可以调用Control.Refind()立马或者重新开始搜索控件。
为了避免函数最终抛出异常,可以使用Control.Exists(maxSearchSeconds,searchIntervalSeconds,printIfNotExists)检查目标控件是否存在
edit = notepadWindow.EditControl()
edit.Exists()
Refind()和Exists()均会使Control.Element无效并重新出发搜索逻辑
还可以使用auto.WaitForExists(control,timeout)等待控件是否在规定时间类出现

import subprocess
import uiautomation as auto
auto.uiautomation.SetGlobalSearchTimeout(15)  # 设置全局搜索超时时间为15秒

def main():
    subprocess.Popen('notepad.exe', shell=True)
    window = auto.WindowControl(searchDepth=1, ClassName='Notepad')
    # 或者使用Compare自定义搜索条件
    # window = auto.WindowControl(searchDepth=1, ClassName='Notepad', Compare=lambda control,depth:control.ProcessId==100)
    edit = window.EditControl()
    # 当第一次调用SendKeys时, uiautomation开始在15秒内搜索控件window和edit
    # 因为SendKeys内部会间接调用Control.Element并且Control.Element值是None
    # 如果在15秒内找不到window和edit,会抛出LookupError异常
    try:
        edit.SendKeys('first notepad')
    except LookupError as ex:
        print("The first notepad doesn't exist in 15 seconds")
        return
    # 第二次调用SendKeys不会触发搜索, 之前的调用保证Control.Element有效
    edit.SendKeys('{Ctrl}a{Del}')#edit.GetValuePattern().SetValue('')
    window.GetWindowPattern().Close()  # 关闭第一个Notepad, window和edit的Element虽然有值,但是无效了

    subprocess.Popen('notepad.exe')  # 运行第二个Notepad
    window.Refind()  # 必须重新搜索
    edit.Refind()  # 必须重新搜索
    edit.SendKeys('second notepad')
    edit.SendKeys('{Ctrl}a{Del}')
    window.GetWindowPattern().Close()  # 关闭第二个Notepad, window和edit的Element虽然有值,但是再次无效了

    subprocess.Popen('notepad.exe')  # 运行第三个Notepad
    if window.Exists(3, 1): # 触发重新搜索
        if edit.Exists(3):  # 触发重新搜索
            edit.SendKeys('third notepad')  # 之前的Exists保证edit.Element有效
            edit.SendKeys('{Ctrl}a{Del}')
        window.GetWindowPattern().Close()
    else:
        print("The third notepad doesn't exist in 3 seconds")


if __name__ == '__main__':
    main()

常用功能
导包:import uiautomation as auto
基本方法
常用鼠标动作
OPERATION_WAIT_TIME默认为0.5
● 单击鼠标左键
Click(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME)
● 单击鼠标中键
MiddleClick(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME)
● 单击鼠标右键
RightClick(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME)
● 按住鼠标左键
PressMouse(x: int, y: int, waitTime: float = OPERATION_WAIT_TIME)
● 释放鼠标左键
ReleaseMouse(waitTime: float = OPERATION_WAIT_TIME)
● 鼠标移动
moveSpeed:1 正常速度,< 1 移动速度较慢,> 1 移动速度更快。
MoveTo(x: int, y: int, moveSpeed: float = 1, waitTime: float = OPERATION_WAIT_TIME)
● 鼠标左键拖放
DragDrop(x1: int, y1: int, x2: int, y2: int, moveSpeed: float = 1, waitTime: float = OPERATION_WAIT_TIME)
● 鼠标右键拖放
RightDragDrop(x1: int, y1: int, x2: int, y2: int, moveSpeed: float = 1, waitTime: float = OPERATION_WAIT_TIME)
● 鼠标滚轮向下
wheelTimes:轮次,默认为1,interval: 间隔,默认为0.05,waitTime: 等待时间,默认为0.5
WheelDown(wheelTimes: int = 1, interval: float = 0.05, waitTime: float = OPERATION_WAIT_TIME)
● 鼠标滚轮向上
WheelUp(wheelTimes: int = 1, interval: float = 0.05, waitTime: float = OPERATION_WAIT_TIME)
窗口操作
基本桌面操作
● 获取屏幕尺寸
x,y = auto.GetScreenSize()
● 显示桌面
auto.ShowDesktop()
● 获取uiautomation运行时间
auto.ProcessTime()
● 判断控件是否一致
auto.ControlsAreSame(control1, control2)
获取窗口对象
● 获取桌面对象
c = auto.GetRootControl()
● 返回运行当前python程序的控制台窗口对象
cmdWindow = auto.GetConsoleWindow()#没有就返回None
● 获取当前鼠标位置对应的窗口
ControlFromCursor()返回当前鼠标位置的控件,GetTopLevelControl()获取了该控件对应的顶级窗口对象
c = auto.ControlFromCursor().GetTopLevelControl()
● 获取当前激活窗口对应的对象
c = auto.GetForegroundControl().GetTopLevelControl()
● 获取本地窗口句柄
win = auto.ControlFromCursor().GetTopLevelControl()
handle = win.NativeWindowHandle
● 根据本地窗口句柄获取窗口控制对象

win2 = auto.ControlFromHandle(handle)
auto.ControlsAreSame(win, win2)
#True

● 确定窗口是否最小化
handle:本机窗口的句柄
auto.IsIconic(win.NativeWindowHandle) -> bool
● 确定窗口是否最大化
auto.IsZoomed(win.NativeWindowHandle) -> bool
查找控件方法
● 获取所有子控件
control.GetChildren()
● 获取首个子控件
control.GetFirstChildControl()
● 获取最后一个子控件
control.GetLastChildControl()
● 获取下一个兄弟控件
control.GetNextSiblingControl()
● 获取前一个兄弟控件
control.GetPreviousSiblingControl()
● 获取父控件
control.GetParentControl()
● 获取顶层窗口控件
control.GetTopLevelControl()
● 获取满足条件的祖先控件

func(control:Control,depth:int)->bool,返回True表示找到控件并返回
control.GetAncestorControl(func)
#输入c,d并返回c的类型是auto.WindowControl的控件
control.GetAncestorControl(lambda c, d: isinstance(c, auto.WindowControl))

窗口操作方法
● 隐藏窗口
win.Hide(0)
● 显示窗口
win.Show(0)
● 窗口最大化/最小化
win.Minimize()
win.Maximize()
● 移动窗口到屏幕中心
win.MoveToCenter()
● 窗口置顶
win.SetTopmost(True)
● 获取窗口标题并修改
win.SetWindowText(win.GetWindowText()+"|小小明")
● 获取当前python程序控制台窗口的标题并设置
auto.GetConsoleTitle()
auto.SetConsoleTitle('自定义控制台标题')
遍历子控件

posted @ 2024-03-24 21:36  大圆头  阅读(2420)  评论(0)    收藏  举报