简介

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

启动应用

有两个常用方式

  1. 连接现存应用进程,进程号在任务管理器-详细信息中可以查看,项目中一般根据进程名称自动获取

app = Application(backend="uia").connect(process=8948)
  1. 启动应用进程,注意路径中特殊字符的转义,/\,不注意有时会出错

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()

窗口选择

打开这个窗口之后,我们需要选择到这个打开的窗口才能操作窗口中的东西,关于窗口的选择有以下两种情况:

  1. 不适用于窗口名为中文的

wind = app.frame
  1. 窗口名可以为中文的,括号内的参数为标题名,对应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

{SPACE}

BACKSPACE

{BACKSPACE} {BS} or {BKSP}

BREAK

{BREAK}

CAPS LOCK

{CAPSLOCK}

DEL or DELETE

{DELETE} or {DEL}

DOWN ARROW

{DOWN}

END

{END}

ENTER

{ENTER} or ~

ESC

{ESC}

HELP

{HELP}

HOME

{HOME}

INS or INSERT

{INSERT} or {INS}

LEFT ARROW

{LEFT}

NUM LOCK

{NUMLOCK}

PAGE DOWN

{PGDN}

PAGE UP

{PGUP}

PRINT SCREEN

{PRTSC}

RIGHT ARROW

{RIGHT}

SCROLL LOCK

{SCROLLLOCK}

TAB

{TAB}

UP ARROW

{UP}

+

{ADD}

-

{SUBTRACT}

*

{MULTIPLY}

/

{DIVIDE}

若需要对控件进行键盘输入,那么可以进行如下的操作:

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")

文章作者: Vsoapmac
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 soap的会员制餐厅
自动化测试 第三方库 个人分享 框架
喜欢就支持一下吧