python的自动精灵-PywinAuto
简介
Pywinauto是一个用于在Windows操作系统上自动化GUI应用程序的Python库。它允许您使用简单的Python代码编程控制和与按钮、文本框和菜单等GUI元素交互。Pywinauto提供了一个API,用于访问和操纵应用程序的图形元素,使得更容易编写自动化测试、执行回归测试或执行其他需要GUI自动化的任务。
安装
pip install pywinauto
Windows端元素定位工具及控件类型(backend)的判断
所有的工具下载链接:GitHub - blackrosezy/gui-inspect-tool: Gui Inspect tool for Windows
Inspect.exe的下载链接:辅助功能工具 - 检查 - Win32 apps | Microsoft Learn
Windows端元素定位工具介绍
使用UISpy或SPY++(文件名为:SPYXX),spy++
显示的很全,各种窗口都能够展示出来,看着十分多非常乱,相比来讲 uispy
就比较简洁实用了,只展示主要的窗口,节目很整洁很清晰。
控件类型(backend)的判断
由于一个界面中有很多种控件类型,所以在pywinauto中不同的控件类型对应着不同的控件类
可使用Inspect工具判断backend适合写哪种。首先根据上面的链接下载工具,然后打开Inspect.exe
点击inspect左上角的下拉列表中切换到UI Automation
鼠标点一下需要测试的程序窗体,inspect就会显示相关信息。
找到ControlType
,若类型以UIA
开头,则说明backend为uia。
第一个pywinauto程序
以下是使用Pywinauto在Windows上自动化记事本应用程序的简单演示:
import pywinauto
# 启动记事本应用程序
app = pywinauto.Application().start("notepad.exe")
# 获取主窗口的引用
window = app.window(title="无标题 - 记事本")
# 与文本框交互
text_box = window.Edit
text_box.set_text("你好,Pywinauto!")
# 保存文件
window.MenuSelect("文件->另存为")
save_as_dlg = app.SaveAs
save_as_dlg.FileName.set_text("hello.txt")
save_as_dlg.Save.click()
# 关闭应用程序
window.MenuSelect("文件->退出")
此代码启动了记事本,将文本框中的文本设置为“你好,Pywinauto!”,将文件保存为“hello.txt”,然后关闭应用程序。使用window.MenuSelect
方法选择菜单项,app.SaveAs
行获取“另存为”对话框的引用。其余的代码与对话框中的文本框和按钮进行交互。
Application对象操作
引入Application对象
from pywinauto.application import Application
启动应用
有两个常用方式
连接现存应用进程,进程号在任务管理器-详细信息中可以查看,项目中一般根据进程名称自动获取
app = Application(backend="uia").connect(process=8948)
启动应用进程,注意路径中特殊字符的转义,
/
和\
,不注意有时会出错
app = Application(backend="uia").start("notepad.exe") # 打开笔记本应用程序,不写路径是该程序可以在cmd命令中直接打开
app = Application(backend="uia").start("C:/folder/notepad.exe") # 打开特定路径的笔记本应用程序
注意,初始化的Application中的backend有两种选择,win32和uia,默认为win32。可使用spy++或Inspect工具判断backend适合写哪种。例如:如果使用Inspect的UIA模式,可见的控件和属性更多的话,backend可选uia,反之,backend可选win32。
常用方法
返回应用程序当前顶部窗口,是WindowSpecification对象,可以继续使用对象的方法往下继续查找控件。如:app.top_window().child_window(title='搜索栏', control_type='Edit')
app.top_window()
根据筛选条件,返回一个窗口, 是WindowSpecification对象,可以继续适用对象的方法往下继续查找控件。如:微信主界面app.window(class_name='WeChatMainWndForPC')
app.window(**kwargs)
**kwargs
中有如下常用形参:
title
: 窗口的标题文本(可以是部分标题,使用模糊匹配)。class_name
: 窗口的类名。handle
: 窗口的句柄(一个唯一的标识符)。found_index
: 当有多个匹配窗口时,指定要返回的窗口的索引。默认为0,表示返回第一个匹配的窗口。timeout
: 查找窗口的超时时间(以秒为单位)。默认为pywinauto
的全局超时设置。visible_only
: 仅查找可见的窗口。默认为True
。enabled_only
: 仅查找启用的(非禁用)窗口。默认为False
。best_match
: 一个模糊匹配算法,用于在多个相似窗口中选择最佳匹配。默认为None
,表示不使用模糊匹配。top_level_only
: 仅查找顶级窗口,忽略子窗口。默认为True
。active_only
: 仅查找当前活动的窗口。默认为False
。control_id
: 控件的标识符,但通常对于顶级窗口不使用。subprocess
: 布尔值,指示是否在子进程中查找窗口。默认为False
。
根据筛选条件返回一个窗口列表,无条件则默认返回全部窗口,列表项为wrapped对象,可以使用wrapped对象的方法,注意不是WindowSpecification对象。如:[<uiawrapper.UIAWrapper - '小李小张 - Google Chrome', Pane, -2064264099699444098>]
app.windows(**kwargs)
强制关闭窗口
app.kill(soft=False)
返回指定秒数期间的CPU使用率百分比
app.cpu_usage()
返回等待进程CPU使用率百分比小于指定的阈值threshold
app.wait_cpu_usage_lower(threshold=2.5, timeout=None, usage_interval=None)
检测软件是否是64位,如果操作的进程是64-bit,返回True
app.is64bit()
窗口选择
打开这个窗口之后,我们需要选择到这个打开的窗口才能操作窗口中的东西,关于窗口的选择有以下两种情况:
不适用于窗口名为中文的
wind = app.frame
窗口名可以为中文的,括号内的参数为标题名,对应Inspect中的Name
wind = app["窗口名"]
窗口的常用方法
关闭界面
window.close()
窗口聚焦(被其他窗口挡住的情况下)
window.set_focus()
最小化界面
window.minimize()
最大化界面
window.maximize()
将窗口恢复为正常大小,比如最小化的让他正常显示在桌面
window.restore()
检查窗体的状态,正常展示为0,最大化为1,最小化为2
window.get_show_state()
判断窗体是否存在
window.exists(timeout=None, retry_interval=None) # timeout:等待时间,一般默认5s;retry_interval:timeout内重试次数
等待窗口处于某种特定状态
# wait_for/wait_for_not:
# * 'exists' means that the window is a valid handle
# * 'visible' means that the window is not hidden
# * 'enabled' means that the window is not disabled
# * 'ready' means that the window is visible and enabled
# * 'active' means that the window is active
# timeout:等待多久
# retry_interval:timeout内重试时间
# eg: window.wait('ready')
window.wait(wait_for, timeout=None, retry_interval=None)
等待窗口在某种特点状态后消失
window.wait_not(wait_for_not, timeout=None, retry_interval=None)
控件操作
在获取窗口后,需要获取程序窗口中的内容,这个内容我们把它称之为控件,我们要对这个窗口的内容进行操作,就需要选择到对应的控件。
获取所有控件
通过print_control_identifiers()这个方法,来打印这个窗口下的直接子控件
notepad_win = app["Untitled - Notepad"]
notepad_win.print_control_identifiers()
得到的结果为:
Control Identifiers:
Notepad - 'Untitled - Notepad' (L531, T217, R1478, B858)
['Untitled - NotepadNotepad', 'Untitled - Notepad', 'Notepad']
child_window(title="Untitled - Notepad", class_name="Notepad")
|
| Edit - '' (L539, T268, R1470, B827)
| ['Edit', 'Untitled - NotepadEdit']
| child_window(class_name="Edit")
|
| StatusBar - '' (L539, T827, R1470, B850)
| ['StatusBar', 'StatusBar UTF-8', 'StatusBar Windows (CRLF)', 'StatusBar Ln 1, Col 1', 'StatusBar 100%', 'Untitled - NotepadStatusBar']
| child_window(class_name="msctls_statusbar32")
其中child_window
为该窗口下的所有子窗口和子控件
控件定位
控件是以层级的方法进行定位的,所以定位控件时也是根据层级进行定位。
首先可以和定位窗口的方法一样定位控件。
app["窗口名"]["控件名"]
也可以调用application的方法进行层级的控件定位,如下:
用于窗口的查找
app.window(**kwargs)
可以不管层级的找后代中某个符合条件的元素,最常用
app.child_window(**kwargs)
返回此元素的父元素,没有参数
app.parent()
返回符合条件的子元素列表,支持索引,是BaseWrapper对象(或子类)
app.children(**kwargs)
返回子元素的迭代器,是BaseWrapper对象(或子类)
app.iter_children(**kwargs)
返回符合条件的所有后代元素列表,是BaseWrapper对象(或子类)
app.descendants(**kwargs)
符合条件后代元素迭代器,是BaseWrapper对象(或子类)
app.iter_children(**kwargs)
以上kwargs
中的一些常见的筛选条件为:
class_name
:类名class_name_re
:类名,基于正则匹配title
:控件的标题,对应inspect中Name字段title_re
:控件的标题,基于正则匹配control_type
:控件类型,inspect界面LocalizedControlType字段的英文名best_match
:有坑,注意绕开auto_id
:inspect界面AutomationId字段,但是很多控件没有这个属性
其他的一些筛选条件为(不常用):
parent=None
process=None
top_level_only=True
visible_only=True
enabled_only=False
handle=None
ctrl_index=None
found_index=None
predicate_func=None
active_only=False
control_id=None
framework_id=None
backend=None
控件的常用属性
获取所有子控件的文字列表,对应inspect中Name字段
widget.children_texts()
获取控件的标题文字,对应inspect中Name字段
widget.window_text() # widget.element_info.name
获取控件的类名,对应inspect中ClassName字段,有些控件没有类名
widget.class_name() # widget.element_info.class_name
获取控件类型,对应inspect界面LocalizedControlType字段的英文名
widget.element_info.control_type
检查widget是否是parent的子控件
widget.is_child(parent)
获取inspect界面LegacyIAccessible开头的一系列字段,源码uiawraper.py中找到了这个方法,非常有用。
widget.legacy_properties().get('Value')
控件的常用操作
空间外围画框,便于查看,支持'red', 'green', 'blue'
widget.draw_outline(colour='green')
打印这个控件下的直接子控件,也是使用print_control_identifiers()
widget.print_control_identifiers(depth=None, filename=None)
滚动
# direction :"up", "down", "left", "right"
# amount:"line" or "page"
# count:int 滚动次数
widget.scroll(direction, amount, count=1)
返回控件的 PIL image对象
widget.capture_as_image()
# 可继续使用其方法如下
widget.capture_as_image().save(img_path)
获取控件上下左右的坐标
ret = widget.rectangle()
print(ret.top)
print(ret.bottom)
print(ret.left)
print(ret.right)
ret.top=177
ret.bottom=941
ret.left=430
ret.right=1490
鼠标操作
使用鼠标操作,一般就是单击,右击。双击。长按,拖动。滑动等操作。要使用pywinauto模拟鼠标操作,需要引入pywinauto中的模拟鼠标库
from pywinauto import mouse
坐标原点是在电脑屏幕的最左上角。
单击
mouse.click(coords=(900,400))
右击
mouse.right_click(coords=(900,400))
双击
mouse.double_click(coords=(900,400))
长按
mouse.press(coords=(900,400))
释放
mouse.release(coords=(900,400))
滑动
mouse.scroll(coords=(900,400),wheel_dist=-1) # 数字 > 0 :向上滑动,数字 < 0 :向下滑动
基于控件的鼠标操作
在获取控件后,可以对控件进行一系列鼠标操作
对控件进行点击
windows.window(class_name="Edit").click() # 点击
windows.window(class_name="Edit").click_input() # 点击(鼠标会跟随到对应的按钮)
windows.window(class_name="Edit").click_input(button="right") # 右击
windows.window(class_name="Edit").click_input(coords=(900,900)) # 点击控件内的特定位置,默认为控件的中间位置
对控件进行双击
windows.window(class_name="Edit").double_click_input() # 双击
windows.window(class_name="Edit").double_click_input(button="right") # 右双击
windows.window(class_name="Edit").double_click_input(coords=(900,900)) # 双击控件内的特定位置,默认为控件的中间位置
键盘操作
要使用pywinauto模拟键盘操作,需要引入pywinauto中的模拟键盘库
from pywinauto.keyboard import send_keys
具体案例:
# 回车
send_keys('{ENTER}')
# F5
send_keys('{VK_F5}')
# ctrl+a
send_keys('^a')
其中,特殊的键盘符号如下表:
按键名称 | 对应符号 |
---|---|
SHIFT | + |
CTRL | ^ |
ALT | % |
SPACE |
|
BACKSPACE |
|
BREAK |
|
CAPS LOCK |
|
DEL or DELETE |
|
DOWN ARROW |
|
END |
|
ENTER |
|
ESC |
|
HELP |
|
HOME |
|
INS or INSERT |
|
LEFT ARROW |
|
NUM LOCK |
|
PAGE DOWN |
|
PAGE UP |
|
PRINT SCREEN |
|
RIGHT ARROW |
|
SCROLL LOCK |
|
TAB |
|
UP ARROW |
|
+ |
|
- |
|
* |
|
/ |
|
若需要对控件进行键盘输入,那么可以进行如下的操作:
app['无标题 - 记事本'].type_keys('hello pywinauto')
菜单选择
如果想选择窗口中的菜单栏里的选项,在主窗体中调用menu_select()
方法,实例:
app.menu_select("File->Exit")
其中的File->Exit
非常容易理解,其实是点击菜单栏中的File
,然后在下拉菜单中选择Exit
。同样的,若想依次点击View,Zoom,Zoom in,案例如下:
app.menu_select("View->Zoom->Zoom in")